2013年2月3日日曜日

Unityで初めてのAIキャラクター(6.見えるようにする(視界のチェック))

さて、前回記事で敵キャラがプレイヤーとの距離にあわせて行動させた後、実際に敵キャラの視界にプレイヤーが映るかどうかの問題を考えていきながらコーディングを進めていくところをみていく。

関連記事


では例のごとく、Unity Gemsからの翻訳をどうぞ!



November 22, 2012


見えるようにする(視界のチェック)


もしSpace Shooterの基本チュートリアルを読んでいれば、視界のチェックについては基本的な考え方を理解しているはずだ。NPCキャラクターの前方を表す transform.forward を プレイヤーの transform.forward と比較する必要がある。二つのベクトルを比較するとき、角度を求めるために交わったり交差する必要がないことに注目すべきだ。ベクトルは空間上で動かしてもその属性(方向や大きさ)はそのままだ。例えば北西という向きはロンドンでも別の都市でもどちらにいても同じだ方角だし、北東という向きは同様にいつでも同じ方角だ。ロンドンで北東の向きと、パリで北という向きと、それらのなす角度は45度です。今回もこの考え方で大丈夫だ。

NPCキャラクターからプレイヤーへの向きをこれからチェックするが、もし二つのforward ベクトルがある範囲の中にある場合、NPCキャラクターからプレイヤーが見える状況となります。

すでに範囲はあるので、視界の範囲チェックは、次の方法で行う。
float angle = 90f;

if(Vector3.Angle(player.transform.position - _transform.position, transform.forward) <= angle){}
これだけで十分だ。ここで90度を設定した理由は、人の眼で180度の範囲で見えてはいるが、実際には限定した範囲しか見れないためだ。ifステートメントの中に距離をネストするか、また次のようにすることが出来る
if((_transform.position - _player.position).sqrMagnitude < squareRange &&
      (Vector3.Angle(player.transform.position - _transform.position, transform.forward) <= angle)){}
まだ問題があって、仮に敵キャラの背後に素早く入ると視界チェックから外れてしまう。このためNPCキャラクターがまだプレイヤーに近いことが分かっているときは範囲外に出るまで見失わない仕組みが必要だ。
    bool AIFunction(){
        if((_transform.position - player.position).sqrMagnitude < attackRange &&
            Vector3.Angle(player.transform.position - _transform.position, transform.forward) <= angle){
                    _delFunc = this.Attack;
                      return true;
       }else{
            _delFunc = this.Walk;
            return false;
        }
    }
これでプレイヤーが範囲内にいるか、かつ、視界にいるかの両方ごチェック出来る。

さてNPCキャラクターから本当に見えるかどうかは、どのようにチェックすれば良いのだろうか。壁の背後にいるときにNPCキャラクターから見えるようにはしたくない。ここでPhysics.Linecastの登場だ。名前通りに、ひとつの位置からもうひとつの位置の間に線を引き、何かが間にあれば、関数に戻される。深入りする前に、Linecastについて知っておくべきことがある。次のように使用した場合の話だ:
if (!Physics.Linecast (_transform.position, player.position)) {
    //視界に入っている
}
この場合、NPCキャラクターの視界に入らないことに気付くことになる。この関数は何かが間にある場合に true を戻しアクションをその場合はスキップする。ただ問題はNPC自身だ。_transform.position はモデルの真ん中にあることが多く、Linecast がNPCキャラクター自身のコライダとの衝突を検知してしまう。これには解決方法が二つある。シンプルな方法は、NPCの目の前に置き、forwardもビューと方向を合わせてラインをそこから引く方法だ。長所は、NPCキャラクターの目の位置から見るので現実の状況に近いので、個人的にオススメだ。もうひとつの方法ではレイヤーを活用して、NPC自身との衝突は無視させることだ。

インスペクタ上でLayersをクリックし、Add Layerを選択する。User Layer 8 を選択してNPCをそこに追加する。NPCではレイヤーを設定するのに加えて、プレイヤーには t を加える。ラインが NPC の位置からプレイヤーの位置まで繋がっているのだがプレイヤーにもコライダがある。

結局 linecast 関数に少し修正を加える:
int layerMask = 1 << 8;

void Start(){
    layerMask = ~layerMask;
}

void Update(){
    Debug.DrawLine (_transform.position, _player.position, Color.yellow);
    if (!Physics.Linecast (_transform.position, _player.position, layerMask)) {
           ("次のオブジェクトに衝突 "+hit.collider.gameObject.name);
    }
}
レイヤー変数は、値を与える代わりにビット演算をを行う。指示として8ビット目に移動して 1-> 10000000 とシフトさせる。これは通信や組込みシステムでは広く使われてる方法だがでゲームでは登場頻度がやや少ない。

Start でも同じようなビット演算を行ない、ほしい結果が01111111なので各々のビットで 7 回演算するのではなく 2 回で実現出来る。~ 記号は、 !  記号と同様にNOT演算を行うけどビット版だというところご違いだ。ビットの場合、0 を 1 に変更して、1 を 0 に変更して逆の演算結果を戻す(8ビット)。これはコンプリメントという名前で呼ばれることがある。さて、ビットの話に深入りするのはこれぐらいにしようか。

Debug.DrawLine はシーンビューでLinecastを見るためにある。Linecast関数はNPCキャラクターとプレイヤーの全ての衝突判定を行う。もし何もない場合は、NPCキャラクターがこちらの姿を見られるということだ。

結局やらないといけないことは次の通りだ:
bool AIFunction(){
        if((_transform.position - player.position).sqrMagnitude < attackRange &&
        Vector3.Angle(player.transform.position - _transform.position, transform.forward) <= angle){
            if (!Physics.Linecast (_transform.position, player.position, layerMask))
                _delFunc = this.Attack;
                    return true;
       }else{
            _delFunc = this.Walk;
            return false;
        }
    }

最後に、NPCキャラクターの動きがサマになってきた。次回の最終セクションでは、NPCキャラクターが安全に隠れることが出来る地点を見つけ、いきなり撃ち始めるのではなく安全地帯に移動してから撃ってくるところをみていく。
------

2013/2/11時点で元記事の次の章のみが削除されました。

次回、いよいよ最終記事に続くぜ!

関連記事

0 件のコメント:

コメントを投稿

ブックマークに追加

このエントリーをはてなブックマークに追加

自己紹介

自分の写真
Unity3D公式マニュアル翻訳やってる人がスマホ(iPhone, Android)のゲーム開発しています。気軽に面白く初心者が遊べる内容がモットー。Blogでは開発情報をひたすら、Twitterではゲーム作成の過程で参考にしている情報を中心につぶやきます

ページビューの合計

過去7日間の人気投稿