注: 这种使用Rigidbody组件的 方式 ,不适合MMO服务器控制逻辑了, 服务器又没有Rigidbody组件的概念。
感兴趣的可以下载下来 跑一下(是下载源代码,不是release )
地址: https://github.com/SunGuangdong/unity-movement-ai
这个库有以下Movement AI : Arrive 抵达, Cohesion 凝聚, Collision Avoidance 碰撞避免, Evade 逃避, Flee 逃离, Follow Path 跟踪路径, Hide 隐藏, Interpose 插入(干预,管闲事), Offset Pursuit 偏移追求, Pursue 追求, Seek 寻求, Separation 分离, Velocity Match 速度匹配, Wall Avoidance 墙壁避免, andWander 漫游.
有的意思都是一样的, 区别是 是否与预测目标的接下来一段时间的位置然后做出正确的决定。
public class ArriveUnit2: MonoBehaviour
{
public Vector3 targetPosition;
private SteeringBasics2 steeringBasics;
private void Start()
{
steeringBasics = GetComponent();
}
private void Update()
{
// 计算线性加速度
Vector3 accel = steeringBasics.Arrive(targetPosition);
// 得到速度
steeringBasics.Steer(accel);
// 使当前的游戏对象的朝向 ,他要去的地方
steeringBasics.LookWhereYoureGoing();
}
}
[RequireComponent (typeof (Rigidbody))]
public class SteeringBasics2 : MonoBehaviour {
public float maxVelocity = 3.5f;
/* 最大加速度 */
public float maxAcceleration = 10f;
/* 目标的半径, 意味着我们已经到达了 */
public float targetRadius = 0.005f;
/* 到这个距离时,开始减速 */
public float slowRadius = 1f;
/* 我们想要达到目标速度 所需时间 */
public float timeToTarget = 0.1f;
public float turnSpeed = 20f;
private Rigidbody rb;
public bool smoothing = true;
public int numSamplesForSmoothing = 5;
private Queue velocitySamples = new Queue();
void Start () {
rb = GetComponent ();
}
internal Vector3 Arrive(Vector3 targetPosition)
{
/* 得到 正确方向*/
Vector3 targetVelocity = targetPosition - transform.position;
// 不让z轴变化
targetVelocity.z = 0;
/* 得到到目标的距离 */
float dist = targetVelocity.magnitude;
/* 如果我们在停止的范围内, 那么停止(目标不是一个点,有范围的)*/
if (dist < targetRadius)
{
rb.velocity = Vector2.zero;
return Vector2.zero;
}
/* 计算速度, 如果接近目标就减速, 否则就全速前进, 到目标就是0了 */
float targetSpeed;
if (dist > slowRadius)
{
targetSpeed = maxVelocity;
}
else
{
targetSpeed = maxVelocity * (dist / slowRadius);
}
/* 实际的速度 = 方向 * 大小 */
targetVelocity.Normalize();
targetVelocity *= targetSpeed;
// 计算我们想要的线性加速度(下一帧的速度 - 这一帧的速度)
Vector3 acceleration = targetVelocity - new Vector3(rb.velocity.x, rb.velocity.y, 0);
// 到达目标速度需要的时间。 默认是一秒,很长。 时间越短,加速度就要越大才行
acceleration *= 1 / timeToTarget;
Debug.Log(acceleration);
/* 不要超过最大加速度 */
if (acceleration.magnitude > maxAcceleration)
{
acceleration.Normalize();
acceleration *= maxAcceleration;
}
return acceleration;
}
internal void Steer(Vector3 linearAcceleration)
{
rb.velocity += linearAcceleration * Time.deltaTime;
// 不能超过最大速度
if (rb.velocity.magnitude > maxVelocity)
{
rb.velocity = rb.velocity.normalized * maxVelocity;
}
}
internal void LookWhereYoureGoing()
{
Vector2 direction = rb.velocity;
if (smoothing)
{
if (velocitySamples.Count == numSamplesForSmoothing)
{
velocitySamples.Dequeue();
}
velocitySamples.Enqueue(rb.velocity);
direction = Vector2.zero;
foreach (Vector2 v in velocitySamples)
{
direction += v;
}
direction /= velocitySamples.Count;
}
LookAtDirection(direction);
}
public void LookAtDirection(Vector2 direction)
{
direction.Normalize();
// 如果我们有一个非零的方向,那就朝着那个方向看,否则什么也不做
if (direction.sqrMagnitude > 0.001f)
{
float toRotation = (Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg);
float rotation = Mathf.LerpAngle(transform.rotation.eulerAngles.z, toRotation, Time.deltaTime * turnSpeed);
transform.rotation = Quaternion.Euler(0, 0, rotation);
}
}
}
重点就是随机呗。 因为使用的是刚体和碰撞体, 所以不会穿过障碍物,虽然会撞上, 不过不重要!
public class Wander3Unit : MonoBehaviour
{
// 实际操作 Rigidbody 组件
private SteeringBasics2 steeringBasics2;
// 主要是得到加速度方向
private Wander3 wander3;
private void Start()
{
steeringBasics2 = GetComponent();
wander3 = GetComponent();
}
private void Update()
{
// 得到加速度
Vector3 accel = wander3.GetSteering();
// 设置刚体的速度 和 角色方向
steeringBasics2.Steer(accel);
steeringBasics2.LookWhereYoureGoing();
}
}
public class Wander3 : MonoBehaviour
{
public float wanderRadius = 1.2f;
public float wanderDistance = 2f;
// 随机的最大位移(1秒)
public float wanderJitter = 40f;
private Vector3 wanderTarget;
private SteeringBasics2 steeringBasics2;
private void Start()
{
// 随机一个 弧度 (角度的取值范围 0~360, 弧度范围 0~2π)
float theta = Random.value * 2 * Mathf.PI;
// 位置 向量(极坐标 得到) // x = rcos(θ), y = rsin(θ),
wanderTarget = new Vector3(wanderRadius * Mathf.Cos(theta),
wanderRadius * Mathf.Sin(theta), 0);
steeringBasics2 = GetComponent();
}
internal Vector3 GetSteering()
{
// 得到一帧时间的 最大位移
float jitter = wanderJitter * Time.deltaTime;
// 向目标的位置添加一个小的随机向量(每一帧都调整新的)
wanderTarget += new Vector3(Random.Range(-1f, 1f) * jitter,
Random.Range(-1f, 1f) * jitter, 0f);
// 得到新的漫游圈
wanderTarget.Normalize();
wanderTarget *= wanderRadius;
// 得到目标位置, 在角色前方 基础上做一个偏移。 c = a + b , 就是对角线
Vector3 targetPosition = transform.position + transform.right * wanderDistance + wanderTarget;
// 为了调试
Debug.DrawLine(transform.position, targetPosition);
// 返回方向上的 最大加速度
return steeringBasics2.Seek(targetPosition);
}
}
internal Vector3 Seek(Vector3 targetPosition)
{
return Seek(targetPosition, maxAcceleration);
}
private Vector3 Seek(Vector3 targetPosition, float maxSeekAccel)
{
// 得到方向
Vector3 accelaration = targetPosition - transform.position;
// z 不变
accelaration.z = 0;
accelaration.Normalize();
accelaration *= maxSeekAccel;
return accelaration;
}
2) 在看看两外一种写法: 跟之前类似
public class Wander4Unit : MonoBehaviour {
// 实际操作 Rigidbody 组件
private SteeringBasics2 steeringBasics2;
// 主要是得到加速度方向
private Wander4 wander4;
private void Start()
{
steeringBasics2 = GetComponent();
wander4 = GetComponent();
}
private void Update()
{
// 得到加速度
Vector3 accel = Wander4.GetSteering();
// 设置刚体 和 方向
steeringBasics2.Steer(accel);
steeringBasics2.LookWhereYoureGoing();
}
}
public class Wander4 : MonoBehaviour
{
// 偏移量
public float wanderOffset = 1.5f;
// 半径
public float wanderRadius = 4;
// 比率
public float wanderRate = .4f;
private float wanderOrientation = 0;
private SteeringBasics2 steeringBasics2;
private void Start()
{
steeringBasics2 = GetComponent();
}
internal Vector3 GetSteering()
{
// 当前角色的方向(弧度)
float characterOrientation = transform.rotation.eulerAngles.z * Mathf.Deg2Rad;
// 随机一个漫游方向(弧度)
wanderOrientation += RandomBinomial() * wanderRate;
// 目标方向结合(弧度), 就是在角色方向上的一个偏移
float targetOrientation = wanderOrientation + characterOrientation;
// 角色的前方(偏移量)
Vector3 targetPosition = transform.position + (OrientationToVector(characterOrientation) * wanderOffset);
// 得到目标位置 向量 c = a + b 就是对角线的方向
targetPosition = targetPosition + (OrientationToVector(targetOrientation) * wanderRadius);
// 调试
Debug.DrawLine(transform.position, targetPosition);
// 得到 最大加速度
return steeringBasics2.Seek(targetPosition);
}
/*返回一个随机数介于-1和1。 值为零的可能性更大。*/
float RandomBinomial()
{
return Random.value - Random.value;
}
/* 返回方向的单位向量 */
Vector3 OrientationToVector(float orientation)
{
// 位置 向量(极坐标 得到) // x = rcos(θ), y = rsin(θ),
return new Vector3(Mathf.Cos(orientation), Mathf.Sin(orientation), 0);
}
}
public class Seek2Unit : MonoBehaviour
{
public Transform target;
private SteeringBasics2 steeringBasics2;
private void Start()
{
steeringBasics2 = GetComponent();
}
private void Update()
{
var accel = steeringBasics2.Seek(target.position);
steeringBasics2.Steer(accel);
steeringBasics2.LookWhereYoureGoing();
}
}
还有另外一个对象和脚本, 让移出屏幕的对象从屏幕的另外一侧移入。 开始的时候生成和屏幕大小一样的碰撞体, 然后再碰撞检测中处理
public class ScreenBoundary : MonoBehaviour {
private Vector3 bottomLeft;
private Vector3 topRight;
private Vector3 widthHeight;
// Use this for initialization
void Start () {
float z = -1*Camera.main.transform.position.z;
bottomLeft = Camera.main.ViewportToWorldPoint(new Vector3(0, 0, z));
topRight = Camera.main.ViewportToWorldPoint(new Vector3(1, 1, z));
widthHeight = topRight - bottomLeft;
transform.localScale = new Vector3(widthHeight.x, widthHeight.y, transform.localScale.z);
}
void OnTriggerStay(Collider other)
{
Transform t = other.transform;
if (t.position.x < bottomLeft.x)
{
t.position = new Vector3(t.position.x + widthHeight.x, t.position.y, t.position.z);
}
if (t.position.x > topRight.x)
{
t.position = new Vector3(t.position.x - widthHeight.x, t.position.y, t.position.z);
}
if (t.position.y < bottomLeft.y)
{
t.position = new Vector3(t.position.x, t.position.y + widthHeight.y, t.position.z);
}
if (t.position.y > topRight.y)
{
t.position = new Vector3(t.position.x, t.position.y - widthHeight.y, t.position.z);
}
}
}
public class PursueUnit2 : MonoBehaviour {
// 得到目标位置 和 当前速度
public Rigidbody target;
// 组件
private Pursue2 pursue;
private SteeringBasics2 steeringBasics;
private void Start()
{
steeringBasics = GetComponent();
pursue = GetComponent();
}
private void Update()
{
// 得到线性加速度
Vector3 accel = pursue.GetSteering(target);
// 设置刚体速度
steeringBasics.Steer(accel);
// 朝向
steeringBasics.LookWhereYoureGoing();
}
}
[RequireComponent(typeof(SteeringBasics2))]
public class Pursue2 : MonoBehaviour
{
// 未来预测的最大预测时间
public float maxPrediction = 1f;
private Rigidbody rb;
private SteeringBasics2 steeringBasics;
private void Start()
{
rb = GetComponent();
steeringBasics = GetComponent();
}
internal Vector3 GetSteering(Rigidbody target)
{
// 计算到目标的距离
Vector3 displacement = target.position - transform.position;
float distance = displacement.magnitude;
// 当前的速度
float speed = rb.velocity.magnitude;
// 计算预测时间 (不能让预测时间能跑的距离 超过当前的距离)
float prediction;
if (speed <= distance / maxPrediction) // (maxPrediction * speed <= distance )
{
prediction = maxPrediction;
}
else
{
prediction = distance / speed;
}
// 目标 : 在目标位置基础上添加预测的部分
Vector3 explicitTarget = target.position + target.velocity * prediction;
return steeringBasics.Seek(explicitTarget);
}
}
5 FollowPath沿着路径走
这个肯定没有 我之前研究 贝塞尔曲线 广泛啊 地址: 《 Unity游戏中使用贝塞尔曲线 》
public class FollowPathUnit : MonoBehaviour {
// 是否循环
public bool pathLoop = false;
// 原路返回
public bool reversePath = false;
// 编辑器设置路径的关键点
public LinePath path;
// 其他组件
private SteeringBasics steeringBasics;
private FollowPath followPath;
void Start () {
// 计算距离
path.calcDistances();
steeringBasics = GetComponent();
followPath = GetComponent();
}
void Update () {
path.draw();
// 执行原路返回的逻辑
if (reversePath && isAtEndOfPath())
{
path.reversePath();
}
// 计算线性加速度
Vector3 accel = followPath.getSteering(path, pathLoop);
// 设置刚体速度
steeringBasics.steer(accel);
// 朝向
steeringBasics.lookWhereYoureGoing();
}
// 到达最后节点
public bool isAtEndOfPath()
{
return Vector3.Distance(path.endNode, transform.position) < followPath.stopRadius;
}
}
[RequireComponent (typeof (SteeringBasics))]
public class FollowPath : MonoBehaviour {
// 用于结束的判断
public float stopRadius = 0.005f;
// 偏移量
public float pathOffset = 0.71f;
// 路径方向
public float pathDirection = 1f;
// 其他组件
private SteeringBasics steeringBasics;
private Rigidbody rb;
void Start () {
steeringBasics = GetComponent ();
rb = GetComponent ();
}
// 得到线性加速度
public Vector3 getSteering (LinePath path) {
return getSteering (path, false);
}
public Vector3 getSteering (LinePath path, bool pathLoop) {
Vector3 targetPosition;
return getSteering(path, pathLoop, out targetPosition);
}
public Vector3 getSteering (LinePath path, bool pathLoop, out Vector3 targetPosition) {
// 如果路径只有一个节点, 那么只需转到该位置
if (path.Length == 1) {
targetPosition = path[0];
}
//否则, 在该路径上找到最接近的点, 然后转到该位置。
else
{
if (!pathLoop)
{
/* 查找此路径上的 最终目标 */
Vector2 finalDestination = (pathDirection > 0) ? path[path.Length - 1] : path[0];
// 如果我们足够接近最终目的地, 就停止
/* If we are close enough to the final destination then either stop moving or reverse if
* the character is set to loop on paths */
if (Vector2.Distance(transform.position, finalDestination) < stopRadius)
{
targetPosition = finalDestination;
rb.velocity = Vector2.zero;
return Vector2.zero;
}
}
/* 在给定路径上,得到最接近的位置点的参数*/
float param = path.getParam(transform.position);
/* 向下移动的路径 */
param += pathDirection * pathOffset;
/* 得到目标位置 */
targetPosition = path.getPosition(param, pathLoop);
}
return steeringBasics.arrive(targetPosition);
}
}
[System.Serializable]
public class LinePath {
// 路径上的节点
public Vector3[] nodes;
// 节点间距离
private float[] distances;
// 最大距离(起始节点 到 最后节点的距离)
[System.NonSerialized]
public float maxDist;
// 定义索引器
public Vector3 this[int i]
{
get
{
return nodes[i];
}
set
{
nodes[i] = value;
}
}
public int Length
{
get {
return nodes.Length;
}
}
// 最后一个节点
public Vector3 endNode {
get {
return nodes[nodes.Length-1];
}
}
///* 构造函数 */
//public LinePath(Vector3[] nodes) {
// this.nodes = nodes;
// calcDistances();
//}
/* 遍历路径的节点,并确定路径中每个节点到起始节点的距离 */
public void calcDistances() {
distances = new float[nodes.Length];
distances[0] = 0;
for(var i = 0; i < nodes.Length - 1; i++) {
distances[i+1] = distances[i] + Vector3.Distance(nodes[i], nodes[i+1]);
}
maxDist = distances[distances.Length-1];
}
/* 为了调试 ,在场景中绘制路径线 */
public void draw() {
for (int i = 0; i < nodes.Length-1; i++) {
Debug.DrawLine(nodes[i], nodes[i+1], Color.cyan, 0.0f, false);
}
}
/* 得到在 路径上最接近的点的参数*/
public float getParam(Vector3 position) {
// 找到这个点 最接近的路径 的索引
int closestSegment = getClosestSegment(position);
float param = this.distances[closestSegment] + getParamForSegment(position, nodes[closestSegment], nodes[closestSegment+1]);
return param;
}
/* 找到最接近路径的索引 */
public int getClosestSegment(Vector3 position) {
// 遍历到所有线段的 距离
float closestDist = distToSegment(position, nodes[0], nodes[1]);
int closestSegment = 0;
for(int i = 1; i < nodes.Length - 1; i++) {
float dist = distToSegment(position, nodes[i], nodes[i+1]);
if(dist <= closestDist) {
closestDist = dist;
closestSegment = i;
}
}
return closestSegment;
}
/* 给定一个参数,它得到路径上的位置*/
public Vector3 getPosition(float param, bool pathLoop = false) {
/* 不要越界, 确保param没有经过开始或结束的路径 */
if (param < 0) {
param = (pathLoop) ? param + maxDist : 0;
} else if (param > maxDist) {
param = (pathLoop) ? param - maxDist : maxDist;
}
/* Find the first node that is farther than given param */
// 找到比给定的param还要远的第一个节点
int i = 0;
for(; i < distances.Length; i++) {
if(distances[i] > param) {
break;
}
}
/* Convert it to the first node of the line segment that the param is in */
// 将其转换为param所在的行段的第一个节点
if (i > distances.Length - 2) {
i = distances.Length - 2;
} else {
i -= 1;
}
/* Get how far along the line segment the param is */
// 沿着这条线段走多远
float t = (param - distances[i]) / Vector3.Distance(nodes[i], nodes[i+1]);
/* Get the position of the param */
// 得到该参数的位置
return Vector3.Lerp(nodes[i], nodes[i+1], t);
}
// 给出一个点到线段的距离。p是点,v和w是线段的两个点
private static float distToSegment(Vector3 p, Vector3 v, Vector3 w) {
Vector3 vw = w - v;
float l2 = Vector3.Dot(vw, vw);
if (l2 == 0)
{ // 两点重合
return Vector3.Distance(p, v);
}
float t = Vector3.Dot(p - v, vw) / l2;
if (t < 0) { // 以 v 点为钝角 (离v最近)
return Vector3.Distance(p, v);
}
if (t > 1) { // 以 w 为钝角 (理解为V为锐角, 但是pv 长度大于 vw)(离w最近)
return Vector3.Distance(p, w);
}
// 在线段上的点 (t 在 0~1 之间, 正好是插值的时间), 逻辑帧牛逼!
Vector3 closestPoint = Vector3.Lerp(v, w, t);
return Vector3.Distance(p, closestPoint);
}
// 找到与所给出的线段vw,最近点p的参数
// 跟函数 distToSegment 类似, p在vw线段上,距离v的长度
private static float getParamForSegment(Vector3 p, Vector3 v, Vector3 w) {
Vector3 vw = w - v;
float l2 = Vector3.Dot(vw, vw);
if (l2 == 0)
{// 两点重合
return 0;
}
float t = Vector3.Dot(p - v, vw) / l2;
if(t < 0) {
t = 0;
} else if (t > 1) {
t = 1;
}
return t * Mathf.Sqrt(l2);
}
// // 移除几点
//public void removeNode(int i ) {
// Vector3[] newNodes = new Vector3[nodes.Length - 1];
// int newNodesIndex = 0;
// for (int j = 0; j < newNodes.Length; j++) {
// if(j != i) {
// newNodes[newNodesIndex] = nodes[j];
// newNodesIndex++;
// }
// }
// this.nodes = newNodes;
// calcDistances();
//}
// 反转路径,就是直接把节点反转, 然后重新初始化一下内容
public void reversePath()
{
Array.Reverse(nodes);
calcDistances();
}
}
public class FleeUnit2 : MonoBehaviour {
public Transform target;
public SteeringBasics2 steeringBasics;
public Flee2 flee;
private void Start()
{
steeringBasics = GetComponent();
flee = GetComponent();
}
private void Update()
{
// 得到线性加速度
Vector3 accel = flee.GetSteering(target.position);
// 设置刚体速度
steeringBasics.Steer(accel);
// 设置朝向
steeringBasics.LookWhereYoureGoing();
}
}
public class Flee2 : MonoBehaviour {
// 跟目标保持的距离
public float panicDist = 3.5f;
// 是否开启快到目标时 减速
public bool decelerateOnStop = true;
public float maxAcceleration = 10f;
// 跟 SteeringBasics2 中的意思类似
public float timeToTarget = .1f;
private Rigidbody rb;
private void Start()
{
rb = GetComponent();
}
public Vector3 GetSteering(Vector3 targetPosition)
{
// 得到方向
Vector3 acceleration = transform.position - targetPosition;
// 不需要逃离了
if (acceleration.magnitude > panicDist)
{
// 如果我们要减速,就放慢速度
if (decelerateOnStop && rb.velocity.magnitude > 0.001f)
{
if (acceleration.magnitude > maxAcceleration)
{
// 减速到0速 需要的时间
acceleration = -rb.velocity / timeToTarget;
}
return acceleration;
}
else
{
rb.velocity = Vector2.zero;
return Vector3.zero;
}
}
// 以最大速度逃离
return GiveMaxAccel(acceleration);
}
private Vector3 GiveMaxAccel(Vector3 v)
{
// 移除z 影响
v.z = 0;
v.Normalize();
v *= maxAcceleration;
return v;
}
}