66350


NodeAの体力をNodeBと接触する度に減らし、NodeAの体力が０になった瞬間にNodeAを消滅、といった処理を行いたいです。  
どうやら、一度接触する度に値がリセットされてしまい、NodeAの体力が０にならないようです。  
下記詳細になります。  
（NodeAをasteroidLife 、NodeBをmissile としています。）

Swift SpriteKit
衝突処理（didBegin）にて、asteroid(Node)に値（Life）の情報を加え、missile(Node)と衝突される度にasteroid(Node)の値を減らし、値が0になった時点でasteroid(Node)を消滅、といったコードを書きたいです。  
（下記コードで試しました。）

[code]

    var asteroidLife: Int = 100 // asteroidの体力
    let missilePW: Int = 50     // missileの攻撃力
    
    if target.categoryBitMask == missileCategory  {
        tagetNode.removeFromParent()
        asteroidLife -= missilePW
    
        if asteroidLife <= 0 {
            asteroidNode.removeFromParent()
        }
    }
    
[/code]

上記のコードでは１度接触する度にasteroidLifeがリセットされてしまい、 if asteroidLife <= 0 が実行されません。

１度目の接触にて更新されたasteroidLifeの値を２度目の接触でも使用できるようになれば、、と思っておりますが、方法が思いつきません。

何かいい方法はありますでしょうか。

一応全コードを下に添付致します。

よろしくお願いいたします。

[code]

    import SpriteKit
    import GameplayKit
    
    class GameScene: SKScene, SKPhysicsContactDelegate {
    
        var gameVC: GameViewController!
    
        var spaceship:SKSpriteNode! = SKSpriteNode(imageNamed: "spaceship") 
        var button : SKSpriteNode! 
        var LifeLabel: SKLabelNode!
    
        let spaceshipCategory : UInt32 = 0b0001
        let missileCategory : UInt32 = 0b0010
        let asteroidCategory  : UInt32 = 0b0100
    
        var Life: Int = 500 {
            didSet {
                LifeLabel.text = "Life: \(Life)"
            }
        }
    
        var timar: Timer?
    
        func DegreeToRadian(Degree : Double!) -> CGFloat{
            return CGFloat(Degree) / CGFloat(180.0 * M_1_PI)
        }
    
        override func didMove(to view: SKView) {
    
            physicsWorld.gravity = CGVector(dx: 0, dy: 0)
            physicsWorld.contactDelegate = self
    
            self.backgroundColor = UIColor.black 
    
            self.spaceship.alpha = 1 
            self.spaceship.position = CGPoint(x: view.frame.width / -2 + 100, y: view.frame.height / -2 + 100) 
            self.spaceship.size = CGSize(width: 150, height: 150) 
            self.spaceship.zRotation = DegreeToRadian(Degree: 0)
            self.spaceship.isUserInteractionEnabled = false 
            self.spaceship.physicsBody = SKPhysicsBody(circleOfRadius: self.spaceship.frame.width * 0.1) 
            self.spaceship.physicsBody?.categoryBitMask = spaceshipCategory
            self.spaceship.physicsBody?.collisionBitMask = spaceshipCategory | missileCategory | asteroidCategory
            self.spaceship.physicsBody?.contactTestBitMask = asteroidCategory
    
            self.spaceship.name  = "spaceship"
            self.addChild(self.spaceship) 
    
            self.button = self.childNode(withName: "button") as? SKSpriteNode 
    
            if let button = self.button {
                button.name = "button" 
                button.alpha = 0.0 
                button.run(SKAction.fadeIn(withDuration: 2.0)) 
            }
    
            LifeLabel = SKLabelNode(text: "Life:500")
            LifeLabel.fontSize = 50
            LifeLabel.position = CGPoint(x: -frame.width / 2 + LifeLabel.frame.width + 50, y: frame.height / 2 - 200)
            addChild(LifeLabel)
    
            timar = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { _ in
                self.addasteroid()
            })
    
        }
    
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            if isPaused { return }
    
            if let touch = touches.first { 
                let locatin = touch.location(in: self) 
                if self.atPoint(locatin).name == "button" { 
    
                    let missile = SKSpriteNode(imageNamed: "missile")
                    missile.physicsBody = SKPhysicsBody(circleOfRadius: missile.frame.height / 2 ) 
                    missile.physicsBody?.categoryBitMask = missileCategory
                    missile.physicsBody?.collisionBitMask = spaceshipCategory | missileCategory | asteroidCategory
                    missile.physicsBody?.contactTestBitMask = asteroidCategory
    
                    missile.position = CGPoint(x: self.spaceship.position.x , y: self.spaceship.position.y + 10 )
                    missile.size = CGSize(width: 75, height: 75) 
                    addChild(missile)
    
                    let topButton = SKAction.moveTo(y: frame.height + 10 , duration: 0.3)
                    let remove = SKAction.removeFromParent() 
                    missile.run(SKAction.sequence([topButton, remove])) 
    
                    if self.atPoint(locatin).name == "spaceship" {
                        return
                    }
    
                }
            }
        }
    
        override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    
            let touch = touches.first 
            let buttonLocation = touch?.location(in: self)  
            switch self.atPoint(buttonLocation!).name == "button" { 
                case true:
                    break
    
                default:
                    let location = touches.first!.location(in: self) 
                    let action = SKAction.move(to: CGPoint(x: location.x, y:location.y + 20), duration: 0.1) 
                    spaceship.run(action) 
            }
    
        }
    
        override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        }
    
        override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        }
    
        override func update(_ currentTime: TimeInterval) {
            super.update(currentTime)
        }
    
        func addasteroid() {
    
            let names = ["asteroid1","asteroid2","asteroid3"]
            let index = Int.random(in: 0...2) 
            let name = names[index] 
            let asteroid = SKSpriteNode(imageNamed: name)
    
            let XHighest: CGFloat = self.frame.width / 2 - spaceship.size.width 
            let XLowest: CGFloat = self.frame.width / -2 + spaceship.size.width 
            let random = CGFloat.random(in: XLowest...XHighest) 
            asteroid.position = CGPoint(x: random, y: frame.width) 
            asteroid.size = CGSize(width: 100, height: 100) 
    
            asteroid.physicsBody = SKPhysicsBody(circleOfRadius: asteroid.frame.width)
            asteroid.physicsBody?.categoryBitMask = asteroidCategory
            asteroid.physicsBody?.collisionBitMask = spaceshipCategory | missileCategory | asteroidCategory
            asteroid.physicsBody?.contactTestBitMask = spaceshipCategory | missileCategory
    
            addChild(asteroid) 
    
            let move = SKAction.moveTo(y: frame.width / -2 - 100, duration: 1.0) 
            let remove = SKAction.removeFromParent()
            asteroid.run(SKAction.sequence([move, remove]))
        }
    
        func didBegin(_ contact: SKPhysicsContact) {
    
            var asteroid: SKPhysicsBody
            var target: SKPhysicsBody
    
            if contact.bodyA.categoryBitMask == asteroidCategory {
                asteroid = contact.bodyA
                target = contact.bodyB
    
            } else {
                asteroid = contact.bodyB
                target = contact.bodyA
            }
    
            guard let asteroidNode = asteroid.node else { return }
            guard let tagetNode = target.node else { return }
            guard let Bakuhatu = SKEmitterNode(fileNamed: "Bakuhatu") else { return }
            Bakuhatu.position = asteroidNode.position
            addChild(Bakuhatu)
    
            var asteroidLife: Int = 100
            let missilePW: Int = 50
    
            if target.categoryBitMask == missileCategory  {
                tagetNode.removeFromParent()
                asteroidLife -= missilePW
    
                if asteroidLife <= 0 {
                    asteroidNode.removeFromParent()
                }
            }
    
            self.run(SKAction.wait(forDuration: 1.0)) {
                Bakuhatu.removeFromParent()
            }
    
            if target.categoryBitMask == spaceshipCategory {
                asteroidNode.removeFromParent()
                Life -= 100
    
                if Life <= 0 {
                    GameOver()
                }
    
            }
    
        }
    
        func GameOver() {
            isPaused = true
            timar?.invalidate()
            Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { _ in
                self.gameVC.performSegue(withIdentifier: "gameover", sender: nil)
            }
        }
    
    }
    
[/code]


CC BY-SA 4.0
2020-05-06T10:52:39.567
66349
2020-05-06T11:55:43.293
2020-05-06T11:03:02.857
3060
39881
question
swift xcode spritekit
Swift SpriteKit 衝突処理（didBegin）が動作しない


現在お示しいただいたコードのように、`var asteroidLife: Int =
100`と言うローカル変数を宣言すれば、その変数は「値がリセットされ」るどころか、変数のスコープを抜け出すと変数自体がなくなってしまいます。

かと言って、`Life`のように`GameScene`クラスのインスタンス変数にしてしまうと、シーンごとに一つのライフしか管理できなくなるので、asteroidが多数でるあなたのゲームには使えないでしょう。(配列等を用意すると言う手もありますが、コードがどんどん複雑になるので、やめておいた方が無難です。余談ですが、Swiftでは型名以外の識別子は小文字で始めるのが普通です。かなり広く守られているルールなので、守られていないあなたのコードは(Swift経験者にとって)非常に読みづらいものとなってしまいます。)

* * *

「個々のasteroidがそれぞれ個別のライフを持つ」と言う状態は、オブジェクト指向には格好の題材で、今回の場合であれば、

  * **asteroid専用のクラスを作ってやる**

と言うのが最も適した方法であるように思われます。

こんな感じで、asteroid専用のクラス(`SKSpriteNode`のサブクラス)を定義してやります。

[code]

    class AsteroidNode: SKSpriteNode {
        var life: Int = 100
    }
    
[/code]

後は、

  * asteroidを生成するときには、`SKSpriteNode`ではなく、`AsteroidNode`を使う
  * asteroidのライフを操作する場合には、`AsteroidNode`の`life`プロパティを使う

と言う修正を行うだけです。

[code]

        func addasteroid() {
    
            let names = ["asteroid1","asteroid2","asteroid3"]
            let index = Int.random(in: 0...2)
            let name = names[index]
            let asteroid = AsteroidNode(imageNamed: name) //<- `SKSpriteNode`ではなく、`AsteroidNode`にする
    
            let xHighest: CGFloat = self.frame.width / 2 - spaceship.size.width
            let xLowest: CGFloat = self.frame.width / -2 + spaceship.size.width
            let random = CGFloat.random(in: xLowest...xHighest)
            asteroid.position = CGPoint(x: random, y: frame.width)
            asteroid.size = CGSize(width: 100, height: 100)
    
            asteroid.physicsBody = SKPhysicsBody(circleOfRadius: asteroid.frame.width)
            asteroid.physicsBody?.categoryBitMask = asteroidCategory
            asteroid.physicsBody?.collisionBitMask = spaceshipCategory | missileCategory | asteroidCategory
            asteroid.physicsBody?.contactTestBitMask = spaceshipCategory | missileCategory
    
            addChild(asteroid)
    
            let move = SKAction.moveTo(y: frame.width / -2 - 100, duration: 1.0)
            let remove = SKAction.removeFromParent()
            asteroid.run(SKAction.sequence([move, remove]))
        }
    
        func didBegin(_ contact: SKPhysicsContact) {
    
            var asteroid: SKPhysicsBody
            var target: SKPhysicsBody
    
            if contact.bodyA.categoryBitMask == asteroidCategory {
                asteroid = contact.bodyA
                target = contact.bodyB
    
            } else {
                asteroid = contact.bodyB
                target = contact.bodyA
            }
    
            guard let asteroidNode = asteroid.node as? AsteroidNode else { return } //<- `AsteroidNode`になっていることを確認
            guard let targetNode = target.node else { return }
            guard let bakuhatu = SKEmitterNode(fileNamed: "Bakuhatu") else { return }
            bakuhatu.position = asteroidNode.position
            addChild(bakuhatu)
    
            let missilePW: Int = 50
    
            if target.categoryBitMask == missileCategory  {
                targetNode.removeFromParent()
                asteroidNode.life -= missilePW //<- ローカル変数ではなく`AsteroidNode`のインスタンス変数を操作する
    
                if asteroidNode.life <= 0 { //<- 〃
                    asteroidNode.removeFromParent()
                }
            }
    
            self.run(SKAction.wait(forDuration: 1.0)) {
                bakuhatu.removeFromParent()
            }
    
            if target.categoryBitMask == spaceshipCategory {
                asteroidNode.removeFromParent()
                Life -= 100
    
                if Life <= 0 {
                    GameOver()
                }
    
            }
    
        }
    
[/code]

将来のことを考えると、`SpaceshipNode`クラスや`MissileNode`クラスも定義した方がいいかもしれません。


CC BY-SA 4.0
2020-05-06T11:42:24.733
66350
2020-05-06T11:55:43.293
2020-05-06T11:55:43.293
13972
13972
66349
answer