モーリーのメモ

アプリ開発等(プログラミング、CG作成)、興味を持ったことを実践してまとめるブログです。

モーリーのメモ

マリオみたいな2Dアクションゲームを作る! その4 接触イベントで着地判定する:Cocos Creator

 ◆ Cocos Creatorスーパーマリオみたいな2Dアクションゲームを作ります。◆
 ◆ このシリーズの他の記事を見るには『PlatformerGame』タグをクリックして下さい。◆
 ◆ 最初から読みたい場合はココをクリックして下さい。◆
 
 こちらの記事の続きです。

 
 今回は、プレイヤーがブロック(地面など構造物のノード)に着地しているかどうかを接触イベントで判定する処理を追加します。連続するブロックを走り抜ける時に、着地を誤判定しないように工夫します。
 
【 注意 】この記事で使用しているCocos Creator v2.3.3は、VS Codeデバッグ実行でブレークポイントが機能しないバグがあので、v.2.3.2を使用して下さい。
 v.2.3.2は、Cocos DashboardのEditorのDownloadボタンからインストール出来ます。
 既存のプロジェクトのEditor Versionは、下図のように切り替えます。一度確認のメッセージが表示されます。
    f:id:mmorley:20200422191834p:plain

使用環境

 私が使用している環境です。

プレイヤーが着地しているか判定する

 接触イベントで、接点(接触した場所)の座標を取得できます。
 接点の座標が足元(足裏)かどうかで、プレイヤーが着地しているかどうかを判定します。
 また着地しているブロック(地面など構造物のノード)を数えることで誤判定を防ぎます。
 
 下記のように実装します。

  • プレイヤーに接触イベントを設定します。
    接触イベントには下記の2つがあります。
    • BeginContactイベント:コライダーが接触を開始した時に発生します。
    • EndContactイベント:コライダーが接触を終了した時に発生します。
  • プレイヤーの足元がブロックに触れたかどうかを判定します。
    接触イベントでプレイヤーと対象物の接点(下図の赤い点)の座標が得られます。
    この座標が足元かどうかで判定します。
    下部 上部
    f:id:mmorley:20200412224035p:plain:w200 f:id:mmorley:20200412224111p:plain:w200 f:id:mmorley:20200412224134p:plain:w200
    f:id:mmorley:20200412224051p:plain:w200 f:id:mmorley:20200412224123p:plain:w200 f:id:mmorley:20200412224145p:plain:w200
  • ブロックかどうかはコライダーのプロパティの『tag』の値で判別することにします。
    今後、ブロックのコライダーのみ『tag』=0に設定します。
  • プレイヤーが着地しているブロック(地面など構造物のノード)を数えます。
    全てのノードはUUID(Universal Unique Identifier)と呼ばれる固有の識別子を持っています。
    着地しているブロックのUUIDを保持する配列を作って下記のように処理します。
    • BeginContactで、プレイヤーが接触を開始したブロックのUUIDを配列に追加します。
    • EndContactで、プレイヤーが接触を終了したブロックのUUIDを配列から削除します。
    配列の長さが1以上なら着地していることになります。
     
    ちなみに、誤判定が生じるやり方の例です。
    BeginContactで着地中フラグ=true、EndContactで着地中フラグ=falseとした場合、
    f:id:mmorley:20200413080805p:plain:w250
    2つのブロックの境界で下記の順序でイベントが起きる場合があります。
    1. 『②のブロックのBeginContact』が発生、着地中フラグ=true
    2. 『①のブロックのEndContact』が発生、着地中フラグ=false
    上記の場合は、②のブロックに触れているのに着地中フラグ=falseになります。
    『①のブロックのEndContact』と『②のブロックのBeginContact』の発生順が入れ替わることがあるので誤判定が生じます。
 

    Cocos Creatorで作業

  1. 『Node Tree』パネルで『player』を選択して、『Properties』パネルで『RigidBody』の『Enabled Contact Listener』を設定します。
    f:id:mmorley:20200413114240p:plain
  2. 『Assets』パネルで『assets/script/player』をダブルクリックします。
    VS Codeが起動します。

    VS Codeで作業

  3. 『onBeginContact()』と『onEndContact()』を追加します。

        onKeyUp () {
            /* 省略 */
        }
    
        landingArray: { [key: string]: boolean; } = {}; // 接触しているブロックのUUIDを保持する連想配列
        isLanging: boolean = true; // 着地中かどうか true:着地中
        onBeginContact (contact: cc.PhysicsContact,
            selfCollider: cc.PhysicsCollider, otherCollider: cc.PhysicsCollider) { // 接触開始時の処理
    
            // 接点の場所を判定する
            let points = contact.getWorldManifold().points; // 接点のワールド座標を取得
            let playerBody: cc.RigidBody = selfCollider.body; // プレイヤーのボディを取得
            let relativePoint: cc.Vec2 = cc.Vec2.ZERO; // 変換後の座標
            let isPlayerBottom: boolean = true; // プレイヤーの足元かどうか、true:足元
            for (let i = 0; i < points.length; i++) { // 接点の数だけループする
                playerBody.getLocalPoint(points[i], relativePoint); // 接点をワールド座標からプレイヤーのローカル座標に変換する
                relativePoint.divSelf(this.node.scaleY); // プレイヤーノードのスケールを反映
                if (relativePoint.y >= -60.5) { // 足元ではない場合
                    isPlayerBottom = false; // falseにする
                }
            }
    
            // 足元に接触したブロックのUUIDを配列に追加する
            if (otherCollider.tag == 0) { // ブロックのコライダー(tag=0)の場合
                if (isPlayerBottom) { // 足元の場合
                    this.landingArray[otherCollider.uuid] = true; // UUIDを配列に追加する
                    this.isLanging = true; // 着地中にする
                    // this.jumpCount = 0;
                }
            }
       
        }
    
        onEndContact (contact: cc.PhysicsContact,
            selfCollider: cc.PhysicsCollider, otherCollider: cc.PhysicsCollider) { // 接触終了時の処理
    
            // 接触終了したブロックのUUIDを配列から削除する
            delete this.landingArray[otherCollider.uuid]; // 配列にUUIDがあれば削除する
            if (Object.keys(this.landingArray).length == 0) { // 接触しているブロックの数が0の場合
                this.isLanging = false; // 着地中フラグをfalseにする
            }
        }
    

  4. 『onKeyDown()』のジャンプ処理の着地判定の部分を変更します。

        /* 省略 */
        onKeyDown (event: cc.Event.EventKeyboard) {  // キーを押した時の処理
            switch(event.keyCode) { // 押されたキーの種類で分岐
                /* 省略 */
                case cc.macro.KEY.w: // 『w』キーの場合
                case cc.macro.KEY.up: // 『↑』キーの場合
                case cc.macro.KEY.space: // 『スペース』キーの場合
                    // ジャンプ処理
                    if (!this.inputJump) { // 『ジャンプ』の入力が0の場合
                        this.inputJump = 1; //『ジャンプ』の入力を1にする
                        let velocity: cc.Vec2 = this.getComponent(cc.RigidBody).linearVelocity; // 現在の速度を取得
                        if (this.isLanging) { // 着地している場合
                            velocity.y = this.jumpSpeed; // ジャンプする(y+方向にジャンプ速度を与える)
                        }
                        this.getComponent(cc.RigidBody).linearVelocity = velocity; // 速度を更新する
                    }
                    break;
            }
        }
    

     変更前は、着地してからY方向の速度が0になるまで若干の遅延があるせいで、操作の反応が悪い時がありましたが、変更後は改善されました。

今回作成したファイル

 今回の作業によって下記のファイルのようになります。

 
 今回はここまでです。お疲れさまでした。
 
 続きは、こちらの記事です。