五、逃避
逃避行为与追逐行为的不同是它试图使AI角色逃离预测位置。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SteeringForEvade :Steering { public GameObject target; private Vector3 desiredVelocity;//预期速度 private Vehicle m_vehicle;//获得被操控的AI角色 private float maxSpeed; void Start() { m_vehicle = GetComponent<Vehicle>(); maxSpeed = m_vehicle.maxSpeed; } public override Vector3 Force() { Vector3 toTarget = target.transform.position - transform.position; float lookaheadTime = toTarget.magnitude / (maxSpeed + target.GetComponent<Vehicle>().velocity.magnitude);//向前预测的时间 desiredVelocity = (transform.position - (target.transform.position+target.GetComponent<Vehicle>().velocity*lookaheadTime)).normalized * maxSpeed; return (desiredVelocity - m_vehicle.velocity); } }
六、随机徘徊
很多时候,我们需要让游戏中的角色随机移动,比如说士兵的巡逻。我们希望这种随机移动看上去是真实的,而不是一直循环某个路径。
利用操控行为来实现随机派啊坏有多种不同的方法,最简单的方式是利用前面提到的靠近行为。在场景中随机放置目标,让角色靠近目标,每隔一定时间就随机改变目标的位置。这个方法很简单,但缺点也很明显,比如角色可能会突然掉头,因为目标可能移动到了角色的后面。
解决这个问题的原理与内燃机的气缸曲轴转动相似。在角色(气缸)通过连杆连接到曲轴上,目标被限定到曲轴圆周上,移向目标。为了看得更随机,每帧给目标附加一个随机的位移。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SteeringForWander : Steering { public float wanderRadius; //徘徊半径 public float wanderDistance; //徘徊距离 public float wanderJitter; //每秒加到目标的随即位移的最大值 public bool isPlanar; private Vector3 desiredVelocity;//预期速度 private Vehicle m_vehicle;//获得被操控的AI角色 private float maxSpeed; private Vector3 circleTarget; private Vector3 wanderTarget; void Start() { m_vehicle = GetComponent<Vehicle>(); maxSpeed = m_vehicle.maxSpeed; isPlanar = m_vehicle.isPlanar; circleTarget = new Vector3(wanderRadius * 0.707f, 0, wanderRadius * 0.707f); //选取与安全上的一个点作为初始点 } public override Vector3 Force() { Vector3 randomDisplacement = new Vector3((Random.value - 0.5f) * 2 * wanderJitter, (Random.value - 0.5f) * 2 * wanderJitter, (Random.value - 0.5f) * 2 * wanderJitter); if (isPlanar) randomDisplacement.y = 0; circleTarget+=randomDisplacement;//将随机位移加到初始点上 circleTarget = wanderRadius * circleTarget.normalized;//由于新位置很可能不在圆周上,因此需要投影到圆周上 wanderTarget = m_vehicle.velocity.normalized * wanderDistance + circleTarget + transform.position;//之前计算出的值是相对于AI的,需要转换为世界坐标 desiredVelocity = (wanderTarget - transform.position).normalized * maxSpeed; return (desiredVelocity - m_vehicle.velocity); } }
七、路径跟随
就像赛道上的赛车需要导航一样,路径跟随会产生一个操控力,使AI角色沿着由事先设置的轨迹构成路径的一系列路点移动。
最简单的方式是将当前路点设置为路点列表中的第一个路点,用靠近行为来靠近这个路点,至非常接近这个点;然后靠近列表中的下一个路点,一直到最后一个路点。
在实现这一功能时,需要设置一个"路点半径"参数,这个参数的设置会引起路径形状的变化
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SteeringFollowPath : Steering { public GameObject[] waypoints = new GameObject[4];//由节点表示的路径 private Transform target; private int currentNode; private float arriveDistance; private float sqrArriveDistacne; private int numberOfNodes; private Vector3 force; private Vector3 desiredVelocity;//预期速度 private Vehicle m_vehicle;//获得被操控的AI角色 private float maxSpeed; private bool isPlanar; public float slowDownDistacne; void Start() { numberOfNodes = waypoints.Length; m_vehicle = GetComponent<Vehicle>(); maxSpeed = m_vehicle.maxSpeed; isPlanar = m_vehicle.isPlanar; currentNode = 0; target = waypoints[currentNode].transform; arriveDistance = 1.0f; sqrArriveDistacne = arriveDistance * arriveDistance; } public override Vector3 Force() { force = new Vector3(0, 0, 0); Vector3 dist = target.position - transform.position; if (isPlanar) dist.y = 0; if(currentNode==numberOfNodes-1) { if(dist.magnitude>slowDownDistacne) { desiredVelocity=dist.normalized*maxSpeed; force=desiredVelocity-m_vehicle.velocity; } else { desiredVelocity = dist - m_vehicle.velocity; force = desiredVelocity - m_vehicle.velocity; } } else { if(dist.sqrMagnitude<sqrArriveDistacne) { currentNode++; target = waypoints[currentNode].transform; } desiredVelocity= dist.normalized * maxSpeed; force = desiredVelocity - m_vehicle.velocity; } return force; } }
八、避开障碍
避开障碍是指操控AI角色避开路上的障碍物,例如在路径上有一颗树,当角色距离树比较近时,就会产生一个"排斥力",使AI角色不至于撞上树。当有好几棵树时,至产生躲避最近的树的操控力,这样,AI角色就会一个一个地躲开这些树。
在这个算法中,首先需要发现障碍物。AI角色唯一需要担心的就是挡在其路线前方的那些物体。算法的分析步骤如下:
1、用角色前进的速度生成一个向亮ahead
ahead=position+normalize(velocity)*MAX_SEE_AHEAD。ahead的长度决定了AI能看到的距离
2、每个障碍物都用一个几何形状表示,这里采用包围球来标识场景中的每个障碍。
一种可能的方法是检测ahead向量与障碍物的包围球是否相交。这里采用简化的方法。
需要一个向量ahead2,ahead2=ahead*0.5
3、接下来进行碰撞检测。只需要比较向量的终点与球心的距离d是否小于球的半径。如果ahead与ahead2中的一个向量在球内,那么说明障碍物在前方。如果监测到了多个障碍物,那么选择最近的那个。
4、计算操控力
avoidance_force=ahead-obstacle_center
avoidance_force=normalize(avoidance_force)*MAX_AVOID_FORCE
采用这种方法的缺点是,当AI角色接近障碍而操控力正在使其原理的时候,即使AI正在旋转,也可能会检测到碰撞。一种改进方法是根据AI角色的当前速度调整ahead向量,计算方法如下:
Dynamic_length=length(velocity)/MAX_VELOCITY
ahead=position+normalize(velocity)*dynamic_length
这时,dynamic_length的范围是0~1,当全速移动时,值是1
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SteeringForCollisionAvoidance :Steering { public bool isPlanar; private Vector3 force; private Vector3 desiredVelocity;//Ԥ���ٶ� private Vehicle m_vehicle;//��ñ��ٿص�AI��ɫ private float maxSpeed; private float maxForce; public float avoidanceForce; public float MAX_SEE_AHEAD = 2.0f;//�ܼ��������� private GameObject[] allColliders; void Start() { m_vehicle = GetComponent<Vehicle>(); maxSpeed = m_vehicle.maxSpeed; isPlanar = m_vehicle.isPlanar; maxForce = m_vehicle.maxForce; if(avoidanceForce>maxForce) avoidanceForce = maxForce; allColliders = GameObject.FindGameObjectsWithTag("obstacle");//�洢TagΪobstacle��������Ϊ��ײ�� } public override Vector3 Force() { RaycastHit hit; Vector3 force = new Vector3(0, 0, 0); Vector3 velocity = m_vehicle.velocity; Vector3 normalizedVelocity = velocity.normalized; Debug.DrawLine(transform.position, transform.position + normalizedVelocity * MAX_SEE_AHEAD * (velocity.magnitude / maxSpeed));//����һ�����ߣ���Ҫ���������������ཻ����ײ�� if (Physics.Raycast(transform.position, normalizedVelocity, out hit, MAX_SEE_AHEAD * velocity.magnitude / maxSpeed)) { Vector3 ahead = transform.position + normalizedVelocity * MAX_SEE_AHEAD * (velocity.magnitude / maxSpeed); //���������ij����ײ���ཻ����ʾ������ײ force=ahead-hit.collider.transform.position;//���������ײ����IJٿ��� force *= avoidanceForce; if (isPlanar) force.y = 0; foreach(GameObject c in allColliders)//�ı���ײ�����ɫ { if (hit.collider.gameObject == c) { c.GetComponent<Renderer>().material.color = Color.black; } else c.GetComponent<Renderer>().material.color = Color.white; } } else//���ǰ��û�м���ײ�壬��������ײ��ı���ɫ { foreach (GameObject c in allColliders) { c.GetComponent<Renderer>().material.color = Color.white; } } return force; } }