はじめに
SpriteKit でノードを衝突させる方法についてです。
SKPhysicsBody
とりあえずまずはノードに SKPhysicsBody を設定してやる必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
final class HogeViewController: UIViewController { @IBOutlet private weak var skView: SKView! override func viewDidLoad() { super.viewDidLoad() let scene = HogeScene(size: skView.frame.size) skView.showsPhysics = true skView.presentScene(scene) } } final class HogeScene: SKScene { override func didMove(to view: SKView) { physicsWorld.gravity = .zero let red = SKSpriteNode(imageNamed: "frog_red") red.position = .init(x: frame.midX, y: frame.midY) red.physicsBody = .init(texture: red.texture!, size: red.texture!.size()) addChild(red) let green = SKSpriteNode(imageNamed: "frog_green") green.position = .init(x: frame.midX - 100, y: frame.midY - 100) green.physicsBody = .init(rectangleOf: .init(width: 80, height: 80)) addChild(green) let blue = SKSpriteNode(imageNamed: "frog_blue") blue.position = .init(x: frame.midX + 100, y: frame.midY - 100) blue.physicsBody = .init(circleOfRadius: 20) addChild(blue) } } |
skView.showsPhysics = true により physicsBody を青線で表示してくれます。
イニシャライザは下記があるようです。
1 2 3 4 5 6 7 8 9 10 11 12 |
init(circleOfRadius: CGFloat) init(circleOfRadius: CGFloat, center: CGPoint) init(rectangleOf: CGSize) init(rectangleOf: CGSize, center: CGPoint) init(polygonFrom: CGPath) init(texture: SKTexture, size: CGSize) init(texture: SKTexture, alphaThreshold: Float, size: CGSize) init(bodies: [SKPhysicsBody]) init(edgeLoopFrom: CGRect) init(edgeFrom: CGPoint, to: CGPoint) init(edgeLoopFrom: CGPath) init(edgeChainFrom: CGPath) |
下記のプロパティとメソッドがあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
// 重力の影響を受けるかどうか(デフォはtrue) var affectedByGravity: Bool // 力によって回転するかどうか(デフォはtrue) var allowsRotation: Bool // 力によって動かされるかどうか(デフォはtrue) var isDynamic: Bool // 質量(kg) var mass: CGFloat // 密度(kg/㎡) var density: CGFloat // 面積(㎡) var area: CGFloat // 摩擦力(0.0 ~ 1.0でデフォは0.2) var friction: CGFloat // どれだけ減衰するか(0.0 ~ 1.0でデフォは0.2) var restitution: CGFloat // A property that reduces the body’s linear velocity.(0.0 ~ 1.0でデフォは0.1) var linearDamping: CGFloat // A property that reduces the body’s rotational velocity.(0.0 ~ 1.0でデフォは0.1) var angularDamping: CGFloat // 属するカテゴリ(デフォは0xFFFFFFFF) var categoryBitMask: UInt32 // 衝突できるSKPhysicsBodyのカテゴリ(デフォは0xFFFFFFFF) var collisionBitMask: UInt32 // 衝突時に通知するSKPhysicsBodyのカテゴリ(デフォは0x00000000) var contactTestBitMask: UInt32 // 精度の高い衝突判定をするかどうか(デフォはfalse) var usesPreciseCollisionDetection: Bool // 速度(m/秒) var velocity: CGVector // 角速度(ラジアン/秒) var angularVelocity: CGFloat // オブジェクトが物理シミュレーション内で静止しているかどうか var isResting: Bool // physicsBodyが設定されているノード var node: SKNode? // physicsBodyに接続されているすべてのSKPhysicsJoint var joints: [SKPhysicsJoint] // 影響するSKFieldNodeのカテゴリ(デフォは0xFFFFFFFF) var fieldBitMask: UInt32 // 電荷 var charge: CGFloat // 親ノードに固定されているかどうか(デフォはfalse) var pinned: Bool // 接触しているすべてのSKPhysicsBody func allContactedBodies() -> [SKPhysicsBody] // 重心に力を加える(重力とかずっと力がかかるやつ?) func applyForce(CGVector) // トルクを与える func applyTorque(CGFloat) // 特定の位置に力を加える func applyForce(CGVector, at: CGPoint) // 重心にインパルスを与える(一時的に力がかかるやつ?) func applyImpulse(CGVector) // 角運動量を与えるインパルスを与える func applyAngularImpulse(CGFloat) // 特定の位置にインパルスを与える func applyImpulse(CGVector, at: CGPoint) |
衝突判定で重要なのは下記。
1 2 3 4 5 6 |
// 属するカテゴリ(デフォは0xFFFFFFFF) var categoryBitMask: UInt32 // 衝突できるSKPhysicsBodyのカテゴリ(デフォは0xFFFFFFFF) var collisionBitMask: UInt32 // 衝突時に通知するSKPhysicsBodyのカテゴリ(デフォは0x00000000) var contactTestBitMask: UInt32 |
衝突判定
ScneKit の話ですが bitMask の考え方は同じです。
SceneKitの衝突判定(Swift)
衝突させる
物体が衝突するかどうかは下記2つの値が関係しています。
1 2 3 4 |
// 属するカテゴリ(デフォは0xFFFFFFFF) var categoryBitMask: UInt32 // 衝突できるSKPhysicsBodyのカテゴリ(デフォは0xFFFFFFFF) var collisionBitMask: UInt32 |
categoryBitMask
と collisionBitMask
の AND(論理積)が 0 以外の場合に衝突が起きます。
こんな感じで実装して applyImpulse
で動かしてみるとこうなります。
collisionBitMask完全に理解した! pic.twitter.com/6mULSNc3EW
— am10 (@am103141592) August 4, 2022
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
let red = SKShapeNode(rectOf: .init(width: 60, height: 60)) red.name = "red" red.fillColor = .systemRed red.position = .init(x: frame.midX - 100, y: frame.midY) red.physicsBody = .init(rectangleOf: .init(width: 60, height: 60)) red.physicsBody?.affectedByGravity = false addChild(red) let blue = SKShapeNode(rectOf: .init(width: 60, height: 60)) blue.name = "blue" blue.fillColor = .systemBlue blue.position = .init(x: frame.midX, y: frame.midY) blue.physicsBody = .init(rectangleOf: .init(width: 60, height: 60)) blue.physicsBody?.affectedByGravity = false addChild(blue) let yellow = SKShapeNode(rectOf: .init(width: 60, height: 60)) yellow.name = "yellow" yellow.fillColor = .systemYellow yellow.position = .init(x: frame.midX + 100, y: frame.midY) yellow.physicsBody = .init(rectangleOf: .init(width: 60, height: 60)) yellow.physicsBody?.affectedByGravity = false addChild(yellow) red.physicsBody?.categoryBitMask = 0b000001 red.physicsBody?.collisionBitMask = 0b000011 blue.physicsBody?.categoryBitMask = 0b000010 blue.physicsBody?.collisionBitMask = 0b000100 yellow.physicsBody?.categoryBitMask = 0b001000 yellow.physicsBody?.collisionBitMask = 0b001000 |
重要なのはここ!
1 2 3 4 5 6 7 8 |
red.physicsBody?.categoryBitMask = 0b000001 red.physicsBody?.collisionBitMask = 0b000011 blue.physicsBody?.categoryBitMask = 0b000010 blue.physicsBody?.collisionBitMask = 0b000100 yellow.physicsBody?.categoryBitMask = 0b001000 yellow.physicsBody?.collisionBitMask = 0b001000 |
■red を動かした場合
- red の categoryBitMask(0b000001)と blue の collisionBitMask(0b000100)の論理積は 0
- blue の categoryBitMask(0b000010)と red の collisionBitMask(0b000011)の論理積は 10
結果 red と blue は衝突はしますが blue は動きません。
■blue を動かした場合
- blue の categoryBitMask(0b000010)と red の collisionBitMask(0b000011)の論理積は 10
結果 red と blue は衝突し red も動きます。
■yellow を動かした場合
- yellow の categoryBitMask(0b001000)と red の collisionBitMask(0b000011)の論理積は 0
- yellow の categoryBitMask(0b001000)と blue の collisionBitMask(0b000100)の論理積は 0
- yellow の collisionBitMask(0b001000)と red の categoryBitMask(0b000001)の論理積は 0
- yellow の collisionBitMask(0b001000)と blue の categoryBitMask(0b000010)の論理積は 0
結果 yellow は red, blue ともに衝突しません。
衝突判定
物体が衝突を通知するかどうかは下記2つの値が関係しています。
1 2 3 4 |
// 属するカテゴリ(デフォは0xFFFFFFFF) var categoryBitMask: UInt32 // 衝突時に通知するSKPhysicsBodyのカテゴリ(デフォは0x00000000) var contactTestBitMask: UInt32 |
categoryBitMask
と contactTestBitMask
の AND(論理積)が 0 以外の場合に衝突が通知されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
physicsWorld.contactDelegate = self let red = SKShapeNode(rectOf: .init(width: 60, height: 60)) red.name = "red" red.fillColor = .systemRed red.position = .init(x: frame.midX - 60, y: frame.midY) red.physicsBody = .init(rectangleOf: .init(width: 60, height: 60)) red.physicsBody?.affectedByGravity = false addChild(red) let blue = SKShapeNode(rectOf: .init(width: -60, height: 60)) blue.name = "blue" blue.fillColor = .systemBlue blue.position = .init(x: frame.midX, y: frame.midY) blue.physicsBody = .init(rectangleOf: .init(width: 60, height: 60)) blue.physicsBody?.affectedByGravity = false addChild(blue) red.physicsBody?.categoryBitMask = 0b000001 red.physicsBody?.contactTestBitMask = 0b000011 blue.physicsBody?.categoryBitMask = 0b000010 blue.physicsBody?.contactTestBitMask = 0b000100 extension HogeScene: SKPhysicsContactDelegate { func didBegin(_ contact: SKPhysicsContact) { print("\(contact.bodyA.node!.name!)と\(contact.bodyB.node!.name!)の衝突!!") } func didEnd(_ contact: SKPhysicsContact) { print("\(contact.bodyA.node!.name!)と\(contact.bodyB.node!.name!)の衝突終了!!") } } |
上記のように実装すると red, blue どちらから衝突させた場合も didBegin
, didEnd
ともに呼ばれました。
- red の categoryBitMask(0b000001)と blue の contactTestBitMask(0b000100)の論理積は 0
- blue の categoryBitMask(0b000010)と red の contactTestBitMask(0b000011)の論理積は 10
上記の結果どちらの場合も衝突通知が発生するものと思われます。
yellow も置いて下記のように bitMask を設定するとこうなります。
- red と blue
衝突しますが通知はされません。 - blue と yellow
衝突しないし通知もされません。 - red と yellow
衝突しないですが通知はされます。
1 2 3 4 5 6 7 8 9 10 11 |
red.physicsBody?.categoryBitMask = 0b000001 red.physicsBody?.collisionBitMask = 0b000011 red.physicsBody?.contactTestBitMask = 0b001001 blue.physicsBody?.categoryBitMask = 0b000010 blue.physicsBody?.collisionBitMask = 0b000100 blue.physicsBody?.contactTestBitMask = 0b000100 yellow.physicsBody?.categoryBitMask = 0b001000 yellow.physicsBody?.collisionBitMask = 0b001000 yellow.physicsBody?.contactTestBitMask = 0b001000 |
おわりに
これでノードの衝突もできるようになりました!ビットマスクがややこしいですがこれで衝突させ放題です!
参考
SpriteKitの衝突処理について(categoryBitMask collisionBitMask contactTestBitMask 使い方)
コメント