塔防:炮塔跟踪敌人和射击问题

时间:2017-07-13 04:39:26

标签: swift sprite-kit skspritenode

这是我的代码:

func bombTowerTurnShoot() {
    var prevDistance:CGFloat = 1000000
    var closesetZombie = zombieArray[0]
        self.enumerateChildNodes(withName: "bomb tower") {
            node, stop in
            if self.zombieArray.count > 0 {
            for zombie in self.zombieArray {
            if let bombTower = node as? SKSpriteNode {
                let angle = atan2(closesetZombie.position.x - bombTower.position.x , closesetZombie.position.y - bombTower.position.y)
                let actionTurn = SKAction.rotate(toAngle: -(angle - CGFloat(Double.pi/2)), duration: 0.2)
                bombTower.run(actionTurn)
                let turretBullet = SKSpriteNode(imageNamed: "Level 1 Turret Bullet")
                turretBullet.position = bombTower.position
                turretBullet.zPosition = 20
                turretBullet.size = CGSize(width: 20, height: 20)
                //turretBullet.setScale (frame.size.height / 5000)
                turretBullet.physicsBody = SKPhysicsBody(circleOfRadius: max(turretBullet.size.width / 2, turretBullet.size.height / 2))
                turretBullet.physicsBody?.affectedByGravity = false
                turretBullet.physicsBody!.categoryBitMask = PhysicsCategories.Bullet //new contact
                turretBullet.physicsBody!.collisionBitMask = PhysicsCategories.None
                turretBullet.physicsBody!.contactTestBitMask = PhysicsCategories.Zombie
                self.addChild(turretBullet)
                var dx = CGFloat(closesetZombie.position.x - bombTower.position.x)
                var dy = CGFloat(closesetZombie.position.y - bombTower.position.y)
                let magnitude = sqrt(dx * dx + dy * dy)
                dx /= magnitude
                dy /= magnitude
                let vector = CGVector(dx: 4.0 * dx, dy: 4.0 * dy)
                func fire () {
                    turretBullet.physicsBody?.applyImpulse(vector)
                }
                func deleteBullet() {
                    turretBullet.removeFromParent()
                }
                turretBullet.run(SKAction.sequence([SKAction.wait(forDuration: 0), SKAction.run(fire), SKAction.wait(forDuration: 2.0), SKAction.run(deleteBullet) ]))

                let distance = hypot(zombie.position.x - bombTower.position.x, zombie.position.y - bombTower.position.y)
                if distance < prevDistance {
                    prevDistance = distance
                    closesetZombie = zombie
                }


                }
            }
        }
    }
}

这段代码的作用是将炮塔转向最近的僵尸并射击它。据我所知,炮塔转向最近的僵尸(如果你能分辨出这个代码是否真的完成了我想知道的话)。我遇到的更大问题是炮塔有时射击不止一颗子弹。我认为这是因为它试图射击阵列中的所有僵尸而不是指定的(最靠近塔)。我怎样才能使炮塔只拍摄最接近的僵尸?

class GameScene: SKScene, SKPhysicsContactDelegate {//new contact
     var zombieArray:[SKSpriteNode] = []
...
...
}

一旦添加它们,我将所有僵尸追加到数组中,并在它们死后将它们从数组中移除。

1 个答案:

答案 0 :(得分:1)

基本上,我不知道你到底做错了什么。你有很多东西在进行,并试图弄清楚这个bug可能比重写它需要更长的时间(至少对我而言)。这就是我所做的。

以下是github上项目的链接:

https://github.com/fluidityt/ShootClosestZombie/tree/master

对我来说,这就是将行动分离到一些不同的方法,并将行动与逻辑分开。

你有这么多事情,很难测试/看哪些部件工作正常。这是有一些更小的方法进入的地方,以及将动作与逻辑分离。你的行动可能工作正常,但也许它不会因为逻辑错误而被调用。

所以,我如何实现这一点就是让你的炸弹炮塔成为它自己的类......那样我们就可以让炸弹炮塔负责其大部分动作,然后让gameScene处理大部分动作实现/和/或逻辑。

我上传的演示显示了两个炮塔,每个帧自动定位到最近的僵尸,然后每秒射击它们。点击屏幕添加更多僵尸。

炮塔独立跟踪最近的僵尸给他们所以如果你在左边和右边生成一个僵尸,那么左炮塔将向左僵尸射击,右炮塔将在右僵尸射击(只有一次!)。

enter image description here

class BombTower: SKSpriteNode {

  static let bombName = "bomb tower"

  var closestZombie: SKSpriteNode!

  func updateClosestZombie() {
    let gameScene = (self.scene! as! GameScene)
    let zombieArray = gameScene.zombieArray

      var prevDistance:CGFloat = 1000000
      var closestZombie = zombieArray[0]

      for zombie in zombieArray {

        let distance = hypot(zombie.position.x - self.position.x, zombie.position.y - self.position.y)
        if distance < prevDistance {
          prevDistance = distance
          closestZombie = zombie
        }
      }
    self.closestZombie = closestZombie
  }

  func turnTowardsClosestZombie() {
    let angle = atan2(closestZombie.position.x - self.position.x , closestZombie.position.y - self.position.y)
    let actionTurn = SKAction.rotate(toAngle: -(angle - CGFloat(Double.pi/2)), duration: 0.2)
    self.run(actionTurn)
  }

  private func makeTurretBullet() -> SKSpriteNode {
    let turretBullet = SKSpriteNode(imageNamed: "Level 1 Turret Bullet")
    turretBullet.position = self.position
    turretBullet.zPosition = 20
    turretBullet.size = CGSize(width: 20, height: 20)
    //turretBullet.setScale (frame.size.height / 5000)

    turretBullet.physicsBody = SKPhysicsBody(circleOfRadius: max(turretBullet.size.width / 2, turretBullet.size.height / 2))
    turretBullet.physicsBody?.affectedByGravity = false
    //    turretBullet.physicsBody!.categoryBitMask = PhysicsCategories.Bullet //new contact
    //    turretBullet.physicsBody!.collisionBitMask = PhysicsCategories.None
    //    turretBullet.physicsBody!.contactTestBitMask = PhysicsCategories.Zombie

    return turretBullet
  }

  private func fire(turretBullet: SKSpriteNode) {
    var dx = CGFloat(closestZombie.position.x - self.position.x)
    var dy = CGFloat(closestZombie.position.y - self.position.y)
    let magnitude = sqrt(dx * dx + dy * dy)
    dx /= magnitude
    dy /= magnitude

    let vector = CGVector(dx: 4.0 * dx, dy: 4.0 * dy)

    turretBullet.physicsBody?.applyImpulse(vector)
  }

  func addBulletThenShootAtClosestZOmbie() {
    let bullet = makeTurretBullet()
    scene!.addChild(bullet)
    fire(turretBullet: bullet)
  }
}
// TODO: delete bullets, hit detection, and add SKConstraint for tracking instead of update.
// Also, I think that we are iterating too much looking for nodes. Should be able to reduce that.
// Also also, there are sure to be bugs if zombieArray is empty.
class GameScene: SKScene {

  var zombieArray: [SKSpriteNode] = []

  private func makeBombArray() -> [BombTower]? {
    guard self.zombieArray.count > 0 else { return nil }

    var towerArray: [BombTower] = []
    self.enumerateChildNodes(withName: BombTower.bombName) { node, _ in towerArray.append(node as! BombTower) }
    guard towerArray.count > 0 else { return nil }

    return towerArray
  }

  private func towersShootEverySecond(towerArray: [BombTower]) {

    let action = SKAction.run {
      for bombTower in towerArray {
        guard bombTower.closestZombie != nil else { continue } // I haven't tested this guard statement yet.
        bombTower.addBulletThenShootAtClosestZOmbie()
      }
    }
    self.run(.repeatForever(.sequence([.wait(forDuration: 1), action])))
  }

  override func didMove(to view: SKView) {
    // Demo setup:
    removeAllChildren()

    makeTestZombie: do {
      spawnZombie(at: CGPoint.zero)
    }
    makeTower1: do {
      let tower = BombTower(color: .yellow, size: CGSize(width: 55, height: 55))
      let turretGun = SKSpriteNode(color: .gray, size: CGSize(width: 25, height: 15))
      turretGun.position.x = tower.frame.maxX + turretGun.size.height/2
      tower.name = BombTower.bombName
      tower.addChild(turretGun)
      addChild(tower)
    }
    makeTower2: do {
      let tower = BombTower(color: .yellow, size: CGSize(width: 55, height: 55))
      let turretGun = SKSpriteNode(color: .gray, size: CGSize(width: 25, height: 15))
      turretGun.position.x = tower.frame.maxX + turretGun.size.height/2
      tower.addChild(turretGun)
      tower.position.x += 200
      tower.name = BombTower.bombName
      addChild(tower)
    }

    guard let towerArray = makeBombArray() else { fatalError("couldn't make array!") }

    towersShootEverySecond(towerArray: towerArray)
  }

  private func spawnZombie(at location: CGPoint) {
    let zombie = SKSpriteNode(color: .blue, size: CGSize(width: 35, height: 50))
    zombieArray.append(zombie)
    zombie.position = location
    zombie.run(.move(by: CGVector(dx: 3000, dy: -3000), duration: 50))
    addChild(zombie)
  }

  // Just change this to touchesBegan for it to work on iOS:
  override func mouseDown(with event: NSEvent) {
    let location = event.location(in: self)
    spawnZombie(at: location)
  }

  // I think this could be a constrain or action, but I couldn't get either to work right now.
  private func keepTowersTrackingNearestZombie() {
    guard let towerArray = makeBombArray() else { return }
    for tower in towerArray {
      tower.updateClosestZombie()
      tower.turnTowardsClosestZombie()
    }
  }

  override func update(_ currentTime: TimeInterval) {
    keepTowersTrackingNearestZombie()
  }
}