40337


どうやってもプレイヤーが壁を押してしまいます、どうすれば、skactionなどで移動したプレイヤー（RPG想定）が、壁に当たった時にそこで止まる様な仕様を実装できますか？

画面のピンク色の長方形の部分のNodeは川になっていて、歩行中のプレイヤーが歩けないようにしたくて、テクスチャーは入れずにダイナミックを入れ、Massを100くらいにしたりして、プレイヤーが接触したらそこで止まる様にしたいのですが、何をどうしても、SKActionやCGvecterで移動したプレイヤーがこのNodeを押してしまいます。  
PindにしてもプライヤーのMassやdentityを0にしても押してしまいます。プレイヤーの動きをはねのける様な、そんな壁の存在が欲しいのです。

[code]

    import SpriteKit
    import GameplayKit
    
    class GameScene: SKScene,SKPhysicsContactDelegate {
    
        var textureSide:[SKTexture] = []
        var textureUp:[SKTexture]   = []
        var textureDown:[SKTexture] = []
    
        var upB:SKSpriteNode!
        var downB:SKSpriteNode!
        var rightB:SKSpriteNode!
        var leftB:SKSpriteNode!
    
        var playerposition = CGPoint(x: 0, y: 0)
    
        var yellow:SKSpriteNode!
    
        var player:SKSpriteNode!
        var wall:SKSpriteNode!
    
        var rightpoti = true
    
        enum direction {
            case right
            case left
            case up
            case down    
        }
    
        override func didMove(to view: SKView) {
    
            physicsWorld.gravity = CGVector(dx: 0, dy: 0)
    
            wall = self.childNode(withName: "wall") as! SKSpriteNode
            wall.physicsBody?.usesPreciseCollisionDetection = true
            wall.zPosition = 5
    
            player = SKSpriteNode(imageNamed: "walkman8")
            player.position = playerposition
            player.physicsBody?.usesPreciseCollisionDetection = true
            player.zPosition = 2
            player.setScale(2)
            player.physicsBody = SKPhysicsBody(rectangleOf: player.frame.size)
            player.physicsBody?.categoryBitMask = 1
            player.physicsBody?.collisionBitMask = 0
            player.physicsBody?.contactTestBitMask = 0
            player.physicsBody?.density = 1
            player.physicsBody?.mass = 1
            player.physicsBody?.restitution = 2
            addChild(player)
    
            upB = self.childNode(withName: "upB") as! SKSpriteNode
            downB = self.childNode(withName: "downB") as! SKSpriteNode
            rightB = self.childNode(withName: "rightB")  as! SKSpriteNode
            leftB = self.childNode(withName: "leftB")  as! SKSpriteNode
    
            yellow = self.childNode(withName: "yellow") as! SKSpriteNode
            yellow.position = CGPoint(x: -0, y: -600)
    
            let atras = SKTextureAtlas(named: "walker")
    
            for i in 1...3{
                textureSide.append(atras.textureNamed("walkman" + String(i)))
            }
    
            for i in 6...7{
                textureDown.append(atras.textureNamed("walkman" + String(i)))
            }
    
            for i in 8...10{
                textureUp.append(atras.textureNamed("walkman" + String(i)))
            }
    
            let animation = SKAction.animate(with: textureDown, timePerFrame: 0.6 )
            player.run(SKAction.repeatForever(animation))
        }
    
        func didBegin(_ contact: SKPhysicsContact) {
            print("bodyA:\(String(describing: contact.bodyA.node?.name))")
    
            if contact.bodyA == wall  {
                player.run(SKAction.rotate(byAngle: 30, duration: 1))
            }
        }
    
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            for touch in touches {
                let location = touch.location(in: self)
    
                if self.atPoint(location) == upB {
                    movePlayer(Direction: .up)
                }
                if self.atPoint(location) == downB {
                    movePlayer(Direction: .down)
                }
                if self.atPoint(location) == leftB {
                    movePlayer(Direction: .left)
                }
                if self.atPoint(location) == rightB {
                    movePlayer(Direction: .right)
                }
            }
        }
    
        override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        }
    
        override func update(_ currentTime: TimeInterval) {
        }
    
        func movePlayer(Direction:direction){
            player.physicsBody?.velocity = CGVector(dx: 0, dy: 100)
        }
    }
    
[/code]

[![画像の説明をここに入力](https://i.stack.imgur.com/7YFaR.png)](https://i.stack.imgur.com/7YFaR.png)  
[![画像の説明をここに入力](https://i.stack.imgur.com/ulXzj.png)](https://i.stack.imgur.com/ulXzj.png)


CC BY-SA 3.0
2017-12-13T20:14:45.693
40254
2017-12-19T12:49:30.323
2017-12-17T15:39:33.457
3060
26638
question
spritekit
spritekitでプレイヤーが移動した時に、壁に当たったら動かない様にしたい


まずは、`SKPhysicsBody`の各プロパティがどのような意味を持つか、きちんと理解しましょう。

##
[SKPhysicsBody](https://developer.apple.com/documentation/spritekit/skphysicsbody)

長文ですが、(Appleのリファレンスページにしては珍しく)コード例も多数ありますし、ざっくり眺めて、どこにどんなことが書いてあるのか程度は頭に入れておいた方が良いでしょう。

  * [`isDynamic`](https://developer.apple.com/documentation/spritekit/skphysicsbody/1520132-isdynamic) (sksエディタのDynamicに相当)

物理シミュレーションの対象になるかどうかを決定します。物理シミュレーションというのは要は衝突時の反動や重力による影響を言います。よって「衝突時の反動で動いちゃダメ」なら必ず`false`(エディタ上ではチェックを入れない)にします。

  * [`categoryBitMask`](https://developer.apple.com/documentation/spritekit/skphysicsbody/1519869-categorybitmask)(Category Mask)と[`collisionBitMask`](https://developer.apple.com/documentation/spritekit/skphysicsbody/1520003-collisionbitmask)(Collision Mask)

衝突処理を行うかどうかは、あるノードの`categoryBitMask`の値と相手ノードの`collisionBitMask`の値によって決定されます。決定には二進法が絡むので、後でもうちょっと説明します。

あなたが当初「キャラクターがピンクの正方形の上‌​を移動し始めた（川の上を歩いちゃった）」という動作を見られたのは、このプロパティが適切に設定されていなかったためと考えられます。

  * `categoryBitMask`と[`contactTestBitMask`](https://developer.apple.com/documentation/spritekit/skphysicsbody/1519781-contacttestbitmask)(Contact Mask)

`SKPhysicsContactDelegate`による接触判定の対象になるかどうかを決定します。要は`didBegin(_:)`のような接触判定用のメソッドが呼び出されるかどうかを制御します。

* * *

## `categoryBitMask`と`collisionBitMask`の設定の仕方

あなたがご所望の動作をさせるためには、「壁」は物理シミュレーションで動いてはいけないが、「壁」と「プレイヤー」は衝突処理を行わないといけない、のでこれらを正しく設定しないといけません。

### `categoryBitMask`の設定

衝突処理・接触判定を考えて、画面上に表示されるノードを何種類かにカテゴリ分けして、それぞれに別のビットを割り当てておきます。

(例)  
プレーヤーキャラ `0b0001` (10進法だと1)  
敵キャラ `0b0010` (2)  
不動の障害物 `0b0100` (4)

※SpriteKitの紹介記事ではシフト演算(`<<`)を使っている場合が多いですが、Swiftでは2進法のリテラルが書けるので、その方がわかりやすいように思います。実際には32種類まで指定できますが、長くなるので4ビット分しか示していません。(他の28ビットは全部0。)  
※あなたの画面写真では壁のこの項目が0になってしまっていますが、これでは衝突処理や接触判定の対象にはなりません。(*)

### `collisionBitMask`の設定

衝突処理を行いたい相手の`categoryBitMask`を設定します。`categoryBitMask`はそれぞれ別のビットに指定されているはずなので、相手を複数指定することができます。

(例)  
プレーヤーキャラ⇒敵キャラと不動の障害物に対して衝突処理を行う  
`0b0110` (6) ※敵キャラの`categoryBitMask` `0b0010`と不動の障害物の`0b0100`を指定

敵キャラ⇒プレーヤーキャラと不動の障害物に対して衝突処理を行う  
`0b0101` (5) ※プレーヤーキャラの`0b0001`と不動の障害物の`0b0100`を指定

不動の障害物⇒プレーヤーキャラと敵キャラに対して衝突処理を行う  
`0b0011` (4) ※プレーヤーキャラの`0b0001`と敵キャラの`0b0010`を指定

(*)
上記のように衝突には2つのノードが関わるのでどちらに設定すべきかというところですが、他の設定項目によっては、片方だけの設定ではうまくいかない場合があります。「プレーヤー⇒不動の障害物」「不動の障害物⇒プレーヤー」の双方の視点から矛盾無い設定にしておいた方が良いでしょう。

### `contactTestBitMask`の設定

上記`collisionBitMask`と同様の考え方で設定してください。デリゲートメソッドの呼び出しは、SpriteKit的には重い処理になるので、可能な限り接触判定の相手は少なめに設定した方が良いでしょう。

* * *

## コードでは

以下のような設定を、`didMove(to:)`メソッド内の適当な場所に追加してみてください。もちろんsksエディタ上でどの場所にどんな値を入れれば良いかが理解出来ていれば、そちらの修正だけでコードの方はいじらなくても大丈夫のはずです。

[code]

        wall.physicsBody?.isDynamic = false
        wall.physicsBody?.categoryBitMask = 0b0100 //<- 不動の障害物用のビット
        wall.physicsBody?.collisionBitMask = 0b0011 //<- プレーヤーの`categoryBitMask`を含める
        wall.physicsBody?.contactTestBitMask = 0b0011
    
        player.physicsBody?.categoryBitMask = 0b0001 //<- プレーヤー用のビット
        player.physicsBody?.collisionBitMask = 0b0110 //<- 不動の障害物用の`categoryBitMask`を含める
        player.physicsBody?.contactTestBitMask = 0b0100 //<- 壁との接触判定は行うが、敵キャラ(まだあなたのコードにはないですが)との接触判定はない、という設定
    
[/code]

* * *

ご質問へのコメントに書いたように、あなたのプロジェクト全部を参照できるわけではありませんので、上記の修正だけで確実にご所望の動作になるかはわかりません。何かありましたら、コメント等でお知らせください。


CC BY-SA 3.0
2017-12-17T02:57:30.570
40337
2017-12-19T12:49:30.323
2017-12-19T12:49:30.323
13972
13972
40254
answer