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

Tower defense: turret tracking enemy and shooting issues

这是我的代码:

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] = []
...
...
}

我将所有僵尸添加到数组中,一旦它们死亡,我就将它们从数组中移除。

基本上,我不知道你做错了什么。你有很多事情要做,试图找出错误可能比重写它花费的时间更长(至少对我而言)。这就是我所做的。

这是 github 上项目的 link:

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

对我来说,这就是将操作分成不同的方法,并将一般操作与逻辑分开。

您发生了太多事情,很难测试/查看哪些部分工作正常。这是使用更小的方法以及将动作与逻辑分开的地方。您的动作可能工作正常,但可能由于逻辑错误而未被调用。

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

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

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

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