ブロックと敵、ドラゴンと炎と衝突のパターンを増やしていく。ゲームっぽくなってきました。
単純な衝突は前回が参考になるかも。
今回は以下の衝突を演出。
炎と敵が衝突した場合は両方消滅。スパーク演出あり。
炎とブロックは炎のみ消滅。スパーク演出あり。
ドラゴンとブロックはスパーク演出のみ。
後、画面の端と炎は炎のみ消滅。
ドラゴンと壁は衝突のみ。(ドラゴンがブロックに押し出されてゲーム画面外に出ないようにする)
enum CollisionMember {
case Block
case Fire
case Enemy
case Dragon
case Edge
func toInt() ->UInt32{
switch self{
case CollisionMember.Block:
return 0x1 << 0
case CollisionMember.Fire:
return 0x1 << 1
case CollisionMember.Enemy:
return 0x1 << 2
case CollisionMember.Dragon:
return 0x1 << 3
case CollisionMember.Edge:
return 0x1 << 4
default:
return 0x1 << 0
}
}
}
衝突時に何が衝突したかを判定するのは何故かbitMask。特に便利ってわけでもないけど列挙体で表現してみました。単に列挙体を使いたかっただけかもしれません。
override func didMoveToView(view: SKView) {
/* 画面外にキャラが行かなくなるようにする */
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
self.physicsBody?.categoryBitMask = CollisionMember.Edge.toInt()
self.physicsBody?.contactTestBitMask = CollisionMember.Fire.toInt()
// ブロック
let violet = UIColor(red: 0.28, green: 0.24, blue: 0.55, alpha: 1.0)
let block = SKSpriteNode(color: violet, size: CGSizeMake(300, 50))
block.position = CGPoint(x:700, y:CGRectGetMidY(self.frame))
block.lightingBitMask = 1
block.zPosition = 10
//衝突イベントには必須
block.physicsBody = SKPhysicsBody(rectangleOfSize: block.size)
block.physicsBody?.categoryBitMask = CollisionMember.Block.toInt()
block.physicsBody?.contactTestBitMask = CollisionMember.Dragon.toInt() | CollisionMember.Fire.toInt()
// 重力の影響なし
block.physicsBody?.dynamic = false
// 背景と同じ速度で動く
block.runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.moveToX(-430, duration: 13.0),
SKAction.moveToX(self.size.width+480, duration: 0.0)])
))
self.addChild(block)
ブロックを生成し、背景の流れる速度と同じくらいで向かってくるようにする。しかも永遠に。
肝心の衝突に関するプロパティは以下でちょっと詳しく。
block.physicsBody?.categoryBitMask = CollisionMember.Block.toInt()
categoryBitMaskで、自分がどういった存在かをセットする。この場合、ブロック。
block.physicsBody?.contactTestBitMask = CollisionMember.Dragon.toInt() | CollisionMember.Fire.toInt()
contactTestBitMaskでぶつかる対象をセット。ここでセットしてないものとぶつかっても衝突イベントが発生しない。この場合はドラゴンと炎。
次にドラゴン。
//キャラ画像
let dragonImage = UIImage(named: "charImage1.png")
let texture = SKTexture(image: dragonImage!)
let dragon = SKSpriteNode(texture: texture)
dragon.name = "dragon"
dragon.position = CGPoint(x: 80, y: CGRectGetMidY(self.frame))
dragon.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(250, 250))
dragon.physicsBody?.categoryBitMask = CollisionMember.Dragon.toInt()
dragon.physicsBody?.contactTestBitMask = CollisionMember.Block.toInt() | CollisionMember.Enemy.toInt()
// ぶつかった時に傾くかどうか
dragon.physicsBody?.allowsRotation = false
地味に重要なのがこれ。
// ぶつかった時に傾くかどうか
dragon.physicsBody?.allowsRotation = false
衝突したときに向きが変わらないようにする。
炎は前回とほぼ変わらず。直接bitMaskを指定していたのを列挙体に変更。やはり数字より意味が伝わりやすいように思える。
fire.physicsBody?.categoryBitMask = CollisionMember.Fire.toInt()
fire.physicsBody?.contactTestBitMask = CollisionMember.Block.toInt()
最後に敵。敵は3秒ごとにランダムに呼び出されるようにした。この辺のロジックはこちらを参考に。というかほぼそのまま使わせて貰っています。
// エネミー作成
func createEnemy(){
let y = Int(arc4random() % 450) + 150
let enemy = SKSpriteNode(color: UIColor.blueColor(), size: CGSizeMake(50, 50))
enemy.position = CGPoint(x:1000, y:y)
enemy.lightingBitMask = 1
enemy.zPosition = 10
//衝突イベントには必須
enemy.physicsBody = SKPhysicsBody(rectangleOfSize: enemy.size)
enemy.physicsBody?.categoryBitMask = CollisionMember.Enemy.toInt()
enemy.physicsBody?.contactTestBitMask = CollisionMember.Dragon.toInt() | CollisionMember.Fire.toInt()
// 重力の影響なし
enemy.physicsBody?.dynamic = false
let line = SKAction.moveByX(-1000, y: 0, duration: 3.5)
let remove = SKAction.removeFromParent()
let sequence = SKAction.sequence([line, remove])
enemy.runAction(sequence)
self.addChild(enemy)
}
衝突時に呼び出されるイベント内でそれぞれが衝突した場合を書くのだけど、ここはもっとすっきりした書き方がないものだろうか
//衝突時のイベント
func didBeginContact(contact: SKPhysicsContact) {
//このメソッドを上書きすると衝突したイベントの衝突後の処理を書かねばならない
if((CollisionMember.Fire.toInt() == contact.bodyA.categoryBitMask ||
CollisionMember.Fire.toInt() == contact.bodyB.categoryBitMask) &&
(CollisionMember.Block.toInt() == contact.bodyA.categoryBitMask ||
CollisionMember.Block.toInt() == contact.bodyB.categoryBitMask))
{
// 衝突時のスパーク演出
let firePath = NSBundle.mainBundle().pathForResource("Spark", ofType: "sks")
let fire = NSKeyedUnarchiver.unarchiveObjectWithFile(firePath!) as SKEmitterNode
fire.position.x = contact.contactPoint.x
fire.position.y = contact.contactPoint.y + 30
fire.numParticlesToEmit = 80
self.addChild(fire)
// 火の方を消す
if(contact.bodyA.categoryBitMask == CollisionMember.Fire.toInt()){
contact.bodyA.node?.removeFromParent()
} else {
contact.bodyB.node?.removeFromParent()
}
}
//ドラゴンとブロックが衝突した場合
if((CollisionMember.Dragon.toInt() == contact.bodyA.categoryBitMask ||
CollisionMember.Dragon.toInt() == contact.bodyB.categoryBitMask ) &&
(CollisionMember.Block.toInt() == contact.bodyA.categoryBitMask ||
CollisionMember.Block.toInt() == contact.bodyB.categoryBitMask)){
// 衝突時のスパーク演出
let firePath = NSBundle.mainBundle().pathForResource("Spark", ofType: "sks")
let fire = NSKeyedUnarchiver.unarchiveObjectWithFile(firePath!) as SKEmitterNode
//fire.particleColor = UIColor(red: 238/255.0, green: 130/255.0, blue: 238/255, alpha: 1.0)
fire.particleColorSequence = nil;
fire.particleColorBlendFactor = 1.0
fire.particleColor = UIColor.redColor()
fire.position.x = contact.contactPoint.x
fire.position.y = contact.contactPoint.y + 30
fire.numParticlesToEmit = 80
self.addChild(fire)
}
// 炎と敵が衝突した場合
if((CollisionMember.Fire.toInt() == contact.bodyA.categoryBitMask ||
CollisionMember.Fire.toInt() == contact.bodyB.categoryBitMask ) &&
(CollisionMember.Enemy.toInt() == contact.bodyA.categoryBitMask ||
CollisionMember.Enemy.toInt() == contact.bodyB.categoryBitMask)){
let firePath = NSBundle.mainBundle().pathForResource("Spark", ofType: "sks")
let fire = NSKeyedUnarchiver.unarchiveObjectWithFile(firePath!) as SKEmitterNode
fire.position.x = contact.contactPoint.x
fire.position.y = contact.contactPoint.y + 30
fire.numParticlesToEmit = 80
self.addChild(fire)
// 両方を消す
contact.bodyA.node?.removeFromParent()
contact.bodyB.node?.removeFromParent()
}
// キャラと敵が衝突した場合
if((CollisionMember.Dragon.toInt() == contact.bodyA.categoryBitMask ||
CollisionMember.Dragon.toInt() == contact.bodyB.categoryBitMask ) &&
(CollisionMember.Enemy.toInt() == contact.bodyA.categoryBitMask ||
CollisionMember.Enemy.toInt() == contact.bodyB.categoryBitMask)){
// 衝突時のスパーク演出
let firePath = NSBundle.mainBundle().pathForResource("Spark", ofType: "sks")
let fire = NSKeyedUnarchiver.unarchiveObjectWithFile(firePath!) as SKEmitterNode
fire.particleColorSequence = nil;
fire.particleColorBlendFactor = 1.0
fire.particleColor = UIColor.redColor()
fire.position.x = contact.contactPoint.x
fire.position.y = contact.contactPoint.y + 30
fire.numParticlesToEmit = 80
self.addChild(fire)
// 敵の方を消す
if(contact.bodyA.categoryBitMask == CollisionMember.Enemy.toInt()){
contact.bodyA.node?.removeFromParent()
} else {
contact.bodyB.node?.removeFromParent()
}
}
// 壁と炎が衝突した場合
if((CollisionMember.Fire.toInt() == contact.bodyA.categoryBitMask ||
CollisionMember.Fire.toInt() == contact.bodyB.categoryBitMask ) &&
(CollisionMember.Edge.toInt() == contact.bodyA.categoryBitMask ||
CollisionMember.Edge.toInt() == contact.bodyB.categoryBitMask)){
// 火の方を消す
if(contact.bodyA.categoryBitMask == CollisionMember.Fire.toInt()){
contact.bodyA.node?.removeFromParent()
} else {
contact.bodyB.node?.removeFromParent()
}
}
}
衝突時のスパークは元々作ったものをソースからでも色やサイズを変更出来るので流用してます。
衝突は以上。以降に衝突のイベントが増えても基本的にこのロジックに追加していくやり方でなんとかなると思います。敵はもう少し種類を増やして、ドラゴンも攻撃パターンを増やしたいが、その前にドラゴンのHPや攻撃による点数といったものを先に実装していきたい。