LoginSignup
3
3

More than 5 years have passed since last update.

spritekitのゲームのあたり判定について

Posted at

現在spritkitでゲームを作っているプログラミング初心者です。

画面をタップ、連打すると地面(下)に立っていた
キャラクター(メーターの代わり)が重力に逆らい
上にあがり、メーターの上部(設置した天井)にメモリが衝突すると
得点になり(ゲージが1たまり)、その後また同じことを繰り返すようにリセットようにしたいのですが、困っています。(得点は引き継ぎで何点取れるかをカウントするゲームです。)

以前作ったアングリーバード?のようなゲームを基にして作ろうと思っています。
今できないのが、上の天井(設置したもの)に当たった際、得点を加算することです。
あたり判定がうまく使えません。
何かヒントを教えてください。

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.setupCeilingAndLand()
        // プレイキャラを構築
        self.setupPlayer()
        // 障害物のサンゴを構築
        self.setupCoral()
        // スコアラベルの構築
        self.setupScoreLabel()
    }



    /// 天井と地面を構築
    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

            // スコアをカウントアップするノードを作成
            let scoreNode = SKNode()
            scoreNode.position = CGPoint(x: self.size.width , y: self.size.height / 2.0)

            // スコアノードに物理シミュレーションを設定
            scoreNode.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 10.0, height: self.frame.size.height))
            scoreNode.physicsBody?.dynamic = false
            scoreNode.physicsBody?.categoryBitMask = ColliderType.Score
            scoreNode.physicsBody?.contactTestBitMask = ColliderType.Player
            ceiling.addChild(scoreNode)
            ceiling.runAction(coralAnim)

            self.coralNode.addChild(coral)            // アニメーションを設定
            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.height / 12)
            let y = CGFloat(arc4random_uniform(height * 2) + height)

            // 地面から伸びるサンゴを作成
            let under = SKSpriteNode(texture: coralUnder)
            under.position = CGPoint(x: 0.0, y: y)

            // サンゴに物理シミュレーションを設定
            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 above = SKSpriteNode(texture: coralAbove)
            above.position = CGPoint(x: 0.0, y: y + (under.size.height / 2.0) + 160.0 + (above.size.height / 2.0))

            // サンゴに物理シミュレーションを設定
            above.physicsBody = SKPhysicsBody(texture: coralAbove, size: above.size)
            above.physicsBody?.dynamic = false
            above.physicsBody?.categoryBitMask = ColliderType.Coral
            above.physicsBody?.contactTestBitMask = ColliderType.Player
            coral.addChild(above)

            // スコアをカウントアップするノードを作成
            let scoreNode = SKNode()
            scoreNode.position = CGPoint(x: (above.size.width / 2.0) + 5.0, y: self.frame.height / 2.0)

            // スコアノードに物理シミュレーションを設定
            scoreNode.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 10.0, height: self.frame.size.height))
            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?) {


    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 {
                // スコアを加算しラベルに反映
                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
                }
        } else if (contact.bodyA.categoryBitMask & rawNoneType) == rawNoneType ||
            (contact.bodyB.categoryBitMask & rawNoneType) == rawNoneType {
                // なにもしない
        } else {
            // baseNodeに追加されたものすべてのアニメーションを停止
            baseNode.speed = 0.0

            // プレイキャラのBitMaskを変更
            player.physicsBody?.collisionBitMask = ColliderType.World
            // プレイキャラに回転アニメーションを実行
            let rolling = SKAction.rotateByAngle(CGFloat(M_PI) * player.position.y * 0.01, duration: 1.0)
            player.runAction(rolling, completion:{
                // アニメーション終了時にプレイキャラのアニメーションを停止
                self.player.speed = 0.0
            })
        }
    }

}
3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3