大家好,我是七七,今天来给大家介绍的是Unity中用操控行为实现的跟随领队行为。
看本文若是想了解和实现,只看本文即可,若是想彻底弄透,建议从七七的游戏AI专栏开始看。
废话不多说,先上视频:
跟随队长
我们的目标是让后面的人跟着领头的人,并遵循一些规则
对于领头的人:
- 随机地移动
- 检测前方是否有人
对于跟随的人;
- 跟的不要太近
- 人与人直接不要拥挤
- 如果发现挡道领头人路了,赶紧让开
我们只需要实现这些规则,就可以得到理想的效果,这与神经网络的思想类似,下面我们就来实现这些规则。
这个脚本是挂载领头人身上的,目的是在前方LEADER_BEHIND_DIST处画一个圆球,若是说前方有人,则让这些人避开。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DrawGizmos : MonoBehaviour
{
public float evadeDistance;
public Vector3 center;
private Vehicle vehicleScript;
private float LEADER_BEHIND_DIST;
void Start()
{
vehicleScript = GetComponent();
LEADER_BEHIND_DIST = 2.0f;
}
void Update()
{
center = transform.position + vehicleScript.velocity.normalized * LEADER_BEHIND_DIST;
}
void OnDrawGizoms()
{
Gizmos.DrawWireSphere(center, evadeDistance);
}
}
这个脚本是挂在一个空物体上的,目的是生成多个跟随者
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateBotsForFollowLeader : MonoBehaviour
{
public GameObject botPrefab;
public GameObject leader;
public int botCount;
public float minX = -5f;
public float maxX = 5.0f;
public float minZ = -5.0f;
public float maxZ = 5.0f;
public float Yvalue = 1.026003f;
void Start()
{
Vector3 spawnPosition;
GameObject bot;
for(int i = 0; i < botCount; i++)
{
spawnPosition = new Vector3(Random.Range(minX, maxX), Yvalue, Random.Range(minZ, maxZ));//随机产生一个生成位置
bot = Instantiate(botPrefab, spawnPosition, Quaternion.identity) as GameObject;
bot.GetComponent().leader = leader;
bot.GetComponent().target = leader;
bot.GetComponent().enabled = false;
bot.GetComponent().leader = leader;
}
}
}
这个脚本是挂在跟随者身上的,是为了确定跟随者的路径目标点,即领导人身后LEADER_BEHIND_DIST处
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(SteeringForArrive))]
public class SteeringForLeaderFollowing : Steering
{
public Vector3 target;
private Vector3 desiredVelocity;//预期速度
private Vehicle m_vehicle;//获得被操控的AI角色
private float maxSpeed;
private bool isPlanar;
public GameObject leader;
private Vehicle leaderController;
private Vector3 leaderVelocity;
private float LEADER_BEHIND_DIST=2.0f;
private SteeringForArrive arriveScript;
private Vector3 randomOffset;
void Start()
{
m_vehicle = GetComponent();
maxSpeed = m_vehicle.maxSpeed;
isPlanar = m_vehicle.isPlanar;
leaderController=leader.GetComponent();
arriveScript= GetComponent();//为抵达行为指定目标点
arriveScript.target = new GameObject("arriveTarget");
arriveScript.target.transform.position = leader.transform.position;
}
public override Vector3 Force()
{
leaderVelocity = leaderController.velocity;
target=leader.transform.position+LEADER_BEHIND_DIST*(-leaderVelocity).normalized;//计算目标点
arriveScript.target.transform.position = target;
return new Vector3(0, 0, 0);
}
}
这个脚本是挂在跟随者身上的,目的是为了避免跟随者挡住领导人的路
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EvadeController : MonoBehaviour
{
public GameObject leader;
private Vehicle leaderLocomotion;
private Vehicle m_vehicle;
private bool isPlanar;
private Vector3 leaderAhead;
private float LEADER_BEHIND_DIST;
private Vector3 dist;
public float evadeDistance;
private float sqrEvadeDistance;
private SteeringForEvade evadeScript;
void Start()
{
leaderLocomotion = leader.GetComponent();
evadeScript= GetComponent();
m_vehicle= GetComponent();
isPlanar=m_vehicle.isPlanar;
LEADER_BEHIND_DIST = 2.0f;
sqrEvadeDistance=sqrEvadeDistance*sqrEvadeDistance;
}
void Update()
{
leaderAhead=leader.transform.position+leaderLocomotion.velocity.normalized*LEADER_BEHIND_DIST; //计算领队前方的一个点
dist = transform.position - leaderAhead;
if (isPlanar)
{
dist.y = 0;
}
if(dist.sqrMagnitude < sqrEvadeDistance)
{
evadeScript.enabled = true;
Debug.DrawLine(transform.position, leader.transform.position);
}
else
{
evadeScript.enabled = false;
}
}
}
有3个基类
UnityAI——操控行为编程的主要基类-CSDN博客
创建一个场景,一个Plane
创建一个Cube作为领队,起名为Leader,为其挂上三个脚本,如下所示:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AILocomotion : Vehicle
{
private CharacterController controller; //AI�Ľ�ɫ������
private Rigidbody theRigidbody;
private Vector3 moveDistance;//AI��ɫÿ�ε��ƶ�����
void Start()
{
controller = GetComponent();
theRigidbody = GetComponent();
moveDistance = new Vector3(0, 0, 0);
base.Start();//���û����start��������������ij�ʼ��
}
//������ز�����FixedUpdate�и���
void FixedUpdate()
{
velocity += acceleration * Time.fixedDeltaTime;//�����ٶ�
if (velocity.sqrMagnitude > sqrMaxSpeed) //��������ٶ�
velocity = velocity.normalized * maxSpeed;
moveDistance = velocity * Time.fixedDeltaTime;
if (isPlanar)
{
velocity.y = 0;
moveDistance.y = 0;
}
if (controller != null)//����Ѿ�ΪAI��ɫ���ӽ�ɫ����������ô���ý�ɫ������ʹ���ƶ�
controller.SimpleMove(velocity);
//�����ɫ��û��ɫ��������ҲûRigidbody
//����Rigidbody����Ҫ�ɶ���ѧ�ķ�ʽ�������ƶ�
else if (theRigidbody == null || !theRigidbody.isKinematic)
transform.position += moveDistance;
else //��Rigidbody���ƽ�ɫ���˶�
theRigidbody.MovePosition(theRigidbody.position+moveDistance);
if(velocity.sqrMagnitude>0.00001)//���³�������ٶȴ���һ����ֵ��Ϊ�˷�ֹ������
{
Vector3 newForward = Vector3.Slerp(transform.forward, velocity, damping * Time.deltaTime);
if(isPlanar)
newForward.y = 0;
transform.forward = newForward;
}
//�������߶���
gameObject.GetComponent().Play("walk");
}
}
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();
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);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DrawGizmos : MonoBehaviour
{
public float evadeDistance;
public Vector3 center;
private Vehicle vehicleScript;
private float LEADER_BEHIND_DIST;
void Start()
{
vehicleScript = GetComponent();
LEADER_BEHIND_DIST = 2.0f;
}
void Update()
{
center = transform.position + vehicleScript.velocity.normalized * LEADER_BEHIND_DIST;
}
void OnDrawGizoms()
{
Gizmos.DrawWireSphere(center, evadeDistance);
}
}
创建一个空物体,起名为follersGenerator,用于生成跟随者,并添加脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateBotsForFollowLeader : MonoBehaviour
{
public GameObject botPrefab;
public GameObject leader;
public int botCount;
public float minX = -5f;
public float maxX = 5.0f;
public float minZ = -5.0f;
public float maxZ = 5.0f;
public float Yvalue = 1.026003f;
void Start()
{
Vector3 spawnPosition;
GameObject bot;
for(int i = 0; i < botCount; i++)
{
spawnPosition = new Vector3(Random.Range(minX, maxX), Yvalue, Random.Range(minZ, maxZ));//随机产生一个生成位置
bot = Instantiate(botPrefab, spawnPosition, Quaternion.identity) as GameObject;
bot.GetComponent().leader = leader;
bot.GetComponent().target = leader;
bot.GetComponent().enabled = false;
bot.GetComponent().leader = leader;
}
}
}
创建一个方块预设,作为跟随者,挂上下列脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AILocomotion : Vehicle
{
private CharacterController controller; //AI�Ľ�ɫ������
private Rigidbody theRigidbody;
private Vector3 moveDistance;//AI��ɫÿ�ε��ƶ�����
void Start()
{
controller = GetComponent();
theRigidbody = GetComponent();
moveDistance = new Vector3(0, 0, 0);
base.Start();//���û����start��������������ij�ʼ��
}
//������ز�����FixedUpdate�и���
void FixedUpdate()
{
velocity += acceleration * Time.fixedDeltaTime;//�����ٶ�
if (velocity.sqrMagnitude > sqrMaxSpeed) //��������ٶ�
velocity = velocity.normalized * maxSpeed;
moveDistance = velocity * Time.fixedDeltaTime;
if (isPlanar)
{
velocity.y = 0;
moveDistance.y = 0;
}
if (controller != null)//����Ѿ�ΪAI��ɫ���ӽ�ɫ����������ô���ý�ɫ������ʹ���ƶ�
controller.SimpleMove(velocity);
//�����ɫ��û��ɫ��������ҲûRigidbody
//����Rigidbody����Ҫ�ɶ���ѧ�ķ�ʽ�������ƶ�
else if (theRigidbody == null || !theRigidbody.isKinematic)
transform.position += moveDistance;
else //��Rigidbody���ƽ�ɫ���˶�
theRigidbody.MovePosition(theRigidbody.position+moveDistance);
if(velocity.sqrMagnitude>0.00001)//���³�������ٶȴ���һ����ֵ��Ϊ�˷�ֹ������
{
Vector3 newForward = Vector3.Slerp(transform.forward, velocity, damping * Time.deltaTime);
if(isPlanar)
newForward.y = 0;
transform.forward = newForward;
}
//�������߶���
gameObject.GetComponent().Play("walk");
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SteeringForArrive : Steering
{
public bool isPlanar = true;
public float arrivalDistance = 0.3f;
public float characterRadius = 1.2f;
public float slowDownDistance;
public GameObject target;
private Vector3 desiredVelocity;//预期速度
private Vehicle m_vehicle;//获得被操控的AI角色
private float maxSpeed;
void Start()
{
m_vehicle = GetComponent();
maxSpeed = m_vehicle.maxSpeed;
isPlanar = m_vehicle.isPlanar;
}
public override Vector3 Force()
{
Vector3 toTarget = target.transform.position - transform.position;
Vector3 desiredVelocity;
Vector3 returnForce;
if (isPlanar)
toTarget.y = 0;
float distance = toTarget.magnitude;
if (distance > slowDownDistance)
{
desiredVelocity = toTarget.normalized * maxSpeed;
returnForce = desiredVelocity - m_vehicle.velocity;
}
else
{
desiredVelocity = toTarget - m_vehicle.velocity;
returnForce = desiredVelocity - m_vehicle.velocity;
}
return returnForce;
}
void OnDrawGizmos()
{
Gizmos.DrawWireSphere(target.transform.position, slowDownDistance);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(SteeringForArrive))]
public class SteeringForLeaderFollowing : Steering
{
public Vector3 target;
private Vector3 desiredVelocity;//预期速度
private Vehicle m_vehicle;//获得被操控的AI角色
private float maxSpeed;
private bool isPlanar;
public GameObject leader;
private Vehicle leaderController;
private Vector3 leaderVelocity;
private float LEADER_BEHIND_DIST=2.0f;
private SteeringForArrive arriveScript;
private Vector3 randomOffset;
void Start()
{
m_vehicle = GetComponent();
maxSpeed = m_vehicle.maxSpeed;
isPlanar = m_vehicle.isPlanar;
leaderController=leader.GetComponent();
arriveScript= GetComponent();//为抵达行为指定目标点
arriveScript.target = new GameObject("arriveTarget");
arriveScript.target.transform.position = leader.transform.position;
}
public override Vector3 Force()
{
leaderVelocity = leaderController.velocity;
target=leader.transform.position+LEADER_BEHIND_DIST*(-leaderVelocity).normalized;//计算目标点
arriveScript.target.transform.position = target;
return new Vector3(0, 0, 0);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Radar : MonoBehaviour
{
private Collider[] colliders;//碰撞体的组数
private float timer = 0;//计时器
public List neighbors;
public float checkInterval = 0.3f;//设置检测的时间间隔
public float detectRadius = 10f;//设置邻域半径
public LayerMask layersChecked;//设置检测哪一层的游戏对象
void Start()
{
neighbors = new List();
}
void Update()
{
timer += Time.deltaTime;
if(timer > checkInterval)
{
neighbors.Clear();
colliders = Physics.OverlapSphere(transform.position, detectRadius, layersChecked);//查找当前AI角色邻域内的所有碰撞体
for(int i = 0; i < colliders.Length; i++)//对于每个检测到的碰撞体,获取Vehicle组件,并且加入邻居列表钟
{
if (colliders[i].GetComponent())
neighbors.Add(colliders[i].gameObject);
}
timer = 0;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SteeringForSeparation : Steering
{
public float comforDistance = 1;//可接受的距离
public float multiplierInsideComfortDistance = 2;//当AI角色与邻居距离过近时的惩罚因子
public override Vector3 Force()
{
Vector3 steeringForce = new Vector3(0, 0, 0);
foreach(GameObject s in GetComponent().neighbors)//遍历这个AI角色的邻居列表中的每个邻居
{
if ((s != null) && (s != this.gameObject))
{
Vector3 toNeighbor = transform.position - s.transform.position;//计算当前AI角色与邻居s之间的距离
float length=toNeighbor.magnitude;
steeringForce += toNeighbor.normalized / length;//计算这个邻居引起的操控力
if (length < comforDistance)
steeringForce *= multiplierInsideComfortDistance;
}
}
return steeringForce;
}
}
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();
maxSpeed = m_vehicle.maxSpeed;
}
public override Vector3 Force()
{
Vector3 toTarget = target.transform.position - transform.position;
float lookaheadTime = toTarget.magnitude / (maxSpeed + target.GetComponent().velocity.magnitude);//向前预测的时间
desiredVelocity = (transform.position - (target.transform.position+target.GetComponent().velocity*lookaheadTime)).normalized * maxSpeed;
return (desiredVelocity - m_vehicle.velocity);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EvadeController : MonoBehaviour
{
public GameObject leader;
private Vehicle leaderLocomotion;
private Vehicle m_vehicle;
private bool isPlanar;
private Vector3 leaderAhead;
private float LEADER_BEHIND_DIST;
private Vector3 dist;
public float evadeDistance;
private float sqrEvadeDistance;
private SteeringForEvade evadeScript;
void Start()
{
leaderLocomotion = leader.GetComponent();
evadeScript= GetComponent();
m_vehicle= GetComponent();
isPlanar=m_vehicle.isPlanar;
LEADER_BEHIND_DIST = 2.0f;
sqrEvadeDistance=sqrEvadeDistance*sqrEvadeDistance;
}
void Update()
{
leaderAhead=leader.transform.position+leaderLocomotion.velocity.normalized*LEADER_BEHIND_DIST; //计算领队前方的一个点
dist = transform.position - leaderAhead;
if (isPlanar)
{
dist.y = 0;
}
if(dist.sqrMagnitude < sqrEvadeDistance)
{
evadeScript.enabled = true;
Debug.DrawLine(transform.position, leader.transform.position);
}
else
{
evadeScript.enabled = false;
}
}
}
最后再给各个角色装上刚体,设置好Leader等参数就可以了。
很多行为我们都可以通过设定规则来实现,可能乍一看行为很难摸索,但慢慢分析出其中的规则并逐一实现后,问题往往就会被解决