マップで敵を動かそう
※マップについての基本は、「マップをマスターしよう」を参照してください。以下では、基本を知っている人を対象として説明しています。
1.利用状況
パックマンなど、背景にマップ(地図)を使っていて、プレーヤに敵が迫ってくるゲームを作ってみましょう。
2.マップとプレーヤを表示しよう
周囲が壁で囲まれたマップを作り、プレーヤーを配置します。なお、このマップでは プレイヤーや敵が動ける場所を0 として、 壁を1 としています。
下記では プレイヤーや敵が動ける場所(=0)を白 で、 壁(=1)を赤 で表示しています。
プレーヤーは青で表示しています。プレーヤーは矢印キーで動かせます。
//マップとプレーヤーを表示
int[][] mymap = {{1, 1, 1, 1, 1, 1, 1, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 1, 1, 1, 1}};
int imax = mymap.length; // mymapの行数int jmax = mymap[0].length; // mymapの列数
int pi = 1;int pj = 1;
void setup() { size(400, 400);}
void draw() { //マップの描画 for (int i = 0; i < imax; i++) { for (int j = 0; j < jmax; j++) { if (mymap[i][j] == 0) { fill(255, 255, 255); } else if (mymap[i][j] == 1) { fill(255, 0, 0); } else if (mymap[i][j] == 2) { fill(0, 255, 0); } rect(j * 50, i * 50, 50, 50); //1マス50の大きさ } }
//プレーヤーの表示 fill(0, 0, 255); rect(pj*50, pi*50, 50, 50);}
void keyPressed() { //---プレーヤーの動き--- // 上方向 if (keyCode == UP && mymap[pi-1][pj] == 0) { pi -= 1; } // 下方向 if (keyCode == DOWN && mymap[pi+1][pj] == 0) { pi += 1; } // 左方向 if (keyCode == LEFT && mymap[pi][pj-1] == 0) { pj -= 1; } // 右方向 if (keyCode == RIGHT && mymap[pi][pj+1] == 0) { pj += 1; }}
3.敵を配置し、自動的に動かそう
まず、敵がランダムに動くようにしましょう。
//敵の動き(ランダム)・障害物なし
int[][] mymap = {{1, 1, 1, 1, 1, 1, 1, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 1, 1, 1, 1}};
int imax = mymap.length; // mymapの行数int jmax = mymap[0].length; // mymapの列数
int pi, pj, ei, ej;int ed;boolean gameFlg;
void setup() { size(400, 400); pi = 1; pj = 1; ei = 3; ej = 3; gameFlg = true;}
void draw() { map_draw(); player_draw(); enemy_draw(); if (gameFlg == false) { gameover(); }}
void map_draw() { //マップの描画 for (int i = 0; i < imax; i++) { for (int j = 0; j < jmax; j++) { if (mymap[i][j] == 0) { fill(255, 255, 255); } else if (mymap[i][j] == 1) { fill(255, 0, 0); } else if (mymap[i][j] == 2) { fill(0, 255, 0); } rect(j * 50, i * 50, 50, 50); //1マス50の大きさ } }}
void player_draw() { //プレーヤーの表示 fill(0, 0, 255); rect(pj*50, pi*50, 50, 50);}
void keyPressed() { //---プレーヤーの動き--- // 上方向 if (keyCode == UP && mymap[pi-1][pj] != 1) { pi -= 1; } // 下方向 if (keyCode == DOWN && mymap[pi+1][pj] != 1) { pi += 1; } // 左方向 if (keyCode == LEFT && mymap[pi][pj-1] != 1) { pj -= 1; } // 右方向 if (keyCode == RIGHT && mymap[pi][pj+1] != 1) { pj += 1; }}
void enemy_draw() { //---敵の動き--- if (frameCount % 15 == 0) { ed = int(random(4)); // 上方向 if (ed == 0 && mymap[ei-1][ej] != 1) { ei -= 1; } // 下方向 if (ed == 2 && mymap[ei+1][ej] != 1) { ei += 1; } // 左方向 if (ed == 3 && mymap[ei][ej-1] != 1) { ej -= 1; } // 右方向 if (ed == 1 && mymap[ei][ej+1] != 1) { ej += 1; } } //---敵の表示--- fill(255, 200, 0); rect(ej*50, ei*50, 50, 50);
//---キャッチ判定--- if (ej == pj && ei == pi) { gameFlg = false; }}
void gameover() { //ゲームオーバー処理 textSize(80); fill(0); text("GAME", 100, 200); text("OVER", 105, 270); noLoop();}
4.敵がプレーヤに近づくようにしよう
敵がプレーヤーに近づくようにしましょう。そのためには、敵はプレーヤーとの距離が短くなる時にのみ動くようにします。ここでの距離は、いわゆる「 マンハッタン距離 」(プレーヤーと敵とのx座標値の差の絶対値とy座標値の差の絶対値を合計することで距離を計算する方法)で計算しています。
//敵の動き(追撃)・障害物なし
int[][] mymap = {{1, 1, 1, 1, 1, 1, 1, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 1, 1, 1, 1}};
int imax = mymap.length; // mymapの行数int jmax = mymap[0].length; // mymapの列数
int pi, pj, ei, ej;int ed;boolean gameFlg;
void setup() { size(400, 400); pi = 1; pj = 1; ei = 6; ej = 6; gameFlg = true;}
void draw() { map_draw(); player_draw(); enemy_draw(); if (gameFlg == false) { gameover(); }}
void map_draw() { //マップの描画 for (int i = 0; i < imax; i++) { for (int j = 0; j < jmax; j++) { if (mymap[i][j] == 0) { fill(255, 255, 255); } else if (mymap[i][j] == 1) { fill(255, 0, 0); } else if (mymap[i][j] == 2) { fill(0, 255, 0); } rect(j * 50, i * 50, 50, 50); //1マス50の大きさ } }}
void player_draw() { //プレーヤーの表示 fill(0, 0, 255); rect(pj*50, pi*50, 50, 50);}
void keyPressed() { //---プレーヤーの動き--- // 上方向 if (keyCode == UP && mymap[pi-1][pj] == 0) { pi -= 1; } // 下方向 if (keyCode == DOWN && mymap[pi+1][pj] == 0) { pi += 1; } // 左方向 if (keyCode == LEFT && mymap[pi][pj-1] == 0) { pj -= 1; } // 右方向 if (keyCode == RIGHT && mymap[pi][pj+1] == 0) { pj += 1; }}
void enemy_draw() { //---敵の動き--- if (frameCount % 15 == 0) { ed = int(random(4)); // 上方向 if (ed == 0 && mymap[ei-1][ej] == 0) { if (abs(pi-ei)+abs(pj-ej) > abs(pi-(ei-1))+abs(pj-ej)) { ei -= 1; } } // 下方向 if (ed == 2 && mymap[ei+1][ej] == 0) { if (abs(pi-ei)+abs(pj-ej) > abs(pi-(ei+1))+abs(pj-ej)) { ei += 1; } } // 左方向 if (ed == 3 && mymap[ei][ej-1] == 0) { if (abs(pi-ei)+abs(pj-ej) > abs(pi-ei)+abs(pj-(ej-1))) { ej -= 1; } } // 右方向 if (ed == 1 && mymap[ei][ej+1] == 0) { if (abs(pi-ei)+abs(pj-ej) > abs(pi-ei)+abs(pj-(ej+1))) { ej += 1; } } }
//---敵の表示--- fill(255, 200, 0); rect(ej*50, ei*50, 50, 50);
//---キャッチ判定--- if (ej == pj && ei == pi) { gameFlg = false; }}
void gameover() { //ゲームオーバー処理 textSize(80); fill(0); text("GAME", 100, 200); text("OVER", 105, 270); noLoop();}
5.障害物を設置してみよう
マップに障害物を設置してみましょう。例えば、マップのデータを下記のように変えてみましょう。
int[][] mymap = {{1, 1, 1, 1, 1, 1, 1, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 1, 1, 0, 0, 1}, {1, 0, 0, 1, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 1, 1, 1, 1}};
実は、この「敵はプレーヤーとの距離が短くなる時にのみ動く」だけだと、プレーヤーと敵との間に障害物があった場合、 行き詰ってしまう (敵が動けなくなってしまう)ことがあります。(行き詰ることを確認するには、プレーヤーを動かさないで何回か実行してみると良いでしょう)
6.障害物を回避させよう
行き詰まりを検知し、行き詰ったら障害物を回避するように自動的に動くようにしましょう。
//敵の動き(追撃)・障害物回避
int[][] mymap = {{1, 1, 1, 1, 1, 1, 1, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 1, 1, 0, 0, 1}, {1, 0, 0, 1, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 1, 1, 1, 1}};
int imax = mymap.length; // mymapの行数int jmax = mymap[0].length; // mymapの列数
int pi, pj, ei, ej;int ed;boolean gameFlg;int avoidCount;int ngCount;
void setup() { size(400, 400); pi = 1; pj = 1; ei = 6; ej = 6; gameFlg = true; avoidCount = 0; ngCount = 0;}
void draw() { map_draw(); player_draw(); enemy_draw(); if (gameFlg == false) { gameover(); }}
void map_draw() { //マップの描画 for (int i = 0; i < imax; i++) { for (int j = 0; j < jmax; j++) { if (mymap[i][j] == 0) { fill(255, 255, 255); } else if (mymap[i][j] == 1) { fill(255, 0, 0); } else if (mymap[i][j] == 2) { fill(0, 255, 0); } rect(j * 50, i * 50, 50, 50); //1マス50の大きさ } }}
void player_draw() { //プレーヤーの表示 fill(0, 0, 255); rect(pj*50, pi*50, 50, 50);}
void keyPressed() { //---プレーヤーの動き--- // 上方向 if (keyCode == UP && mymap[pi-1][pj] != 1) { pi -= 1; } // 下方向 if (keyCode == DOWN && mymap[pi+1][pj] != 1) { pi += 1; } // 左方向 if (keyCode == LEFT && mymap[pi][pj-1] != 1) { pj -= 1; } // 右方向 if (keyCode == RIGHT && mymap[pi][pj+1] != 1) { pj += 1; }}
void enemy_draw() { //---敵の動き--- if (frameCount % 15 == 0) { ed = int(random(4)); // 上方向 if (ed == 0 && mymap[ei-1][ej] != 1) { //その方向(ed==0:上)へは移動可能であり、 if (avoidCount == 0) { //回避モードではない(ノーマル)場合、 if (abs(pi-ei)+abs(pj-ej) > abs(pi-(ei-1))+abs(pj-ej)) { //プレーヤーに近づくなら、移動する。 ei -= 1; } else { //その方向へは移動可能で回避モードではないけど、プレーヤーに近づかないために移動できない場合は ngCount += 1; //ngCountを1つ増やす。(ngCountが規定の数(例えば10回など)に達すると、回避モード(avoidCountが0以外)になる) } } else { //その方向へは移動可能だけど、回避モードになっている場合は、 ei -= 1; //無条件にその方向へ進む。プレーヤに近くなるかどうかは考えない avoidCount -= 1; //回避モードのカウンタを一つ減らす(やがて0になって回避モードが終わる) } } // 下方向 if (ed == 2 && mymap[ei+1][ej] != 1) { if (avoidCount == 0) { if (abs(pi-ei)+abs(pj-ej) > abs(pi-(ei+1))+abs(pj-ej)) { ei += 1; } else { ngCount += 1; } } else { ei += 1; avoidCount -= 1; } } // 左方向 if (ed == 3 && mymap[ei][ej-1] != 1) { if (avoidCount == 0) { if (abs(pi-ei)+abs(pj-ej) > abs(pi-ei)+abs(pj-(ej-1))) { ej -= 1; } else { ngCount += 1; } } else { ej -= 1; avoidCount -= 1; } } // 右方向 if (ed == 1 && mymap[ei][ej+1] != 1) { if (avoidCount == 0) { if (abs(pi-ei)+abs(pj-ej) > abs(pi-ei)+abs(pj-(ej+1))) { ej += 1; } else { ngCount += 1; } } else { ej += 1; avoidCount -= 1; } } }
//---敵の表示--- fill(255, 200, 0); rect(ej*50, ei*50, 50, 50);
//---行き詰り判定--- if (ngCount >= 10) { //ngCountが規定の数(ここでは10回)に達すると、回避モード(avoidCountが0以外。ここでは5)にする avoidCount = 5; //これから5回は、プレーヤーに近づくかどうかに関係なく、ランダムに移動する。 ngCount = 0; println("***行き詰り発生***", ei, ej); }
//---キャッチ判定--- if (ej == pj && ei == pi) { gameFlg = false; }}
void gameover() { //ゲームオーバー処理 textSize(80); fill(0); text("GAME", 100, 200); text("OVER", 105, 270); noLoop();}
7.(応用)敵の動きを自分で作ってみよう
敵の動きを新たに作ってみましょう。これまでの動きは、現在地点からランダムに上下左右の方向に動くというものでした。下記のコードは、敵が斜めに動くサンプルです。障害物に当たるまでまっすぐ進んで行きます。
皆さんも自分のオリジナルの動きを作ってみましょう。将棋の飛車や桂馬の動きも面白いでしょう。ネットでパックマンについて調べてみるのも良いかもしれません。
//敵の動き(斜めに直進)
int[][] mymap = {{1, 1, 1, 1, 1, 1, 1, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 1, 1, 1, 1}};
int imax = mymap.length; // mymapの行数int jmax = mymap[0].length; // mymapの列数int ei, ej, speedi, speedj;
void setup() { size(400, 400); ei = 6; ej = 4; speedi = -1; speedj = -1;}
void draw() { map_draw(); enemy_draw2();}
void map_draw() { //マップの描画 for (int i = 0; i < imax; i++) { for (int j = 0; j < jmax; j++) { if (mymap[i][j] == 0) { fill(255, 255, 255); } else if (mymap[i][j] == 1) { fill(255, 0, 0); } else if (mymap[i][j] == 2) { fill(0, 255, 0); } rect(j * 50, i * 50, 50, 50); //1マス50の大きさ } }}
void enemy_draw2() { //---敵の動き--- if (frameCount % 30 == 0) { boolean refi = false; //i方向(上下)への反射可否 boolean refj = false; //j方向(左右)への反射可否 if (mymap[ei+speedi][ej+speedj] != 0) { //前方に障害物があったら、進行方向を変更する if (mymap[ei-speedi][ej+speedj] != 1) { //i方向(上下)への反射可否チェック refi=true; //i方向(上下)への反射可 } if (mymap[ei+speedi][ej-speedj] != 1) { //j方向(左右)への反射可否チェック refj=true; //j方向(左右)への反射可 } //進行方向の変更 if(refi){ speedi = -speedi; } if(refj){ speedj = -speedj; } if (!refi && !refj) { //i方向へもj方向へも反射不可の場合は戻る speedi = -speedi; speedj = -speedj; } }
//新しい位置に更新 ei = ei + speedi; ej = ej + speedj; }
//---敵の表示--- fill(255, 200, 0); rect(ej*50, ei*50, 50, 50);}
※このコードは教材用のため、簡略化しています。そのため、障害物の形状によってはおかしな反射をする場合があります。コードを解析して、改造してみましょう。