初めてのPlayCanvas(2)

PlayCanvasはHTMLで動作するゲームを作成するためのクラウドベースのゲームエンジンです。詳細は参考ページに譲りますが、Unityライクなエディタを用いてハイクオリティなゲームを作成することが可能で、例えば以下のようなプロダクトが本家[紹介ページ](https://playcanvas.com/explore/featured)にて紹介されています:

* [SWOOOP](http://playcanv.as/p/JtL2iqIH)
* [BMW i8](http://playcanv.as/p/RqJJ9oU9)

これらはPC、スマートフォン限らず任意のブラウザ上で動作し、実際に手元のiPhone(iOS 8.4.1でした)でも動かすことができました。

[前回](http://qiita.com/p-baleine@github/items/5f9204c654badeccbe66)に引き続き「Roll a ball」を完成させます。

この記事の完成形のサンプルコードとゲームは以下にあります:

* コード: https://playcanvas.com/project/362488/overview/roll-a-ball
* ゲーム: http://playcanv.as/p/ItYPPOJX

# ゲームの作成(つづき)

前回までで、左カーソルを押下するとPlayer(球体)がGround(地盤)を転がる部分まで実装しました。如何コードをPlayerController.jsに追記して、Playerの移動を上下左右カーソルに対応させます。

```js
pc.script.create("PlayerController", function(app) {
    // コンストラクタ
    var PlayerController = function(entity) {
        this.entity = entity;
    };

    PlayerController.prototype = {
        // すべてのリソースがロードされた後、最初の`update`が呼ばれる前に一度だけコールされる
        initialize: function() {
            // キーダウンイベントのハンドラに自身の`onKeyDown`メソッドを登録
            app.keyboard.on(pc.EVENT_KEYDOWN, this.onKeyDown, this);
        },

        // 毎フレームごとにコールされる、`dt`は前回の`update`コールからの秒数
        update: function(dt) {
            if (app.keyboard.isPressed(pc.KEY_LEFT)) {
                // 押下されたのが左キーだった場合
                // X軸方向に大きさ-1の衝撃をエンティティに与えることでエンティティを動かす
                this.entity.rigidbody.applyImpulse(-1, 0, 0);
            }
            if (app.keyboard.isPressed(pc.KEY_RIGHT)) {
                // 押下されたのが右キーだった場合
                this.entity.rigidbody.applyImpulse(1, 0, 0);
            }
            if (app.keyboard.isPressed(pc.KEY_UP)) {
                // 押下されたのが上キーだった場合
                this.entity.rigidbody.applyImpulse(0, 0, -1);
            }
            if (app.keyboard.isPressed(pc.KEY_DOWN)) {
                // 押下されたのが下キーだった場合
                this.entity.rigidbody.applyImpulse(0, 0, 1);
            }
        },
        
        onKeyDown: function(event) {
            // デフォルトのキーイベントをキャンセルする
            event.event.preventDefault();
        }
    };

    return PlayerController;
});     
```

## 壁の作成

地盤を囲う壁を作成します。「HIERARCHY」ペインの「Ground」を右クリックして「New Entry」→「Box」を選択します。こうすると「Ground」の子要素として新しい「Box」が作成されます、親である「Ground」に対する相対配置で子要素を設定する必要があります。子要素の「Box」を選択して「INSPECTOR」ペインの「Position」と「Scale」に以下の値を入力します。

Position
　x: -0.5, y: 0, z: 0
Scale
　x: 0.05, y: 3, z: 1

また「ADD COMPONENT」→「RIGID BODY」でリジッドボディを追加(TypeはStatic)、「ADD COMPONENT」→「COLLISION」でコリジョンを追加して、「COLLISION」に以下値を設定しておきます。

Half Extents
　x: 0.5, y: 1, z: 10

「HIERARCHY」ペインにて今作成した「Box」を右クリックして「Duplicate」をクリックし、「Box」の複製を3つ作成し、各々に以下値を入力して壁を作成します。

２つ目の壁
Position
　x: 0.5, y: 0, z: 0
Scale
　x: 0.05, y: 3, z: 1
Half Extents
　x: 0.5, y: 1, z: 10

３つめの壁
Position
　x: 0 y: 0, z: 0.5
Scale
　x: 1, y: 3, z: 0.05
Half Extents
　x: 10, y: 1, z: 0.5

４つめの壁
Position
　x: 0 y: 0, z: -0.5
Scale
　x: 1, y: 3, z: 0.05
Half Extents
　x: 10, y: 1, z: 0.5

![スクリーンショット 2015-10-25 18.30.09.png](https://qiita-image-store.s3.amazonaws.com/0/14712/41cf0fe1-bd42-3628-ea07-1c65909ebb3d.png)


「▶」をクリックして動作を確認します、上下左右キーを押すとプレイヤーが転がって、壁にぶつかるのが確認できます。

勢い良くプレイヤーを動かすとプレイヤーは壁を越えて落ちていってしまいます。プレイヤーの動作を制限するために、「INSPECTOR」ペインにてプレイヤーの「RIGID BODY」→「Linear Factor」に以下値を設定します(Yを0にします)。

Linear Factor
　x: 1, y: 0, z, 1

## キューブ(立方体)の追加

プレイヤーが捕獲するキューブを追加します。

「HIERARCHY」ペインの「Root」を右クリック→「New Entity」→「Box」を選択し、「INSPECTOR」ペインで「Name」を「Cube」にしておきます。また、「INSPECTOR」ペインよりEntityの値を以下のように変更します。

Position
　x: 0, y, 1.5, z: 8
Rotation
　x: 45, y: 45, z: 45
Scale
　x: .75, y: .75, z: .75

また「MODEL」の「Cast」にチェックを入れて影を付けます。

「INSPECTOR」ペインより「ADD COMPONENT」→「RIGID BODY」を選択してリジッドボディを追加します。他のエンティティからの物理的な相互作用が働かないように、「RIGID BODY」のTypeにはKinemaicを選択します。又、「ADD COMPONENT」→「COLLISION」を選択してコリジョンを追加します。

今作成したキューブを右クリック→「Dupulicate」で5、6個複製して、これらを「Ground」上に配置します。赤色(X軸)と青色(Z軸)の矢印をマウスでドラッグして好みの配置に移動させてみてください。

「▶」をクリックして動作を確認します、上下左右キーを押下してPlayerが転がり、キューブに触れると反発するのを確認します。

![スクリーンショット 2015-10-25 18.43.15.png](https://qiita-image-store.s3.amazonaws.com/0/14712/d2477ce2-4c86-bb02-fdb6-0754dc88d41a.png)

## キューブに挙動を追加

このままだとキューブは静止したままで面白みに欠けるため、キューブを回転させてみます。「ASSET ＋」→「New Script」をクリックし、「CubeController.js」という名前で作成します。「Edit」をクリックして、以下内容で保存します。

```js
pc.script.create("CubeController", function(app) {
    // コンストラクタ
    var CubeController = function(entity) {
        this.entity = entity;
    };

    CubeController.prototype = {
        // キューブの動作の速度
        VELOCITY: new pc.Vec3(50, 50, 10),

        // すべてのリソースがロードされた後、最初の`update`が呼ばれる前に一度だけコールされる
        initialize: function() {
            // このエンティティのリジッドボディの回転方向の速度を調整
            this.entity.rigidbody.angularVelocity = this.VELOCITY;
        },

        update: function(dt) {
            // 回転を与える
            this.entity.rigidbody.applyTorque(-15, 0, 0);
        }
    };

    return CubeController;
});
```

エディタに戻って「INSPECTOR」にて「Cube」を選択、「ADD COMPONENT」→「SCRIPT」をクリックし、「SCRIPTS」で「ADD SCRIPT」を選択、「ASSETS」ペイより今作成した「CubeController.js」をドラッグします。先ほど作成たキューブ全てについて同様に「CubeController.js」スクリプトを付けます。

「▶」をクリックして動作を確認します、キューブが回転しているのを確認します。

## プレイヤーによるキューブの獲得

このゲームでは、上下左右キーで操作するプレイヤーがすべてのキューブを捕獲することが目標となります。プレイヤーエンティティがキューブに衝突した際に、キューブが捕獲されるロジックを実装します。先ほど作成したCubeController.jsを以下内容に編集して保存します。

```js
pc.script.create("CubeController", function(app) {
    // コンストラクタ
    var CubeController = function(entity) {
        this.entity = entity;
    };

    CubeController.prototype = {
        // キューブの動作の速度
        VELOCITY: new pc.Vec3(50, 50, 10),

        // すべてのリソースがロードされた後、最初の`update`が呼ばれる前に一度だけコールされる
        initialize: function() {
            // このエンティティのリジッドボディの回転方向の速度を調整
            this.entity.rigidbody.angularVelocity = this.VELOCITY;
            // `collisionstart`イベントのハンドラに`destroy`メソッドを追加
            this.entity.collision.on("collisionstart", this.destroy, this);
        },

        update: function(dt) {
            // 回転を与える
            this.entity.rigidbody.applyTorque(-15, 0, 0);
        },

        destroy: function(result) {
            if (result &&
                result.other.rigidbody &&
                result.other.name === app.root.findByName("Player").name) {
                // コリジョンの相手の名前が`Player`の場合に自身を消去する
                this.entity.destroy();
            }
        }
    };

    return CubeController;
});
```

「▶」をクリックして動作を確認します、上下左右キーでPlayerエンティティを操作し、これがキューブに衝突するとキューブが消失するのを確認します。

# 終わりに

PlayCanvasは日本語のドキュメントリソースが少ないためハードルが高く感じますが、実際に触ってみるとUnityライクなエディタは直感的で触りやすく、また[APIドキュメント](http://developer.playcanvas.com/en/api/)や[フォーラム](http://forum.playcanvas.com/)が充実しており、なにより[たくさんのプロジェクト](https://playcanvas.com/explore)がソースを公開してくれているため、想定程苦労しなくてむしろ拍子抜けする、というのが自分の実感です。…ただ本家チュートリアルも[ビデオ](https://www.youtube.com/playlist?list=PL0KdXFF26E4Bpjx5R3B8LH6blmU-h3JLV)(2分 ✕ 8本)くらいしかなくて、もうちょっと充実してほしいなぁ…今後に期待しています。