モーリーのメモ

プログラミングやCG作成等、アプリ開発を中心に情報を収集中!

マニュアルのチュートリアルをやってみる! その2:Cocos Creator

 こちらの記事の続きです。
mmorley.hatenablog.com
 引き続き『Cocos Creator』の『User Manual』のチュートリアルの『Create the first game』をやってみます。
 今回は、スクリプトソースコード)の作成がメインです。
 アニメーションの設定、キーボード入力の受付、ループ処理等を行っています。
 また、『Cocos Creator』で初めて使う『Prefab』も使用しています。

使用環境

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

  • Mac OS X El Capitan Version 10.11.4
  • Cocos Creator Version 1.0
  • ブラウザ:Google Chrome Version 49.0.2623.110 (64-bit)

メインキャラクタのスクリプトを作成

 『Cocos Creator』によるゲーム開発の中心的なアイデアの1つは、コンテンツの作成と機能の開発をスムーズに・並行的に・連携して行えることです。
これまでのセクションでは、芸術的なコンテンツに焦点を当てました。次に、機能を開発するためのスクリプトを作成します。完成したプログラム・スクリプトがコンテンツ・クリエーターによって簡単に使用できることが分かります。
 
 あなたがプログラムを書いたことがなくても心配ありません。チュートリアルでは、必要なコードを全て提供します。正しい位置にコピー&ペーストするだけです。この部分はあなたのプログラマのパートナーに助けを求めることが出来ます。次にメインキャラクタの行動を操作するためのスクリプトを作成します。

スクリプトの作成

  1. 『Assets』パネルで『assets』フォルダを右クリックし、『Create』-『Folder』を選択
    f:id:mmorley:20160407142017p:plain:w400
  2. 作成したフォルダを右クリックし、『Rename』を選択して、名前を『scripts』に変更(この中に全てのスクリプトを保存します。)
  3. 『scripts』フォルダを右クリックし、『Create』-『JavaScript』を選択し、『JavaScript』のスクリプトを作成
    f:id:mmorley:20160407142317p:plain
  4. 作成したスクリプトの名前を『Player』に変更し、このスクリプトをダブルクリックしてコードエディタを開く

コンポーネントのプロパティを作成

 スクリプトには、あらかじめ設定されたコードのブロックがあります。これらのコードはコンポーネントスクリプトを書くために必要な構造です。『Cocos Creator』では、このような構造のスクリプトは、シーンのノードに取り付けて、ノードを操作する様々な機能を与えることができるコンポーネントです。まずプロパティを設定して、シーンでそれらを調整する方法を確認します。
 
 コードエディタの『Player』スクリプトで『properties』の部分を探し、それを次の内容に書き換えて、Macなら『command + s』キー、Windowsなら『ctrl + s』キーを押して保存して下さい。

// Player.js
    //...省略
    properties: {
        // メインキャラクタのジャンプの高さ
        jumpHeight: 0, 
        // メインキャラクタのジャンプの時間
        jumpDuration: 0, 
        // 最大移動速度
        maxMoveSpeed: 0, 
        // 加速度
        accel: 0, 
    },
    //...省略

 追加したこれらのプロパティはメインキャラクタの移動を規定します。後で『Properties』パネルで直接これらの数値を設定するので、コード内の数値を気にする必要はありません。
 
 メインキャラクタのノードに『Player』コンポーネントを追加します。『Node Tree』パネルで『Player』ノードを選択し、『Properties』パネルで『Add Component』ボタンをクリックします。そして『Add Custom Component』-『Player』を選択し、メインキャラクタのノードに『Player』コンポーネントを追加します。
f:id:mmorley:20160407143637p:plain
 『Properties』パネルで追加した『Player』コンポーネントを確認します。(『Player』ノードを選択する必要があります。)下図のように、メインキャラクタのジャンプと移動に関するプロパティを設定します。
f:id:mmorley:20160407144355p:plain:w300
 『jumpDuration』の単位だけ秒です。他の値の単位はピクセルです。『Player』コンポーネントの現在の設定:メインキャラクタのジャンプの高さは200ピクセルです。ジャンプの最高地点に達するには0.3秒かかります。水平方向の最大移動速度は400ピクセル/秒です。水平方向の加速度は350ピクセル/秒です。

 これらの数値は1つの例です。後でゲームを実行した際にこれらの数値をお好みで、いつでも『Properties』パネルで変更して下さい。コードで変更する必要はありません。とても便利です。

ジャンプと移動のコードを作成

 次に、メインキャラクタのジャンプのメソッドを追加します。『setJumpAction』というメソッドを『properties: {...},』ブロックの下に作ります。

// Player.js
    properties: {
        //...省略
    },

    setJumpAction: function () {
        // ジャンプアップ
        var jumpUp = cc.moveBy(this.jumpDuration, cc.p(0, this.jumpHeight)).easing(cc.easeCubicActionOut());
        // ジャンプダウン
        var jumpDown = cc.moveBy(this.jumpDuration, cc.p(0, -this.jumpHeight)).easing(cc.easeCubicActionIn());
        // リピート
        return cc.repeatForever(cc.sequence(jumpUp, jumpDown));
    },

 『Cocos2d-js』エンジンのアクションが、メインキャラクタのジャンプアニメーションを実現するために使用されています。詳細については『Cocos2d-js API』を確認して下さい。
 『onLoad』メソッドで、追加した『setJumpAction』メソッドを呼び出し、アクションを開始するために『runAction』を実装します。

// Player.js
    onLoad: function () {
        // ジャンプアクションを初期化
        this.jumpAction = this.setJumpAction();
        this.node.runAction(this.jumpAction);
    },

 『onLoad』メソッドはシーンをロードした後にすぐに実施されます。そのためそこに初期化に関する操作やロジックを書きます。
 
 スクリプトを保存した後、最初のゲーム実行が出来ます!
 
 『Cocos Creator』の画面上部のプレビューボタンをクリックして下さい。『Cocos Creator』は、自動的にデフォルトのブラウザを開いて、そこでゲームを実行します。シーン内で活き活きと連続してジャンプするメインキャラクタ(紫のモンスター)が表示されるはずです。

移動の操作

 同じ場所で馬鹿みたいに上下にジャンプするだけのメインキャラクタは将来有望には見えません。メインキャラクタに、ジャンプ方向を操作するための『A』と『D』のキー操作を追加します。『setJumpAction』メソッドの下に『setInputControl』メソッドを追加します。

// Player.js
    setJumpAction: function () {
       //...省略
    },

    setInputControl: function () {
        var self = this;
        // キーボードのイベントリスナーを追加
        cc.eventManager.addListener({
            event: cc.EventListener.KEYBOARD,
            // キーを押したときに、それが設定した方向ボタンであれば、対応する方向に加速度を設定します。
            onKeyPressed: function(keyCode, event) {
                switch(keyCode) {
                    case cc.KEY.a:
                        self.accLeft = true;
                        self.accRight = false;
                        break;
                    case cc.KEY.d:
                        self.accLeft = false;
                        self.accRight = true;
                        break;
                }
            },
            // キーを離したときに、その方向の加速を辞めます。
            onKeyReleased: function(keyCode, event) {
                switch(keyCode) {
                    case cc.KEY.a:
                        self.accLeft = false;
                        break;
                    case cc.KEY.d:
                        self.accRight = false;
                        break;
                }
            }
        }, self.node);
    },

 次に『onLoad』メソッドに、メインキャラクタの左右の加速のスイッチと、現在の水平方向の速度を追加します。そして新たに追加した『setInputControl』メソッドを呼び出します。シーンをロードした後に、キーボードのモニタリングを開始します。

// Player.js
    onLoad: function () {
        // ジャンプアクションの初期化
        this.jumpAction = this.setJumpAction();
        this.node.runAction(this.jumpAction);

        // 加速方向の切り替えスイッチ
        this.accLeft = false;
        this.accRight = false;
        // メインキャラクタの現在の水平方向の速度
        this.xSpeed = 0;

        // キーボード入力のリスナーを初期化
        this.setInputControl();
    },

 最後に、『update』メソッドにメインキャラクタの加速度、速度、現在の位置の設定を追加します。

// Player.js
    update: function (dt) {
        // 現在の加速方向に応じてフレームごとに速度を更新
        if (this.accLeft) {
            this.xSpeed -= this.accel * dt;
        } else if (this.accRight) {
            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;
    },

 『update』は、シーンがロードされた後、フレームごとに1度呼び出されます。通常、ここには頻繁に行う計算やタイムリーな更新が必要な内容を書きます。私達のゲームでは、フレームごとに『update』でキーボードの入力によって加速の方向を取得した後、メインキャラクタの速度と位置を計算する必要があります。
 
 スクリプトを保存した後、あなたはお茶を淹れてから、最新の結果を確認するためにプレビューをクリックすることが出来ます。ブラウザでプレビューを開いた後、マウスでゲームのシーンをクリック(ブラウザの制約により、キーボードの入力はゲームシーンをクリックした後に受け付けられるため)した後、『A』と『D』キーを押してメインキャラクタを左右に移動させることが出来ます。
 
 動きが少し遅すぎますか?メインキャラクタは十分に高くジャンプしないですか?ジャンプの時間を長くしたいですか?問題ありません!これらはいつでも調整出来ます。『Player』コンポーネントに異なるプロパティ値を設定するだけで、思い通りにゲームを調整できます。これは設定例です。

Jump Height: 150
Jump Duration: 0.3
Max Move Speed: 400
Accel: 1000

 これはメインキャラクタを可能な限りすばしっこくする設定です。どのような値を設定するかは、すべてあなたが望むゲームのスタイルによるところです。

星を作成

 メインキャラクタが自由に飛べるようになったので、プレイヤーに目標を設定します。シーンに星が連続的に現れ、プレイヤーがモンスターを操作して星に触れさせると得点になります。メインキャラクタが触れた星は消えて、すぐにランダムな位置に新しい星が再作成されます。

Prefabの作成

 繰り返し作成する必要があるノードは、『Prefab』リソースとして保存出来ます。それはノードを動的に生成するためのテンプレートにすることが出来ます。『Prefab』の詳細については、『Prefab』の章をお読み下さい。
 
 最初に、『Assets』パネルから『assets/textures/star』のリソースをシーンにドラッグします。位置はどこでも構いません。『Prefab』の作成のための作業場所としてシーンを使用するだけです。作成後、シーンからこのノードを削除します。
 
 星の位置を変更したり、プロパティを与えたりする必要はありません。しかし、メインキャラクタに触れられた星を消すためには、星に特別なコンポーネントを追加する必要があります。『Player』スクリプトを追加するのと同じ方法で、『assets/scripts/』に『Star』という名前でスクリプトを追加します。
f:id:mmorley:20160407230145p:plain
 次に編集を開始するためにこのスクリプトをダブルクリックします。『star』コンポーネントに必要な唯一のプロパティは、メインキャラクタの得点の条件となる距離です。

// Star.js
    properties: {
        // メインキャラクタと星との距離がこの値より小さい場合、得点となる
        pickRadius: 0
    },

 スクリプトを保存した後、新しく追加した『star』ノードにこのスクリプトを追加します。
f:id:mmorley:20160415100706p:plain
 『Pick Radius』のプロパティの値を『Properties』パネルで60に設定します。
f:id:mmorley:20160407231204p:plain
 『Star Prefab』 に必要な設定は完了しています。『Node Tree』パネルから『star』ノードをドラッグし、『Assets』パネルの『assets』フォルダの下に置きます。
f:id:mmorley:20160407232024p:plain
すると『star』という名前の『Prefab』リソースが生成されます。
f:id:mmorley:20160407232259p:plain
 シーンから『star』ノードを削除します。星を生成するために、スクリプトで星の『Prefab』リソースを動的に使用することが出来ます。

ゲームコントロールスクリプトの追加

 星の生成は、ゲームのメインロジックの一部分です。『Game』という名前のスクリプトを追加し、ゲームのメインロジックのスクリプトにします。得点、失敗、リスタートのロジックは、後でこのスクリプトに追加します。
 
 『Game』スクリプトを追加して『assets/scripts』フォルダの下に置きます。ダブルクリックでこのスクリプトを開きます。最初に星の生成に必要なプロパティを追加します。

// Game.js
    properties: {
        // このプロパティは星の『Prefab』リソースを引用します。
        starPrefab: {
            default: null,
            type: cc.Prefab
        },
        // 星が消えている時間の乱数の幅
        maxStarDuration: 0,
        minStarDuration: 0,
        // 星の位置の高さを確認するための『ground』ノード
        ground: {
            default: null,
            type: cc.Node
        },
        // メインキャラクタのジャンプの高さを取得し、メインキャラクタの移動の切り替えをコントロールするための『Player』ノード
        player: {
            default: null,
            type: cc.Node
        },
    },

 スクリプトを保存した後、『Node Tree』パネルで『Game』コンポーネントを『Canvas』ノードに追加します。(『Canvas』ノードを選択した後に、スクリプトを『Properties』パネルへドラッグするか、『Properties』パネルの『Add Component』ボタンをクリックして『Add Custom Component』-『Game』を選択します。次に『Assets』パネルから『star』の『Prefab』リソースを『Game』コンポーネントのプロパティにドラッグします。ここで、初めてプロパティに"引用"を設定します。プロパティの宣言時に型を"引用"型(例えば、ここに書かれたcc.Prefab型)として宣言した場合にのみ、リソースまたはノードは、プロパティにドラッグすることができます。) 
f:id:mmorley:20160415111705p:plain:w550
 
 それから『Min Star Duration』と『Max Star Duration』プロパティの値を3と5に設定します。後で星を生成する時に、この2つの値の間の乱数値を星が消える時間に設定します。

ランダムな位置で星を生成

 次に『Game』スクリプトの『on Load』メソッドの後に、星を生成するロジックを追加します。

// Game.js
    onLoad: function () {
        // 『ground』のアンカーポイントのy座標を取得
        this.groundY = this.ground.y + this.ground.height/2;
        // 新しい星を生成
        this.spawnNewStar();
    },

    spawnNewStar: function() {
        // 事前に設定したテンプレートでシーンに新しいノードを生成
        var newStar = cc.instantiate(this.starPrefab);
        // 新しく追加したノードを『Canvas』ノードの下に置く
        this.node.addChild(newStar);
        // 星にランダムな位置を設定
        newStar.setPosition(this.getNewStarPosition());
    },

    getNewStarPosition: function () {
        var randX = 0;
        // 地面の高さとメインキャラクタのジャンプの高さによって、星のアンカーポイントのy座標をランダムに取得
        var randY = this.groundY + cc.random0To1() * this.player.getComponent('Player').jumpHeight + 50;
        // 画面の幅によって、星のアンカーポイントのx座標をランダムに取得
        var maxX = this.node.width/2;
        randX = cc.randomMinus1To1() * maxX;
        // 星のアンカーポイントの位置を返す
        return cc.p(randX, randY);
    },

 スクリプトを保存した後、プレビューボタンをクリックします。するとブラウザでゲームがスタートした後に、動的に星が生成されていることが分かります。同じ方法により、ゲーム内の『Prefab』テンプレートを使用して事前に設定したノードを動的に生成することが出来ます。
f:id:mmorley:20160407233816p:plain:w400

メインキャラクタが星を採取するアクションを追加

 メインキャラクタが星を採取するアクションのロジックを追加します。ここでの重要な点は、星とメインキャラクタの距離が採取可能な距離より短いか判断するために、それらのノードの位置の常に取得する必要があることです。どのようにメインキャラクタのノードの"引用"を取得しますか?以前に行っている2つのことを思い出して下さい。

  1. 『Game』コンポーネントで『Player』と名付けられたプロパティがあります。それはメインキャラクタのノードの"引用"を保存しました。
  2. それぞれの星は『Game』スクリプトで動的に生成されています。

 従って、唯一必要なのは、『Game』スクリプトで『Star』ノードの実体が生成された後に、『star』に『Game』コンポーネントの実体を渡し、それを保持することです。その後、いつでも『game.player』によってメインキャラクタノードを参照できます。『Game』スクリプトを開き、『spawnNewStar』メソッドの最後に次のコードを追加しましょう。

// Game.js
    spawnNewStar: function() {
        // ...省略
        // 『star』コンポーネントに『Game』コンポーネントの実体を渡す。
        newStar.getComponent('Star').game = this;
    },

 保存した後『Star』スクリプトを開いて、距離を判定するために『Game』コンポーネントで"引用"された『Player』ノードを使用します。『getPlayerDistance』と『onPicked』と名付けたメソッドを『onLoad』メソッドの下に追加します。

// Star.js
    getPlayerDistance: function () {
        // 『Player』ノードの位置によって判定します。
        var playerPos = this.game.player.getPosition();
        // 2つのノード間の距離を計算します。
        var dist = cc.pDistance(this.node.position, playerPos);
        return dist;
    },

    onPicked: function() {
        // 星が採取された時に、新しく星を生成するために『Game』スクリプトでインターフェースを呼び出します。
        this.game.spawnNewStar();
        // その後、現在の『star』ノードを破棄します。
        this.node.destroy();
    },

 次に『update』メソッドに、フレームごとの距離の判定を追加します。もし距離が『pickRadius』プロパティによって設定された採取可能距離より短い場合に採取する動作を実装します。

// Star.js
    update: function (dt) {
        // フレームごとに星とメインキャラクタの間の距離が採取距離より短いか判定します。
        if (this.getPlayerDistance() < this.pickRadius) {
            // 採取する動作を呼び出します。
            this.onPicked();
            return;
        }
    },

 スクリプトを保存した後、再びプレビューとテストを行います。メインキャラクタが星に近づいたときに、星が消えてランダムな位置に新しい星が生成されていることが分かります。

 まだ続くのですが、今回はここまでとします。

あとがき

 どう訳すのが正解かわかりませんが、『quotation』を"引用"と訳しています。
 他のデモプロジェクトでも『Prefab』はよく使われているので、うまく活用できるようになりたいです。
 

この記事の続き

 この記事の続きです。
mmorley.hatenablog.com