大家好,我是七七,今天来给大家介绍的是Unity中用操控行为实现的跟随领队行为。
看本文若是想了解和实现,只看本文即可,若是想彻底弄透,建议从七七的游戏AI专栏开始看。
废话不多说,先上视频:
跟随队长
我们的目标是让后面的人跟着领头的人,并遵循一些规则
对于领头的人:
- 随机地移动
- 检测前方是否有人
对于跟随的人;
- 跟的不要太近
- 人与人直接不要拥挤
- 如果发现挡道领头人路了,赶紧让开
我们只需要实现这些规则,就可以得到理想的效果,这与神经网络的思想类似,下面我们就来实现这些规则。
规则1
这个脚本是挂载领头人身上的,目的是在前方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<Vehicle>(); LEADER_BEHIND_DIST = 2.0f; } void Update() { center = transform.position + vehicleScript.velocity.normalized * LEADER_BEHIND_DIST; } void OnDrawGizoms() { Gizmos.DrawWireSphere(center, evadeDistance); } }
规则2
这个脚本是挂在一个空物体上的,目的是生成多个跟随者
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<SteeringForLeaderFollowing>().leader = leader; bot.GetComponent<SteeringForEvade>().target = leader; bot.GetComponent<SteeringForEvade>().enabled = false; bot.GetComponent<EvadeController>().leader = leader; } } }
规则3
这个脚本是挂在跟随者身上的,是为了确定跟随者的路径目标点,即领导人身后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<Vehicle>(); maxSpeed = m_vehicle.maxSpeed; isPlanar = m_vehicle.isPlanar; leaderController=leader.GetComponent<Vehicle>(); arriveScript= GetComponent<SteeringForArrive>();//为抵达行为指定目标点 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); } }
规则4
这个脚本是挂在跟随者身上的,目的是为了避免跟随者挡住领导人的路
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<Vehicle>(); evadeScript= GetComponent<SteeringForEvade>(); m_vehicle= GetComponent<Vehicle>(); 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个基类
第一步
创建一个场景,一个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<CharacterController>(); theRigidbody = GetComponent<Rigidbody>(); 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<Animation>().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<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); } }
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<Vehicle>(); LEADER_BEHIND_DIST = 2.0f; } void Update() { center = transform.position + vehicleScript.velocity.normalized * LEADER_BEHIND_DIST; } void OnDrawGizoms() { Gizmos.DrawWireSphere(center, evadeDistance); } }