Having node, follow at constant speed

I'm trying to create a small mini-game where you drag a ball around the screen and every 10 seconds a ball is added that follows you. while you can drag the ball around the screen and the ball follows you, but when another ball is added to the group of balls together. I think it's because the ball follows me depending on how fast I go. so there is a certain way that I can make the balls follow me at a certain speed all the time, like 10 pixels per second or something like that, and this should prevent the balls from grouping.

I am currently working on a score, so it should grow soon every time you survive. and you will die if you touch one of the balls.

below is the code and short gif of my current code

! ( https://gyazo.com/1d6a56527bfd0884e8a26cff730f4e03 )

  import SpriteKit
  import GameplayKit

   struct physicsCatagory{
     static let me : UInt32 = 0x1 << 1
     static let enemy : UInt32 = 0x1 << 2

    }

  class GameScene: SKScene, SKPhysicsContactDelegate {


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") }
}

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

private func moveFollowerToTarget(_ sprites: FollowerAndTarget) {
    let action = SKAction.move(to: sprites.target.position, duration: 1)
    sprites.follower.run(action)
}



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

let enemySpeed: CGFloat = 300
var me = SKSpriteNode()
// 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
var died = Bool()




override func didMove(to view: SKView) {

    createScene()
}



func createEnemy () {
    if died == true{

    }
    else {
    let enemy = SKSpriteNode(imageNamed: "enemy1")
    enemy.name = makeEnemyName()
    addEnemyToDict(enemy: enemy, target: me)
    moveFollowerToTarget((follower: enemy, target: me))
    enemy.size = CGSize(width: 60, height: 60)
    enemy.position = CGPoint(x:667, y: 200)
    enemy.physicsBody?.restitution = 0.5
    enemy.physicsBody = SKPhysicsBody(circleOfRadius: 60)
    enemy.physicsBody?.affectedByGravity = false
    enemy.zPosition = 2
    enemy.physicsBody?.linearDamping = 0
    enemy.physicsBody?.isDynamic = true
    enemy.physicsBody?.categoryBitMask = physicsCatagory.enemy
    enemy.physicsBody?.collisionBitMask = physicsCatagory.me
    enemy.physicsBody?.contactTestBitMask = physicsCatagory.me
    addChild(enemy)
    }
}


func didBegin(_ contact: SKPhysicsContact) {

    let firstBody = contact.bodyA
    let secondBody = contact.bodyB

    if firstBody.categoryBitMask == physicsCatagory.me && secondBody.categoryBitMask == physicsCatagory.enemy || firstBody.categoryBitMask == physicsCatagory.enemy && secondBody.categoryBitMask == physicsCatagory.me {
        died = true
        restartScene()
    }

}
var lose: SKLabelNode!


func restartScene(){
    self.removeAllChildren()
    self.removeAllActions()
    died = false

    if let nextScene = GameScene(fileNamed: "menuScene"){
        nextScene.scaleMode = self.scaleMode
        let transition = SKTransition.fade(withDuration: 1)
        view?.presentScene(nextScene, transition: transition)
    }
}

func createScene(){
    me = self.childNode(withName: "me") as! SKSpriteNode
    me.physicsBody = SKPhysicsBody(circleOfRadius: 20)
    me.physicsBody?.affectedByGravity = false
    me.physicsBody?.categoryBitMask = physicsCatagory.me
    me.physicsBody?.collisionBitMask = physicsCatagory.enemy
    me.zPosition = 2

    self.physicsWorld.contactDelegate = self

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

    run(SKAction.repeatForever(SKAction.sequence([SKAction.run(createEnemy), SKAction.wait(forDuration: 4.0)])))
}
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))
        allEnemiesMoveToTarget()
    }




}

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()
}

      

}

+3


source to share


1 answer


Here you go:



import SpriteKit
import GameplayKit

struct physicsCatagory{
  static let me    : UInt32 = 0x1 << 1
  static let enemy : UInt32 = 0x1 << 2
  static let coin  : UInt32 = 0x1 << 3
}

class GameScene: SKScene, SKPhysicsContactDelegate {

  var lose: SKLabelNode!
  var me = SKSpriteNode()
  // 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
  let enemySpeed: CGFloat = 3
  var died = Bool()
  var timer = SKLabelNode()
  var timerValue: Int = 0 {
    didSet {
      timer.text = "\(timerValue)"
    }
  }

  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") }
  }

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

  // dont change anything outside of this, this is what makes the enemy follow you, so i have to have the enemy follow me at a constant speed
  private func moveFollowerToTarget(_ sprites: FollowerAndTarget) {
    let location = me.position

    // Aim
    let dx = location.x - sprites.follower.position.x
    let dy = location.y - sprites.follower.position.y
    let angle = atan2(dy, dx)

    sprites.follower.zRotation = angle

    // Seek
    let vx = cos(angle) * enemySpeed
    let vy = sin(angle) * enemySpeed

    sprites.follower.position.x += vx
    sprites.follower.position.y += vy
  }


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

      moveFollowerToTarget(sprites)
    }
  }

  private func keepEnemiesSeparated() {

    for sprites in spriteDictionary.values {

      let iterator = sprites.follower
      iterator.constraints = []

      // get every other follower:
      var otherFollowers: [SKSpriteNode] = []
      for sprites in spriteDictionary.values {
        if sprites.follower == iterator { continue }
        else { otherFollowers.append(sprites.follower) }
      }

      // Assign constrain
      for follower in otherFollowers {
        let distanceBetween = CGFloat(60)
        let constraint = SKConstraint.distance(SKRange(lowerLimit: distanceBetween), to: follower)
        iterator.constraints!.append(constraint)
      }
    }
  }

  func createEnemy () {
    if died { return }

    let enemy = SKSpriteNode(color: .green, size: CGSize(width: 60, height: 60))
    enemy.size = CGSize(width: 60, height: 60)
    enemy.zPosition = 2
    enemy.position.y -= size.height / 2
    enemy.physicsBody = {
      let pb = SKPhysicsBody(circleOfRadius: 30)
      pb.restitution = 0.5
      pb.affectedByGravity = false
      pb.linearDamping = 0
      pb.isDynamic = true
      pb.categoryBitMask    = physicsCatagory.enemy
      pb.collisionBitMask   = physicsCatagory.me
      pb.contactTestBitMask = physicsCatagory.me
      return pb
    }()

    enemy.name = makeEnemyName()
    addEnemyToDict(enemy: enemy, target: me)
    moveFollowerToTarget((follower: enemy, target: me))
    keepEnemiesSeparated()

    addChild(enemy)
  }

  func createCoin () {
    let coin = SKSpriteNode(color: .yellow, size: CGSize(width: 20, height: 20))
    let height = self.view!.frame.height
    let width = self.view!.frame.width

    let randomPosition = CGPoint( x:CGFloat( arc4random_uniform( UInt32( floor( width  ) ) ) ),
                                  y:CGFloat( arc4random_uniform( UInt32( floor( height ) ) ) )
    )

    coin.position = randomPosition
    addChild(coin)
  }

  func restartScene(){
    self.removeAllChildren()
    self.removeAllActions()
    died = false

    let nextScene = GameScene(size: self.size)
    nextScene.scaleMode = self.scaleMode
    let transition = SKTransition.fade(withDuration: 1)
    view?.presentScene(nextScene, transition: transition)
  }

  func createScene(){
    me = SKSpriteNode(color: .blue, size: CGSize(width: 60, height: 60))
    me.physicsBody = SKPhysicsBody(circleOfRadius: 30)
    me.physicsBody?.affectedByGravity = false
    me.physicsBody?.categoryBitMask = physicsCatagory.me
    me.physicsBody?.collisionBitMask = physicsCatagory.enemy
    me.zPosition = 2

    timer = SKLabelNode(fontNamed: "Chalkduster")
    timer.text = "\(timerValue)"
    addChild(me)
    addChild(timer)

    let wait = SKAction.wait(forDuration: 1)

    let block = SKAction.run({
      [unowned self] in

      if self.timerValue >= 0{
        self.timerValue += 1
      }else{
        self.removeAction(forKey: "countdown")
      }
    })

    let sequence = SKAction.sequence([wait,block])

    run(SKAction.repeatForever(sequence), withKey: "countdown")

    self.physicsWorld.contactDelegate = self

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

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

    run(SKAction.repeatForever(SKAction.sequence([SKAction.run(createCoin), SKAction.wait(forDuration: TimeInterval(arc4random_uniform(11) + 5))])))

  }


  override func didMove(to view: SKView) {
    scene?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
    createScene()
  }

  func didBegin(_ contact: SKPhysicsContact) {

    let firstBody = contact.bodyA
    let secondBody = contact.bodyB

    if firstBody.categoryBitMask == physicsCatagory.me    && secondBody.categoryBitMask == physicsCatagory.enemy
    || firstBody.categoryBitMask == physicsCatagory.enemy && secondBody.categoryBitMask == physicsCatagory.me {
      died = true
      restartScene()
    }
  }

  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))
      allEnemiesMoveToTarget()
    }
  }

  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()
  }
}

      

+2


source







All Articles