SpriteKitでプレイヤーが障害物に衝突したときに自動的に画面中央に戻す方法を知りたい
Angry Birdsのような背景を動かすことで進んでるように見せるゲームを考えています。
地面の障害物(アイテム・ここではサンゴcoral)に衝突したら、プレイヤーは画面中央に自動的に戻ってしまうようにしたいのですがどうしたらいいでしょうか?
おそらく衝突の瞬間にremoveTo
を使えばいいと思うのですが、、
教えてください!
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
// MARK: - 定数定義
/// 定数
struct Constants {
/// Player画像
static let PlayerImages = ["shrimp01", "shrimp02", "shrimp03", "shrimp04"]
}
/// 衝突の判定につかうBitMask
struct ColliderType {
/// プレイキャラに設定するカテゴリ
static let Player: UInt32 = (1 << 0)
/// 天井・地面に設定するカテゴリ
static let World: UInt32 = (1 << 1)
/// サンゴに設定するカテゴリ
static let Coral: UInt32 = (1 << 2)
/// スコア加算用SKNodeに設定するカテゴリ
static let Score: UInt32 = (1 << 3)
/// スコア加算用SKNodeに衝突した際に設定するカテゴリ
static let None: UInt32 = (1 << 4)
}
// MARK: - 変数定義
/// プレイキャラ以外の移動オブジェクトを追加する空ノード
var baseNode: SKNode!
/// サンゴ関連のオブジェクトを追加する空ノード(リスタート時に活用)
var coralNode: SKNode!
/// プレイキャラ
var player: SKSpriteNode!
/// スコアを表示するラベル
var scoreLabelNode: SKLabelNode!
/// スコアの内部変数
var score: UInt32!
// MARK: - 関数定義
override func didMoveToView(view: SKView) {
// 変数の初期化
score = 0
// 物理シミュレーションを設定
self.physicsWorld.gravity = CGVector(dx: 0.0, dy: -2.0)
self.physicsWorld.contactDelegate = self
// 全ノードの親となるノードを生成
baseNode = SKNode()
baseNode.speed = 1.0
self.addChild(baseNode)
// 障害物を追加するノードを生成
coralNode = SKNode()
baseNode.addChild(coralNode)
// 背景画像を構築
self.setupBackgroundSea()
// 天井と地面を構築
self.setupCeilingAndLand()
// プレイキャラを構築
self.setupPlayer()
// 障害物のサンゴを構築
self.setupCoral()
// スコアラベルの構築
self.setupScoreLabel()
}
/// 背景画像を構築
func setupBackgroundSea() {
// 背景画像を読み込む
let texture = SKTexture(imageNamed: "background")
texture.filteringMode = .Nearest
// 必要な画像枚数を算出
let needNumber = 2.0 + (self.frame.size.width / texture.size().width)
// 左に画像一枚分移動アニメーションを作成
let moveAnim = SKAction.moveByX(-texture.size().width, y: 0.0, duration: NSTimeInterval(texture.size().width / 10.0))
// 元の位置に戻すアニメーションを作成
let resetAnim = SKAction.moveByX(texture.size().width, y: 0.0, duration: 0.0)
// 移動して元に戻すアニメーションを繰り返すアニメーションを作成
let repeatForeverAnim = SKAction.repeatActionForever(SKAction.sequence([moveAnim, resetAnim]))
// 画像の配置とアニメーションを設定
for var i:CGFloat = 0; i < needNumber; ++i {
// SKTextureからSKSpriteNodeを作成
let sprite = SKSpriteNode(texture: texture)
// 一番奥に配置
sprite.zPosition = -100.0
// 画像の初期位置を設定
sprite.position = CGPoint(x: i * sprite.size.width, y: self.frame.size.height / 2.0)
// アニメーションを設定
sprite.runAction(repeatForeverAnim)
// 親ノードに追加
baseNode.addChild(sprite)
}
}
func setupBackground(){
// 背景画像のスプライトを配置
let background = SKSpriteNode(imageNamed: "background")
background.position = CGPoint(x: self.size.width*0.5, y: self.size.height*0.5)
background.size = self.size
self.addChild(background)}
/// 天井と地面を構築
func setupland(){
// 背景画像のスプライトを配置
let land = SKSpriteNode(imageNamed: "land")
land.position = CGPoint(x: self.size.width*0.5, y: self.size.height*0.5)
land.size = self.size
self.addChild(land)}
/// 天井と地面を構築
func ceiling(){
// 背景画像のスプライトを配置
let ceiling = SKSpriteNode(imageNamed: "ceiling")
ceiling.position = CGPoint(x: self.size.width*0.5, y: self.size.height*0.5)
ceiling.size = self.size
self.addChild(ceiling)}
func setupCeilingAndLand() {
// 地面画像を読み込み
let land = SKTexture(imageNamed: "land")
land.filteringMode = .Nearest
// 必要な画像枚数を算出
var needNumber = 2.0 + (self.frame.size.width / land.size().width)
// 左に画像一枚分移動アニメーションを作成
let moveLandAnim = SKAction.moveByX(-land.size().width, y: 0.0, duration:NSTimeInterval(land.size().width / 100.0))
// 元の位置に戻すアニメーションを作成
let resetLandAnim = SKAction.moveByX(land.size().width, y: 0.0, duration: 0.0)
// 移動して元に戻すアニメーションを繰り返すアニメーションを作成
let repeatForeverLandAnim = SKAction.repeatActionForever(SKAction.sequence([moveLandAnim, resetLandAnim]))
// 画像の配置とアニメーションを設定
for var i:CGFloat = 0.0; i < needNumber; ++i {
// SKTextureからSKSpriteNodeを作成
let sprite = SKSpriteNode(texture: land)
// 画像の初期位置を設定
sprite.position = CGPoint(x: i * sprite.size.width, y: sprite.size.height / 2.0)
// 画像に物理シミュレーションを設定
sprite.physicsBody = SKPhysicsBody(texture: land, size: land.size())
sprite.physicsBody?.dynamic = false
sprite.physicsBody?.categoryBitMask = ColliderType.World
// アニメーションを設定
sprite.runAction(repeatForeverLandAnim)
// 親ノードに追加
baseNode.addChild(sprite)
}
// 天井画像を読み込み
let ceiling = SKTexture(imageNamed: "ceiling")
ceiling.filteringMode = .Nearest
// 必要な画像枚数を算出
needNumber = 2.0 + self.frame.size.width / ceiling.size().width
// 画像の配置とアニメーションを設定
for var i:CGFloat = 0.0; i < needNumber; i++ {
// SKTextureからSKSpriteNodeを作成
let sprite = SKSpriteNode(texture: ceiling)
// 画像の初期位置を設定
sprite.position = CGPoint(x: i * sprite.size.width, y: self.frame.size.height - sprite.size.height / 2.0)
// 画像に物理シミュレーションを設定
sprite.physicsBody = SKPhysicsBody(texture: ceiling, size: ceiling.size())
sprite.physicsBody?.dynamic = false
sprite.physicsBody?.categoryBitMask = ColliderType.World
// アニメーションを設定
sprite.runAction(repeatForeverLandAnim)
// 親ノードに追加
baseNode.addChild(sprite)
}
}
/// プレイヤーを構築
func setupPlayer() {
// Playerのパラパラアニメーション作成に必要なSKTextureクラスの配列を定義
var playerTexture = [SKTexture]()
// パラパラアニメーションに必要な画像を読み込む
for imageName in Constants.PlayerImages {
let texture = SKTexture(imageNamed: imageName)
texture.filteringMode = .Linear
playerTexture.append(texture)
}
// キャラクターのアニメーションをパラパラ漫画のように切り替える
let playerAnimation = SKAction.animateWithTextures(playerTexture, timePerFrame: 0.2)
// パラパラアニメーションをループさせる
let loopAnimation = SKAction.repeatActionForever(playerAnimation)
// キャラクターを生成
player = SKSpriteNode(texture: playerTexture[0])
// 初期表示位置を設定
player.position = CGPoint(x: self.frame.size.width * 0.35, y: self.frame.size.height * 0.6)
// アニメーションを設定
player.runAction(loopAnimation)
// 物理シミュレーションを設定
player.physicsBody = SKPhysicsBody(texture: playerTexture[0], size: playerTexture[0].size())
player.physicsBody?.dynamic = true
player.physicsBody?.allowsRotation = false
// 自分自身にPlayerカテゴリを設定
player.physicsBody?.categoryBitMask = ColliderType.Player
// 衝突判定相手にWorldとCoralを設定
player.physicsBody?.collisionBitMask = ColliderType.World | ColliderType.Coral
player.physicsBody?.contactTestBitMask = ColliderType.World | ColliderType.Coral
self.addChild(player)
}
/// 障害物のサンゴを構築
func setupCoral() {
// サンゴ画像を読み込み
let coralUnder = SKTexture(imageNamed: "coral_under")
coralUnder.filteringMode = .Linear
let coralAbove = SKTexture(imageNamed: "coral_above")
coralAbove.filteringMode = .Linear
// 移動する距離を算出
let distanceToMove = CGFloat(self.frame.size.width + 2.0 * coralUnder.size().width)
// 画面外まで移動するアニメーションを作成
let moveAnim = SKAction.moveByX(-distanceToMove, y: 0.0, duration:NSTimeInterval(distanceToMove / 100.0))
// 自身を取り除くアニメーションを作成
let removeAnim = SKAction.removeFromParent()
// 2つのアニメーションを順に実行するアニメーションを作成
let coralAnim = SKAction.sequence([moveAnim, removeAnim])
// サンゴを生成するメソッドを呼び出すアニメーションを作成
let newCoralAnim = SKAction.runBlock({
// サンゴに関するノードを乗せるノードを作成
let coral = SKNode()
coral.position = CGPoint(x: self.frame.size.width + coralUnder.size().width * 2, y: 0.0)
coral.zPosition = -40.0
// 地面から伸びるサンゴのy座標を算出
let height = UInt32(self.frame.size.width / 12)
let y = CGFloat(arc4random_uniform(height * 2) + height)
// 地面から伸びるサンゴを作成
let under = SKSpriteNode(texture: coralUnder)
under.position = CGPoint(x: 0.0, y: y)
under.zRotation = CGFloat(M_PI / 2)
// サンゴに物理シミュレーションを設定
under.physicsBody = SKPhysicsBody(texture: coralUnder, size: under.size)
under.physicsBody?.dynamic = false
under.physicsBody?.categoryBitMask = ColliderType.Coral
under.physicsBody?.contactTestBitMask = ColliderType.Player
coral.addChild(under)
// スコアをカウントアップするノードを作成
let scoreNode = SKNode()
scoreNode.position = CGPoint(x: 0.0, y: y)
// スコアノードに物理シミュレーションを設定
scoreNode.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 5, height: self.frame.size.width))
scoreNode.physicsBody?.dynamic = false
scoreNode.physicsBody?.categoryBitMask = ColliderType.Score
scoreNode.physicsBody?.contactTestBitMask = ColliderType.Player
coral.addChild(scoreNode)
coral.runAction(coralAnim)
self.coralNode.addChild(coral)
})
// 一定間隔待つアニメーションを作成
let delayAnim = SKAction.waitForDuration(2.5)
// 上記2つを永遠と繰り返すアニメーションを作成
let repeatForeverAnim = SKAction.repeatActionForever(SKAction.sequence([newCoralAnim, delayAnim]))
// この画面で実行
self.runAction(repeatForeverAnim)
}
/// スコアラベルを構築
func setupScoreLabel() {
// フォント名"Arial Bold"でラベルを作成
scoreLabelNode = SKLabelNode(fontNamed: "Arial Bold")
// フォント色を黄色に設定
scoreLabelNode.fontColor = UIColor.blackColor()
// 表示位置を設定
scoreLabelNode.position = CGPoint(x: self.frame.width / 2.0, y: self.frame.size.height * 0.9)
// 最前面に表示
scoreLabelNode.zPosition = 100.0
// スコアを表示
scoreLabelNode.text = String(score)
self.addChild(scoreLabelNode)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
// ゲーム進行中のとき
if 0.0 < baseNode.speed {
for touch in touches {
_ = touch.locationInNode(self)
// プレイヤーに加えられている力をゼロにする
player.physicsBody?.velocity = CGVector.zero
// プレイヤーにy軸方向へ力を加える
player.physicsBody?.applyImpulse(CGVector(dx: 0.0, dy: 23.0))
}
} else if baseNode.speed == 0.0 && player.speed == 0.0 {
// ゲームオーバー時はリスタート
// 障害物を全て取り除く
coralNode.removeAllChildren()
// スコアをリセット
score = 0
scoreLabelNode.text = String(score)
// プレイキャラを再配置
player.position = CGPoint(x: self.frame.size.width * 0.35, y: self.frame.size.height * 0.6)
player.physicsBody?.velocity = CGVector.zero
player.physicsBody?.collisionBitMask = ColliderType.World | ColliderType.Coral
player.zRotation = 0.0
// アニメーションを開始
player.speed = 1.0
baseNode.speed = 1.0
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
// MARK: - SKPhysicsContactDelegateプロトコルの実装
/// 衝突開始時のイベントハンドラ
func didBeginContact(contact: SKPhysicsContact) {
// 既にゲームオーバー状態の場合
if baseNode.speed <= 0.0 {
return
}
let rawScoreType = ColliderType.Score
let rawNoneType = ColliderType.None
if (contact.bodyA.categoryBitMask & rawScoreType) == rawScoreType ||
(contact.bodyB.categoryBitMask & rawScoreType) == rawScoreType {
let move = SKAction.moveTo(CGPoint(x: 25.5, y: 100.0) ,duration: 1.5)
// スコアを加算しラベルに反映
score = score + 1
scoreLabelNode.text = String(score)
// スコアラベルをアニメーション
let scaleUpAnim = SKAction.scaleTo(1.5, duration: 0.1)
let scaleDownAnim = SKAction.scaleTo(1.0, duration: 0.1)
scoreLabelNode.runAction(SKAction.sequence([scaleUpAnim, scaleDownAnim]))
// スコアカウントアップに設定されているcontactTestBitMaskを変更
if (contact.bodyA.categoryBitMask & rawScoreType) == rawScoreType {
contact.bodyA.categoryBitMask = ColliderType.None
contact.bodyA.contactTestBitMask = ColliderType.None
} else {
contact.bodyB.categoryBitMask = ColliderType.None
contact.bodyB.contactTestBitMask = ColliderType.None
}
}
}
}