-1

Below I have part of the code for a simple game I am making. in the game, your finger around the screen a ball is underneath your finger. Then every 10 seconds a ball gets added in which follows your ball. I have an SKAaction which calls my add enemy function every 10 seconds which spawns the enemy. The issue is that I can't make the add enemy function update every frame because the SKAction won't let me call it if its updating every frame, so I'm not sure what to do in order for the the ball to be added in every 10 seconds and to have that ball track your location. Because currently it only tracks the initial location of the ball when it was added in. any help is appreciated, thank you.

import SpriteKit
import GameplayKit

class GameScene: SKScene {

    var me = SKSpriteNode()

    override func didMove(to view: SKView) {

        me = self.childNode(withName: "me") as! SKSpriteNode

        let border = SKPhysicsBody (edgeLoopFrom: self.frame)
        border.friction = 0
        self.physicsBody = border

        run(SKAction.repeatForever(SKAction.sequence([SKAction.run(createEnemy), SKAction.wait(forDuration: 10.0)])))
    }

    func createEnemy () {

        let enemy = SKSpriteNode(imageNamed: "ball 1")
        enemy.name = "enemy"
        enemy.position = CGPoint(x:667, y: -200)

        enemy.run(SKAction.moveTo(x: me.position.x, duration: 2))
        enemy.run(SKAction.moveTo(y: me.position.y, duration: 2))

        enemy.zPosition = +1
        addChild(enemy)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

        for touch in touches{

            let location = touch.location(in: self)
            me.run(SKAction.moveTo(x: location.x, duration: 0))
            me.run(SKAction.moveTo(y: location.y, duration: 0))
        }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {

        for touch in touches{

            let location = touch.location(in: self)
            me.run(SKAction.moveTo(x: location.x, duration: 0))
            me.run(SKAction.moveTo(y: location.y, duration: 0))
        }
    }

    override func update(_ currentTime: TimeInterval) {


    }
}
Tristan Beaton
  • 1,742
  • 2
  • 14
  • 25
  • What wasn't already answered in [your previous question](https://stackoverflow.com/questions/44058895/how-to-have-a-enemy-be-added-in-every-10-seconds-and-have-this-enemy-track-your?rq=1)? – John Montgomery May 22 '17 at 23:56
  • 1
    no, I had used all the previous information people had told me to build this, and everything works except for this – peter chrisanthopoulos May 23 '17 at 00:13
  • 1
    Generally you shouldn't use `SKAction` to implement something like [homing missile](https://stackoverflow.com/a/36235426/3402095) If you need to update something every frame, you use update: method of a `SKScene`. So what you are doing might work if correctly configured, but still, it is not a peformant way to implement what you want. – Whirlwind May 23 '17 at 10:11

1 Answers1

0
  1. Keep a list of all of your currently alive enemies in a dictionary via their .name property. The values in this dictionary are a tuple. This tuple keeps track of the enemy's instance, and its target's instance.

I do this in your createEnemy via makeEnemyName and addEnemyToDict

  1. during update give each enemy a new action to follow its target. This is done by iterating through the dictionary, then using each key's value to moveFollowerToTarget. This information is stored in the tuple type we created.

import SpriteKit

class GameScene22: SKScene {
  // MARK: - My code:

  // Tuple to keep track of enemy objects:
  typealias FollowerAndTarget = (follower: SKSpriteNode, target: SKSpriteNode)

  // [followerName: (followerSprite, targetSprite):
  var spriteDictionary: [String: FollowerAndTarget] = [:]

  // Give each enemy a unique name for the dictionary:
  var enemyCounter = 0

  // Assign to each enemy a unique name for the dictionary:
  private func makeEnemyName() -> String {
    enemyCounter += 1
    return "enemy\(enemyCounter)"
  }

  private func addEnemyToDict(enemy: SKSpriteNode, target: SKSpriteNode) {
    if let name = enemy.name { spriteDictionary[name] = (enemy, target) }
    else { print("enemy not found") } // error!
  }

  private func removeEnemyFromDict(enemy: SKSpriteNode) {
    if let name = enemy.name { spriteDictionary[name] = nil }
    else { print("enemy not removed from dictionary!") } // error!
  }

  // Note, you did not set the enemy to have a constant speed, so the enemy will move at random speeds
  // based on how far they are away from the target.
  private func moveFollowerToTarget(_ sprites: FollowerAndTarget) {
    let action = SKAction.move(to: sprites.target.position, duration: 2)
    sprites.follower.run(action)
  }

  private func allEnemiesMoveToTarget() {
    for sprites in spriteDictionary.values {
      moveFollowerToTarget(sprites)
    }
  }

  // Use this carefully if you are using phsyics later on:
  func killEnemy(_ enemy: SKSpriteNode) {

    // Remove this enemy from the update loop:
    removeEnemyFromDict(enemy: enemy)
    enemy.removeAllActions()
    enemy.removeFromParent()
    enemy.physicsBody = nil
  }

  // MARK: - Your code (mostly):

  var me = SKSpriteNode()

  override func didMove(to view: SKView) {
    me = SKSpriteNode(color: .blue, size: CGSize(width: 50, height: 50))
    me.name = "me"
    addChild(me)

    self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
    let border = SKPhysicsBody (edgeLoopFrom: self.frame)
    border.friction = 0
    self.physicsBody = border

    run(SKAction.repeatForever(SKAction.sequence([SKAction.run(createEnemy), SKAction.wait(forDuration: 10.0)])))
  }

  func createEnemy () {

    let enemy = SKSpriteNode(color: .purple, size: CGSize(width: 30, height: 30))
    enemy.name = makeEnemyName()
    addEnemyToDict(enemy: enemy, target: me)

    moveFollowerToTarget((follower: enemy, target: me))
    enemy.zPosition = +1
    addChild(enemy)
  }

  override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    for touch in touches{

      let location = touch.location(in: self)
      me.run(SKAction.moveTo(x: location.x, duration: 0))
      me.run(SKAction.moveTo(y: location.y, duration: 0))
    }
  }

  override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {

    for touch in touches{

      let location = touch.location(in: self)
      me.run(SKAction.moveTo(x: location.x, duration: 0))
      me.run(SKAction.moveTo(y: location.y, duration: 0))
      allEnemiesMoveToTarget()
    }
  }

  override func update(_ currentTime: TimeInterval) {
    // Will iterate through dictonary and then call moveFollowerToTarget()
    // thus giving each enemy a new movement action to follow.
    allEnemiesMoveToTarget()
  }
}
Fluidity
  • 3,985
  • 1
  • 13
  • 34
  • 1
    hey mark thanks so much. I understand what your trying to do here and I think it could work. I did copy and paste this into Xcode and run it. but sadly it adds in an enemy every 10 seconds but the enemy doesn't follow me. Ill try work it out. thank you – peter chrisanthopoulos May 25 '17 at 18:33
  • @peterchrisanthopoulos this should work. I will debug it later. remind me if I forget :P – Fluidity May 25 '17 at 19:55
  • 1
    thanks a lot :) I think it has something to do with the movefollowertotarget function, because everything else seems to work fine. that function is connected to the add enemy function so I'm not sure what it could be. – peter chrisanthopoulos May 25 '17 at 20:01
  • @peterchrisanthopoulos did you get this working? ( I see answer accepted ) – Fluidity May 26 '17 at 02:11
  • 1
    no it's just you answered the general question and gave me a good idea of what I need to do, currently the balls spawn in and I can bump them around because I added physics bodys, but hey don't follow me, like I said I think it has to do with the movefollowertotarget function, you should try running it in excise to see what happens – peter chrisanthopoulos May 26 '17 at 02:14
  • @peterchrisanthopoulos ok so all of the methods are working correctly, the issue is that giving it a new action each frame seems to keep it in a perpetual state of pause... put the moveaction in touchesMoved, you will see they move around a lot, but if you move quickly then they freeze. figuring this out now.. – Fluidity May 26 '17 at 02:27
  • 1
    So do I put the whole movefollowertotarget function into touches moved? – peter chrisanthopoulos May 26 '17 at 02:32
  • nono, just take the `allFollowersMoveToTarget()` and put it in `touchesMoved` and you will see they chase around until you start moving quickly. Take it out of update. – Fluidity May 26 '17 at 02:34
  • 1
    Oh yes I just did that I see what you mean, it's almost like it does the opposite, when you drag the ball they don't follow you, but when you let go they do – peter chrisanthopoulos May 26 '17 at 02:37
  • @peterchrisanthopoulos Ok I fixed it. I'm not sure why my first one didn't work.. I had it timed out right in the SKLoop but I'm not the best with actions!! Anyway I will update my answer with the working code in a minute. – Fluidity May 26 '17 at 02:39
  • @peterchrisanthopoulos ok it's up. you just need to implement the `killNode` properly (can be tricky with physics) and also make them move at a constant speed (search here for that, or check out raywenderlich) – Fluidity May 26 '17 at 02:41
  • @peterchrisanthopoulos IDK why people downvoted your question ( 2 people ) it was a good question... There was no way to know how to implement this system unless you have done hashmaps / dictionaries before. It wasn't an overly complicated solution but it did require at least a few steps to implement and wasn't 100% obvious... sorry that some people on here are prudes to new users :{. I just gave you some rep from comments here so hopefully that will balance it out a bit. Just hang in there till you get 100+ rep people will be nicer (sorry!) – Fluidity May 26 '17 at 02:43
  • @peterchrisanthopoulos also, I made this system a bit more flexible than you wanted, so you can set the enemies target to follow as any SKSPriteNode (not just the player) so if you have a powerup or something that acts as a decoy, you can just toss that node into the enemy create function and then they will follow the decoy for however long.. or whatever you can think of. or not, it's just there if you want it. Just iterate through the dictionary and set the `target:` value to whatever you want. default is player right now. – Fluidity May 26 '17 at 02:46
  • thank you so much, I've been trying to do this forever, I tried to up vote but I need at least 15 rep :(. there is some clumping of the balls when added in but I think I'm going to fix this by giving them physics body's and high restitution so they just bounce of each other – peter chrisanthopoulos May 26 '17 at 02:49
  • @peterchrisanthopoulos np. you are actually going to run into MASSIVE problems using physics with this. whenever you use `moveTo` and physics there are issues. You ideally want to use `applyImpulse` or other physicsBody functions to move sprites around. One idea is to use `SKConstraints` and add new constraints to each enemy so they don't overlap. This will buy you some time until you figure out your physics system (and help you learn constraints) – Fluidity May 26 '17 at 02:50
  • I was also thinking I could find a way to randomize the speed they tracked me to make it harder to – peter chrisanthopoulos May 26 '17 at 02:53
  • @peterchrisanthopoulos np if you post a new question on here just ping me here and I will take a look at it. – Fluidity May 26 '17 at 02:53
  • sorry to bother you one last time, but in your code you said I didn't set a enemy to have a consent speed, how exactly should I do this, I've tried everything from velocity to changing the duration of the time it takes for the ball to get to you. – peter chrisanthopoulos May 26 '17 at 21:01
  • @peterchrisanthopoulos check this answer out: https://stackoverflow.com/questions/19105631/how-do-you-move-an-ios-sksprite-at-a-consistent-speed and you can use to convert to Swift. But basically, you need to use MATH (shudder) for a lot of things physics related in SpriteKit.. especially if you are using physicsBodies you will need to convert eventually to impulses instead of `moveTo` https://stackoverflow.com/questions/36230619/how-to-move-enemy-towards-a-moving-player – Fluidity May 26 '17 at 21:18
  • @peterchrisanthopoulos here is my email address: fluidityT@gmail.com – Fluidity May 26 '17 at 21:18
  • https://stackoverflow.com/questions/38681205/moving-an-object-across-the-screen-at-a-certain-speed-sprite-kit another answer – Fluidity May 26 '17 at 21:26
  • @peterchrisanthopoulos dont forget to upvote this one too! – Fluidity Jun 12 '17 at 03:37