読者です 読者をやめる 読者になる 読者になる

B-Teck!

お仕事からゲームまで幅広く

【JavaScript】相手を追尾するAIを考える その1

JavaScript

はじめに

この記事にこんなコメントがつきました。
beatdjam.hatenablog.com

コメント失礼します。 ゲーム開発を学んでいるプログラマーの卵です。

敵AIの追尾について勉強していて、 縦横マス目移動でプレイヤーを追尾出来るような敵の処理にしたいのですが、中々上手くいっていない状態です。

どのようにしたら縦横マス目移動で追尾してくれますでしょうか。 よろしくお願いします。

今回は状況がわからないため、一番簡単な障害物のないXY平面上でPlayerを追尾するEnemyという想定で、
どのように考えていけばいいかを説明していきたいと思います。

追尾の考え方

追尾というのは難しく考えてしまいがちですが、実質「現在の状態から相手に1マス近づく」という行動の繰り返しです。

f:id:beatdjam:20160627202855p:plain

1マスずつ近づくために必要な状況を切り分けて見ると、どう実装するべきかがわかってきます。

問題の切り分け

さて、実際どう実装するかを考えていきましょう。
X座標、Y座標を近づけたい場合は下記の3つの状況が考えられると思います。
f:id:beatdjam:20160627205039p:plain
X座標・Y座標両方があっていない場合は、今回はランダムでどちらかを選択するようにしてみましょう。

実装

今回はJavaScriptで実装してみました。動かすのが楽だったので。
moveEnemyとgetMovePosが今回の解説部分です。
コンソールに、Playerに近づいていく様子が出力されます。

おわりに

ざっくりとした説明でしたが、雰囲気だけでも掴んでいただけたでしょうか。
しかし、今回の実装ではマップ上に障害物や進めないエリアがあった場合に簡単に動けなくなってしまいます。
この問題を解決するには、経路探索と呼ばれるロジックを実装する必要があるのですが…、少し長くなってしまうので次回以降に記事にできたらいいなと思います。
基本の考え方として、「相手の位置を確定する」、「自分がどう移動するかを考える」という点は変わらないので、参考になれば幸いです。

最後に一応こちらにもソースを置いておきます。

"use strict"
process.stdin.resume();
process.stdin.setEncoding('utf8');

/*
* 設定値エリア(色々変えてみてください)
*/
//player、enemyそれぞれにx,y座標を設定する
var player = {"x":1,"y":1};
var enemy = {"x":4,"y":3};

//移動回数
var count = 6;

//マップの広さ
var xmax = 4;
var ymax = 4;

/*
* 実行
*/
//enemyを移動
for(var i = 0;i < count;i++){
  //現在のマップ出力
  renderingMap(player,enemy,xmax,ymax);  
  //enemyを動かす
  enemy = moveEnemy(player,enemy);
}

/*
* 関数定義
*/
//playerの座標に応じてenemyを移動させる
function moveEnemy(player,enemy){
  //playerとenemyの距離差分を取得する
  var xDistance = enemy.x - player.x;
  var yDistance = enemy.y - player.y;
  
  //playerとenemyに距離がある場合
  if(xDistance === 0){
    //x座標が同じ場合y座標のみ移動
    enemy.y = getMovePos(yDistance, enemy.y);
  }else if(yDistance === 0){
    //y座標が同じ場合x座標のみ移動
    enemy.x = getMovePos(xDistance, enemy.x);
  }else{
    //どちらも差がある場合、ランダムでどちらか1座標移動する
    var rand = Math.floor( Math.random() * 2);
    if(rand === 0){
      enemy.y = getMovePos(yDistance, enemy.y);      
    }else{
      enemy.x = getMovePos(xDistance, enemy.x);        
    }
  }
  
  return enemy;
}

//playerの位置関係によって移動方向を分け、移動先を返す
function getMovePos(distance, pos){
    if(distance > 0){
      pos--;
    }else{
      pos++;        
    }     
    return pos;  
}

//map描画
function renderingMap(player, enemy,xmax,ymax){
    for(var j = 1;j <= ymax;j++){
        var tmpStr = "";
        for(var k = 1;k <= xmax;k++){
            if(player.x === k && player.y === j){
                tmpStr += "p";
            }else if(enemy.x === k && enemy.y === j){
                tmpStr += "e";
            }else{
                tmpStr += "*";
            }
        }
        console.log(tmpStr);  
    }
    console.log(" ");
}