Unity学习笔记07人工智能
有限状态机Finite State Machine (FSM)
蒙特卡洛树搜索:MCST
最常用的寻路算法是:A*(A star)
寻路相关概念
NavMesh:描述场景中可通过区域的数据结构,可以自动创建。
NavMesh Agent:待控制角色,在移动时可以自动躲避障碍物。
Off-Mesh Link :将两个不可直接通过区域连接起来。
NavMesh Obstacle :可移动的障碍物,让agent(待控制角色)能够在寻路时避开。
应用
场景的物体要设置成静态物体(static)才能被烘焙成导航网格。
调出 Window-->AI-->Navigation窗口
Navigation窗口下的子窗口
1:Agents,被导航的对象
2:Areas,导航区域设置
3:Object,可以对选中的物体进行导航相关的设置
4:Bake,烘焙子窗口,点击Bake,场景中可通过的部分会被用蓝色标识出来。Agent Radius,调整角色的半径。Max Slope :可通过的最大坡度。Step Height :可通过的楼梯高度
被导航的物体要添加Component-->Navigaton-->Nav Mesh Agent。
使用下面的脚本,作用是让物体移动到鼠标点击的地方
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class daohang : MonoBehaviour
{
private NavMeshAgent agent;
// Start is called before the first frame update
void Start()
{
agent = GetComponent<NavMeshAgent>();
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))//鼠标左键0,右键1
{
RaycastHit hit;
if(Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition),out hit,100))
{
agent.destination = hit.point;
}
}
}
}
新建两个空物体,一个放在初始位置,另一个放在,目标地点,给初始位置的物体添加Off Mesh Link ,将start和
end设置好,Bake后可以看到物体的跳跃路线。
对于非静态的物体,在运行时可以直接过去,如果想要非静态的物体有效果,需要为其添加Nav Mesh Obstacle。
巡逻
使用有限状态机描述敌人智能
原理
1.构造由一系列关键点组成的巡逻路径
2.使用自动寻路方式,让敌人移向目标点
3.到达目标点后,将目标点设置为下一个关键点
实现
建立一个空物体(父物体),将所有敌人经过的路径点(子物体)建在该空物体下(每一个路径点都是一个空物
体,然后将其放到设置的路径点上),建立敌人的控制脚本,将父物体拖拽给脚本。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class AI_Enemy : MonoBehaviour
{
public float patrolSpeed = 2f;//敌人的巡逻速度
public float patrolWaitTime = 1f;//敌人到达路径点时的停留时间
public Transform patrolWayPoints;//用来保存所有的路径点,是父物体,子物体为路径点
private NavMeshAgent agent;//自动寻路的智能体
private float patrolTimer;//用来记录敌人到达路径点的时候停留时间是多少
private int wayPointIndex;//用来记录当前目标点是哪个路径点
// Start is called before the first frame update
void Start()
{
agent = GetComponent<NavMeshAgent>();
}
// Update is called once per frame
void Update()
{
Patrolling();//在Update时每一帧都会调用
}
void Patrolling()
{
agent.isStopped = false;//默认情况是可以自动寻路的
agent.speed = patrolSpeed;
if(agent.remainingDistance<=agent.stoppingDistance)//判断agent是否移动到了目标点
{
patrolTimer += Time.deltaTime;
if(patrolTimer>=patrolWaitTime)//如果超过了停留时间
{
if(wayPointIndex==patrolWayPoints.childCount-1)//判断是否是最后一个路径点
{
wayPointIndex = 0;
}
else
{
wayPointIndex++;
}
patrolTimer = 0;
}
}
else
{
patrolTimer = 0;//未到目标点将停留时间设置为零;
}
agent.destination = patrolWayPoints.GetChild(wayPointIndex).position;//设置agent的目标点为要过去的点
}
}
视野
使用有限状态机描述敌人智能:

让敌人判断是否"看"到玩家角色
1.敌人面前绑定一个Trigger,只有进入这个区域的玩家才可能被看到
2.使用视野来判断玩家是否在敌人的一定视角范围,利用数学方法判断玩家和敌人视线的偏离
实践
在敌人上面新建一个cube,将其网格隐藏(Mesh Renderer) ,扩大其Box collider至需要的范围,并将其Is
Trigger勾选(碰撞体需要为触发器类型),建立控制脚本,将其拖拽给cube,
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemySight : MonoBehaviour
{
public float fieldOfViewAngle = 120f;//用来模拟视野
public bool playerInSight;//用来判断当前的玩家是否在敌人的视野当中
public Vector3 personalLastSight;//玩家最后一次出现在视野中时,玩家的位置
public static Vector3 resetPos = Vector3.back;//用来记录玩家的默认位置
BoxCollider col;//用来保存当前视野的碰撞体
GameObject player;
// Start is called before the first frame update
void Start()
{
col = GetComponent<BoxCollider>();
player = GameObject.FindGameObjectWithTag("Player");//需要将玩家对象修改为player类型
personalLastSight = resetPos;
}
// Update is called once per frame
void Update()
{
}
private void OnTriggerStay(Collider other)//进入时调用
{
if(other.gameObject==player)//如果在里面的物体是player的h话进行处理
{
playerInSight = false;
Vector3 direction = other.transform.position - transform.position;//玩家位置和敌人位置连线做出的向量direction
float angle = Vector3.Angle(direction, transform.forward);//direction和敌人朝向的夹角
if (angle<fieldOfViewAngle*0.5f)
{
//即使满足上述条件,也只是可能在视野内,因为在线路上可能会有障碍物
RaycastHit hit;//使用物理的方式来判断是否有障碍物,投射一条射线,看是否可以从敌人到达玩家
if(Physics.Raycast(transform.position,direction.normalized,out hit,col.size.z))
{
if(hit.collider.gameObject==player)
{
Debug.Log("kandao player");
playerInSight = true;//中间无障碍物,玩家出现在敌人视野中
personalLastSight = player.transform.position;
}
}
}
}
}
private void OnTriggerExit(Collider other)//出去时调用
{
if(other.gameObject==player)
{
playerInSight = false;
}
}
}
自动攻击
攻击状态
1.当敌人和玩家距离达到攻击状态时触发
2.可以使用子状态(选择不同的攻击方式,按照玩家的反应来决定自己的行为)
实践
为敌人和玩家添加指示方向的物体(这里是z方向),建立子弹预制件,将其设置为Is Trigger(触发),添加简单的子弹脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AI_Enemybullet : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
private void OnTriggerEnter(Collider other)
{
if(other.gameObject.tag=="Player")//需要将玩家的tag设置为Player
{
Destroy(other.gameObject);
Destroy(gameObject);
}
}
}
修改上节的视野脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Ai_enemy_me : MonoBehaviour
{
public float patrolSpeed = 2f;//敌人的巡逻速度
public float patrolWaitTime = 1f;//敌人到达路径点时的停留时间
public Transform patrolWayPoints;//用来保存所有的路径点,是父物体,子物体为路径点
private NavMeshAgent agent;//自动寻路的智能体
private float patrolTimer;//用来记录敌人到达路径点的时候停留时间是多少
private int wayPointIndex;//用来记录当前目标点是哪个路径点
//以下为敌人射击功能
public float shootRotSpeed = 5f;//射击时的转向速度
public float shootFreeTime = 2f;//在射击时敌人不可能一直发来子弹,这个时间就是敌人的冻结时间
private float shootTimer = 0f;//时间的计数器,一旦达到冻结时间,就可以刷新新一次的射击
private EnemySight enemySight;//用该脚本来得到玩家是否在视野中
public Rigidbody Bullet;//获取子弹
private Transform player;//玩家
//追踪
public bool chase;//前面是否已经发现过玩家
// Start is called before the first frame update
void Start()
{
agent = GetComponent<NavMeshAgent>();
//射击
enemySight = transform.Find("ViewRange").GetComponent<EnemySight>();//得到玩家是否在视野中
player = GameObject.FindGameObjectWithTag("Player").transform;
}
// Update is called once per frame
void Update()
{
if (enemySight.playerInSight)//如果看到玩家,则进入射击状态
{
Shooting();
chase = true;
}
else if (chase)//已经看到过玩家,进入追踪状态
{
Chasing();
}
else//未发现,未处于追踪状态,则保持巡逻状态
{
Patrolling();//在Update时每一帧都会调用
}
}
void Shooting()//射击状态
{
Vector3 lookpos = player.position;//首先得到玩家的位置,即目标位置
lookpos.y = transform.position.y;//设置成与敌人的y方向相同高度,避免在射击时出现点头现象
Vector3 targetDir = lookpos - transform.position;//要发射子弹的方向向量
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(targetDir),
Mathf.Min(1, Time.deltaTime * shootRotSpeed));//使用四元数旋转转向玩家所在方向射击
agent.isStopped = true;//射击时需要让玩家停下来
if (Vector3.Angle(transform.forward, targetDir) < 2)//判断敌人是否已经足够朝向射击目标
{
if (shootTimer > shootFreeTime)//判断冻结时间是否刷新
{
Instantiate(Bullet, transform.position, Quaternion.LookRotation(player.position - transform.position));
//Instantiate函数实例化一个对象,三个参数分别为,要实例化的对象,产生位置,产生后的朝向
shootTimer = 0;//发射后重置冻结时间
}
shootTimer += Time.deltaTime;//叠加计数器时间
}
}
void Chasing()//追踪状态
{
}
void Patrolling()//巡逻状态
{
agent.isStopped = false;//默认情况是可以自动寻路的
agent.speed = patrolSpeed;
if (agent.remainingDistance <= agent.stoppingDistance)//判断agent是否移动到了目标点
{
patrolTimer += Time.deltaTime;
if (patrolTimer >= patrolWaitTime)//如果超过了停留时间
{
if (wayPointIndex == patrolWayPoints.childCount - 1)//判断是否是最后一个路径点
{
wayPointIndex = 0;
}
else
{
wayPointIndex++;
}
patrolTimer = 0;
}
}
else
{
patrolTimer = 0;//未到目标点将停留时间设置为零;
}
agent.destination = patrolWayPoints.GetChild(wayPointIndex).position;//设置agent的目标点为要过去的点
}
}
还需将敌人的视野范围的碰撞体设置为Is Trigger类型,将子弹预制件拖拽到敌人脚本开放出来的对应位置
追踪
追踪玩家
1.当玩家出现在视野中是触发这个状态
2.将玩家位置设置为目标位置(为了真实性,当玩家逃出视野时的位置也记录,如果在特定时间内无法找到玩家,
就停止追踪)
实践
改写上节脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Ai_enemy_me : MonoBehaviour
{
public float patrolSpeed = 2f;//敌人的巡逻速度
public float patrolWaitTime = 1f;//敌人到达路径点时的停留时间
public Transform patrolWayPoints;//用来保存所有的路径点,是父物体,子物体为路径点
private NavMeshAgent agent;//自动寻路的智能体
private float patrolTimer;//用来记录敌人到达路径点的时候停留时间是多少
private int wayPointIndex;//用来记录当前目标点是哪个路径点
public float shootRotSpeed = 5f;//射击时的转向速度
public float shootFreeTime = 2f;//在射击时敌人不可能一直发来子弹,这个时间就是敌人的冻结时间
private float shootTimer = 0f;//时间的计数器,一旦达到冻结时间,就可以刷新新一次的射击
private EnemySight enemySight;//用该脚本来得到玩家是否在视野中
public Rigidbody Bullet;//获取子弹
private Transform player;//玩家
public bool chase;//前面是否已经发现过玩家
public float chaseSpeed = 5f;//追踪速度
public float chaseWaitTime = 5f;//追踪等待时间,指的是追上玩家后观察时间
private float chaseTimer;//追踪等待计时器
public float sqrPlayerDist = 3f;//一个阈值,什么时候才算追上了玩家,当距离的平方等于这个值时(用平方是为了计算效率的提高)
// Start is called before the first frame update
void Start()
{
agent = GetComponent<NavMeshAgent>();
enemySight = transform.Find("ViewRange").GetComponent<EnemySight>();//得到玩家是否在视野中
player = GameObject.FindGameObjectWithTag("Player").transform;
}
// Update is called once per frame
void Update()
{
if (enemySight.playerInSight)//如果看到玩家,则进入射击状态
{
Shooting();
chase = true;
}
else if (chase)//已经看到过玩家,进入追踪状态
{
Chasing();
}
else//未发现,未处于追踪状态,则保持巡逻状态
{
Patrolling();//在Update时每一帧都会调用
}
}
void Shooting()//射击状态
{
Vector3 lookpos = player.position;//首先得到玩家的位置,即目标位置
lookpos.y = transform.position.y;//设置成与敌人的y方向相同高度,避免在射击时出现点头现象
Vector3 targetDir = lookpos - transform.position;//要发射子弹的方向向量
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(targetDir),
Mathf.Min(1, Time.deltaTime * shootRotSpeed));//使用四元数旋转转向玩家所在方向射击
agent.isStopped = true;//射击时需要让玩家停下来
if (Vector3.Angle(transform.forward, targetDir) < 2)//判断敌人是否已经足够朝向射击目标
{
if (shootTimer > shootFreeTime)//判断冻结时间是否刷新
{
Instantiate(Bullet, transform.position, Quaternion.LookRotation(player.position - transform.position));
//Instantiate函数实例化一个对象,三个参数分别为,要实例化的对象,产生位置,产生后的朝向
shootTimer = 0;//发射后重置冻结时间
}
shootTimer += Time.deltaTime;//叠加计数器时间
}
}
void Chasing()//追踪状态
{
agent.isStopped = false;//玩家不停止
Vector3 sightingDeltaPos = enemySight.personalLastSight - transform.position;//玩家最后出现的位置-敌人当前位置
if(sightingDeltaPos.sqrMagnitude > sqrPlayerDist)//如果距离大于阈值,需要继续追踪
{
agent.destination = enemySight.personalLastSight;//设置追踪点
}
agent.speed = chaseSpeed;
if(agent.remainingDistance<=agent.stoppingDistance)//追踪过程中到达玩家最后出现的位置
{
chaseTimer += Time.deltaTime;
if(chaseTimer>=chaseWaitTime)//追踪完成
{
chase = false;
chaseTimer = 0f;
}
}
else
{
chaseTimer = 0;
}
}
void Patrolling()//巡逻状态
{
agent.isStopped = false;//默认情况是可以自动寻路的
agent.speed = patrolSpeed;
if (agent.remainingDistance <= agent.stoppingDistance)//判断agent是否移动到了目标点
{
patrolTimer += Time.deltaTime;
if (patrolTimer >= patrolWaitTime)//如果超过了停留时间
{
if (wayPointIndex == patrolWayPoints.childCount - 1)//判断是否是最后一个路径点
{
wayPointIndex = 0;
}
else
{
wayPointIndex++;
}
patrolTimer = 0;
}
}
else
{
patrolTimer = 0;//未到目标点将停留时间设置为零;
}
agent.destination = patrolWayPoints.GetChild(wayPointIndex).position;//设置agent的目标点为要过去的点
}
}
机器学习
所谓的学习指的是系统所做的适应性变化,使得该系统在下一次完成同样的任务时更有效率。
传统人工智能的问题
1.再灵活精巧的算法也是"死"的
2.无法自动适应新的环境
3.无法进化
4.不能对不同的对手采取不同策略
机器学习Machine Learning
1.非监督学习 unsupervised learning
比如要将玩家按照沉浸度分为两类。
获得了玩家的一系列信息(比如游戏时间、内购情况和完成的关卡数量)。
在非监督学习系统中,设定分类为2。
并没有标注玩家信息,却可以对玩家进行分类。
2.监督式学习 supervised learning
可以依据玩家属性直接将玩家映射为某种类型。
对样本进行标注(除了游戏时间、内购情况和完成的关卡数量外,还标注出样本玩家是否是高沉浸度玩家)。
学习到哪些属性影响了玩家是否沉浸。
将学习到的模型用来预测新的玩家可能的状态。
3.强化学习 reinforcement learning
让计算机通过不断地尝试,从错误中学习,最后找到规律。
类似于有一位虚拟的裁判,他给计算机的行为打分。
计算机会尽量执行能够拿高分的行为,并避免低分的行为。
具有分数导向性(这种分数导向性类似于监督学习中的正确标签)。

