Phaser 3 :迷路ゲームを作る④ 経路探索ライブラリを使う、timelineを使う
引き続き迷路ゲームを作っていきます。
今回は経路探索ライブラリを用いて目的地まで自動で移動する処理を作ってみます。
gpnotes.hatenablog.jp
経路探索ライブラリを使う
こちらのライブラリを使って経路探索をしてみます。
github.com
A-starアルゴリズムを用いたライブラリで、typescriptで使えるもので一番使い勝手が良さそうなのがこちらでした。
上記のプロジェクトをダウンロードし、scriptsディレクトリ内に解凍します。
main.tsのリファレンスにAster.tsを追加します。
/// <reference path="AStar.ts" />
今回はズーム処理はしないで全体を見たいと思いますのでtilesizeを20にし、迷路の幅と高さも800*600に戻します。
MyScene1 ~~~ //迷路の道幅 public tileSize: number = 20; public mapSize: number = 1; ~~~ //経路探索ライブラリの読み込み public astar: AStar;
経路探索ライブラリ用の配列を作成する
次にasterに渡す用の配列を作成します。
asterは[迷路行1,迷路行2,迷路行3,...]という作りの配列を渡さなければならないようなので、maze配列からcolumn配列を作成します。
maze配列では0が道、1が壁として数値が入っていますが、Astarの経路探索では配列に入れた数値がウェイトとして採用され、コスト(移動距離)とウェイトの総計が最低になるパスを返すため、目的地によっては壁を通過してしまうことがあります。
例 :xが目的地の場合
[ 0, 0, 0, 0, 0 ] ,
[ 1, 1, 1, 1, 0 ] ,
[ x, 0, 0, 0, 0 ] ,
[ 0, 0, 0, 0, 0 ] ,
右側を通る道のコスト
→,→,→,→, ↓, ↓, ←, ←, ←, ← 計10コスト
壁を突っ切る道のコスト
↓ + ウェイト1, ↓ 計3コスト
→壁を突っ切るルートが採用されてしまう
なので、壁には十分大きな値をウェイトとして設定します。
create() { //経路探索ライブラリ用に配列を作る this.astar = new AStar(new ManhattenHeuristic()); //1行を1つの配列にまとめ、それをプッシュしていく let column: number[][] = new Array(); for (let i = 0; i < this.mazeHeight; i++) { column[i] = new Array(); let row: number[] = [] for (let j = 0; j < this.mazeWidth; j++) { //壁の部分はウェイトを大きな値に設定(ウェイトが低いと壁を通路として選択してしまう場合がある) row.push(this.maze[i][j] * 99999); } column[i] = row; } //asterに作成した配列を渡す this.astar.load(column); //経路表示用のグラフィック this.pathgraphics = this.add.graphics(); this.pathgraphics.depth = -1; }
次にupdateでクリック時のパス解析処理を追加します。
astar.getNodeで現在位置と目的地のノードを取得し、aster.pathにノードを渡すことでパスの配列が得られます。
update(){ ~~ if (this.input.activePointer.justDown) { this.pathgraphics.clear(); this.pathgraphics.fillStyle(0xf08300, 1); //プレイヤーの現在位置とクリックされた位置をasterに渡す let nodeA = this.astar.getNode(Math.floor(this.player.x / this.tileSize), Math.floor(this.player.y / this.tileSize)); let nodeB = this.astar.getNode(Math.floor(this.input.activePointer.x / this.tileSize), Math.floor(this.input.activePointer.y / this.tileSize)); let path = this.astar.path(nodeA, nodeB); for (let p of path) { this.pathgraphics.fillRect(p.x * this.tileSize, p.y * this.tileSize, this.tileSize, this.tileSize); } } ~~ }
実行結果がこちらです。
クリック部分までのパスが作成されていますね。
斜め移動を許すライブラリのようなので、曲がり角では斜め移動が行われています。
tween.timelineで経路に沿ったtweenを作成する
次に、解析したパスにそってプレイヤーオブジェクトを動かす処理を作りましょう。
tween.timelineを使うことでtweenを段階的に実行することができます。
timelineはtimeline.addによって付け足された処理を逐次実行します。
今回の例ではプレイヤーオブジェクトをパス上の点に順次移動させています。
update() { //プレイヤーの移動処理 this.player.angle += this.controller.xRotate * this.speed; this.player.setVelocity(this.controller.xAxis * this.speed, this.controller.yAxis * this.speed); if (this.input.activePointer.justDown) { this.pathgraphics.clear(); this.pathgraphics.fillStyle(0xf08300, 1); //プレイヤーの現在位置とクリックされた位置をasterに渡す let nodeA = this.astar.getNode(Math.floor(this.player.x / this.tileSize), Math.floor(this.player.y / this.tileSize)); let nodeB = this.astar.getNode(Math.floor(this.input.activePointer.x / this.tileSize), Math.floor(this.input.activePointer.y / this.tileSize)); let path = this.astar.path(nodeA, nodeB); //できたpathをforループでタイムラインに渡す let timeline = this.tweens.createTimeline({}); //timelineに追加された順にtweenが実行される for (let p of path) { this.pathgraphics.fillRect(p.x * this.tileSize, p.y * this.tileSize, this.tileSize, this.tileSize); timeline.add({ targets: this.player, x: p.x*this.tileSize + this.tileSize/2, y: p.y * this.tileSize + this.tileSize/2, ease: Phaser.Math.Easing.Linear, duration: 100 }); } timeline.play(); } }
上の実行結果がこちらです。
パスに沿って動いているのがわかりますね。
今回のソースはこちらです。
http://firestorage.jp/download/736eeb06cd7c1d36e50b86273ba4b49140588515