本章主要讲的人工智能算法
1、群组行为
2、有限状态机FSM
3、感知系统
4、自动寻路
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();
}
///
/// 测力并更新受力
///
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
}
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);
}
}
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) //如果夹角小于等于视野夹角一般,说明玩家在视野中
{
//发现玩家
}
}
听觉较简单,但要注意结合声音的判断
1.寻路算法 A*算法
算法介绍
寻路步骤
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;
}
}
}
}