モーリーのメモ

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

モーリーのメモ

物理エンジンを使ったシンプルなゲームを作る! その3完成(スクリプトを作成):Cocos Creator

<今回やること!>

 
 今回はスクリプトの作成、つまりプログラミングを行います。
 Cocos CreatorJavaScriptとTypeScriptの2つの言語が選べますが、TypeScriptを選択しました。TypeScript未経験でも、JavaScript経験者ならすぐに分かると思います。
 TypeScriptについては、下記の記事も御覧ください。

 それでは作成を開始します!

使用環境

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

プロジェクト実行時の処理の流れ

 プロジェクトの実行時に、どのような順序で実行されるかを大まかに説明します。スクリプトをどこから読めば良いか、処理を追いかける際に参考にして下さい。

  1. 開始シーン(プロジェクトの実行時に読み込むシーン)が読み込まれる。
  2. 開始シーンに含まれるCanvasノードや実装したノードが読み込まれる。
  3. 各ノードにコンポーネントとして追加したスクリプトが読み込まれる。
  4. 各ノードのスクリプトに記述されたonLoad()やupdate()等のライフサイクルコールバック関数が実行される。
  5. ユーザーの登録したタッチ操作等のイベント関数が入力に応じて実行される。

ライフサイクルコールバック関数

 ライフサイクルコールバック関数は、『Cocos Creator』によって特定のタイミングで実行されるように定義されている関数です。
 下表のようなものがあります。

    関数名実行されるタイミング
    onLoadノードが最初に読み込まれた時。startより先です。
    startupdateが初めて実行される前。
    update毎フレームの描画処理の前。
    lateUpdate毎フレームの描画処理の後。
    onDestroyコンポーネントまたはノードがdestroy()を呼び出した時。
    onEnable
    • コンポーネントのenabledプロパティがtrueに変わった時。
    • ノードのactiveプロパティがtrueに変わった時。
    • enabledがtrueのノードが読み込れた時。
    onDisable
    • コンポーネントのenabledプロパティがfalseに変わった時。
    • ノードのactiveプロパティがfalseに変わった時。

開始シーンの設定

 デフォルトでは『Cocos Creator』エディタの『Scene』パネルに読み込まれているシーンが開始シーンです。
 開始シーンの設定は次の操作で確認・変更出来ます。

  1. 『Cocos Creator』のエディタのメニューで『Project』→『Project Settings』をクリック
    f:id:mmorley:20190517102038p:plain:w500
  2. 『Project Preview』の『Preview Start Scene』を確認
    デフォルトは『Current Opened Scene』になっています。
    特定のシーンに変更すること出来ます。
    f:id:mmorley:20190517102408p:plain:w450
 

『Game』スクリプトを実装

 ここから、ゲームの作成に戻ります。まずはスクリプトファイルを作成します。
 『Game』スクリプトは『Canvas』ノードに組み込むスクリプトです。

スクリプトを作成

  1. 『Assets』パネルの『assets』の直下に『Folder』を追加し、名前を"Script"にする
  2. 『Assets』パネルで、『assets/Script』フォルダを右クリックして『Create』→『TypeScript』をクリックし、名前を"Game"にする
  3. 『Assets』パネルで、『assets/Script/Game』をダブルクリックして、コードエディタを開く
  4. コードエディタで、『Game.ts』の元のコード削除し、下記のコードを貼り付けて保存

    const {ccclass, property} = cc._decorator;
    
    // 列挙型
    const enum SaveKey { // ユーザーデータを保存する際のkey
        BESTSCORE // ベストスコア
    }
    const enum MainSeq { // メインシーケンス
        WAIT, // 開始待ち
        COUNTDOWN, // 開始処理
        PLAY, // プレイ中
        GAMEOVER // ゲームオーバー
    }
    
    @ccclass
    export default class Game extends cc.Component {
    
        // 変数:CocosCreatorエディターのPropertiesパネルに表示する変数
        @property([cc.TiledMap]) // Propertiesパネルの表示設定
        tiledMaps: cc.TiledMap[] = []; // タイルマップを配列で保持
        @property(cc.Node) // Propertiesパネルの表示設定
        player: cc.Node = null; // Playerノード
        @property(cc.Prefab) // Propertiesパネルの表示設定
        coinPrefab: cc.Prefab = null; // コインのPrefab
        @property(cc.Prefab) // Propertiesパネルの表示設定
        rockPrefab: cc.Prefab = null; // ロック(箱)のPrefab
        @property(cc.Node) // Propertiesパネルの表示設定
        startNode: cc.Node = null; // スタート画面ノード
        @property(cc.Label) // Propertiesパネルの表示設定
        scoreLabel: cc.Label = null; // スコアラベル
        @property(cc.Label) // Propertiesパネルの表示設定
        bestScoreLabel: cc.Label = null; // ベストスコアラベル
        @property(cc.Label) // Propertiesパネルの表示設定
        countLabel: cc.Label = null; // カウントラベル
        @property(cc.Button) // Propertiesパネルの表示設定
        reStartButton: cc.Button = null; // リスタートボタン
    
        // 変数:CocosCreatorエディターのPropertiesパネルに表示しない変数
        tiledMapSize: cc.Size = null; // タイルマップのサイズ
        mSeq: number = 0; // メインシーケンス
        counter: number = 2; // カウントダウンに表示する数
        timer: number = 2; // フレーム時間ごとのカウントダウン
        score: number = 0; // スコア
        bestScore: number = 0; // ベストスコア
        tiledMapIndex: number = 0; // 現在のタイルマップのインデックス
        changeDistance: number = 0; // タイルマップの移動タイミング(プレイヤーがマップ移動距離がこの距離に達したらタイルマップを移動)
        initPosX: number = 0; // プレイヤーの初期位置
        velX: number = 300; // プレイヤーの移動速度
    
        onLoad () { // シーンロード時の処理
            this.reStartButton.node.active = false; // リスタートボタンを無効&非表示
            this.countLabel.node.active = false; // カウントラベルを無効&非表示
            this.player.getComponent("Player").init(this); // Playerノードに実行中のGameクラスを渡す
            this.initPosX = this.player.convertToWorldSpaceAR(cc.Vec2.ZERO).x; // Playerノードの原点をワールド座標に変換し、x座標を取得
            let item: string = cc.sys.localStorage.getItem(SaveKey.BESTSCORE); // 保存したユーザデータからベストスコアを取得
            if (item != null) { // nullではない場合
                this.bestScore = Number(item); // ベストスコアを取得
            } else { // 保存値がない場合
                this.bestScore = 0; // ベストスコアを0にする
            }
            this.bestScoreLabel.string = "Best Score: " + this.bestScore.toString(); // ベストスコアを表示
            let physicsManager = cc.director.getPhysicsManager(); // 実行中の物理エンジンの管理クラスを取得
            physicsManager.enabled = true; // 物理エンジンを有効にする
            physicsManager.gravity = cc.v2(0, -2000); // 重力を設定
            //physicsManager.debugDrawFlags =  // 物理エンジンのデバッグ表示の設定(デバッグ時に必要に応じてコメントを外す)
                //cc.PhysicsManager.DrawBits.e_aabbBit | // 軸平行境界ボックスを描く
                //cc.PhysicsManager.DrawBits.e_jointBit | // ジョイント結合を描く
                //cc.PhysicsManager.DrawBits.e_shapeBit; // 形状を描く
            this.tiledMapSize = cc.size( // タイルマップのサイズを計算
                this.tiledMaps[0].getTileSize().width * this.tiledMaps[0].getMapSize().width, // タイルの幅 × x方向のタイルの枚数
                this.tiledMaps[0].getTileSize().height * this.tiledMaps[0].getMapSize().height); // タイルの高さ × y方向のタイルの枚数
            this.changeDistance = this.tiledMapSize.width; // タイルマップを移動させるタイミングを図るための距離
            for (let i = 0; i < this.tiledMaps.length; i++) { // タイルマップの数だけループ
                this.addPhisicsNode(this.tiledMaps[i]); // 『Tiled』のオブジェクトの情報を元に物理ノードを配置
            }
        }
    
        update (dt) { // フレームごとの処理
            switch (this.mSeq) { // メインシーケンス処理
                case MainSeq.WAIT: // スタートボタンの入力待ち
                    break;
                case MainSeq.COUNTDOWN: // カウントダウン処理
                    this.timer -= dt; // フレームごとの経過時間を減算
                    if (this.timer < (this.counter - 1)) { // カウントラベルの時間から1秒経過した場合
                        this.countLabel.string = (-- this.counter).toString(); // カウントラベルの時間を1秒減算して表示
                    }
                    if (this.timer <= 0) { // カウントが0を切った場合
                        this.countLabel.node.runAction(cc.fadeOut(0.5)); // カウントラベルを透明にする
                        this.player.getComponent(cc.RigidBody).linearVelocity = cc.v2(this.velX, 0); // プレイヤーにx方向の速度を与える
                        let playerAnim =  this.player.getComponent(cc.Animation); // プレイヤーノードのAnimationコンポーネントを取得
                        playerAnim.play("Running"); // 走行中のアニメを再生
                        this.mSeq = MainSeq.PLAY; // シーケンスをプレイ中する
                    }
                    break;
                case MainSeq.PLAY: // プレイ中
                    if (this.player.getComponent(cc.RigidBody).linearVelocity.x < (this.velX - 10)) { // イレギュラーでプレイヤーの速度が落ちた場合
                        this.player.getComponent(cc.RigidBody).linearVelocity.x = this.velX; // 速度を設定値に戻す
                    }
                    break;
            }
            let moveDistance = this.player.convertToWorldSpaceAR(cc.Vec2.ZERO).x - this.initPosX; // プレイヤーの移動距離を取得
            if (moveDistance >= this.changeDistance) { // プレイヤーの移動距離がタイルマップ切り替え距離を超えた場合
                this.changeDistance += this.tiledMapSize.width; // 次のタイルマップ切り替え距離を計算
                for (let i = 0; i < this.tiledMaps[this.tiledMapIndex].node.childrenCount; i++) { // 現在のタイルマップの子ノードの数だけループ
                    this.tiledMaps[this.tiledMapIndex].node.children[i].removeAllChildren(); // 子ノード(物理ノード)を全て削除
                    this.tiledMaps[this.tiledMapIndex].node.children[i].destroyAllChildren(); // 子ノード(物理ノード)を全て破棄                  
                }
                this.tiledMaps[this.tiledMapIndex].node.x += this.tiledMapSize.width * 2; // 走り過ぎたタイルマップを次のタイルマップの先に移動
                this.addPhisicsNode(this.tiledMaps[this.tiledMapIndex]); // 『Tiled』のオブジェクトの情報を元に物理ノードを配置
                this.tiledMapIndex ^= 1; // 現在のタイルマップのインデックスを更新、0→1→0→...を交互に繰り返す
                this.velX += 10; // 速度を上げる
                this.player.getComponent(cc.RigidBody).linearVelocity.x = this.velX; // プレイヤーの速度を更新
            }
            if(Math.abs(this.player.angle) > 30){ // イレギュラーでプレイヤーが傾いた場合
                this.player.angle = 0; // 角度を戻す
                this.player.getComponent(cc.RigidBody).angularVelocity = 0; // 回転を止める
                this.player.getComponent(cc.RigidBody).linearVelocity.x = this.velX; // プレイヤーの速度を戻す
            }
        }
    
        addPhisicsNode (tiledMap: cc.TiledMap) { // 『Tiled』のオブジェクトの情報を元に物理ノードを配置
            let objects = tiledMap.getObjectGroup("static").getObjects(); // 『Tiled』のstaticレイヤーのオブジェクトを取得
            let layerNode = tiledMap.node.getChildByName("static"); // staticノードを取得
            let physicsNode = new cc.Node(); // 物理ノードにするノードを作成
            physicsNode.name = "Static"; // ノード名を変更
            let rigidBody = physicsNode.addComponent(cc.RigidBody); // ノードにRigidBody(剛体)コンポーネントを追加
            rigidBody.type = cc.RigidBodyType.Static; // 剛体のタイプをスタティック(静的)に設定
            let boxCollider: cc.PhysicsBoxCollider = null; // ボックスコライダー(物体形状等のデータ)
            let circleCollider: cc.PhysicsCircleCollider = null; // サークルコライダー
            let polygonCollider:cc.PhysicsPolygonCollider = null; // ポリゴンコライダー
            for (let j = 0; j < objects.length; j++) { // オブジェクトの数だけループ
                let curObject = objects[j]; // オブジェクトを取得
                let pos = cc.v2( // オブジェクトの位置をCocos Creatorの座標(中心が原点)に変換
                    curObject.offset.x + curObject.width / 2 - this.tiledMapSize.width / 2, // マップの幅/2オフセット
                    this.tiledMapSize.height / 2 - curObject.offset.y - curObject.height / 2); // マップの高さ/2オフセットし、正負逆転
                switch (curObject.type) { // タイプ別に処理をする
                    case 0: // ボックス, ポイント
                        boxCollider = physicsNode.addComponent(cc.PhysicsBoxCollider); // 物理ノードにボックスコライダーを追加して取得
                        boxCollider.density = 1; // 密度
                        boxCollider.friction = 0; // 摩擦係数
                        boxCollider.restitution = 0; // 反発係数
                        boxCollider.size = cc.size(curObject.width, curObject.height); // ボックスのサイズ
                        boxCollider.offset = pos; // ボックスの中心位置                   
                        break;
                    case 1: // サークル
                        circleCollider = physicsNode.addComponent(cc.PhysicsCircleCollider); // 物理ノードにサークルコライダーを追加して取得
                        circleCollider.density = 1; // 密度
                        circleCollider.friction = 0; // 摩擦係数
                        circleCollider.restitution = 0; // 反発係数
                        circleCollider.radius = curObject.width / 2; // 円の半径
                        circleCollider.offset = pos; // 円の中心位置
                        break;
                    case 2: // ポリゴン
                        polygonCollider = physicsNode.addComponent(cc.PhysicsPolygonCollider); // 物理ノードにポリゴンコライダーを追加して取得
                        polygonCollider.density = 1; // 密度
                        polygonCollider.friction = 0; // 摩擦係数
                        polygonCollider.restitution = 0; // 反発係数
                        for (let k = 0; k < curObject.points.length; k++) { // ポリゴンの頂点の数だけループ
                            polygonCollider.points[k] = cc.v2(curObject.points[k]); // ポリゴンコライダーに頂点をコピー
                        }
                        polygonCollider.offset = pos; // ポリゴンの始点
                        break;
                }
            }
            layerNode.addChild(physicsNode); // 物理ノードをstaticノードの子ノードとして追加
            this.addPrefab(tiledMap, "coin", this.coinPrefab); // 『Tiled』の"coin"レイヤーの処理
            this.addPrefab(tiledMap, "rock", this.rockPrefab); // 『Tiled』の"rock"レイヤーの処理
        }
    
        addPrefab (tiledMap: cc.TiledMap, name: string, prefab: cc.Prefab) {
            let objects = tiledMap.getObjectGroup(name).getObjects(); // 『Tiled』のオブジェクトを取得
            let layerNode = tiledMap.node.getChildByName(name); // タイルマップの子ノードを取得
            for (let j = 0; j < objects.length; j++) { // オブジェクトの数だけループ
                let curObject = objects[j]; // オブジェクトを取得
                switch (curObject.type) { // タイプ別に処理をする
                    case 0: // ボックス, ポイント
                        let prefabNode = cc.instantiate(prefab); // Prefabからノードを作成
                        prefabNode.position = cc.v2( // ノードの位置を設定
                            curObject.offset.x + curObject.width / 2 - this.tiledMapSize.width / 2, // マップの幅/2オフセット
                            this.tiledMapSize.height / 2 - curObject.offset.y - curObject.height / 2); // マップの高さ/2オフセットし、正負逆転
                            layerNode.addChild(prefabNode); // 子ノードとして追加
                        break;
                }
            }
        }
    
        onClickedStartButton () { // スタートボタンがクリックされた時の処理
            this.startNode.active = false; // 開始画面を無効・非表示にする
            this.countLabel.string = this.counter.toString(); // カウントの値を表示
            this.countLabel.node.active = true; // カウントラベルを表示する
            this.mSeq = MainSeq.COUNTDOWN; // メインシーケンスをカウントダウンにする
        }
    
        onClickedRestartButton () { // リスタートボタンがクリックされた時の処理
            this.node.destroy(); // Gameノードを破棄
            cc.audioEngine.stopAllEffects(); // BGMを停止
            cc.director.loadScene("Game.fire"); // Gameシーンを読み込む
        }
    
        gainedScore () { // 得点した時の処理
            this.score ++; // スコアを加算
            this.scoreLabel.string = "Score: " + this.score.toString(); // スコア表示を更新
        }
    
        setGameOver () { // ゲームオーバーした時の処理
            if (this.score > this.bestScore) { // ベストスコアを上回った場合
                cc.sys.localStorage.setItem(SaveKey.BESTSCORE, this.score.toString()); // ベストスコアを保存
            }
            this.reStartButton.node.active = true; // リスタートボタンを有効にする
            this.player.getComponent(cc.RigidBody).linearVelocity = cc.v2(0, 0); // プレイヤーを停止する
            let physicsManager = cc.director.getPhysicsManager(); // 実行中の物理エンジンの管理クラスを取得
            physicsManager.enabled = false; // 物理エンジンを無効にする
            this.mSeq = MainSeq.GAMEOVER; // メインシーケンスをゲームオーバー(フレーム処理なし)にする
        }
    }
    

スクリプトの補足

  • this.reStartButton.node.active = false
    ノードのアクティブをfalseにすると、非表示になり、イベントを受け取りません。
    『this.reStartButton.node.opacity = 0』だと透明化するだけで、イベントは受け取ります。
  • cc.sys.localStorage.getItem(SaveKey.BESTSCORE)
    キーを指定して、保存済データ(String型)を取得します。
  • cc.sys.localStorage.setItem(SaveKey.BESTSCORE, this.score.toString())
    キーを指定して、データ(String型)を保存します。
  • 物理エンジンの有効化
    下記の処理で物理エンジンが有効になります。

            let physicsManager = cc.director.getPhysicsManager(); // 実行中の物理エンジンの管理クラスを取得
            physicsManager.enabled = true; // 物理エンジンを有効にする
            physicsManager.gravity = cc.v2(0, -2000); // 重力を設定
    

  • 物理ノードのデバッグ表示
    物体の形状や衝突判定の範囲を確認するには、下記のフラグを有効にします。

            physicsManager.debugDrawFlags =  // 物理エンジンのデバッグ表示の設定
                cc.PhysicsManager.DrawBits.e_aabbBit | // 軸平行境界ボックスを描く
                cc.PhysicsManager.DrawBits.e_jointBit | // ジョイント結合を描く
                cc.PhysicsManager.DrawBits.e_shapeBit; // 形状を描く
    

    物体の形状を表示しています。Staticな剛体は緑、Dynamicな物体は赤です。
    f:id:mmorley:20190615112404p:plain:w500

  • update()の処理について
    スタート時のカウントダウン処理とタイルマップの移動を行っています。
    タイルマップを移動する際に、既存のコインや爆弾等の物理ノードは削除して、作り直します。
    一度登録したStaticな剛体は、位置や形状の変更が出来ません。
  • addPhisicsNode()、addPrefab()
    タイルマップのオブジェクト情報に従って、物理ノード(プレハブ化したものも含む)を作成します。
    タイルマップのオブジェクト情報については下記の記事も参考にして下さい。
    mmorley.hatenablog.com
  • onClickedStartButton ()、onClickedRestartButton ()
    『Button』コンポーネントに登録することで、クリックイベントで呼び出されます。

プロパティの関連付けとイベントの登録

  1. 『Cocos Creator』の『Node Tree』パネルで、『Canvas』を選択
  2. 『Properties』パネルの『Add Component』→『Custom Component』→『Game』をクリック
  3. 『Properties』パネルで、『Game』コンポーネントの『Tiled Maps』を2にする
  4. 『Properties』パネルで、『Game』コンポーネントにノードやリソースをドラッグ&ドロップ
    f:id:mmorley:20190615085957p:plain
  5. 『Cocos Creator』の『Node Tree』パネルで、『Canvas/PlayBG/Start』を選択
  6. 『Properties』パネルで、『Button』コンポーネントの『Click Events』を1にする
  7. 『Properties』パネルの『Button』コンポーネントの『[0]』を下図のように設定
    f:id:mmorley:20190614201602p:plain
  8. 『Cocos Creator』の『Node Tree』パネルで、『Main Camera/ReStart』を選択
  9. 『Properties』パネルで、『Button』コンポーネントの『Click Events』を1にする
  10. 『Properties』パネルの『Button』コンポーネントの『[0]』を下図のように設定
    f:id:mmorley:20190615094233p:plain

『Player』スクリプトを実装

 『Player』スクリプトは『Player』ノードに組み込むスクリプトです。

スクリプトを作成

  1. 『Assets』パネルの『assets』の直下に『Folder』を追加し、名前を"Script"にする
  2. 『Assets』パネルで、『assets/Script』フォルダを右クリックして『Create』→『TypeScript』をクリックし、名前を"Player"にする
  3. 『Assets』パネルで、『assets/Script/Player』をダブルクリックして、コードエディタを開く
  4. コードエディタで、『Player.ts』の元のコード削除し、下記のコードを貼り付けて保存

    // インポート定義
    import Game from "./Game"; // Gameクラスをインポート
    
    const {ccclass, property} = cc._decorator;
    
    // 列挙型
    const enum PlayerSeq { // プレイヤーの状態のシーケンス
        WAIT, // 待機中
        RUNNING, // 走行中
        JUMPUP, // ジャンプ上昇中
        JUMPDOWN, // ジャンプ下降中
        GAMEOVER // ゲームオーバー
    }
    
    @ccclass
    export default class Player extends cc.Component {
    
        // 変数:CocosCreatorエディターのPropertiesパネルに表示する変数
        @property(cc.Prefab) // Propertiesパネルの表示設定
        explosionPrefab: cc.Prefab = null; // 爆発のパーティクルのPrefab
        @property({type: cc.AudioClip}) // Propertiesパネルの表示設定
        Bgm: cc.AudioClip = null; // BGM
        @property({type: cc.AudioClip}) // Propertiesパネルの表示設定
        explosionSE: cc.AudioClip = null; // 爆発時のSE
        @property({type: cc.AudioClip}) // Propertiesパネルの表示設定
        jumpSE: cc.AudioClip = null; // ジャンプ時のSE
        @property({type: cc.AudioClip}) // Propertiesパネルの表示設定
        pickUpCoinSE: cc.AudioClip = null; // コインを取得した時のSE
    
        // 変数:CocosCreatorエディターのPropertiesパネルに表示しない変数
        playerAnim: cc.Animation = null; // Animationコンポーネント
        playerRigidBody: cc.RigidBody = null; // プレイヤーノードのRigidBodyコンポーネント
        pSeq : PlayerSeq = PlayerSeq.RUNNING; // プレイヤーの状態のシーケンス
        jumpFlg: boolean = false; // ジャンプ中かどうか
        game: Game = null; // 実行中Gameクラスを取得
    
        init(game:Game) { // Gameクラス側で呼び出す初期化処理
            this.game = game; // 実行中のGameクラスを取得
            cc.audioEngine.setEffectsVolume(0.2); // SEのボリュームを下げる
            cc.audioEngine.playEffect(this.Bgm, true); // BGMを再生、繰り返しはtrue
            this.playerAnim = this.getComponent(cc.Animation); // Animationコンポーネントを取得
            /* アニメ終了時の処理をコードで書いた場合の例
            this.playerAnim.on('stop', function (event) { // アニメ終了時のイベントを登録
                if(this.playerAnim.currentClip.name == "JumpUp") { // 終了したアニメクリップの名前が"JumpUp"の場合
                    this.playerRigidBody.applyLinearImpulse( // 物体に瞬間的に運動量を与える
                        cc.v2(0, 1200), // 運動量のベクトル
                        this.playerRigidBody.getWorldCenter(), // 運動量を与える位置(ワールド座標)
                        true); // 物体がスリープ状態(静止しており物理演算から除外)の場合にスリープを解除するかどうか
                }   
            }, this); // コールバック関数内のthisをPlayerクラスのthisにする
            */
            this.playerRigidBody = this.node.getComponent(cc.RigidBody); // プレイヤーノードのRigidBodyコンポーネントを取得
            cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this); // キーダウンイベントのリスニング(受付)を開始 
        }
    
        onJumpUpAnimCompleted () { // アニメ終了時の処理(呼び出しはCocos Creatorのエディタ側で設定)
            this.playerRigidBody.applyLinearImpulse( // 物体に瞬間的に運動量を与える
                cc.v2(0, 1200), // 運動量のベクトル
                this.playerRigidBody.getWorldCenter(), // 運動量を与える位置(ワールド座標)
                true); // 物体がスリープ状態(静止しており物理演算から除外)の場合にスリープを解除するかどうか
            cc.audioEngine.playEffect(this.jumpSE, false); // ジャンプのSEを再生する、繰り返しはfalse
            this.pSeq = PlayerSeq.JUMPUP; // プレイヤーのシーケンスをジャンプ上昇中にする
        }
    
        onBeginContact ( // コライダーが触れ始めた時の処理
            contact: cc.PhysicsContact, // 接触時の情報
            selfCollider: cc.PhysicsCollider, // 自分(プレイヤー)
            otherCollider: cc.PhysicsCollider) { // 接触した相手
            switch (otherCollider.node.name) { // 接触したノード名ごとの処理
                case "Coin": // Coinと接触した場合
                    cc.audioEngine.playEffect(this.pickUpCoinSE, false); // コイン取得時のSEを再生する、繰り返しはfalse
                    otherCollider.node.destroy(); // コインノードを削除
                    this.game.gainedScore(); // 得点時の処理
                    break;
                case "Rock": // Rockと接触した場合 
                    this.game.setGameOver(); // ゲームオーバー時の処理
                    otherCollider.node.addChild(cc.instantiate(this.explosionPrefab)); // 爆発のパーティクルをRockの子ノードとして追加
                    cc.audioEngine.playEffect(this.explosionSE, false); // 爆発時のSEを再生する、繰り返しはfalse
                    this.pSeq = PlayerSeq.GAMEOVER; // シーケンスをゲームオーバーにする
                    break;
            }
        }
    
        onKeyDown (event) { // キーを押した時の処理
            switch(event.keyCode) {  // 押されたキーごとの処理
                case cc.macro.KEY.space: // 『space』キーの場合
                    if (this.jumpFlg == false) { // ジャンプ中でない場合
                        this.playerAnim.play("JumpUp"); // ジャンプのアニメーションを再生
                        this.jumpFlg = true; // ジャンプ中にする
                    }
                    break;
            }
        }
    
        update (dt) { // フレームごとの処理
            let velY = this.playerRigidBody.linearVelocity.y; // プレイヤーのy方向の移動速度を取得
            switch (this.pSeq) { // プレイヤーシーケンスごとの処理
                case PlayerSeq.WAIT: // 走行中の場合
                    break;
                case PlayerSeq.RUNNING: // 走行中の場合
    
                    break;
                case PlayerSeq.JUMPUP: // ジャンプ上昇中の場合
                    if (velY < -0.1) { // 速度が下向き(下降中)になった場合
                        this.playerAnim.play("JumpDown"); // ジャンプ下降中のアニメーションを再生
                        this.pSeq = PlayerSeq.JUMPDOWN // プレイヤーのシーケンスを下降中にする
                    }
                    break;
                case PlayerSeq.JUMPDOWN: // ジャンプ下降中の場合
                    if (velY >= 0) { // 着地した場合
                        this.jumpFlg = false; // ジャンプフラグをfalseにす
                        this.playerAnim.play("Running"); // 走行中のアニメを再生
                        this.pSeq = PlayerSeq.RUNNING; // プレイヤーのシーケンスを走行中にする
                    }
                    break;
                case PlayerSeq.GAMEOVER: // ゲームオーバーの場合
                    this.playerAnim.stop(); // プレイヤーのアニメーションを停止
                    this.pSeq = PlayerSeq.WAIT; // プレイヤーのシーケンスを待機中にする
                    break;
                    
            }
        }
    }
    

スクリプトの補足

  • init()
    Gameクラス(Game.ts)側でinit()を呼び出すことで、Gameクラスの『実体(実行中のGameクラス)』を受け取ります。
    Gameクラスの実体のメソッドを呼び出すことで、得点処理等が反映されるようになります。
  • onJumpUpAnimCompleted()
    『Timeline』パネルで、『JumpUp』AnimationClipに登録した関数です。
    指定したフレームで呼び出されます。
  • onBeginContact()
    『Player』ノードの『RigidBody』コンポーネントの『Enabled Contact Listener』をオンにすると、接触開始時に呼び出されます。
  • onKeyDown ()
    『cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this)』によって登録することでキーダウン時に呼び出されます。
    スペースキーの入力により、『Player』ノードのジャンプ処理を開始します。すでにジャンプ処理中の場合は処理しません。
  • update()の処理について
    『Player』ノードのy方向(上下)の速度によって、走行・上昇中・下降中を判別し、アニメーションの切り替え等の処理を行います。

プロパティの関連付けとイベントの登録

  1. 『Cocos Creator』の『Node Tree』パネルで、『Player』を選択
  2. 『Properties』パネルの『Add Component』→『Custom Component』→『Player』をクリック
  3. 『Properties』パネルで、『Player』コンポーネントにノードやリソースをドラッグ&ドロップ
    f:id:mmorley:20190615093539p:plain
  4. 『Properties』パネルで、『RigidBody』コンポーネントの『Enabled Contact Listener』にチェックを付ける
    f:id:mmorley:20190615084904p:plain

『Camera』スクリプトを実装

 『Main Camera』ノードに組み込むスクリプトです。

  1. 『Assets』パネルの『assets』の直下に『Folder』を追加し、名前を"Script"にする
  2. 『Assets』パネルで、『assets/Script』フォルダを右クリックして『Create』→『TypeScript』をクリックし、名前を"Camera"にする
  3. 『Assets』パネルで、『assets/Script/Camera』をダブルクリックして、コードエディタを開く
  4. コードエディタで、『Camera.ts』の元のコード削除し、下記のコードを貼り付けて保存

    const {ccclass, property} = cc._decorator;
    
    @ccclass
    export default class Camera extends cc.Component {
    
        // 変数:CocosCreatorエディターのPropertiesパネルに表示する変数
        @property(cc.Node)
        target: cc.Node = null; // カメラのターゲットとなるノード(プレイヤーノード)
    
        // 変数:CocosCreatorエディターのPropertiesパネルに表示する変数
        offsetX: number = null; // ターゲットノードとカメラノードの座標のズレ
    
        onLoad () { // シーンロード時の処理
            this.offsetX = -this.target.position.x; // ターゲットノードとカメラノードの座標のズレを計算
        }
    
        lateUpdate () { // update()後に実行されるフレーム処理
            this.node.position = cc.v2( // カメラノードの位置を更新
                this.target.position.x + this.offsetX, // カメラ座標のx座標を計算
                this.node.position.y); // y座標は変更なし
        }
    }
    

スクリプトの補足

  • Cameraのノード
    ゲームが開始すると『Player』ノードは走り出しますが、『Camera』ノードが追いかけることで、ゲーム画面では『Player』ノードが同じ位置に表示されます。
    『Player』ノードの位置が更新された後に、『 lateUpdate () 』にて『Camera』ノードの位置を更新しています。

プロパティの関連付けとイベントの登録

  1. 『Cocos Creator』の『Node Tree』パネルで、『Main Camera』を選択
  2. 『Properties』パネルの『Add Component』→『Custom Component』→『Camera』をクリック
  3. 『Properties』パネルで、『Camera』コンポーネントにノードやリソースをドラッグ&ドロップ
    f:id:mmorley:20190615094910p:plain
 
 お疲れ様でした!以上で完成です。
 『プレビュー』ボタンを押して、ゲームを実行して下さい。

あとがき

 物理エンジンを有効にすると物理空間(物理ワールド)が作成されます。物理空間の中で物体の位置・動き・衝突などが計算されて、それに結び付けられたノードの位置・角度に反映されるイメージです。
 一度物理空間に配置したコライダーは変更出来ません。Staticな剛体は移動も出来ません。なので、地面等の剛体はタイルマップの子ノードに追加してますが、タイルマップを移動させでも一緒には動きません。物理ノードを作り直す必要があります。