//tips
//自動行動AIの挙動
まずは決められた目的地に移動するEnemyを作成する。黄色キューブに下記のスクリプトをアタッチして動かしてみた。
Asset storeからヒューマノイドをダウンロードすれば最初からスクリプトがアタッチされていることが多いので、もっと簡単にできるが今回はキューブで最初から作っている。
下記のブログが分かりやすかったので参考にした。
https://gametukurikata.com/program/movearounddestination
まずは、フィールドの端から端まで移動させてみた。
using UnityEngine;
using System.Collections;
public class MoveEnemy : MonoBehaviour
{
// 目的地
private Vector3 destination;
// 歩くスピード
[SerializeField]
private float walkSpeed = 1.0f;
// 速度
private Vector3 velocity;
// 移動方向
private Vector3 direction;
private Rigidbody rigd;
void Start()
{
rigd = GetComponent<Rigidbody>(); //Rigidbodyを取得
destination = new Vector3(4, 0f, 4f);//目的地
velocity = Vector3.zero;
}
// Update is called once per frame
void Update()
{
velocity = Vector3.zero;
direction = (destination - transform.position).normalized;
transform.LookAt(new Vector3(destination.x, transform.position.y, destination.z));
velocity = direction * walkSpeed;
Debug.Log(destination);
//velocity.y += Physics.gravity.y * Time.deltaTime;
rigd.velocity = velocity;
}
}
次にRandom.insideUnitCircleで半径1以内のVector2のランダムな点を作成し、スタート地点周辺の一定範囲にランダムに移動させる。目的地到着後にもピクピク動くのでboolを追加して到着後には動かなくした。
using UnityEngine;
using System.Collections;
public class MoveEnemy : MonoBehaviour
{
// 目的地
private Vector3 destination;
// 歩くスピード
[SerializeField]
private float walkSpeed = 1.0f;
// 速度
private Vector3 velocity;
// 移動方向
private Vector3 direction;
private Rigidbody rigd;
private bool arrived;
void Start()
{
rigd = GetComponent<Rigidbody>(); //プレイヤーのRigidbodyを取得
//destination = new Vector3(4, 0f, 4f);
Vector3 startPosition = transform.position;
var randDestination = Random.insideUnitCircle * 3;
destination = startPosition + new Vector3(randDestination.x, 0, randDestination.y);
velocity = Vector3.zero;
}
// Update is called once per frame
void Update()
{
if (!arrived)
{
velocity = Vector3.zero;
direction = (destination - transform.position).normalized;
transform.LookAt(new Vector3(destination.x, transform.position.y, destination.z));
velocity = direction * walkSpeed;
Debug.Log(destination);
//velocity.y += Physics.gravity.y * Time.deltaTime;
rigd.velocity = velocity;
}
if (Vector3.Distance(transform.position, destination) < 0.5f)
{
arrived = true;
}
}
}
次に、MoveEnemyスクリプトをベースに目的地に到着後、別の目的地を設定し、そこに移動する機能を作っていく。
CreateRandomPositionメソッドが呼ばれるとランダム値を加えた目的地が設定するようにSetPositionスクリプトを作成。MoveEnemyスクリプト同様、EnemyAiオブジェクトにアタッチ。
using UnityEngine;
using System.Collections;
public class SetPosition : MonoBehaviour
{
//初期位置
private Vector3 startPosition;
//目的地
private Vector3 destination;
void Start()
{
// 初期位置を設定
startPosition = transform.position;
SetDestination(transform.position);
}
// ランダムな位置の作成
public void CreateRandomPosition()
{
var randDestination = Random.insideUnitCircle * 4;
// 現在地にランダムな位置を足して目的地にする
SetDestination(startPosition + new Vector3(randDestination.x, 0, randDestination.y));
}
// 目的地設定
public void SetDestination(Vector3 position)
{
destination = position;
}
// 目的地取得
public Vector3 GetDestination()
{
return destination;
}
}
MoveEnemyスクリプトをSetPositionスクリプトに合わせて変更。下記によりEnemyAiが徘徊するようになった。
using UnityEngine;
using System.Collections;
public class MoveEnemy : MonoBehaviour
{
// 目的地
private Vector3 destination;
// 歩くスピード
[SerializeField]
private float walkSpeed = 1.0f;
// 速度
private Vector3 velocity;
// 移動方向
private Vector3 direction;
private Rigidbody rigd;
private bool arrived;
private SetPosition setPosition;
// 待ち時間
[SerializeField]
private float waitTime = 1f;
// 経過時間
private float elapsedTime;
void Start()
{
rigd = GetComponent<Rigidbody>(); //プレイヤーのRigidbodyを取得
//destination = new Vector3(4, 0f, 4f);
setPosition = GetComponent<SetPosition>();
setPosition.CreateRandomPosition(); //目的地設定
velocity = Vector3.zero;
arrived = false;
elapsedTime = 0f;
}
// Update is called once per frame
void Update()
{
if (!arrived)
{
velocity = Vector3.zero;
direction = (destination - transform.position).normalized;
transform.LookAt(new Vector3(destination.x, transform.position.y, destination.z));
velocity = direction * walkSpeed;
Debug.Log(destination);
//velocity.y += Physics.gravity.y * Time.deltaTime;
rigd.velocity = velocity;
if (Vector3.Distance(transform.position, destination) < 0.5f)
{
arrived = true;
}
}
else
{
elapsedTime += Time.deltaTime;
// 待ち時間を越えたら次の目的地を設定
if (elapsedTime > waitTime)
{
setPosition.CreateRandomPosition();
destination = setPosition.GetDestination();
arrived = false;
elapsedTime = 0f;
}
Debug.Log(elapsedTime);
}
}
}
次は、プレイヤーがEnemyUIに接近したら反応する仕組みを作っていく。
その前に、スクリプトに含めた下記2つが便利に感じたので、もう少し突っ込んで理解することにした。
・transform.LookAt(new Vector3
三人称視点の正面をとるときに重要。Quaternion.LookRotationとも用いられ方が微妙に異なる。
public void LookAt (Transform target, Vector3 worldUp= Vector3.up);
transform.rotation = Quaternion.LookRotation(向きたい方向,頭上方向);
キャラクターを敵の方向に向かせたい場合は、Transform.LookAtを使えば簡単に敵の方向を向かせられるが挙動が急で不自然に感じる場面がある。
そのような場合は、LookRotationを使い、徐々に敵の方向を向かせる。
また、下記は英文の抜粋だが、LookAt()は向きではなく、場所を示すので、向きのベクトルの方に体をむかせたい場合には、常に現在の自身のポジションを加算してあげる必要がある。その点、rotationは初めから向きを引数に取るので、そのまま向きベクトルを入れることができる。
Transform.LookAt() looks at a position in 3D space. It does not look in a specific direction. If you have a direction you want to look you can either do something like:
transform.LookAt(transform.postion + theDirection);
Or you can do:
transform.rotation = Quaternion.LookRotation(theDiretion);
https://answers.unity.com/questions/452965/get-lookat-vector-of-a-tranform.html
https://answers.unity.com/questions/752130/lookat-fromtorotation-and-lookrotation.html
また、オブジェクトのY軸のみをあるオブジェクト方向に回転させる場合などはTransform.LookAt()を使うとX軸やZ軸まで回転してしまうため、Quaternion.LookRotation()に
計算方向を渡すことで一軸のみの回転を可能にする。
・Vector3.Distance(transform.position, destination)
以前使用したGetAim(Vector2 p1, Vector2 p2)のp1からp2を引き、その差分をベクトルに足すという処理を簡易化できないかと思ったが、単純に距離を返すだけだったので接近範囲にしか仕えなさそうなことがわかった。
Vector3.Distance(Vector3 a, Vector3 b); //a と b の間の距離を返す
また、ルート(√)の計算はCPUに大きな負荷がかかるようで、できるだけ.sqrMagnitudeを使用するように推奨されていることがわかった。.sqrMagnitudeとは二乗のことで、ベクトル..sqrMagnitudeとすることでベクトル距離の二乗を出すことができる。
/長さの2乗 > 離れたい距離の2乗
if(transform.position.sqrMagnitude > squareDistance)