unity中的人工智能专题

1.简介

本章主要讲的人工智能算法
1、群组行为
2、有限状态机FSM
3、感知系统
4、自动寻路

2.群组行为

http://www.red3d.com/cwr/boids
1、简介:模拟鸟群行走或者人群行走过程称之为群组行为 机械—>智能
2、鸟群群组智能化过程
1、扇动翅膀杂乱化(动画随机化)

private IEnumerator Start () {
		anim = GetComponentInChildren<Animation>();
		yield return new WaitForSeconds(Random.Range(0, maxWaitTime));  协程知识,等待几秒后继续执行动画
		anim.Play();
	}

2、分离、队列、聚集

在这里插入图片描述

代码实现

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CrowMove_AI : MonoBehaviour {

	public Vector3 velocity = Vector3.forward;  //原速度

	public float m = 1;                         //质量

	public Vector3 a = Vector3.zero;                      //加速度

	public Vector3 sumForce = Vector3.zero;//合力

	public float forwadWeight = 1;   //恒动力权重
	public Vector3 forwadForce = Vector3.forward; //恒动力权重

	public float separationDistance = 3;  //分离生效范围
	public List<GameObject> seprationNeighbors = new List<GameObject>(); //分离力参考物体
	public float separationWeight = 1;   //分离力权重
	public Vector3 separationForce = Vector3.zero;//分离力


	public float cohesionDistance = 10;  //聚集力生效范围
	public List<GameObject> cohesionNeighbors = new List<GameObject>(); //聚集力参考物体
	public float cohesionWeight = 1;     //聚集力权重
	public Vector3 cohesionForce = Vector3.zero;//聚集力


	public float alignmentDistance = 5;  //共向生效范围
	public List<GameObject> alignmentNeighbors = new List<GameObject>(); //共向力参考物体
	public float alignmentWeight = 1;    //共向力权重
	public Vector3 alignmentForce = Vector3.zero;//共向力

	public float checkInterVal = 0.2f;//测力间隔时间

	private Animation anim;
	public float maxWaitTime = 2f;
	// Use this for initialization
	void Start()
    {
		velocity = new Vector3(Random.Range(0, 1),0, 1);
		InvokeRepeating("CalcForce", checkInterVal,checkInterVal);
        anim = GetComponentInChildren<Animation>();
		Invoke("PlayAnmi", Random.Range(0, maxWaitTime));
    }
	void Update()
	{
		
		a = sumForce / m;
		velocity += a * Time.deltaTime;

		transform.rotation = Quaternion.LookRotation(velocity);
		//transform.LookAt(velocity);
		transform.Translate(velocity * Time.deltaTime, Space.World);
	}
	private void PlayAnmi()
    {
        anim.Play();
    }

    /// <summary>
    /// 测力并更新受力
    /// </summary>
    private void CalcForce()
    {
        sumForce = Vector3.zero;
        separationForce = Vector3.zero;//分离力
        cohesionForce = Vector3.zero;//聚集力
        alignmentForce = Vector3.zero;//共向力


        //计算分离的力
        seprationNeighbors.Clear();
        Collider[] colliders = Physics.OverlapSphere(transform.position, separationDistance);
        foreach(Collider c in colliders)
        {
            if (c != null && c.gameObject != this.gameObject)
            {
		seprationNeighbors.Add(c.gameObject);
            }
        }
	foreach(GameObject neighbor in seprationNeighbors)
        {
	    Vector3 dir = transform.position - neighbor.transform.position;
	    separationForce += (dir.normalized / dir.magnitude)*separationWeight;
	}


        //计算聚集的力
        cohesionNeighbors.Clear();
        colliders = Physics.OverlapSphere(transform.position, cohesionDistance);
        foreach (Collider c in colliders)
        {
            if (c != null && c.gameObject != this.gameObject)
            {
                cohesionNeighbors.Add(c.gameObject);
            }
        }
		Vector3 center = Vector3.zero;
        foreach (GameObject neighbor in cohesionNeighbors)
        {
			center += neighbor.transform.position;
        }
        if (cohesionNeighbors.Count != 0)
        {
			center /= cohesionNeighbors.Count;
			Vector3 dir = center-transform.position;
			cohesionForce += (dir.normalized * dir.magnitude) * cohesionWeight;
		}
		

        //计算共向的力
        alignmentNeighbors.Clear();
		colliders = Physics.OverlapSphere(transform.position, alignmentDistance);
		foreach (Collider c in colliders)
		{
			if (c != null && c.gameObject != this.gameObject)
			{
				alignmentNeighbors.Add(c.gameObject);
			}
		}
		Vector3 avgDir = Vector3.zero;
		foreach (GameObject neighbor in alignmentNeighbors)
		{
			avgDir += neighbor.transform.forward;
		}
        if (alignmentNeighbors.Count != 0)
        {
			avgDir /= alignmentNeighbors.Count;
			alignmentForce = (avgDir - transform.forward) * alignmentWeight;
		}


		//forwadForce *= forwadWeight;  //原本合力要加上恒定动力,后来发现这不科学,
										//实际上鸟的恒定动力最终会被空气阻力抵消
		//求合力
		sumForce = separationForce + cohesionForce + alignmentForce;
		
	}
	// Update is called once per frame
	
}

3.有限状态机FSM

1、五个要素:状态,事件,条件,动作,迁移
2. 设计模式:
在这里插入图片描述

设计模式代码
首先是FsmState父类,它代表着所有状态的共同性质

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PatrolState : FSMState
{
    private List<Transform> path = new List<Transform>();
    private int index = 0;
    public float patrolSpeed = 3f;
    public Transform playerTransform;
    public PatrolState(FSMSystem fsm): base(fsm)
    {

        stateID = StateID.Patrol;
        Transform pathTransform = GameObject.Find("path").transform;
        Transform[] children = pathTransform.GetComponentsInChildren<Transform>();
        foreach(Transform child in children)
        {
            if (child != pathTransform)
            {
                path.Add(child);
            }
        }
        playerTransform = GameObject.Find("Player").transform;

        AddTransition(Transition.SeePlayer, StateID.Chase);
    }

    public override void Act(GameObject npc)
    {
        npc.transform.LookAt(path[index].position);
        npc.transform.Translate(Vector3.forward * Time.deltaTime * patrolSpeed);
        if (Vector3.Distance(npc.transform.position,path[index].position) < 1){
            index++;
            index %= path.Count;
        }
    }

    public override void Reason(GameObject npc)
    {
        if (Vector3.Distance(playerTransform.position, npc.transform.position) < 3)
        {
            fsm.PerformTransition(Transition.SeePlayer);
        }
    }
}

然后是控制所有状态的实现机制的状态机类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FSMSystem{
    private Dictionary<StateID, FSMState> states = new Dictionary<StateID, FSMState>();

    private StateID currentStateID;
    private FSMState currentState;

    public void Update(GameObject npc)
    {
        currentState.Act(npc);
        currentState.Reason(npc);
    }
    public void AddState(FSMState s)
    {
        if (s == null)
        {
            Debug.LogError("FSMState不能为空");return;
        }
        if (currentState == null)
        {
            currentState = s;
            currentStateID = s.ID;
        }
        if (states.ContainsKey(s.ID))
        {
            Debug.LogError("状态" + s.ID + "已经存在,无法重复添加");
        }
        states.Add(s.ID, s);
    }
    public void RemoveState(StateID id)
    {
        if (states.ContainsKey(id) == false)
        {
            Debug.LogError("无法删除不存在的状态:" + id);
        }
        states.Remove(id);
    }
    public void PerformTransition(Transition trans)
    {
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("无法执行空的转换条件");return;
        }
        StateID id = currentState.GetOutPutState(trans);
        if (id == StateID.NullStateID)
        {
            Debug.LogWarning("当前状态" + currentStateID + "无法根据转换条件" + trans + "发生转换");
        }
        if (states.ContainsKey(id) == false)
        {
            Debug.LogError("在状态机不存在状态"+id+"无法执行转换"); return;

        }
        FSMState state = states[id];
        currentState.DoAfterLeaving();
        currentState = state;
        currentStateID = id;
        currentState.DoBeforeEntering();
    
    }

}

然后是所有具体的类的实现
巡逻类:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PatrolState : FSMState
{
    private List<Transform> path = new List<Transform>();
    private int index = 0;
    public float patrolSpeed = 3f;
    public Transform playerTransform;
    public PatrolState(FSMSystem fsm): base(fsm)
    {

        stateID = StateID.Patrol;
        Transform pathTransform = GameObject.Find("path").transform;
        Transform[] children = pathTransform.GetComponentsInChildren<Transform>();
        foreach(Transform child in children)
        {
            if (child != pathTransform)
            {
                path.Add(child);
            }
        }
        playerTransform = GameObject.Find("Player").transform;

        AddTransition(Transition.SeePlayer, StateID.Chase);
    }

    public override void Act(GameObject npc)
    {
        npc.transform.LookAt(path[index].position);
        npc.transform.Translate(Vector3.forward * Time.deltaTime * patrolSpeed);
        if (Vector3.Distance(npc.transform.position,path[index].position) < 1){
            index++;
            index %= path.Count;
        }
    }

    public override void Reason(GameObject npc)
    {
        if (Vector3.Distance(playerTransform.position, npc.transform.position) < 3)
        {
            fsm.PerformTransition(Transition.SeePlayer);
        }
    }
}

追击类:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ChaseState : FSMState {
    private Transform playerTransform;
    public float chaseSpeed = 2f;
    public ChaseState(FSMSystem fsm) : base(fsm)
    {
       
        stateID = StateID.Chase;
        playerTransform = GameObject.Find("Player").transform;
        AddTransition(Transition.CantCatch, StateID.Patrol);
    }
    public override void Act(GameObject npc)
    {
        npc.transform.LookAt(playerTransform.position);
        npc.transform.Translate(Vector3.forward * chaseSpeed * Time.deltaTime);
    }

    public override void Reason(GameObject npc)
    {
        if (Vector3.Distance(playerTransform.position, npc.transform.position) > 6)
        {
            fsm.PerformTransition(Transition.CantCatch);
        }
    }

  
}

最后是在具体角色或敌人类上实现状态机的创建

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enmey : MonoBehaviour {

	private FSMSystem fsm;
	// Use this for initialization
	void Start () {
		InitFSM();
	}
	void InitFSM()
    {
		fsm = new FSMSystem();

		FSMState patrolState = new PatrolState(fsm);

		FSMState chaseState = new ChaseState(fsm);

		fsm.AddState(patrolState);
		fsm.AddState(chaseState);
	}
	// Update is called once per frame
	void Update () {
		fsm.Update(this.gameObject);
	}
}

4.感知系统

1、视觉系统:个体前方锥形范围
听觉系统:个体四周球形或圆形范围,但是范围比视觉小
2、代码实现
(忽略障碍物)视觉:

 public float viewDistance = 3;
 public float viewAngle = 120;
 public Transform playerTransform;
 ...
 if (Vector3.Distance(playerTransform.position, npc.transform.position) < viewDistance)
        {
            Vector3 playerDir = playerTransform.position - npc.transform.position;
            float angle = Vector3.Angle(playerDir, npc.transform.forward);
            if (angle <= viewAngle / 2)
            {
              //发现玩家
            }
            
        }

(考虑障碍物)视觉:

if (Vector3.Distance(playerTransform.position, npc.transform.position) < viewDistance)
{
    Vector3 playerDir = playerTransform.position - npc.transform.position;//获取玩家相对npc的方向

    Ray ray = new Ray(npc.transform.position, playerDir);  //以该方向在NPC处构建射线
    //发射射线,范围为视野距离,接收到第一个碰撞体                                
    if (Physics.Raycast(ray, out RaycastHit hit, viewDistance))
    {
        if (hit.collider.gameObject.name != "Player") //如果碰撞体不是玩家,说明玩家被其他障碍物挡住了
            return;                                    //直接返回,否则继续执行
    }
    float angle = Vector3.Angle(playerDir, npc.transform.forward);  //定义玩家和npc视线正前方夹角
    if (angle <= viewAngle / 2)  //如果夹角小于等于视野夹角一般,说明玩家在视野中
    {
             //发现玩家
    }
    
}

听觉较简单,但要注意结合声音的判断

5.寻路系统

1.寻路算法 A*算法
算法介绍

寻路步骤

  1. 从起点 A 开始, 把它作为待处理的方格存入一个"开启列表", 开启列表就是一个等待检查方格的列表.
  2. 寻找起点 A 周围可以到达的方格, 将它们放入"开启列表", 并设置它们的"父方格"为 A
    .3. 从"开启列表"中删除起点 A, 并将起点 A 加入"关闭列表", “关闭列表"中存放的都是不需要再次检查的方格,放入"关闭列表” , 表示它不需要再执行检查.从 “开启列表” 中找出相对最靠谱的方块, 什么是最靠谱? 它们通过公式 F=G+H 来计算.F = G + HG 表示从起点 A 移动到网格上指定方格的移动耗费 (可沿斜方向移动).H 表示从指定的方格移动到终点 B 的预计耗费 (H 有很多计算方法, 这里我们设定只可以上下左右移动).我们假设横向移动一个格子的耗费为 10, 为了便于计算, 沿斜方向移动一个格子耗费是 14. 为了更直观的展示如何运算 FGH, 图中方块的左上角数字表示 F, 左下角表示 G, 右下角表示 H. 看看是否跟你心里想的结果一样?从 “开启列表” 中选择 F 值最低的方格 C (绿色起始方块 A 右边的方块), 然后对它进行如下处理:
  3. 把它从 “开启列表” 中删除, 并放到 “关闭列表” 中.
  4. 检查它所有相邻并且可以到达 (障碍物和 “关闭列表” 的方格都不考虑) 的方格. 如果这些方格还不在 “开启列表” 里的话, 将它们加入 “开启列表”, 计算这些方格的 G, H 和 F 值各是多少, 并设置它们的 “父方格” 为 C.6. 如果某个相邻方格 D 已经在 “开启列表” 里了, 检查如果用新的路径 (就是经过 C 的路径) 到达它的话, G 值是否会更低一些, 如果新的 G 值更低, 那就把它的 “父方格” 改为目前选中的方格 C, 然后重新计算它的 F 值和 G 值 (H 值不需要重新计算, 因为对于每个方块, H 值是不变的). 如果新的 G 值比较高, 就说明经过 C 再到达 D 不是一个明智的选择, 因为它需要更远的路, 这时我们什么也不做.
    6.就这样, 我们从 “开启列表” 找出 F 值最小的, 将它从 “开启列表” 中移掉, 添加到 “关闭列表”. 再继续找出它周围可以到达的方块, 如此循环下去…
    7.那么什么时候停止呢? —— 当我们发现 “开始列表” 里出现了目标终点方块的时候, 说明路径已经被找到.如何找回路径如上图所示, 除了起始方块, 每一个曾经或者现在还在 “开启列表” 里的方块, 它都有一个 “父方块”, 通过 “父方块” 可以索引到最初的 “起始方块”, 这就是路径.
    代码实现
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Astar : MonoBehaviour
{
    public const int mapWidth = 8;
    public const int mapHeight = 6;
    private Point[,] map = new Point[mapWidth, mapHeight];
    
    // Start is called before the first frame update
    void Start()
    {
        InitMap();
        Point start = map[2, 3];
        Point end = map[6, 3];
        if(FindPath(start, end))
        {
            showPath(end);
        }
    }
    void InitMap()
    {
        for (int x= 0; x < mapWidth; x++)
        {
            for (int y = 0; y < mapHeight; y++)
            {
                map[x, y] = new Point(x, y);
            }
        }
        map[4, 2].IsWall = true;
        map[4, 3].IsWall = true;
        map[4, 4].IsWall = true;
    }
    bool FindPath(Point start,Point end)
    {
        List<Point> openList = new List<Point>();
        List<Point> closeList = new List<Point>();
        openList.Add(start);
        Debug.Log(1);
        while (openList.Count > 0)
        {
            Point minFPoint = FindMinF(openList);
            openList.Remove(minFPoint);
            closeList.Add(minFPoint);
            List<Point> surroundPoints = GetSurroundPoints(minFPoint);
            foreach (Point surroundPoint in surroundPoints)
            {
                if (closeList.Contains(surroundPoint)) continue;
                if (openList.Contains(surroundPoint))
                {
                    if (CalG(surroundPoint, minFPoint) < surroundPoint.G)
                    {
                        surroundPoint.Parent = minFPoint;
                        SetGHF(surroundPoint, end);
                    }
                }
                else
                {
                    openList.Add(surroundPoint);
                    surroundPoint.Parent = minFPoint;
                    SetGHF(surroundPoint, end);
                }
            }
            if (openList.Contains(end)) return true;
        }
        return false;
        //Debug.Log(end.Parent.X+","+end.Parent.Y);
        //showPath(end);
    }

    private static void showPath(Point end)
    {
        Point temp = end;

        while (temp != null)
        {
            Debug.Log(temp.X + "," + temp.Y);
            temp = temp.Parent;
        }
    }

    List<Point> GetSurroundPoints(Point point)
    {
        Point up=null, down=null, left=null, right = null;
        Point ul = null, ur = null, dl = null, dr = null;
        List<Point> list = new List<Point>();
        if (point.Y+1 <= mapHeight-1)
        {
            up = map[point.X, point.Y + 1];
            if (!up.IsWall) list.Add(up);
        }
        if (point.Y - 1 >= 0)
        {
            down = map[point.X, point.Y - 1];
            if (!down.IsWall) list.Add(down);
        }
        if (point.X + 1 <= mapWidth - 1)
        {
            right = map[point.X + 1, point.Y];
            if (!right.IsWall) list.Add(right);
        }
        if (point.X - 1 >= 0) 
        {
            left = map[point.X - 1, point.Y];
            if (!left.IsWall) list.Add(left);
        }
        if (up != null && left != null)
        {
            ul = map[point.X - 1,point.Y + 1];
            if (!ul.IsWall) list.Add(ul);
        }
        if (up != null && right != null)
        {
            ur = map[point.X + 1, point.Y + 1];
            if (!ur.IsWall) list.Add(ur);
        }
        if (down != null && left != null)
        {
            dl = map[point.X - 1, point.Y - 1];
            if (!dl.IsWall) list.Add(dl);
        }
        if (down != null && right != null)
        {
            dr = map[point.X + 1, point.Y - 1];
            if (!dr.IsWall) list.Add(dr);
        }
        return list;
      
    }
    Point FindMinF(List<Point> openList)
    {
        float f = float.MaxValue;
        Point temp = null;

        foreach (var p in openList)
        {
            if (p.F < f)
            {
                f = p.F;
                temp = p;
            }
        }
        return temp;
    }
    float CalG(Point now, Point parent)
    {
        float g = Vector2.Distance(new Vector2(now.X, now.Y), new Vector2(parent.X, parent.Y)) + parent.G;
        return g;
    }
    void SetGHF(Point now,Point end)
    {
       
        float h = Mathf.Abs(end.X - now.X) + Mathf.Abs(end.Y - now.Y);
        float g = 0;
        if (now.Parent == null)
        {
            g = 0;
        }
        else
        {
            g=Vector2.Distance(new Vector2(now.X, now.Y), new Vector2(now.Parent.X, now.Parent.Y))+now.Parent.G;
        }
        float f = g + h;
        now.F = f;
        now.G = g;
        now.H = h;
    }
 
}

2.navmentagent系统
1)选择 staticEnvironment 下的所有游戏对象,在 Inspector 窗口中,设置 Static 状态为Navigation Static,创建 Environment 标签,赋给这些游戏对象。为这些游戏对象统一添加 Box Collider 组件。
2) 进入 Window|AI|Navigation 窗口,选择 staticEnvironment 下的所有游戏对象,如左下图所示;在 Bake 栏内,可根据实际情况设置 Agent Radius 为 0.1,Agent Height 为1,点击 bake,进行 Navmesh 的生成。
3)为 player 添加 Navmesh Agent 属性和 Box Collider 属性。调整 Box Collider 的大小Size,使其能够覆盖 player 整体。调整 Navmesh Agent 组件的 Base Offset 属性,使其刚好位于地面之上。
4)为player 创建 Player脚本,实现鼠标点击,自动寻路的效果。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Player : MonoBehaviour
{
    //public Transform targetTransform;
    public NavMeshAgent agent;
    // Start is called before the first frame update
    void Start()
    {
        agent = GetComponent<NavMeshAgent>();
    }

    // Update is called once per frame
    void Update()
    {
        int button = 0;
        if (Input.GetMouseButtonDown(button))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hitInfo;
            if(Physics.Raycast(ray,out hitInfo))
            {
                Vector3 targetPosition = hitInfo.point;
                agent.destination = targetPosition;
                //targetTransform.position = targetPosition;
            }
        }
    }
}
  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值