当前位置:   article > 正文

UnityAI——个体AI角色的操控行为脚本_unity character.ai

unity character.ai

注:本文用到了前文所用的基类UnityAI——操控行为编程的主要基类-CSDN博客

在一些游戏中,可能会遇到想让AI角色追逐或者避开玩家的情况。

如在飞机模拟游戏中,让导弹跟踪和进攻玩家或玩家的飞行器。这种情况下,可以运用本节介绍的技术。

一、靠近

操控行为中的靠近是指,指定一个目标位置,根据当前的运动速度向量,返回一个操控AI角色到达该目标位置的"操控力",使AI角色自动向该位置移动。

要想让AI角色靠近目标,首先要计算出AI角色在理想情况下到达目标位置的预期速度。该预期速度可看作是从AI角色的当前位置到目标位置的向量。操控向量是预期速度与AI角色当前速度的差,该向量大小随着当前位置的变化而变化,从而形成角色的寻找路径。

由于操控行为是基于力和速度的,速度不会突变,因此,如果角色一直采取靠近行为,那么最终它将会从目标穿过,然后再重新接近目标,如此往复。

当然,如果目标物体包含一个碰撞体,那么,接下来的行为就要由Character Controller决定了。如果不希望出现这种情况,可以采用后面介绍的Arrive行为,也可以另外添加一些处理步骤。

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class SteeringForSeek : Steering
  5. {
  6. public GameObject target;
  7. private Vector3 desiredVelocity;//预期速度
  8. private Vehicle m_vehicle;//获得被操控的AI角色
  9. private float maxSpeed;
  10. private bool isPlanar;
  11. void Start()
  12. {
  13. m_vehicle = GetComponent<Vehicle>();
  14. maxSpeed = m_vehicle.maxSpeed;
  15. isPlanar = m_vehicle.isPlanar;
  16. }
  17. public override Vector3 Force()
  18. {
  19. desiredVelocity = (target.transform.position - transform.position).normalized * maxSpeed;
  20. if (isPlanar)
  21. desiredVelocity.y = 0;
  22. return (desiredVelocity - m_vehicle.velocity);
  23. }
  24. }

二、离开

离开和靠近行为正好相反,它会产生一个操控AI角色离开目标的力,而不是靠近目标的力。它们之间唯一的区别是DeisredVelocity具有相反的方向。

接着,还可以进一步调整,只有当AI角色进入目标周围一定范围内时,才产生离开的力,这样可以模拟出AI角色的有限感知范围

这里采用了Vector3.Distance函数来计算当前位置与目标位置之间的距离。事实上,如果采用Vector3.sqrMagnitude函数,将会得到更快的计算速度,因为省去了计算平方根的时间,这时可以预先计算fearDistance的平方并存储到一个变量中。

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class SteeringForFlee : Steering
  5. {
  6. public GameObject target;
  7. public float fearDistance = 20;
  8. private Vector3 desiredVelocity;//预期速度
  9. private Vehicle m_vehicle;//获得被操控的AI角色
  10. private float maxSpeed;
  11. void Start()
  12. {
  13. m_vehicle = GetComponent<Vehicle>();
  14. maxSpeed = m_vehicle.maxSpeed;
  15. }
  16. public override Vector3 Force()
  17. {
  18. Vector3 temPos = new Vector3(transform.position.x, 0, transform.position.z);
  19. Vector3 temTargetPos = new Vector3(target.transform.position.x, 0, target.transform.position.z);
  20. if (Vector3.Distance(temPos, temTargetPos) > fearDistance)
  21. return new Vector3(0, 0, 0);
  22. desiredVelocity = (transform.position - target.transform.position).normalized * maxSpeed;
  23. return (desiredVelocity-m_vehicle.velocity);
  24. }
  25. }

三、抵达

有时我们希望AI角色能够减速并停到目标位置,避免冲过目标,例如,车辆在接近十字路口时逐渐减速,然后停在路口处,这时就需要用到抵达行为。

在角色距离目标较远时,抵达与靠近行为的状态是一样的,但是接近目标时,不再是全速向目标移动,而代之以使AI角色减速,知道最终恰好停在目标位置。何时开始减速是通过参数进行设置的,这个参数可以看成是停止半径。当角色在停止半径之外时,以最大速度移动;当角色在停止半径之内时,逐渐减小预期速度,直到减小为0.这个参数的设置很关键,它决定了抵达行为的最终效果。

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class SteeringForArrive : Steering
  5. {
  6. public bool isPlanar = true;
  7. public float arrivalDistance = 0.3f;
  8. public float characterRadius = 1.2f;
  9. public float slowDownDistance;
  10. public GameObject target;
  11. private Vector3 desiredVelocity;//预期速度
  12. private Vehicle m_vehicle;//获得被操控的AI角色
  13. private float maxSpeed;
  14. void Start()
  15. {
  16. m_vehicle = GetComponent<Vehicle>();
  17. maxSpeed = m_vehicle.maxSpeed;
  18. isPlanar = m_vehicle.isPlanar;
  19. }
  20. public override Vector3 Force()
  21. {
  22. Vector3 toTarget = target.transform.position - transform.position;
  23. Vector3 desiredVelocity;
  24. Vector3 returnForce;
  25. if (isPlanar)
  26. toTarget.y = 0;
  27. float distance = toTarget.magnitude;
  28. if (distance > slowDownDistance)
  29. {
  30. desiredVelocity = toTarget.normalized * maxSpeed;
  31. returnForce = desiredVelocity - m_vehicle.velocity;
  32. }
  33. else
  34. {
  35. desiredVelocity = toTarget - m_vehicle.velocity;
  36. returnForce = desiredVelocity - m_vehicle.velocity;
  37. }
  38. return returnForce;
  39. }
  40. void OnDrawGizmos()
  41. {
  42. Gizmos.DrawWireSphere(target.transform.position, slowDownDistance);
  43. }
  44. }

四、追逐

追逐行为与靠近行为很相似,只不过目标不再是静止不动,而是另一个可移动的角色。最简单的追逃方式是直接向目标的当前位置靠近,不过这样看上去很不真实。举例来说,众所周知,当动物追猎物时,不是直接向猎物当前的位置奔跑,而是要预判,朝着其未来位置的方向追去,这样才能在最短时间内追上猎物。在AI中,把这种操控行为称为"追逐"。

如何实现这种智能的追逐行为呢?我们可以使用一个简单的预测器,在每一帧重新计算它的值。

假设采用一个线性预测器,有假设在预测时间间隔T时间内角色不会转向,角色经过时间T后的未来位置可以用当前速度乘以T来确定,然后把得到的值加到角色当前位置上,就得到未来位置了。最后,再以预测位置作为目标,应用靠近行为就可以了。

实现追逐行为的一个关键是如何确定T。可以把它设置为一个常数,也可以当追逐者距离目标较远时设为较大的值,而接近目标时设为较小的值。

这里,设定预测时间和追逐者与逃避者之间的距离成正比,与二者的速度成正比。

一些情况下,追逐可能会提前结束。例如:逃避者在前面,几乎面对追逐者,那么追逐者应该直接向逃避者的当前位置移动。二者之间的关系可以通过计算逃避者朝向向量与AI角色朝向向量的点积得到,在下面的代码中,逃避者朝向的反向和AI角色的朝向必须大约在20度范围之内,才可以被认为是面对着的。

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class SteeringForPersuit : Steering
  5. {
  6. public GameObject target;
  7. private Vector3 desiredVelocity;//预期速度
  8. private Vehicle m_vehicle;//获得被操控的AI角色
  9. private float maxSpeed;
  10. void Start()
  11. {
  12. m_vehicle = GetComponent<Vehicle>();
  13. maxSpeed = m_vehicle.maxSpeed;
  14. }
  15. public override Vector3 Force()
  16. {
  17. Vector3 toTarget = target.transform.position - transform.position;
  18. float relativeDirection = Vector3.Dot(transform.forward, target.transform.forward);//计算追逐者的前方与逃避者前方之间的夹角
  19. if (Vector3.Dot(toTarget, transform.forward) > 0 && relativeDirection < -0.95f)//夹角大于0且追逐者基本面对着逃避者
  20. {
  21. desiredVelocity=(target.transform.position - transform.position).normalized*maxSpeed;
  22. return (desiredVelocity - m_vehicle.velocity);
  23. }
  24. float lookaheadTime = toTarget.magnitude / (maxSpeed + target.GetComponent<Vehicle>().velocity.magnitude);//计算预测时间,正比于距离,反比于速度和
  25. desiredVelocity = (target.transform.position + target.GetComponent<Vehicle>().velocity * lookaheadTime - transform.position).normalized * maxSpeed;
  26. return(desiredVelocity - m_vehicle.velocity);
  27. }
  28. }

五、逃避

逃避行为与追逐行为的不同是它试图使AI角色逃离预测位置。

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class SteeringForEvade :Steering
  5. {
  6. public GameObject target;
  7. private Vector3 desiredVelocity;//预期速度
  8. private Vehicle m_vehicle;//获得被操控的AI角色
  9. private float maxSpeed;
  10. void Start()
  11. {
  12. m_vehicle = GetComponent<Vehicle>();
  13. maxSpeed = m_vehicle.maxSpeed;
  14. }
  15. public override Vector3 Force()
  16. {
  17. Vector3 toTarget = target.transform.position - transform.position;
  18. float lookaheadTime = toTarget.magnitude / (maxSpeed + target.GetComponent<Vehicle>().velocity.magnitude);//向前预测的时间
  19. desiredVelocity = (transform.position - (target.transform.position+target.GetComponent<Vehicle>().velocity*lookaheadTime)).normalized * maxSpeed;
  20. return (desiredVelocity - m_vehicle.velocity);
  21. }
  22. }

六、随机徘徊

很多时候,我们需要让游戏中的角色随机移动,比如说士兵的巡逻。我们希望这种随机移动看上去是真实的,而不是一直循环某个路径。

利用操控行为来实现随机派啊坏有多种不同的方法,最简单的方式是利用前面提到的靠近行为。在场景中随机放置目标,让角色靠近目标,每隔一定时间就随机改变目标的位置。这个方法很简单,但缺点也很明显,比如角色可能会突然掉头,因为目标可能移动到了角色的后面。

解决这个问题的原理与内燃机的气缸曲轴转动相似。在角色(气缸)通过连杆连接到曲轴上,目标被限定到曲轴圆周上,移向目标。为了看得更随机,每帧给目标附加一个随机的位移。

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class SteeringForWander : Steering
  5. {
  6. public float wanderRadius; //徘徊半径
  7. public float wanderDistance; //徘徊距离
  8. public float wanderJitter; //每秒加到目标的随即位移的最大值
  9. public bool isPlanar;
  10. private Vector3 desiredVelocity;//预期速度
  11. private Vehicle m_vehicle;//获得被操控的AI角色
  12. private float maxSpeed;
  13. private Vector3 circleTarget;
  14. private Vector3 wanderTarget;
  15. void Start()
  16. {
  17. m_vehicle = GetComponent<Vehicle>();
  18. maxSpeed = m_vehicle.maxSpeed;
  19. isPlanar = m_vehicle.isPlanar;
  20. circleTarget = new Vector3(wanderRadius * 0.707f, 0, wanderRadius * 0.707f); //选取与安全上的一个点作为初始点
  21. }
  22. public override Vector3 Force()
  23. {
  24. Vector3 randomDisplacement = new Vector3((Random.value - 0.5f) * 2 * wanderJitter, (Random.value - 0.5f) * 2 * wanderJitter, (Random.value - 0.5f) * 2 * wanderJitter);
  25. if (isPlanar)
  26. randomDisplacement.y = 0;
  27. circleTarget+=randomDisplacement;//将随机位移加到初始点上
  28. circleTarget = wanderRadius * circleTarget.normalized;//由于新位置很可能不在圆周上,因此需要投影到圆周上
  29. wanderTarget = m_vehicle.velocity.normalized * wanderDistance + circleTarget + transform.position;//之前计算出的值是相对于AI的,需要转换为世界坐标
  30. desiredVelocity = (wanderTarget - transform.position).normalized * maxSpeed;
  31. return (desiredVelocity - m_vehicle.velocity);
  32. }
  33. }

七、路径跟随

就像赛道上的赛车需要导航一样,路径跟随会产生一个操控力,使AI角色沿着由事先设置的轨迹构成路径的一系列路点移动。

最简单的方式是将当前路点设置为路点列表中的第一个路点,用靠近行为来靠近这个路点,至非常接近这个点;然后靠近列表中的下一个路点,一直到最后一个路点。

在实现这一功能时,需要设置一个"路点半径"参数,这个参数的设置会引起路径形状的变化

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class SteeringFollowPath : Steering
  5. {
  6. public GameObject[] waypoints = new GameObject[4];//由节点表示的路径
  7. private Transform target;
  8. private int currentNode;
  9. private float arriveDistance;
  10. private float sqrArriveDistacne;
  11. private int numberOfNodes;
  12. private Vector3 force;
  13. private Vector3 desiredVelocity;//预期速度
  14. private Vehicle m_vehicle;//获得被操控的AI角色
  15. private float maxSpeed;
  16. private bool isPlanar;
  17. public float slowDownDistacne;
  18. void Start()
  19. {
  20. numberOfNodes = waypoints.Length;
  21. m_vehicle = GetComponent<Vehicle>();
  22. maxSpeed = m_vehicle.maxSpeed;
  23. isPlanar = m_vehicle.isPlanar;
  24. currentNode = 0;
  25. target = waypoints[currentNode].transform;
  26. arriveDistance = 1.0f;
  27. sqrArriveDistacne = arriveDistance * arriveDistance;
  28. }
  29. public override Vector3 Force()
  30. {
  31. force = new Vector3(0, 0, 0);
  32. Vector3 dist = target.position - transform.position;
  33. if (isPlanar)
  34. dist.y = 0;
  35. if(currentNode==numberOfNodes-1)
  36. {
  37. if(dist.magnitude>slowDownDistacne)
  38. {
  39. desiredVelocity=dist.normalized*maxSpeed;
  40. force=desiredVelocity-m_vehicle.velocity;
  41. }
  42. else
  43. {
  44. desiredVelocity = dist - m_vehicle.velocity;
  45. force = desiredVelocity - m_vehicle.velocity;
  46. }
  47. }
  48. else
  49. {
  50. if(dist.sqrMagnitude<sqrArriveDistacne)
  51. {
  52. currentNode++;
  53. target = waypoints[currentNode].transform;
  54. }
  55. desiredVelocity= dist.normalized * maxSpeed;
  56. force = desiredVelocity - m_vehicle.velocity;
  57. }
  58. return force;
  59. }
  60. }

八、避开障碍

避开障碍是指操控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

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class SteeringForCollisionAvoidance :Steering
  5. {
  6. public bool isPlanar;
  7. private Vector3 force;
  8. private Vector3 desiredVelocity;//Ԥ���ٶ�
  9. private Vehicle m_vehicle;//��ñ��ٿص�AI��ɫ
  10. private float maxSpeed;
  11. private float maxForce;
  12. public float avoidanceForce;
  13. public float MAX_SEE_AHEAD = 2.0f;//�ܼ���������
  14. private GameObject[] allColliders;
  15. void Start()
  16. {
  17. m_vehicle = GetComponent<Vehicle>();
  18. maxSpeed = m_vehicle.maxSpeed;
  19. isPlanar = m_vehicle.isPlanar;
  20. maxForce = m_vehicle.maxForce;
  21. if(avoidanceForce>maxForce)
  22. avoidanceForce = maxForce;
  23. allColliders = GameObject.FindGameObjectsWithTag("obstacle");//�洢TagΪobstacle��������Ϊ��ײ��
  24. }
  25. public override Vector3 Force()
  26. {
  27. RaycastHit hit;
  28. Vector3 force = new Vector3(0, 0, 0);
  29. Vector3 velocity = m_vehicle.velocity;
  30. Vector3 normalizedVelocity = velocity.normalized;
  31. Debug.DrawLine(transform.position, transform.position + normalizedVelocity * MAX_SEE_AHEAD * (velocity.magnitude / maxSpeed));//����һ�����ߣ���Ҫ���������������ཻ����ײ��
  32. if (Physics.Raycast(transform.position, normalizedVelocity, out hit, MAX_SEE_AHEAD * velocity.magnitude / maxSpeed))
  33. {
  34. Vector3 ahead = transform.position + normalizedVelocity * MAX_SEE_AHEAD * (velocity.magnitude / maxSpeed); //���������ij����ײ���ཻ����ʾ������ײ
  35. force=ahead-hit.collider.transform.position;//���������ײ����IJٿ���
  36. force *= avoidanceForce;
  37. if (isPlanar)
  38. force.y = 0;
  39. foreach(GameObject c in allColliders)//�ı���ײ�����ɫ
  40. {
  41. if (hit.collider.gameObject == c)
  42. {
  43. c.GetComponent<Renderer>().material.color = Color.black;
  44. }
  45. else
  46. c.GetComponent<Renderer>().material.color = Color.white;
  47. }
  48. }
  49. else//���ǰ��û�м�⵽��ײ�壬��������ײ��ı���ɫ
  50. {
  51. foreach (GameObject c in allColliders)
  52. {
  53. c.GetComponent<Renderer>().material.color = Color.white;
  54. }
  55. }
  56. return force;
  57. }
  58. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/396614
推荐阅读
相关标签
  

闽ICP备14008679号