さて、前回記事に引き続き、敵キャラの動きを直していくのだが、ちょっとだけデリゲートに関するお勉強が必要。
関連記事
では例のごとく、Unity Gemsからの翻訳をどうぞ!
November 22, 2012
デリゲートの使い方
FSMチュートリアルに深入りしてデリゲートやクロージャに怯えてPCのない山小屋に閉じこもる前に、シンプルなデリゲートのサンプルを紹介したい。
もしC または C++ の経験があれば、関数ポインタまたはC++のファンクタと比較する必要がある。覚えるべきは:
void Afunction(){
//内容はここに
}
void (*fct)(int);
fct = &Afunction;
デリゲートは何かと似通ってるが、より簡単だ。
これからコードを修正してデリゲートを使用できるようにする。最初にデリゲートを宣言して、デリゲートのインスタンスを作成して最終的に関数を割り当てる。それでようやくデリゲートを使用できるようになる。
delegate void DelFunc();
DelFunc _delegate;
_delegate = Afunction;
static void Afunction(){
}
void Update(){
_delegate();
}
これが基本的な宣言方法でインターネットで見つかるチュートリアルに載っている大部分はここまでであり、リスクをとってこれ以上載せることはしないが、我々はもう少し深入りしたい。static 関数が非staticメンバにアクセス出来ないことは知っていると思う。では今回のNPCキャラクターでシーンに多数ある場合はどうしたらいいのだろうと思うかもしれないけど、同じように動作出来るのだ。
float variable;
delegate void DelFunc();
DelFunc _delegate;
_delegate = this.Afunction; //このポインタに注目できてますか?
void Afunction(){
variable = variable +10;
}
デリゲートを使用する目的そのものは使用できることだけでなく、柔軟性を持たせることだ。目標はひとつの関数が他の複数の関数からコール出来ることだ。次の例をみてほしい:
using UnityEngine;
using System.Collections;
using System;
public class Delegate :MonoBehaviour {
[SerializeField]
float pos;
float val = 10;
// デリゲートの宣言
public delegate void DelegateFunction();
public delegate IEnumerator DelegateEnum();
// オブジェクトのデリゲート
DelegateFunction _delegate;
DelegateEnum _delegateEnum;
bool del;
void Start(){
_delegate = this.Increase;
_delegateEnum = null;
pos = 0;
del = true;
}
void Update () {
// スペースを押すと、del が trueであるため _delegate がコールされる
// (この理由で Start() で関数を割り当てて Null 参照を回避する)
if(Input.GetKeyDown(KeyCode.Space)){
if(del)_delegate();
// _delegateEnum は StartCoroutine でコールされる
else StartCoroutine(_delegateEnum());
}
if(Input.GetKeyDown(KeyCode.I)){
del =true; // del が true であることを確定させる
_delegate =this.Increase; // _delegate に関数を割り当てる
}
if(Input.GetKeyDown(KeyCode.D)){
del =true;
_delegate = this.Decrease;
}
if(Input.GetKeyDown(KeyCode.O)){
del =true;
_delegate = this.Zero;
}
if(Input.GetKeyDown(KeyCode.W)){
del =false; // del が true であることを確定させる
_delegateEnum = this.Wait; // _delegate に関数を割り当てる
}
if(Input.GetKeyDown(KeyCode.A)){
del =false;
_delegateEnum = this.WaitAndAdd;
}
}
public void Increase(){
pos += val*Time.deltaTime;
print (pos);
}
public void Decrease(){
pos -= val*Time.deltaTime;
print (pos);
}
public void Zero(){
pos = 0;
print (pos);
}
public IEnumerator Wait(){
yield return new WaitForSeconds(1.0f);
print("Enum"+pos);
}
public IEnumerator WaitAndAdd(){
print("Adding soon"+pos);
yield return new WaitForSeconds(1.0f);
pos+=2;
print("Added "+pos);
}
}
スクリプトをシーンのオブジェクトにアタッチしてコンソールをみてほしい。エディタをみてみてposには [SerializeField] 属性があるが、インスペクタ上にプライベート変数が表示される。
押されたボタンに応じて、デリゲートに関数が割り当てされる。関数のシグニチャはデリゲートのシグニチャと互いに一致する必要があるため、デリゲートが二つ必要だ。ただしbooleanを使用してupdateで使用される方を判定する。
さらに引数を使用することも可能であり、適切なシグニチャを戻り値とするデリゲートを作成して関数を同じシグニチャで渡します。Actionおよび関数についてはFSMチュートリアルで詳細な説明があるのでここでは説明を省略する。
それではAiScriptを修正してデリゲートを含めよう。マイナーチェンジが必要だ:
using UnityEngine;
using System.Collections;
[RequireComponent (typeof (CharacterController))]
public class AiScript : MonoBehaviour {
CharacterController _controller;
Transform _transform;
float speed = 5;
float gravity = 20;
Vector3 moveDirection;
float maxRotSpeed = 200.0f;
float minTime = 0.1f;
float _velocity;
float range;
[SerializeField] // これによりエディタでプライベート変数が参照できる
Transform[] _waypoint = new Transform[4];
int index;
bool isCorouting;
delegate void DelFunc();
delegate IEnumerator DelEnum();
DelFunc _delFunc;
DelEnum _delEnum;
bool del;
void Start(){
_controller = GetComponent();
_transform = GetComponent();
index = 0;
range =2f;
animation["RotateWait"].wrapMode = WrapMode.Once;
_delFunc = this.Walk;
_delEnum = null;
del = true;
isCorouting = false;
}
void Update(){
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;
}
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;
}
}
}
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;
}
}
Updateは短くなりbooleanチェックをかなり外した。boolean型のdel変数によりどの種類のデリゲートが使用されるべきか判定し、(シンプルかコルーチンか)、isCoroutingにより一回のみコールされることが保証される。そうするとその二つをセットしてでりは実行したいアクションに依存する。
キャラクターは巡回する分には十分だが、まだプレイヤーとの関わり合いがない。次にプレイヤーと相互作用させる方法を見ていく。
------
次回に続くぜ!
関連記事
0 件のコメント:
コメントを投稿