【Unity3D 学习笔记】实现AI角色的自主移动--操控行为
参考书籍《Unity3D人工智能编程精粹》(主编:王洪源)
实验Unity版本--2019.3.2f1
其中参考了GitHub上一位大神的工程,从中得到了些许帮助,链接:https://github.com/GITHUB243884919/Unity3D_AI_2
卑微手动参考目录:
- Unity3D操控行为编程的主要基类
- 个体AI角色的操控行为
1. Unity3D操控行为编程的主要基类
- 将AI角色抽象成质点----Vehicle类:记录角色属性的类,将各种角色抽象为质点,关注角色自身的位置、质量、速度、能够到达的最大速度等信息,是一个角色类。
- 控制AI角色移动--AILocomotion类:控制角色移动的类,计算角色每次移动距离、控制角色播放动画等。
- 各种操控行为的基类---Steering类:角色行为类的基类,后面的一些AI行为类都由此派生。
由于Vehicle中使用到了Steering类,因此先编写Steering类。
Steering类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class Steering : MonoBehaviour
{
//表示每个操控力的权重
public float weight = 1;
//计算控制力的方法 由子类实现
private void Start()
{
}
public virtual Vector3 Force()
{
return new Vector3(0, 0, 0);
}
}
Vehicle类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
*将不同的角色抽象为质点,关注其属性--最大速度、能给角色的最大力、质量、速度等
*/
public class Vehicle : MonoBehaviour
{
//这个AI角色包含的操控行为列表;
private Steering[] steerings;
//设置这个AI角色能等到的最大速度
public float maxSpeed = 10;
//设置能涉及啊到这个AI角色的力的最大值
public float maxForce = 100;
//最大速度平方
protected float sqrMaxSpeed;
//角色质量
public float mass = 1;
//角色速度
public Vector3 velocity;
//控制转向速度
public float damping = 0.9f;
//控制电脑刷新间隔
public float computeInterval = 0.2f;
//是否在二维平面上,如果是,计算两个GameObject得距离时,忽略Y值的不同
public bool isPlanar = true;
//计算得到的控制力;
private Vector3 steeringForce;
//AI角色的加速度
protected Vector3 acceleration;
//计时器
private float timer;
protected void Start()
{
steeringForce = new Vector3(0, 0, 0);
sqrMaxSpeed = maxSpeed * maxSpeed;
timer = 0;
//获得AI角色所包含的操控行为列表;
steerings = GetComponents<Steering>();
}
void Update()
{
timer += Time.deltaTime;
steeringForce = new Vector3(0, 0, 0);
//如果距离上次计算操控力的时间大于设定的时间间隔computeInterval;
//再次计算操控力
if (timer > computeInterval)
{
//将操控行为列表中的所有操控行为为对应的操控力进行带权重的求和;
foreach (Steering s in steerings)
{
if (s.enabled)
steeringForce += s.Force() * s.weight;
}
//使操控力不大于MaxForce;
steeringForce = Vector3.ClampMagnitude(steeringForce, maxForce);
//力除以质量,求出加速度;
acceleration = steeringForce / mass;
//重新计时
timer = 0;
}
}
}
AILocomotion类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AILocomotion : Vehicle
{
//角色控制器
private CharacterController controller;
//角色刚体组件
private Rigidbody theRigidbody;
//角色每次的移动距离
private Vector3 moveDistance;
//角色动画
private Animator m_animator = null;
void Start()
{
controller = GetComponent<CharacterController>();
theRigidbody = GetComponent<Rigidbody>();
moveDistance = new Vector3(0, 0, 0);
m_animator = GetComponent<Animator>();
//调用基类的start函数,进行初始化
base.Start();
}
//物理相关操作在FixedUpdate()中更新,而不是Update()
void FixedUpdate()
{
//计算速度
velocity += acceleration * Time.fixedDeltaTime;
//限制速度,要低于最大速度
if (velocity.sqrMagnitude > sqrMaxSpeed)
velocity = velocity.normalized * maxSpeed;
//计算AI角色的移动距离
moveDistance = velocity * Time.fixedDeltaTime;
//如果要求AI角色在平面上移动,那么将y置0
if (isPlanar)
{
velocity.y = 0;
moveDistance.y = 0;
}
//如果AI角色控制器添加成功,那么利用控制器使其移动
if (controller != null)
controller.SimpleMove(velocity);
//如果AI角色没有角色控制器,也没有Rigidbody
//或有Rigidbody,但要用由动力学的方式控制它的运动,即勾选isKinematic
else if (theRigidbody == null || theRigidbody.isKinematic)
transform.position += moveDistance;
else
theRigidbody.MovePosition(theRigidbody.position + moveDistance);
//****测试出现问题--过度旋转
//Damping转向速度默认0.9过慢 导致看起来过度旋转
if (velocity.sqrMagnitude > 0.00001)
{
//通过当前朝向与速度方向的插值,计算新的朝向
Vector3 newForward = Vector3.Slerp(transform.forward, velocity, damping * Time.deltaTime);
//将y设置为0;
if (isPlanar)
newForward.y = 0;
//将向前方向设置为新的朝向
transform.forward = newForward;
}
//动画设置(如果有)
m_animator.SetBool("isRun", true);
if (velocity == Vector3.zero)
{
m_animator.SetBool("isRun", false);
}
}
}
2.个体AI角色的操控行为
2.1 靠近
设定一个目标,根据角色当前的速度向量,返回一个操控AI角色到达该目标的“操控力”,使AI角色自动向该位置移动。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SteeringForSeek : Steering
{
//寻找的目标
public GameObject target;
//预期速度
private Vector3 desiredVelocity;
//获取被操控的AI角色,以便查询这个AI角色的最大速度信息;
private Vehicle m_vehicle;
//最大速度
private float maxSpeed;
//是否仅在二维平面上运动
private bool isPlanar;
// Use this for initialization
void Start()
{
//获取组件以及读取数据
m_vehicle = GetComponent<Vehicle>();
maxSpeed = m_vehicle.maxSpeed;
isPlanar = m_vehicle.isPlanar;
}
//计算操控向量(操控力)
public override Vector3 Force()
{
//计算预期速度
desiredVelocity = (target.transform.position - transform.position).normalized * maxSpeed;
if (isPlanar)
desiredVelocity.y = 0;
//通过向量相减(预期速度与当前速度的差),得出驱使物体达到目标的操控力
return (desiredVelocity - m_vehicle.velocity);
}
}
场景:胶囊体为AI角色,小正方体为目标
脚本:胶囊体添加脚本AILocomotion.cs、CharacterController.cs、SteeringForSeek.cs。
设置:在胶囊体的SteeringForSeek.cs下设置靠近目标--小正方体
结果:胶囊体靠近小正方体,由于速度不能骤减,胶囊体在目标处来回移动。
靠近
2.2 离开
AI角色离目标一定距离,产生离开的力
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SteeringForFlee : Steering
{
public GameObject target; //远离目标
public Vector3 desiredVelocity; //期望速度
public Vehicle m_vehicle; //操控力作用的物体
public float maxSpeed; //作用最大速度
public float fearDistance = 20; //逃跑距离
private void Start()
{
m_vehicle = GetComponent<Vehicle>();
maxSpeed = m_vehicle.maxSpeed;
}
public override Vector3 Force()
{
Vector3 tmpPos = new Vector3(transform.position.x, 0, transform.position.z);
Vector3 tmpTargetPos = new Vector3(target.transform.position.x, 0, target.transform.position.z);
//如果AI与目标距离大于逃跑距离,那么操控力置0,返回0向量
if (Vector3.Distance(tmpPos, tmpTargetPos) > fearDistance){
m_vehicle.velocity = Vector3.zero;
return new Vector3(0, 0, 0);
}else{
//如果小于逃跑距离,那么计算逃跑的操控力
desiredVelocity = (transform.position - target.transform.position).normalized * maxSpeed;
return (desiredVelocity - m_vehicle.velocity);
}
}
}
场景:胶囊体为AI角色,小正方体为目标
脚本:胶囊体添加脚本AILocomotion.cs、CharacterController.cs、SteeringForFlee.cs。
设置:在胶囊体的SteeringForFlee.cs下设置靠近目标--小正方体,以及产生离开力时与目标的距离
结果:胶囊远离目标
离开
2.3 抵达
AI角色减速停到目标位置
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SteeringForArrive : Steering
{
public GameObject target;
private Vehicle m_vehicle;
private Vector3 desiredVelocity;
private float maxSpeed;
public bool isPlanar = true;
public float arrivalDistance = 0.3f;
public float characterRadius = 1.2f;
public float slowDownDistance;
private void Start()
{
m_vehicle = GetComponent<Vehicle>();
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;
}
private void OnDrawGizmos()
{
//显示减速范围
Gizmos.DrawWireSphere(target.transform.position, slowDownDistance);
}
}
场景:胶囊体为AI角色,小正方体为目标
脚本:胶囊体添加脚本CharacterController.cs、AILocomotion.cs、SteeringForArrive.cs。
设置:在胶囊体的SteeringForArrive.cs下设置抵达目标--小正方体,抵达目标的距离Arrival Distance,减速的距离SlowDownDistance
结果:胶囊体进入减速区后开始减速,最终停止在目标位置
抵达-场景
抵达-结果
2.4 追逐
追逐者不是追向被追逐者,而是预测被追逐者未来的位置,而追向这个未来的位置
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SteeringForPursuit : Steering
{
public GameObject target;
private Vector3 desiredVelocity;
private Vehicle m_vehicle;
private float maxSpeed;
// Use this for initialization
void Start()
{
m_vehicle = GetComponent<Vehicle>();
maxSpeed = m_vehicle.maxSpeed;
}
public override Vector3 Force()
{
Vector3 toTarget = target.transform.position - transform.position;
float relativeDirection = Vector3.Dot(transform.forward, target.transform.forward);
Debug.Log("relativeDirection:" + relativeDirection);
//物体与目标朝向所成夹角的度数的范围的
//以及物体朝向与 物体与目标距离所成向量的夹角的度数范围
//来作为物体追逐目标时候是采取预测追逐还是靠近追逐的判断条件
if ((Vector3.Dot(transform.forward, toTarget) > 0) && relativeDirection < -0.95f)
{
desiredVelocity = (target.transform.position - transform.position).normalized * maxSpeed;
return (desiredVelocity - m_vehicle.velocity);
}
//计算预测时间
float lookaheadTime = toTarget.magnitude / (maxSpeed + target.GetComponent<Vehicle>().velocity.magnitude);
//计算预测速度
desiredVelocity = (target.transform.position + target.GetComponent<Vehicle>().velocity * lookaheadTime - transform.position).normalized * maxSpeed;
//如果加了以下的if语句,那么玩家永远的目标后面,追不上
/*
if (lookaheadTime > 1.0f)
{
lookaheadTime = 1.0f;
}
*/
//返回操控力向量
return (desiredVelocity - m_vehicle.velocity);
}
}
场景:下面的为追逐者,上方为被追逐者,小方块为被追逐者的目标。
脚本:追逐者添加AILocomotion.cs、SteeringForPursuit.cs。被追逐者添加AILocomotion.cs、SteeringForSeek.cs。
设置:追逐者在SteeringForPursuit.cs设置追逐目标为被追逐者,被追逐者在SteeringForSeek.cs设置target为小方块。
结果:追逐者向预测的被追逐者的位置追去。
2.5 逃避
逃避行为使AI角色逃避追逐者的追逐
using UnityEngine;
using System.Collections;
public class SteeringForEvade : Steering
{
public GameObject target;
private Vector3 desiredVelocity;
private Vehicle m_vehicle;
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 - GetComponent<Vehicle>().velocity);
}
}
场景:下面的为追逐者,上方为被追逐者,小方块为被追逐者的目标。
脚本:追逐者添加AILocomotion.cs、SteeringForPursuit.cs。被追逐者添加AILocomotion.cs、SteeringForArrive.cs、SteeringForEvade.cs。
设置:追逐者在SteeringForPursuit.cs设置追逐目标为被追逐者,被追逐者在SteeringForArrive.cs设置target为小方块,SteeringForEvade.cs下设置目标为追逐者。
结果:追逐者向预测的被追逐者的位置追去,被追逐者跑向目标,当追逐者接近时逃离追逐者。
逃避行为