欢迎光临散文网 会员登陆 & 注册

Unity学习笔记07人工智能

2021-11-30 08:14 作者:挽秋_z  | 我要投稿

自动寻路

有限状态机Finite State Machine (FSM)

蒙特卡洛树搜索:MCST

最常用的寻路算法是:A*(A star)

PathFinding.js(寻路算法网站)

寻路相关概念

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

让计算机通过不断地尝试,从错误中学习,最后找到规律。

类似于有一位虚拟的裁判,他给计算机的行为打分。

计算机会尽量执行能够拿高分的行为,并避免低分的行为。

具有分数导向性(这种分数导向性类似于监督学习中的正确标签)。











Unity学习笔记07人工智能的评论 (共 条)

分享到微博请遵守国家法律