⇒ 動作確認はこちらからどうぞ。
これまで作成したパックマンではモンスターはランダムに移動するだけなので、なかなかパックマンを捕まえることができませんでした。そこで追跡アルゴリズムを考えます。
では追跡をするときはどうやって追跡させればよいのでしょうか?
パックマンの位置をしらべて、モンスターはそこへ接近するように動かします。壁があってその方向に動けない場合は動ける方向に移動させます。迷路の形状やお互いの位置関係から単純に両者を接近させようとすると逆に遠回りになることもありますが、迷路はそんなに複雑な形状をしていないので、まあいいでしょう。モンスターを誘導してうまく逃げるという遊び方もあるでしょう。
モンスターはUターンは原則としてしないので(本物のパックマンではパワー餌を食べたわけではないのに一斉に方向転換することがある)、方向転換可能地点で適切な方向に転換させます。
X座標を調べてモンスターの座標のほうが大きい場合は左へ、小さい場合は右、Y座標を調べてモンスターの座標のほうが大きい場合は上へ、小さい場合は下へ移動させます。左右の移動と上下の移動をズレが大きいほうを優先的に採用します。
MonsterクラスのMove関数を少し変えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
class Monster { Move() { this.Position.CenterX = Math.round(this.Position.CenterX * 10) / 10; this.Position.CenterY = Math.round(this.Position.CenterY * 10) / 10; // もし角にいるなら進行方向を変える if (this.IsCrossPosition() || this.Direct == Direct.None) { ThinkDirect(this.Id); // ここを変更した } // もし巣のなかにいるなら前後に移動させる(ここは前と同じ) if (this.IsMonsterInHome()) { this.MonsterMoveInHome(); } // 巣から出た直後のT字路ではパックマンがいる側に移動させる if (this.Direct == Direct.North && this.Position.CenterX == 12.5 && this.Position.CenterY == 10) { ThinkDirect(this.Id); // ここも変更した } // 移動させる(ここは前と同じ) if (this.Direct == Direct.West) this.Position.CenterX -= 0.1; if (this.Direct == Direct.East) this.Position.CenterX += 0.1; if (this.Direct == Direct.North) this.Position.CenterY -= 0.1; if (this.Direct == Direct.South) this.Position.CenterY += 0.1; // ワープさせる(ここは前と同じ) if ((this.Position.CenterX == 0 && this.Position.CenterY == 13) && this.Direct == Direct.West) this.Position.CenterX = 25; if ((this.Position.CenterX == 25 && this.Position.CenterY == 13) && this.Direct == Direct.East) this.Position.CenterX = 0; // イジケ状態から回復させる(ここは前と同じ) if (CounterattackTime <= 0) this.IsIjike = false; } } |
そしてモンスターの移動方向を考えさせるThinkDirect関数を以下のようにしてみます。
GetMonsterDirect関数にパックマンの座標を与えればパックマンを追いかけるようになりますが、問題点もあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
function ThinkDirect(id: number) { let monster = monsters[id]; let pacX = pacman.Position.CenterX; let pacY = pacman.Position.CenterY; // イジケ状態でないならパックパンに接近させる。そうでないなら離れさせる monster.Direct = GetMonsterDirect(monster, pacX, pacY, !monster.IsIjike); } function GetMonsterDirect(monster: Monster, pacX: number, pacY: number, approach: boolean): number { let directs = monster.GetDirectsMonsterMove(); if (directs.length == 1) { // 移動できる方向がひとつしかないならそれを選択するしかない return directs[0]; } let monX = monster.Position.CenterX; let monY = monster.Position.CenterY; let dx = monX - pacX; let dy = monY - pacY; let nextDirect = Direct.None; // X座標のほうが大きく離れているならX座標をあわせる方向に移動する if (Math.abs(dx) > Math.abs(dy)) { if (dx > 0) { if (approach && directs.find(direct => direct == Direct.West)) nextDirect = Direct.West; if (!approach && directs.find(direct => direct == Direct.East)) nextDirect = Direct.East; } if (dx < 0) { if (approach && directs.find(direct => direct == Direct.East)) nextDirect = Direct.East; if (!approach && directs.find(direct => direct == Direct.West)) nextDirect = Direct.West; } // X座標を合わせられないならY座標で評価する if (nextDirect == Direct.None) { if (dy > 0) { if (approach && directs.find(direct => direct == Direct.North)) nextDirect = Direct.North; if (!approach && directs.find(direct => direct == Direct.South)) nextDirect = Direct.South; } if (dy < 0) { if (approach && directs.find(direct => direct == Direct.South)) nextDirect = Direct.South; if (!approach && directs.find(direct => direct == Direct.North)) nextDirect = Direct.North; } } } else { if (dy > 0) { if (approach && directs.find(direct => direct == Direct.North)) nextDirect = Direct.North; if (!approach && directs.find(direct => direct == Direct.South)) nextDirect = Direct.South; } if (dy < 0) { if (approach && directs.find(direct => direct == Direct.South)) nextDirect = Direct.South; if (!approach && directs.find(direct => direct == Direct.North)) nextDirect = Direct.North; } if (nextDirect == Direct.None) { if (dx > 0) { if (approach && directs.find(direct => direct == Direct.West)) nextDirect = Direct.West; if (!approach && directs.find(direct => direct == Direct.East)) nextDirect = Direct.East; } if (dx < 0) { if (approach && directs.find(direct => direct == Direct.East)) nextDirect = Direct.East; if (!approach && directs.find(direct => direct == Direct.West)) nextDirect = Direct.West; } } } // この方法で決まらないなら適当に選ぶしかない if (nextDirect == Direct.None) { let index = Math.floor((Math.random() * directs.length)); nextDirect = directs[index]; } return nextDirect; } |
このアルゴリズムの問題点はモンスターが1箇所に集中してしまうことです。パックマンを先頭にしたモンスターの大名行列ができてしまいます。
ところでモンスターにはそれぞれ名前があり、個性もあります。
赤 オイカケ アカベエ
パックマンの後ろをひたすら追いかける
ピンク マチブセ ピンキー
頭脳タイプで先回りをする。パックマンのいる地点の少し前を目指して動く。
青 キマグレ アオスケ
気まぐれタイプ。パックマンを中心にして、赤の点対称の位置を最短距離で目指して行動する。
オレンジ オトボケ グズタ
好き勝手タイプ。何も考えず自由に行動する。パックマンより遠いところにいるときは追いかけ、近いところにいるときは逃げる。
実際には必ずこのとおりに動くわけではないのですが、ここでは上記にあわせてつくります。
ThinkDirectの引数でGetMonsterDirectに与える座標を変えればいいですね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
function ThinkDirect(id: number) { let monster = monsters[id]; let pacX = pacman.Position.CenterX; let pacY = pacman.Position.CenterY; if (id == 0) { // アカベエはパックマンをそのまま追う monster.Direct = GetMonsterDirect(monster, pacX, pacY, !monster.IsIjike); } if (id == 1) { // ピンキーはパックマンが向いている方向の少し先を目指す let pacDirect = pacman.Direct; if (pacDirect == Direct.None) // pacDirect == Direct.Noneのときは前回移動成功時の方向 pacDirect = pacman.PrevDirect; if (pacDirect == Direct.North) monster.Direct = GetMonsterDirect(monster, pacX, pacY - 5, !monster.IsIjike); if (pacDirect == Direct.South) monster.Direct = GetMonsterDirect(monster, pacX, pacY + 5, !monster.IsIjike); if (pacDirect == Direct.East) monster.Direct = GetMonsterDirect(monster, pacX + 5, pacY, !monster.IsIjike); if (pacDirect == Direct.West) monster.Direct = GetMonsterDirect(monster, pacX - 5, pacY, !monster.IsIjike); } if (id == 2) { // アオスケはパックマンを中心にして、赤の点対称の位置を目指す let red = monsters[0]; let targetX = red.Position.CenterX * 2 - pacX; let targetY = red.Position.CenterY * 2 - pacY; monster.Direct = GetMonsterDirect(monster, targetX, targetY, !monster.IsIjike); } if (id == 3) { // 距離の2乗を求める // グズタは遠ければ追い、近ければ逃げる let dis2 = (monster.Position.CenterX - pacX) ** 2 + (monster.Position.CenterY - pacY) ** 2; if (dis2 > 100) monster.Direct = GetMonsterDirect(monster, pacX, pacY, !monster.IsIjike); else monster.Direct = GetMonsterDirect(monster, pacX, pacY, monster.IsIjike); } } |
一応、これでそれっぽい動きになりました。