【问题标题】:Tower defense: turret tracking enemy and shooting issues塔防:炮塔跟踪敌人和射击问题
【发布时间】:2017-07-13 04:39:26
【问题描述】:

这是我的代码:

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

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

【问题讨论】:

  • 我们需要zombieArray声明的代码。
  • 刚刚添加,希望对您有帮助。
  • 它确实对这个问题有所帮助,但我已经在下面回答了你的问题。您是否了解将操作分为两种方法的必要性? findClosestZombie 然后fireAtZombie。现在,您正在对循环的每次迭代以及 self.enumeratedChildNodes 的每次迭代都进行这两项操作
  • 哦,对不起,我没有看到你的回答,我的错。

标签: swift sprite-kit skspritenode


【解决方案1】:

基本上,我不知道你到底做错了什么。你有很多事情要做,并且试图找出错误可能比重写它需要更长的时间(至少对我来说)。所以我就是这么做的。

这里是github上的项目链接:

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

对我来说,这就是将动作分离为不同的方法,并将一般的动作与逻辑分离。

您做了很多事情,很难测试/查看哪些部件工作正常。这就是使用较小的方法以及将操作与逻辑分开的地方。您的操作可能工作正常,但可能由于逻辑错误而没有被调用。

所以,我的实现方式是让你的炸弹炮塔成为自己的类.. 这样我们就可以让炸弹炮塔负责它的大部分动作,然后让 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()
  }
}

【讨论】:

  • 它仍在发射不止一颗子弹。我认为这与最近的僵尸有关。因为我认为它有时能够根据我为确定最近的僵尸而编写的代码找到两个单独的最近的僵尸。有没有办法让炮塔在这种情况下只选择一个?或者你看到我找到最近的僵尸的功能可能有缺陷吗?
  • @B.Toaster 是的。此代码不是一个有效的示例,只是为了帮助您了解发生了什么。您需要将 processZombie 拆分为 findClosest 和 shootAt。然后在你找到最接近的位置后放上shootAt
  • 你正在寻找和拍摄多次,因为 proceasZombie 被多次调用,而 processZombie 既寻找又拍摄(需要拆分)
  • @B.Toaster 好的,这是塔防吧?让我看看我能不能在这里做一些有用的东西..
  • 嗯,它有点像塔防。无论如何感谢您的帮助和支持。我已经为此工作了一段时间,过去一周一直被困在这里。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多