phina.jsでグリッド内を一歩一歩八方に移動する

![movenamachang.gif](https://qiita-image-store.s3.amazonaws.com/0/62354/6ea0cfe3-0b5b-4c66-8004-074c74269b07.gif)
http://runstant.com/runstataccount/projects/df867ee6

やや、ややこしくなります。

# まずはキーボードで動かしてみよう

キーボードによる移動に関してはこちら

[phina.js-Tips-029 Spriteをキーボードで操作する - Qiita]


方向は定数にして持っていたほうがいいとUnityかなにかの記事で読んだことがあるので、まず定数で

```js
// 方向
const DIRECTIONS = {
  UP:-1,
  DOWN:1,
  RIGHT:1,
  LEFT:-1.
}
```

としました。(値かぶっているが良いのか？)

元は自由移動でしたが、まずは一つずつの移動を目指していので、移動のifはifelseに変えて実装。


```js
            // ここ押してるキーの名前取れたらいいのに。
            if (key.getKey('left')) {
                // move あとで
            } else if (key.getKey('right')) {
                // move
            } else if (key.getKey('up')) {
                // move
            } else if (key.getKey('down')) {
                // move
            }
```

DIRECTIONS書いているときはこれif文2つで上下、左右±1だから `+ DIRECTIONS.hoge`でいけるなーと思っていたのにそうならなくてちょっと残念。

完璧余談でこういう同種パターンは久しぶりに[swith(true)]使いたいですね。

で、移動単位は当然グリッド単位なのですが、
ここで困ったことに、オブジェクトはグリッド位置を持っていない。
`this.gridY.span()`の返り値が実数値なので当たり前ですけど…
じゃあ、画面上の位置からどのグリッドに属するかをspan()の逆算のようにしてくれればそれを使おう！
と軽く調べたものの、なさそう。誰もしなさそう。
そもそもグリッドは点なので、ちょっとずれたら一致できないのでは…(まだ四角形だと思っている)。

(今考えるとグリッドの縦横サイズが判明しているので実数値の加減算でも悪くはないのですが、とにかく16*16の範囲でxy指定してジャンプしたかった)

しかたがないので自身にグリッド位置を覚えさせ、それを増減させながら、メインシーンのspanに食べさせていくことに。

```js:グリッド位置を覚えておく
        const player = Sprite('player').addChildTo(this);
        player.setOrigin(0,0);
        player.xOffset = 4 ;
        player.yOffset = -4 ;

        player.x = this.gridX.span(8) + player.xOffset; // こっちはウィンドウ位置
        player.xGrid = 8;  // こっちグリッド位置

        player.y = this.gridY.span(8) + player.yOffset;
        player.yGrid = 8;
```

移動は色々と試行しちょっと関数化してこう

```js
            const key = app.keyboard;
            var moveTo = (xGrid, yGrid) => {
                player.xGrid = xGrid;
                player.yGrid = yGrid;

                player.tweener.moveTo(this.gridX.span(xGrid) + player.xOffset, this.gridY.span(yGrid) + player.yOffset, 250).play();
            };
            // ここ押してるキーの名前取れたらいいのに。
            if (key.getKey('left')) {
                moveTo((player.xGrid + DIRECTIONS.LEFT), player.yGrid);
            } else if (key.getKey('right')) {
                moveTo((player.xGrid + DIRECTIONS.RIGHT), player.yGrid);
            } else if (key.getKey('up')) {
                moveTo(player.xGrid, (player.yGrid + DIRECTIONS.UP));
            } else if (key.getKey('down')) {
                moveTo(player.xGrid, (player.yGrid + DIRECTIONS.DOWN));
            }
```

瞬間移動もあれなのでアニメーション化も一緒に。

[phina.js-Tips Tweenerでオブジェクトを移動させる【moveTo】 - Qiita]

で、動くようになったんですが、**動きすぎる。**
行きすぎる。滑る。**まだ氷ステージを作ったつもりはない。**

# 一歩一歩を噛み締めよう

原因は毎フレーム処理が走るから。
1Fだけボタンを押すことは難しいし、処理がスタックするので滑る。

本来ならここで、移動中か入力受付中かを表すstatusを後々を見越して導入して、
入力時にロック→移動終了で解除
という超かっこいいコードを書いたのですが、執筆中の参考資料あさりで`tweener.playing`をチェックすることで一動作だけ受け付けるコードを見つけてお蔵入り。
状態は必ず出るでしょうけど、今はわかりやすいのでこちらで。
八方向にも対応して、関数もデフォルト引数で整理。


```js
        const player = Sprite('player').addChildTo(this);
        player.setOrigin(0,0);
        player.xOffset = 4 ;
        player.yOffset = -4 ;

        // grid保持はvector2のほうがいいかも
        player.x = this.gridX.span(8) + player.xOffset;
        player.xGrid = 8;

        player.y = this.gridY.span(8) + player.yOffset;
        player.yGrid = 8;

        player.update = (app) => {
            if(player.tweener.playing) {
                return;
            }

            const key = app.keyboard;
            const moveTo = (xGrid = 0, yGrid = 0) => {
                player.xGrid += xGrid;
                player.yGrid += yGrid;

                player.tweener.moveTo(this.gridX.span(player.xGrid) + player.xOffset, this.gridY.span(player.yGrid) + player.yOffset, 250).play();
            };

            if        (key.getKey('left') && key.getKey('up')) {
                moveTo(DIRECTIONS.LEFT, DIRECTIONS.UP);
            } else if (key.getKey('left') && key.getKey('down')) {
                moveTo(DIRECTIONS.LEFT, DIRECTIONS.DOWN);
            } else if (key.getKey('right') && key.getKey('up')) {
                moveTo(DIRECTIONS.RIGHT, DIRECTIONS.UP);
            } else if (key.getKey('right') && key.getKey('down')) {
                moveTo(DIRECTIONS.RIGHT, DIRECTIONS.DOWN);
            } else if (key.getKey('left')) {
                moveTo(DIRECTIONS.LEFT);
            } else if (key.getKey('right')) {
                moveTo(DIRECTIONS.RIGHT);
            } else if (key.getKey('up')) {
                moveTo(0, DIRECTIONS.UP);
            } else if (key.getKey('down')) {
                moveTo(0, DIRECTIONS.DOWN);
            }
        };
```


<!--

updateってFPS単位で走るんですよね。毎フレーム更新。おそらく。
そんななか、一回だけキーボード入力して動かしたいとなると…
1/60(30?)秒間だけキーを押すことに…。

なので、一度移動を開始したら完了するまで入力を受け付けないようにすることで、入力しすぎを抑制してみる。
状態の誕生。

```js
const STATUS = {
    RECEPTION:'RECEPTION', // 
    PROCESSING:'PROCESSING',
}
```

状態は大域変数で持つ

```js

phina.globalize();

var gameStatus = STATUS.RECEPTION;

```


```js
        player.update = (app) => {
            // 処理中なら終了
            if(gameStatus == STATUS.PROCESSIONG) {
                return;
            }
            // まず処理中に
            gameStatus = STATUS.PROCESSIONG;

            const key = app.keyboard;
            var moveTo = (xGrid, yGrid) => {
                player.xGrid = xGrid;
                player.yGrid = yGrid;

                player.tweener.moveTo(this.gridX.span(xGrid) + player.xOffset, this.gridY.span(yGrid) + player.yOffset, 250).call(() => {
                    // 移動が完了したら受付可に
                    gameStatus = STATUS.RECEPTION;
                }).play();
            };
            // ここ押してるキーの名前取れたらいいのに。
            if (key.getKey('left')) {
                moveTo((player.xGrid + DIRECTIONS.LEFT), player.yGrid);
            } else if (key.getKey('right')) {
                moveTo((player.xGrid + DIRECTIONS.RIGHT), player.yGrid);
            } else if (key.getKey('up')) {
                moveTo(player.xGrid, (player.yGrid + DIRECTIONS.UP));
            } else if (key.getKey('down')) {
                moveTo(player.xGrid, (player.yGrid + DIRECTIONS.DOWN));
            } else {
                // 未移動なら移動後の解除が起こらないのでここで解除
                gameStatus = STATUS.RECEPTION;
            }
        };
```


-->


#　参考

状態持たせるときに見ていたものも。

* [phina.js Tweenerを使いこなそう! Tweener 基本編 - Qiita]  

移動終了時のコールバック無いのかな？というときの`call()`。状態の解除。

* [phina.js-Tips Tweenerでオブジェクトを移動させる【moveTo】 - Qiita]
* [【phina.js】2回目以降のTweenerアニメーションが動かないとき - ハイファットマンの友人Y]

上記事でしっかり`play`つけようと書かれているのに、付けなくても動くじゃんと省略して自ら罠にハマっていきました。

* [phina.js のキーボード操作（倉庫番を作ってみた話） - Qiita]

`tweener.playing`の発見。
移動といえばリアルタイムのグリッドレスが多かったので参考になった。他も折を見て読む。




# TODO

* 端っこ判定がまだ。
* 移動中の画像変更
 * 移動中アニメーションまでいくとスプライトシート処理に… 
* 同時押し判定が厳しい。updateに少しwait入れようかと考えたが、JSは灰色魔術近く書かないとwaitやsleepが出来ないので頑張って1F同時押ししてもらう。


# きになる

* `Keyboard.getKeyDirection()`が正規化される。このせいで斜め入力がx,y微妙な値になる。-1,0,1だと理想、だったと思う。

# 前回

[phina.jsで改善されたグリッド内に画像を表示する - Qiita]

グリッド整理は後ほど…

[phina.jsで改善されたグリッド内に画像を表示する - Qiita]:http://qiita.com/khsk/items/1f7ee70411dfaeb55781

[phina.js のキーボード操作（倉庫番を作ってみた話） - Qiita]:http://qiita.com/YFukuyama/items/346678a6038e1db8e9be

[phina.js-Tips-029 Spriteをキーボードで操作する - Qiita]:http://qiita.com/alkn203/items/c2496fc6c0f772f54308



[swith(true)]:http://qiita.com/khsk/items/90f07f8bfeeb66cbaf32

[phina.js-Tips Tweenerでオブジェクトを移動させる【moveTo】 - Qiita]:http://qiita.com/alkn203/items/a5b6cddaaeb79da6ea21


[【phina.js】2回目以降のTweenerアニメーションが動かないとき - ハイファットマンの友人Y]:http://hyonny.hatenablog.com/entry/phinajs-tweener

[phina.js Tweenerを使いこなそう! Tweener 基本編 - Qiita]:http://qiita.com/simiraaaa/items/b80fec588fa2db272f67