Cocos Creator 3 カンタン2Dゲーム制作(1)【トゥイーンの使い方】

__更新履歴__
- 2022/08/28 タップイベントの座標の取得/変換方法を変更しました。

# ページ一覧

__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)【トゥイーンの使い方】← __本記事__
- [(2)【プレハブの使い方】](https://qiita.com/Hiro256/items/26c4da8b46565e319471)
- [(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)

# 2Dゲーム制作の開始
これからは実際に2Dゲームを作成していき、具体的なツールの使い方を紹介していきます。

まず「FishGame」という名前で2Dのテンプレートを使い、新規にプロジェクトを作成します。

## アセットの登録
以下の画像をローカルに保存します。
ファイル名は左から順に`Sea.jpg`、`Fish.png`、`Coin.png`、`Syuriken.png`とします。（次の無料素材サイトを利用しました。[IllustAC](https://www.ac-illust.com/)、[素材のプチッチ](http://putiya.com/)、[ストックマテリアル](https://stockmaterial.net/)）

<img width="120px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/b0d752f8-c023-bce3-ba70-b0dedc755056.jpeg
"><img width="120px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/4a529d27-5cbc-22f0-0169-60ac2d04c5bf.png"><img width="80px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/dfa69ac7-5bc4-c5c3-be2e-9ede010d352c.png"><img width="90px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/2349a50c-aa7f-d8cd-5a1d-e0deff3d5cd2.png">

CocosCreatorの画面を開き、アセットパネルに4つの素材ファイルをドラッグ＆ドロップします。すると、ファイルが`Assets`ツリーの直下にコピーされます。`Assets`右クリック→`Create`→`Folder`でフォルダ作成ができるのでファイルが多い場合は任意でフォルダ分けします。ここでは`Images`というフォルダにすべて入れます。

:::note info
取り込んだファイルはプロジェクトフォルダ内にコピーされるので、元ファイルは移動/削除しても問題ありません。
:::

次に、`Assets`右クリック→`Create`→`Scene`で新しいシーンを作成します。名前を`SeaScene`とします。アセットのファイル名を変更する場合は`F2`を押します。
現時点のアセットパネルは次のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/be426c17-32ad-5e91-6742-78e501adc553.png)


## シーンの編集 ～海中のシーン作成～
アセットパネルにある`SeaScene`をダブルクリックします。するとヒエラルキーパネルに`SeaScene`が表示され、そのシーンが編集できるようになります。
アセットパネルから`Sea`、`Fish`を選択し、ヒエラルキーパネルの`Canvas`の直下にドラッグ＆ドロップで入れます。ノードの順番は表示する際に関係しますので、必ず次の図のように配置してください。（他のノードとの入れ子関係にも注意してください。）
配置すると、ヒエラルキーパネルで編集した内容がシーンパネルに表示されます。
![SeaScene01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/4d5ae8c7-7b90-7dcc-707e-5a7e5cb5f89a.png)

## ノードの位置の変更 ～魚の場所～
ヒエラルキーパネルにある`Fish`を選択します。シーンパネル上の魚に二つの矢印が出ていますので、中心部分のブルーの四角をドラッグすると魚の位置が移動できます。ここではシーンの左下に配置します。

:::note info
シーンパネルは「右クリック＋マウス移動」または「スペースキー＋マウス移動」で表示範囲を変えられます。
:::

ノードの位置はインスペクターパネルからも変更可能で、`Node`という見出しの下に`Position`があるので、ここでは`x, y`をそれぞれ`-300, -200`とします。`ctrl+S`でシーンを保存します。
<img width="300px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/969bdf4c-c654-e9cd-1571-08e3cb4a1b42.png">

### ノードの基準点について
ノードの位置は、中心点が基準となっています。下の図は現在のキャンバスの位置を示します。

<img width="80%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/2d3826b1-0669-a4f5-3697-a822db932c22.png">

## スクリプトの作成 ～魚を動かす～
ここでは、背景をタップ（クリック）するとその方向に向かって魚が一定距離近づく機能を追加します。

アセットパネルから、`Assets`右クリック→`Create`→`TypeScript`→`NewComponent`で新しいスクリプトを作成します。名前を`TouchFish`とします。ファイルをダブルクリックするとVSCodeが起動しまので、以下のコードを記述します。

:::note info
スクリプトのクラス名は、作成した際のファイル名が適用されます。後からファイル名を変更した場合、クラス名は自動で変更しないため、スクリプト内のクラス名を手動で書き換えます（4行目、5行目の2か所）。
:::

```TS:TouchFish.ts
import { _decorator, Component, Node, tween, Vec3, Vec2, view, EventTouch, log, Rect, UITransform } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('TouchFish')
export class TouchFish extends Component {
    @property
    moveDuration: number = 0.6; // 魚の一回の移動時間(秒)。

    _isMoveing: boolean = false; // 移動中かどうかのフラグ。

    start() {
        log('TouchFish - Start');
        this.enableTouch(true);
    }

    onDestroy() {
        this.enableTouch(false);
    }

    // タッチイベントの有効/無効切り替え
    enableTouch(enable: boolean) {
        if (enable) {
            this.node.on(Node.EventType.TOUCH_START, (event: EventTouch) => {
                this.moveFish(event);
            }, this);
        } else {
            this.node.off(Node.EventType.TOUCH_START);
        }
    }

    // 魚の移動処理
    moveFish(event: EventTouch) {
        log('TouchFish - moveFish');
        if (this._isMoveing) {
            // 移動中は処理をしない
            return;
        }
        this._isMoveing = true;

        // 魚のノード取得
        let fishNode = this.node.parent.getChildByName("Fish");

        // タップした座標を取得
        let loc: Vec2 = event.getUILocation();
        let uitf = this.node.getComponent(UITransform);
        let eveLoc = uitf.convertToNodeSpaceAR(new Vec3(loc.x, loc.y));

        // 魚の向きを決定
        let fishDirection = fishNode.position.x < eveLoc.x ? 1 : -1;
        fishNode.setScale(new Vec3(fishDirection, 1, 1));

        // トゥイーン
        tween(fishNode)
            .to(this.moveDuration, { position: eveLoc }, {
                easing: "quadOut",
            })
            .call(() => {
                //  トゥイーン後の処理
                log('TouchFish - has finished tween');
                this._isMoveing = false;
            })
            .start();
    }
}
```

:::note info
外部ソフトでの編集後は必ずCocosCreaterの画面を開きアクティブにします。アクティブにすることで、外部ソフトの編集がCocosCreater側に反映されます。
:::

CocosCreatorに戻り、ヒエラルキーパネルにある`sea`を選択します。インスペクターパネルの一番下の、`Add Component`ボタンを押します。別の小窓が出るので、`Search Name`と書かれたエリアに`touch`と入力します。登録済みのスクリプトが`Custom Script`の下に表示されるので、`TouchFish`を選択します。`ctrl+S`でシーンを保存します。

<img width ="400px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/5bbb6691-d1ab-c1d9-892e-6fa61b4c51a3.png">

<img width ="200px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/19c6ade3-ade0-e6d7-054c-e507c58e2c4d.png">

インスペクターパネルの一番下に`SeaScript`が追加されました。これによりシーン上の`sea`ノードと`TouchFish`スクリプトが紐付けされました。
<img width ="300px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/386c76d5-4a96-f7ef-fbd0-504a97a027c9.png">

## ブラウザで動きを確認する

ここまで作成したものを実際に動かしてみます。
画面上の、次に示す赤枠の部分を確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/27b7ff05-2a65-6c9e-e224-efb96a6d379d.png)

惑星のマークはブラウザの意味、`Current scene`は今シーンパネルで開いているシーンを対象としています。
▶マークを押すと（または`ctrl+P`）、ブラウザが立ち上がりゲームが実行します。

<img width ="500px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271948/22af9501-38c4-f7ee-d532-c8b1a10b068d.png">

正しく作成されていれば、画面をクリックすると、魚がクリックした場所に移動します。

# スクリプトの解説

## startメソッド/onDestroyメソッド
`start`メソッドは、初めに一度だけ実行されるメソッドです。初期設定などをここに書きます。
ここでは、`enableTouch(true)`メソッドを呼び出し、イベントを有効にしています。
一方、`onDestroy`メソッドはインスタンスが破棄されるときに呼び出されるメソッドです。
`enableTouch(false)`メソッドを呼び出し、イベントを無効化します。

## イベントの有効化
`enableTouch()`メソッド内では、背景のノードのタッチイベントに対して`moveFish`メソッドを呼び出すよう設定しています。そのとき、タッチされた時の情報が入っている`enent`変数を渡しています。
```ts:
let self = this;
this.node.on(Node.EventType.TOUCH_START, (event: EventTouch) => {
    self.moveFish(event);
}, this);
```

:::note
`EventType.TOUCH_START`であってもパソコンのマウスボタンクリックでイベントを捕捉します。
一方、`EventType.MOUSE_DOWN`ではスマホ等のマウスのないデバイスではイベントが捕捉できません。
:::

:::note
イベントのon/offは`EventType`の単位で切り替えができます。
:::

## 魚の移動処理
`moveFish()`メソッドの内容を見ていきます。

まず、`Sea`の親ノード、つまり`Canvas`ノードを取得して、その子ノードである`Fish`ノードを名前を指定して取得します。
```ts:
// 魚のノード取得
let fishNode = this.node.parent.getChildByName("Fish");
```

`event.getUILocation()`によりイベント発生時に渡された、タップされた位置を取得します。
`UITransform`コンポーネントを取得し、そこから`convertToNodeSpaceAR`メソッドにより、ゲーム内で扱っている座標の体系に変換します。
```ts:
// タップした座標を取得
let loc: Vec2 = event.getUILocation();
let uitf = this.node.getComponent(UITransform);
let eveLoc = uitf.convertToNodeSpaceAR(new Vec3(loc.x, loc.y));
```

タップされた位置が、魚より右が、左かによって魚の向きを変えるため、両座標の`x`の値を比較します。
魚のノードに対し`setScale()`で`x`にマイナス値を入れると画像が左右逆になります。
```ts:
// 魚の向きを決定
let fishDirection = fishNode.position.x < eveLoc.x ? 1 : -1;
fishNode.setScale(new Vec3(fishDirection, 1, 1));
```
## tween（トゥイーン）について
`tween`という機能を使って魚をタップされた位置まで移動します。`tween`は移動先と秒数を指定すると自動でアニメーションを作ってくれる機能です。
`to`で、時間とtweenの条件を指定します。
`call`に終了後の処理を記述します。`start`はすぐに実行するという指示です。
```ts:
// トゥイーン
tween(fishNode)
    .to(this.moveDuration, { position: eveLoc }, {
        easing: "quadOut",
    })
    .call(() => {
        //  トゥイーン後の処理
        log('TouchFish - has finished tween');
        this._isMoveing = false;
    })
    .start();
```
### easingについて

移動の際の効果を指定します。ここでは「初速が早く、停止位置に近づくと減速する」ようにするため、`quadOut`としましたが、`linear`とすると緩急をつけづに等速で移動します。
それ以外の効果についてはこちらを参照ください。
[https://docs.cocos.com/creator/manual/en/tween/tween-function.html](https://docs.cocos.com/creator/manual/en/tween/tween-function.html)


### position以外の設定
今回は`position`だけの指定でしたが、`scale`、`rotation`も以下のように指定可能です。

```ts:
let quat: Quat = new Quat();
Quat.fromEuler(quat, 0, 0, 45);
tween(fishNode)
    .to(this.moveDuration, {
        position: new Vec3(eveLoc.x, eveLoc.y),
        scale: new Vec3(1.2, 1.2, 1),
        rotation: quat,
    }, //以降省略
```

### toとby
`to`は画面の座標を指定します。このゲームではタップされた位置にノードを移動させるゲームなのでtoを使います。
一方`by`は今ある位置からの差分を指定します。例えば、キャラクターの位置を右に200pxだけ動かすという場合は、移動先の座標を計算しなくても200pxの指定だけですみます。

## logについて
各所に書かれている`log`ではその文字列を`console`に出力します。ブラウザでの実行時には`F12`の開発者モードで`console`の内容を確認できます。