モーリーのメモ

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

モーリーのメモ

チュートリアルのスクリプトをTypeScriptで書く!:Cocos Creator

<今回やること!>

 
 Cocos Creatorは、スクリプトJavaScriptとTypeScriptで書くことが出来ます。
 TypeScriptはJavaScriptから派生したもので、その名が示すように、型(Type)の定義が厳密になっています。

 TypeScriptを使うメリットは、型定義が厳密であるがために、オートコンプリート(コードの自動補完)が確実に使えることです。JavaScriptの場合は値やオブジェクトの代入時に型が決まるので、オートコンプリートが機能しない場合があります。
 そして型が厳密なことで代入ミスなどのケアレスミスが防げます。
 
 TypeScriptに詳しい方からはメリットはまだまだあると言われそうですが、私がTypeScriptを使う1番のメリットは上記の点によりコーディングの効率が上がることです。また、とりあえずJavaScriptからTypeScriptへ書き換える場合には、それほど変更点がないのも良い点です。
 
 以前、Cocos CreatorではCoffeeScriptが使用できましたが、現在は無くなっています。
 ですがTypeScriptは、Microsoftが開発した言語で、エディタのVSCodeMicrosoft製です。Googleの社内標準言語に採用されたとの記事がありましたし、人気のプログラミング言語でも上位に挙げられるので、将来性はありそうです。
 
 TypeScriptのコードは、ファイルごとにまとめて記載します。
 チュートリアルをやってない方は、チュートリアルの記事を御覧ください。

使用環境

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

TypeScriptのスクリプトファイルを追加

  1. 『Cocos Creator』のエディタの『Assets』パネルで、右クリック(または『+』をクリック)して、『TypeScript』をクリック
    f:id:mmorley:20190522132949p:plain

TypeScriptプロジェクトの構成ファイルを追加

 プロジェクトのコンパイルに必要なルートファイルとコンパイラオプションを指定します。

  1. 『Cocos Creator』のエディタのメニューで『Developer』→『VS Code Workflow』→『Add Type Script Config』をクリック
    プロジェクトフォルダに『.vscode/tsconfig.json』が追加されます。
    f:id:mmorley:20190516145223p:plain

JavaScriptからTypeSciptへの変更点

 大きく異なる部分について説明します。

プロパティの宣言

 TypeScriptの場合、『Cocos Creator』のエディタの『Properties』パネルに表示する変数には、デコレータ『@property(型)』を付けます。
 数字、ブーリアンの変数の宣言のようにデコレータと変数で、指定する型が異なる特殊なものもあります。
 『cc.AudioClip』のデコレータは『@property({type: cc.AudioClip})』のようにラベルを書かないとエラーが出ました。

       @property(cc.Prefab) // 『Properties』パネルへ表示する、cc.Prefab型
        starPrefab: cc.Prefab = null; // cc.Prefab型で宣言し、デフォルト値はnull
    
        @property(cc.Integer) // 『Properties』パネルへ表示する、cc.Integer型
        maxStarDuration: number = 0; // デフォルト値は0、number型で宣言
    
        @property(cc.Boolean) // 『Properties』パネルへ表示する、cc.Boolean型
        flag : boolean = true; // デフォルト値は0、boolean型で宣言
    
        @property({type: cc.AudioClip}) // 『Properties』パネルへ表示する、cc.AudioClip型
        scoreAudio: cc.AudioClip = null; // デフォルト値はnull、cc.AudioClip型で宣言
    
        // デコレータ『@property(型)』がないので『Properties』パネルへ表示しない
        minStarDuration: number = 0; // 星が消えるまでの時間の最小値
    

 その他のプロパティ宣言の例については下記のページが参考になります。

クラスのインポート

 他のスクリプトファイルのクラスを呼び出す場合は、あらかじめインポートします。

     import Game from "./Game"; // 同じフォルダのGame.tsからGameクラスをインポート
    

変数の宣言(var、let)

 TypeScriptでは変数をletで宣言しています。
 しかし最近はJavasScriptでもletを使うほうが良いです。
 varとletの違いはスコープ(有効範囲)です。varは関数スコープ、letはブロックスコープです。

  • スコープの違いの例

            (function(){
                var x = 1;  // ①
                (function(){
                    var x = 2; // 別の関数内なのでこのxは①と別物
                })();
                cc.log("x = " + x);  // x = 1と出力される
            })();
    
            (function(){
                var x = 1;  // ②
                {
                    var x = 2; // 同じ関数内なのでこのxは②と同じ
                }
                cc.log("x = " + x);  // x = 2と出力される
            })();
    
            (function(){
                let x = 1; // ③
                {
                    let x = 2; // 同じブロック内なのでこのxは③と同じ
                }
                cc.log("x = " + x);  // x = 1と出力される
            })();
    

TypeScript版のチュートリアルスクリプト

 チュートリアルでは、『Game.js』、『Star.js』、『Player.js』の3つのスクリプトファイルを作成しました。
 それぞれをTypeScriptで作成して、『〜.ts』ファイルにしました。

『Game.ts』

    const {ccclass, property} = cc._decorator;
    
    @ccclass
    export default class Game extends cc.Component {
        /* 
        // オプションの設定例
        @property({
            type: cc.Node,
            tooltip: "『Player』ノード" 
        })
        player: cc.Node = null;
        */
        @property(cc.Node) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示
        player: cc.Node = null; // 『Player』ノード取得用の変数
    
        timer: number = 0; // タイマーを初期化
        starDuration: number = 0; // 星の持続時間(星が消えるまでの時間)を初期化
    
        @property(cc.Prefab) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示
        starPrefab: cc.Prefab = null; // 星のプレハブ取得用の変数
        @property(cc.Integer) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示
        maxStarDuration: number = 0; // 星が消えるまでの時間の最大値
        @property(cc.Integer) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示
        minStarDuration: number = 0; // 星が消えるまでの時間の最小値
        @property(cc.Node) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示
        ground: cc.Node = null; // 『ground』ノード取得用の変数
        @property(cc.Label) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示
        scoreDisplay: cc.Label = null; // 『score』ラベル取得用の変数
        @property({type: cc.AudioClip})// 『Cocos Creatorエディタ』の『Properties』パネルへ表示
        scoreAudio: cc.AudioClip = null; // 得点の効果音
        @property(cc.Boolean)// 『Cocos Creatorエディタ』の『Properties』パネルへ表示
        test: boolean = null; // 得点の効果音
    
        groundY: number = 0; // 『ground』のアンカーポイントのy座標を取得
        score: number = 0; // 得点を初期化
    
        onLoad() {
            // 『ground』のアンカーポイントのy座標
            this.groundY = this.ground.y + this.ground.height / 2;
            // 新しい星を生成
            this.spawnNewStar();
        }
        
        spawnNewStar() {
            // 事前に設定したテンプレートでシーンに新しいノードを生成
            let newStar = cc.instantiate(this.starPrefab);
            // 新しく追加したノードを『Canvas』ノードの下に置く
            this.node.addChild(newStar);
            // 星にランダムな位置を設定
            newStar.setPosition(this.getNewStarPosition());
            // 生成した『Star』ノードの『Star』コンポーネントに、『Game』コンポーネントの実体を渡す。
            newStar.getComponent('Star').init(this);
            // タイマーをリセットし、星の持続時間に乱数値(min〜max)を設定
            this.starDuration = this.minStarDuration + Math.random() * (this.maxStarDuration - this.minStarDuration);
            // タイマーをリセット
            this.timer = 0;
        }
    
        getNewStarPosition() {
            // 星のアンカーポイントのy座標 = 地面の高さ+キャラクターのジャンプの高さの範囲の乱数 + 地面にめり込ませないための調整値
            let randY = this.groundY + Math.random() * this.player.getComponent('Player').jumpHeight + 50;
            // x座標の最大値(絶対値)= 画面幅の半分
            let maxX = this.node.width / 2;
            // 星のアンカーポイントのx座標=(-1から1の乱数)×(x座標の最大値)
            let randX = (Math.random() - 0.5) * 2 * maxX;
            // 星のアンカーポイントの位置を返す
            return cc.v2(randX, randY);
        }
    
        gainScore() {
            this.score += 1; // スコアを加算
            // 『scoreDisplay』ラベルのスコア表示を更新
            this.scoreDisplay.string = 'Score: ' + this.score.toString();
            // 得点時の効果音を再生
            cc.audioEngine.playEffect(this.scoreAudio, false);
        }
        
        gameOver() {
            this.player.stopAllActions(); // 『Player』ノードのジャンプアクションを停止
            cc.director.loadScene('game'); // 『game』シーンをロードする
        }
    
        update(dt) {
            if (this.timer > this.starDuration) { // タイマーが星の持続時間を超えた場合
                this.gameOver(); // ゲーム失敗のロジックを呼び出す
                return;
            }
            this.timer += dt; // タイマーを加算
        }
    }
    

『Star.ts』

    import Game from "./Game"; // 同じフォルダのGame.tsからGameクラスをインポート
    
    const {ccclass, property} = cc._decorator;
    
    @ccclass
    export default class Star extends cc.Component {
    
        @property(cc.Integer) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示
        pickRadius: number = 0 ;// キャラクターと星の距離がこの値より小さい場合に得点とする
    
        game: Game = null;
    
        init(game:Game) {
            this.game = game;
        }
    
        getPlayerDistance () {
            // 『Player』ノードの位置を取得
            let playerPos = this.game.player.getPosition();
            // this.node(星ノード)と『Player』ノードの距離を計算
            let dist = this.node.position.sub(playerPos).mag();
            return dist; // 計算した距離を返す
        }
    
        onPicked () {
           // 『Game』コンポーネントの『spawnNewStar』メソッドを呼び出して、新しく星を生成
            this.game.spawnNewStar();
            // 『Game』スクリプトの得点用のメソッドを呼び出す
            this.game.gainScore();
            // 『star』ノードを破棄
            this.node.destroy();
        }
    
        update (dt) {
            if (this.getPlayerDistance() < this.pickRadius) { // キャラクターと星の間の距離がpickRadiusより小さい場合
                this.onPicked(); // onPickedメソッド(採取の処理)を呼び出す
                return;
            }
            // 経過時間と星の持続時間から、透明度の割合を計算
            let opacityRatio = 1 - this.game.timer / this.game.starDuration;
            // 透明度の最小値
            let minOpacity = 50; 
            // 星のノードの透明度を設定
            this.node.opacity = minOpacity + Math.floor(opacityRatio * (255 - minOpacity));
        }
    }
    

『Player.ts』

    const {ccclass, property} = cc._decorator;
    
    @ccclass
    export default class Player extends cc.Component {
    
        @property(cc.Integer) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示
        jumpHeight: number = 0; // キャラクターのジャンプの高さ
        @property(cc.Integer) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示
        jumpDuration: number = 0; // キャラクターのジャンプの時間
        @property(cc.Integer) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示
        maxMoveSpeed: number = 0; // 最大移動速度
        @property(cc.Integer) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示
        accel: number = 0; // 加速度
        @property({type: cc.AudioClip}) // 『Cocos Creatorエディタ』の『Properties』パネルへ表示
        jumpAudio: cc.AudioClip = null; // ジャンプの効果音のリソース
    
        accLeft: boolean = false; // 左の加速フラグをfalseにする
        accRight: boolean = false;; // 右の加速フラグをfalseにする
        xSpeed: number = 0; // キャラクタの現在の水平速度をにする
    
        setJumpAction () {
            // ジャンプアップ(ジャンプの最高地点まで移動)するアクションを定義
            let jumpUp = cc.moveBy(this.jumpDuration, cc.v2(0, this.jumpHeight)).easing(cc.easeCubicActionOut());
            // ジャンプダウン(ジャンプで地上まで落下)するアクションを定義
            let jumpDown = cc.moveBy(this.jumpDuration, cc.v2(0, -this.jumpHeight)).easing(cc.easeCubicActionIn());
            // アクションが終わった後に呼び出されるコールバック関数を追加
            let callback = cc.callFunc(this.playJumpSound, this);
            // リピート(ジャンプアップ→ジャンプダウン→着地音再生→ジャンプアップ→・・・の繰り返し)するアクションを返す
            return cc.repeatForever(cc.sequence(jumpUp, jumpDown, callback));
        }
    
        playJumpSound () {
            // 着地音を再生
            cc.audioEngine.playEffect(this.jumpAudio, false);
        }
    
        onKeyDown (event) {  // キーを押した時の処理
            switch(event.keyCode) { 
                case cc.macro.KEY.a: // 『a』キーの場合
                    this.accLeft = true; // 『右に加速フラグ』をtrueにする
                    break;
                case cc.macro.KEY.d: //  『d』キーの場合
                    this.accRight = true;// 『左に加速フラグ』をtrueにする
                    break;
            }
        }
        
        onKeyUp (event) { //キーを放した時の処理
            switch(event.keyCode) {
                case cc.macro.KEY.a: // 『a』キーの場合
                    this.accLeft = false; // 『右に加速フラグ』をfalseにする
                    break;
                case cc.macro.KEY.d: //  『d』キーの場合
                    this.accRight = false; // 『左に加速フラグ』をfalseにする
                    break;
            }
        }
    
        onLoad () {
            // ジャンプアクションを作成
            let jumpAction = this.setJumpAction();
            // ジャンプアクションを開始
            this.node.runAction(jumpAction);
            // キーダウンイベントのリスニング(受付)を開始 
            cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
            // キーアップイベントのリスニング(受付)を開始  
            cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this); 
        }
    
        onDestroy () { // ノードが破棄される時の処理
            // キーダウンイベントのリスニング(受付)を終了
            cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
            // キーアップイベントのリスニング(受付)を終了
            cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this); 
        }
    
        update (dt) {
            if (this.accLeft) { // 左の加速フラグがtrueの場合
                this.xSpeed -= this.accel * dt; // 現在の水平速度を更新
            } else if (this.accRight) { // 右の加速フラグがtrueの場合
                this.xSpeed += this.accel * dt; // 現在の水平速度を更新
            }
        
            // 現在の水平速度が最大速度を超える場合
            if ( Math.abs(this.xSpeed) > this.maxMoveSpeed ) {
                // 方向はそのままで、最大速度にする
                this.xSpeed = this.maxMoveSpeed * this.xSpeed / Math.abs(this.xSpeed);
            }
        
            this.node.x += this.xSpeed * dt; // 現在のキャラクタの水平位置を更新
        }
    }
    

あとがき

 private・public・protected等の宣言は、マニュアルやtsファイルのテンプレートで省略されていたので、同様に省略しました。
 これからはTypeScriptで作ろうと思います。ブログ的には見る人を狭める気がしますが、JavaScript分かる人はTypeScriptすぐに分かるのではないかと思います。