JavaScript
phina.js

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

movenamachang.gif
http://runstant.com/runstataccount/projects/df867ee6

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

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

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

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

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

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

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

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

            // ここ押してるキーの名前取れたらいいのに。
            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に食べさせていくことに。

グリッド位置を覚えておく
        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;

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

            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をチェックすることで一動作だけ受け付けるコードを見つけてお蔵入り。
状態は必ず出るでしょうけど、今はわかりやすいのでこちらで。
八方向にも対応して、関数もデフォルト引数で整理。

        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);
            }
        };

 参考

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

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

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

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

TODO

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

きになる

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

前回

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

グリッド整理は後ほど…