Cocos Creator 3 カンタン2Dゲーム制作(2)【プレハブの使い方】

# ページ一覧

__Cocos Creator 3 導入編__
- [Cocos Creator 3 ダウンロード](https://qiita.com/Hiro256/items/941b196f2e3f0db9e7da)
- [Cocos Creator 3 インストール](https://qiita.com/Hiro256/items/056e922af7821e482e88)

__Cocos Creator 3 カンタン2Dゲーム制作__
- [プロジェクト作成と画面の説明](https://qiita.com/Hiro256/items/ee21f1aa5dc452f8e992)
- [(1)【トゥイーンの使い方】](https://qiita.com/Hiro256/items/872f82fe7c875106faaf)
- (2)【プレハブの使い方】← __本記事__
- [(3)【衝突判定（コライダの使い方）】](https://qiita.com/Hiro256/items/153777d22a3e24e4bb5a)
- [(4)【アニメーションクリップの使い方】](https://qiita.com/Hiro256/items/f077e64bdc047bc8ef16)
- [(5)【パーティクルの使い方】](https://qiita.com/Hiro256/items/4b91f426d88cfd9e2e82)
- [(6)【ラベルアトラスの使い方】](https://qiita.com/Hiro256/items/a0716a722a709414afe6)



# アイテムを登場させる

魚が動くようになったところで、次にアイテムを登場させます。

## アイテムの作成
ヒエラルキーパネルの`Canvas`を右クリックし、`Create`→`Empty Node`を選択します。`Node`が追加されるので名前を`Items`に変更します。

<img width="300px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/98606066-f5b4-a4f4-2195-6ced6ad53b05.png">

アセットパネルから`Coin`と`Syuriken`をドラッグ＆ドロップでヒエラルキーパネルの`Items`の配下にコピーします。ノードの構成は以下のようになります。
<img width="200px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/bf14e619-5c1a-9937-8dd4-06fdfa20be74.png">

`Coin`のノードを選んで、インスペクターパネルの`cc.UITransform`の下にある`Content Size`を`60`,`60`にします。同様の手順で`Syuriken`のノードも`80`,`80`にします。（なおノードの`pisotion`はどこでもいいです）
<img width="300px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/216c6b0a-5086-1f58-36ff-604d52d9e7bd.png">

## アイテムを出現させるスクリプト

アセットパネルから、`Assets`右クリック→`Create`→`TypeScript`→`NewComponent`で新しいスクリプトを作成します。名前を`SpawnItems`とします。
もう一つ、同じ手順で`FallingItem`という名前のスクリプトを作ります。
それぞれのファイルに下記の内容を記述します。
スクリプトファイルが増えてきたので`Assets`の下に`Script`というフォルダを作りスクリプトファイルを入れます。

```ts:SpawnItems.ts
import { _decorator, Component, Prefab, instantiate } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('SpawnItems')
export class SpawnItems extends Component {

    @property(Prefab)
    prefabCoin: Prefab = null;

    @property(Prefab)
    prefabObstacle: Prefab = null;

    _intervalCoin: number = 0;
    _intervalObstacle: number = 0;

    start() {
        this.setIntervalCoin();
        this.setIntervalObstacle();
    }

    setIntervalCoin() {
        this._intervalCoin += Math.random() * 3 + 1;

    }
    setIntervalObstacle() {
        this._intervalObstacle += Math.random() * 3 + 0.1;
    }

    update(deltaTime: number) {
        // コインの出現条件
        this._intervalCoin -= deltaTime;
        if (this._intervalCoin < 0) {
            this.node.addChild(instantiate(this.prefabCoin));
            this.setIntervalCoin();
        }

        // 障害物の出現条件
        this._intervalObstacle -= deltaTime;
        if (this._intervalObstacle < 0) {
            this.node.addChild(instantiate(this.prefabObstacle));
            this.setIntervalObstacle();
        }
    }
}
```

```ts:FallingItem.ts
import { _decorator, Component, Vec3, UITransform, view } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('FallingItem')
export class FallingItem extends Component {
    @property
    speed: number = 400;

    start() {
        const viewSize = view.getVisibleSize();
        const pos = {
            x: Math.random() * viewSize.x - (viewSize.x / 2),
            y: viewSize.y / 2 + 50
        };
        this.node.setPosition(new Vec3(pos.x, pos.y));
    }

    update(deltaTime: number) {
        const uitf = this.getComponent(UITransform);
        if (this.node.position.y < -(view.getVisibleSize().y / 2 + uitf.height)) {
            this.node.destroy();
            return;
        }
        let moveY = this.speed * deltaTime;
        this.node.setPosition(new Vec3(this.node.position.x, this.node.position.y - moveY));
    }
}
```

## ノードとスクリプトの紐づけ

ヒエラルキーパネルにある`Items`を選択します。インスペクターパネルの一番下の、`Add Component`ボタンを押します。別の小窓が出るので、`Search Name`と書かれたエリアに`spawn`と入力します。登録済みのスクリプトが`Custom Script`の下に表示されるので、`SpawnItems`を選択します。
同様の手順で、`Coin`と`Syuriken`にも`FallingItem`のスクリプトを紐づけます。`ctrl+S`でシーンを保存します。
これまでに作成したノードとスクリプトファイルの関係を図示すると以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/de209f09-9678-e11d-59a1-a7fbb07ff842.png)

# prefab（プレハブ）の作成

これまで、表示するノードはすべてシーン上に配置してきました。
今回はprefab（プレハブ）という機能を使って沢山のアイテムが出現する仕組みを作成します。

## ヒエラルキーパネルからのプレハブの作成
アセットパネルでフォルダー`Prefab`を作ります。
そこにヒエラルキーパネルにある`Coin`と`Syuriken`をアセットパネルの方へドラッグ＆ドロップでコピーします。すると緑色のアセットが登録されます。これが`prefab`（プレハブ）で、ヒエラルキーパネルにあったノードのコピー品がアセットとして登録されました。
ヒエラルキーパネルにあった元の2つは不要になったので削除します。

プレハブをダブルクリックすると、シーンパネルにそのプレハブが映り、インスペクターパネルの最下部に先ほど登録した`FallingItem`のスクリプトが紐づいてることが確認できます。
（アセットパネルの`SeaScene`をダブルクリックして戻ります。）

<img width="200px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/d8494949-47eb-6707-2468-e1a65dd87e8a.png">

## プレハブを使ったアイテムの出現処理

ヒエラルキーパネルにある`Items`を選択します。
インスペクターパネルの最下部にほど紐づけた`SpawnItems`が確認できます。その中に空の枠が2か所あります。
アセットパネルにある`Coin`と`Syuriken`を、それぞれ`Prefab Coin`、`Prefab Obstacle`の枠にコピーします。（枠の右にあるアイコンをクリックしてそこから選択することもできます）
これにより、`SpawnItems`のスクリプト内で、アセットパネルにあるプレハブを使えるよう紐づけが行えました。

<img width="400px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/b36561b1-3214-5fa2-7850-e2ba66958a67.png">

## ここまでの動きの確認

▶ボタン（または`ctrl+P`）で動きを確認してみましょう。正しく作れていれば上からアイテムが降って来るのが確認できます。

# スクリプトの解説

## アイテムを出現させるスクリプト

まずは`SpawnItems.ts`から。このスクリプトは一定間隔でアイテムを出現させる処理を行います。

### プロパティデコレータ
クラス内のプロパティですが、これはプロパティデコレータといて、コード上に`@property()`というデコレータを付けることで、インスペクターパネルで値の設定ができるようになるものです。先ほどの手順でプレハブをドラッグ＆ドロップでセットしたのはこのプロパティに保持するためです。

```ts:
@property(Prefab)
prefabCoin: Prefab = null;
```

### アイテム出現処理
`start`メソッドで`this.setIntervalCoin()`を呼び出し、最初にアイテムを出現するまでの秒数をランダムで決定します。値は`this._intervalCoin`に格納します。

`update`メソッドは画面の描写が行われるごとに毎回呼び出されます。
`deltaTime`には前回の描画から今回までの経過時間が入っており、`this._intervalCoin`からその値を引いていき、0になるまで繰り返します。0になったとき、すなわち予定した時間が経過したとき、新たに`instantiate`メソッドを使てプレハブを複製し、`this.node.addChild()`で、自身のノード（`items`）に追加し出現させます。出現させた後は`this.setIntervalCoin()`を呼び出し、次回出現するまでの秒数をランダムで決定します。

```ts:
this.node.addChild(instantiate(this.prefabCoin));
```
コインについて解説しましたが、手裏剣についてもランダムの秒数を多少変えているだけで同様の仕組みです。

## アイテムを落下・消滅させるスクリプト

次に、`SpawnItems.ts`について。
`start`メソッドでは画面の最上部より上に、アイテムを配置します。横の位置(`x`)はランダムです。
`this.node.setPosition()`によりノードの位置を設定ます。

`update`メソッドは`SpawnItems.ts`同様、画面の描写が行われるごとに毎回呼び出される処理を書きます。
まず、`this.getComponent(UITransform)`でノードのサイズ情報を取得します。そして画面の最下部より下に行っているかどうかを判定し、そうであれば`this.node.destroy()`で自分自身のノードを削除します。
まだ、画面上にあるなら`deltaTime`と`speed`を使って、落下後の表示位置を計算します。

## スクリプトの役割分担
さて、これらのスクリプトでポイントとなるのが「アイテムの出現処理」「アイテムの移動・消滅」をそれぞれ別スクリプトとして用意したことです。その気になれば1つのスクリプトにすべて処理を記述することも可能ですが、分けるメリットについて見ていきます。
一つはノードごとの役割が明確になり、それぞれ自律的に処理できることです。`Coin`や`Syuriken`は「画面から見えなくなったら消える」という事が保証されているので、`Items`ノードは「アイテムの出現処理」の機能だけを担えばよく、アイテムが沢山あっても一つ一つを監視する必要がありません。結果的にコードを簡潔に書くことができます。