※ 2013/2/11時点で元記事のうち、この章のみ削除されました
さて、いよいよ本シリーズの最終記事だ。敵キャラも隠れた位置をみつけて、そこからこちらに向けて撃っていく、そういう場面のコーディングで総仕上げといこう。
関連記事
では例のごとく、Unity Gemsからの翻訳をどうぞ!
November 22, 2012
しゃがんで隠れる(隠れる場所を探す))
最後のセクションは良く戦争モノのゲームである場面だが、敵が遠く離れている場合、どこかに隠れ場所を見つけて、そこに隠れながら撃つ動作をする。時々、別の場所に敵は別の地点に移動をして(プレイヤーはこのときにNPCをやっつけるチャンスなので重要だ)。では関数を定義して、どの場所が安全で、プレイヤーとの距離はどこが一番近いかをチェックしていく。
そこでゲーム上での安全な場所を、おそらくは隠れられる何かのすぐ後ろなどに見つけて Safe (安全) とタグ付けする。次にそういう場所の中で最も近い場所を得る関数が必要だ。そこから安全な場所とプレイヤーとの距離をみて、もしその地点の方ご近ければそこに移動して撃ち始める。
Transform safePlace;
void FindClosestSafe() {
        GameObject[] gos;
        gos = GameObject.FindGameObjectsWithTag("Safe");
        GameObject closest=null;
        float distance = Mathf.Infinity;
        Vector3 position = transform.position;
        foreach (GameObject go in gos) {
            Vector3 diff = go.transform.position - position;
            float curDistance = diff.sqrMagnitude;
            if (curDistance < distance) {
                closest = go;
                distance = curDistance;
            }
        }
        safePlace =  closest.transform;
    }
それによって最も近い安全な場所を取得出来るようになる。それでは TakeCover 関数がさらに必要だ。
void TakeCover(){
    if((_transform.position - safePlace.position).sqrMagnitude >range ){
            Move(safePlace);
            animation.CrossFade("walk");
        }else{
            print ("ズキュン ズキュン!!");
        }
    }
これは単に安全な場所に移動して着いたら撃ち始めるというだけだ。着いた瞬間にズキュン ズキュンといくわけだが、まあ要領は分かってもらえるとおもう。
最後に、AIFunction関数にコードを追加する:
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)){
         FindClosestSafe() ;
         Vector3 a = safePlace.position - _transform.position;
         Vector3 b = player.position - _transform.position;
         if(a.sqrMagnitude < b.sqrMagnitude){
            _delFunc = this.TakeCover;
         }else{
            _delFunc = this.Attack;
         }
      }
      return true;
   }else{
      _delFunc = this.Walk;
      return false;
   }
}
これで完成だ!!!
そしてこのチュートリアルで作り上げた完成コードを掲載する:
using UnityEngine;
using System.Collections;
[RequireComponent (typeof (CharacterController))] 
public class First : MonoBehaviour {
#region variables
    CharacterController _controller;
    Transform _transform;
    Transform player;
    [SerializeField]
    Transform[] _waypoint = new Transform[4];
    Transform safePlace;
#region movement variables
    float speed = 5;
    float gravity = 20;
    Vector3 moveDirection;
    float maxRotSpeed = 200.0f;
    float minTime = 0.1f;
    float _velocity;
    float range;
    float attackRange;
    float angle = 90f;
    int index;
    bool isCorouting;
    int layerMask = 1 << 8;
#endregion
#region delegate variable
    delegate void DelFunc();
    delegate IEnumerator DelEnum();
    DelFunc _delFunc;
    DelEnum _delEnum;
    bool del;
#endregion
#endregion
    void Start(){
        _controller = GetComponent();
        _transform = GetComponent();
        player = GameObject.Find("Player").GetComponent();
        index = 0;
        range =2f;attackRange = 2000f;
        animation["RotateWait"].wrapMode = WrapMode.Once;
        _delFunc = this.Walk;
        _delEnum = null;
        del = true;
        isCorouting = false;
        layerMask = ~layerMask;
     }
    void Update(){
        if(AIFunction()&&isCorouting){
            StopAllCoroutines();
            del = true;
        }
        if(del)
            _delFunc();
        else if(!isCorouting){
            isCorouting = true;
            StartCoroutine(_delEnum());
        }
    }
    void Move(Transform target){
        moveDirection = _transform.forward;
        moveDirection *= speed;
        moveDirection.y -= gravity * Time.deltaTime;
        _controller.Move(moveDirection * Time.deltaTime);
        var newRotation = Quaternion.LookRotation(target.position - _transform.position).eulerAngles;
        var angles = _transform.rotation.eulerAngles;
        _transform.rotation = Quaternion.Euler(angles.x, 
            Mathf.SmoothDampAngle(angles.y, newRotation.y, ref _velocity, minTime, maxRotSpeed), angles.z);
    }
    void NextIndex(){
        if(++index == _waypoint.Length) index = 0;
    }
    #region movement functions  
    void Walk(){    
        if((_transform.position - _waypoint[index].position).sqrMagnitude >range){
            Move(_waypoint[index]);
            animation.CrossFade("walk");
        }else{
            switch(index){
                case 0:
                    del = false;
                    isCorouting = false;
                    _delEnum = this.RotateWait;
                    break;
                case 1 :
                    del =false;
                    isCorouting = false;
                    _delEnum = this.Wait;
                    break;
                default:
                    NextIndex();break;
            }
        }
    }
    void Attack(){
        if((_transform.position - player.position).sqrMagnitude >range ){
            Move(player);
            animation.CrossFade("walk");
        }else{
            print ("Slap slap!!");
        }
    }
    void TakeCover(){
        if((_transform.position - safePlace.position).sqrMagnitude >range ){
            Move(safePlace);
            animation.CrossFade("walk");
        }else{
            animation.CrossFade("walk");
            print ("Pow pow!!");
        }
    }
    #endregion
    #region animation functions
    IEnumerator RotateWait(){
        if(!animation.IsPlaying("RotateWait"))animation.CrossFade("RotateWait");
        yield return new WaitForSeconds(animation["RotateWait"].length);
        NextIndex();
        del =true;
    }
    IEnumerator Wait(){
        animation.CrossFade ("idle");
        yield return new WaitForSeconds(2.0f);
        NextIndex ();
        del = true;
    }
    #endregion
    #region AI function
    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)){
                    FindClosestSafe() ;
                    Vector3 a = safePlace.position - _transform.position;
                    Vector3 b = player.position - _transform.position;
                    if(a.sqrMagnitude < b.sqrMagnitude){
                        print ("safe");
                        _delFunc = this.TakeCover;
                    }else{
                        print ("player");
                        _delFunc = this.Attack;
                    }
                }
                return true;
       }else{
            _delFunc = this.Walk;
            return false;
        }
    }
    void FindClosestSafe() {
        GameObject[] gos;
        gos = GameObject.FindGameObjectsWithTag("Safe");
        GameObject closest=null;
        float distance = Mathf.Infinity;
        Vector3 position = transform.position;
        foreach (GameObject go in gos) {
            Vector3 diff = go.transform.position - position;
            float curDistance = diff.sqrMagnitude;
            if (curDistance < distance) {
                closest = go;
                distance = curDistance;
            }
        }
        safePlace =  closest.transform;
    }
    #endregion
}
これでチュートリアルもほぼ終わりまで来たわけだが、デリゲートによって様々なことが容易になったことに気付いてもらえただろうか。関数をいくつか追加きたにも関わらず、Update関数は変更せずに済んでいる。デリゲートのお陰でNPCキャラクターのメイン関数を変更することなく、ビヘイビアを追加出来るのだ。
結論
これでNPCキャラクターについて、さらに開発を進めるための基本は覚えたといってよい。これが皆さんの役に立って、さらに発展的なデリゲートのチュートリアルに進んでいくための手助けになれば、と願う。
さらに敵キャラが壁や木をすり抜けたりせずに動き回らせる方法をみてきた。敵キャラがプレイヤーを視界に入れて走ってくるもカバーした。また簡単なデリゲートについても学んだ。後はこの知識を活かしてさらに高度なことを実現してもらいたい。
今回のチュートリアルも気に入ってもらえたら何よりだ。
------
※ 2013/2/11時点で元記事のうち、この章のみ削除されました。
プロジェクトサンプルはアップしていただけました!目次ページを参照ください。
プロジェクトファイルを公開してもらえるように Mike Talbot氏にお願いしてて今週のどこかにはアップロードしてくれるとのこと。楽しみだな~
先人の知恵を活用して開発していこうぜ!
 
0 件のコメント:
コメントを投稿