Menu

关于第一楼AI部分的设计及其代码实现

2018年6月24日 - 游戏开发

首先AI的部分使用了框架进行了状态管理,FsmSystem  FsmState进行状态间的管理,同时使用状态机进行怪物状态的转换,有如下几个状态chaseState patrolState

FsmSystem

using Entity;
using Entity.Monster;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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

public FSMState CurrentState { get { return currentState; } }

private MonsterBase m_monster;

public FSMSystem(GameObject npc)
{
m_monster = npc.GetComponentInChildren<MonsterBase>();
}

public void Update(GameObject npc,Animator ani)
{
currentState.Act(m_monster,ani);
currentState.Reason(m_monster, ani);
}

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 + “已经存在,无法重复添加”);return;
}
states.Add(s.ID, s);
}
public void DeleteState(StateID id)
{
if (id == StateID.NullStateID)
{
Debug.LogError(“无法删除空状态”);return;
}
if (states.ContainsKey(id) == false)
{
Debug.LogError(“无法删除不存在的状态:” + id);return;
}
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 + “发生转换”);return;
}
if (states.ContainsKey(id) == false)
{
Debug.LogError(“在状态机里面不存在状态” + id + “,无法进行状态转换!”);return;
}
FSMState state = states[id];
currentState.DoAfterLeaving();
currentState = state;
currentStateID = id;
currentState.DoBeforeEntering();
}
}

进行状态的注册,还有加载,删除,重置状态

FsmState

using Entity.Monster;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum Transition
{
NullTransition=0,
SeePlayer,
LostPlayer,
SeeNPC,
CanAttack
}
public enum StateID
{
NullStateID=0,
Patrol,
Chase,
See,
Attack
}

abstract class FSMState{

protected StateID stateID;
public StateID ID { get { return stateID; } }
protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();

protected FSMSystem fsm;

public FSMState(FSMSystem fsm)
{
this.fsm = fsm;
}

public void AddTransition(Transition trans,StateID id)
{
if (trans == Transition.NullTransition)
{
Debug.LogError(“不允许NullTransition”);return;
}
if (id == StateID.NullStateID)
{
Debug.LogError(“不允许NullStateID”); return;
}
if (map.ContainsKey(trans))
{
Debug.LogError(“添加转换条件的时候,” + trans + “已经存在于map中”);return;
}
map.Add(trans, id);

}
public void DeleteTransition(Transition trans)
{
if (trans == Transition.NullTransition)
{
Debug.LogError(“不允许NullTransition”); return;
}
if (map.ContainsKey(trans)==false)
{
Debug.LogError(“删除转换条件的时候,” + trans + “不存在于map中”); return;
}
map.Remove(trans);
}
public StateID GetOutputState(Transition trans)
{
if (map.ContainsKey(trans))
{
return map[trans];
}
return StateID.NullStateID;

}

public virtual void DoBeforeEntering() { }
public virtual void DoAfterLeaving() { }
public abstract void Act(MonsterBase npc,Animator ani);
public abstract void Reason(MonsterBase npc,Animator ani);//判断转换条件n
}

状态转换条件的判断受到FSMSystem系统的控制

也是那几种状态的基类

AttackState

using Entity;
using Entity.Monster;
using Other;
using Skill.Context;
using UnityEngine;

class AttackState : FSMState
{

private Status _status = Status.Attack_1;

private enum Status
{
Idle,
Attack_1,
Skill
}

private Vector3 tall = new Vector3(0, 2, 0);
private Transform playerTransform;
private Animator m_animator;
private float dir;
private CharacterController m_controller;

private SkillContext m_att,m_skill;

public AttackState(FSMSystem fsm, GameObject obj,SkillContext att,SkillContext skill) : base(fsm)
{
m_animator = obj.GetComponent<Animator>();
m_controller = obj.GetComponent<CharacterController>();
stateID = StateID.Attack;
playerTransform = GameObject.Find(“Player”).transform;
m_att = att;
m_skill = skill;
}

private float _timer=0.0f;

public override void Act(MonsterBase npc, Animator ani)
{
ani.SetFloat(“speed”, 0.0f);
npc.Move(0);
if (npc.SkillManager.GetActivateSkill() != null) return;
dir = Vector3.Distance(npc.transform.position, playerTransform.transform.position);

if (_status == Status.Attack_1)
{
if (dir < npc.CanAttackDistance)
{
m_att.Use();
//monster.TakeDamage(3);
}
_status = Status.Idle;
}
else if (_status == Status.Skill)
{
if (dir < npc.CanAttackDistance)
{
m_skill.Use();
//ani.SetTrigger(“skill”);
//EntityBase monster = npc.GetComponent<EntityBase>();
//monster.TakeDamage(5);
}
_status = Status.Idle;
}
else if(_status == Status.Idle)
{
if (_timer > npc.IdleTime)
{
_timer = 0.0f;
int r = Random.Range(0, 100);
if (r > 70)
_status = Status.Attack_1;
else
_status = Status.Skill;
}
_timer += Time.fixedDeltaTime;
}
}

public override void Reason(MonsterBase npc, Animator ani)
{
if (npc.SkillManager.GetActivateSkill() != null) return;
if (Vector3.Distance(playerTransform.position, npc.transform.position) > npc.CanAttackDistance)
{
var o = npc.transform.position + new Vector3(0, 0.8f, 0);
var t = playerTransform.position + new Vector3(0, 0.5f, 0);
Ray lookRay = new Ray(o, (t – o).normalized);
//Ray lookRay = new Ray(npc.transform.position + tall, npc.transform.forward);
RaycastHit hit;
if (Physics.Raycast(lookRay, out hit, 10, Layers.Player | Layers.Building))
{
if (hit.collider.gameObject.tag == “Player”)
{
Debug.DrawRay(npc.transform.position + tall, npc.transform.forward, Color.red, 5f);
fsm.PerformTransition(Transition.SeePlayer);
}
}
else
fsm.PerformTransition(Transition.LostPlayer);
}
}
}

 

ChaseState

using Entity;
using Entity.Monster;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

class ChaseState : FSMState
{
private EntityBase m_player;
private EntityBase m_self;
private Transform playerTransform;
private Animator m_animator;

private CharacterController m_controller;
public ChaseState(FSMSystem fsm, GameObject obj) : base(fsm)
{
m_animator = obj.GetComponent<Animator>();
m_controller = obj.GetComponentInParent<CharacterController>();
m_self = obj.GetComponent<EntityBase>();
stateID = StateID.Chase;
playerTransform = GameObject.Find(“Player”).transform;

}

private enum Status
{
Chase,

}

private float _dirFactor = 1.0f;
/// <summary>
/// 攻击方法
/// </summary>
/// <param name=”npc”></param>
/// <param name=”ani”></param>
public override void Act(MonsterBase npc, Animator ani)
{
if (npc.SkillManager.GetActivateSkill() != null) return;
if (npc.HasStatusFlag(EntityStatus.Hurting)) return;
if(Vector3.Distance(playerTransform.position, npc.transform.position)>= npc.CanAttackDistance)
{
var dir = (playerTransform.position-npc.ParentTransform.position).normalized;
if (Vector3.Dot(dir, npc.ParentTransform.forward) < 0.0f)
_dirFactor = -1.0f;
else
_dirFactor = 1.0f;
npc.Move(npc.MoveSpeed*_dirFactor);

ani.SetFloat(“speed”, 1.0f);
}
}

public override void Reason(MonsterBase npc, Animator ani)
{
//if (_status !=Status.Chase) return;
if (Vector3.Distance(playerTransform.position, npc.ParentTransform.position) > npc.ChaseLostDistance)
{
fsm.PerformTransition(Transition.LostPlayer);
//ani.SetFloat(“speed”, 0.0f);
}
if (Vector3.Distance(playerTransform.position, npc.ParentTransform.position) < npc.CanAttackDistance)
{
fsm.PerformTransition(Transition.CanAttack);
}
}
}

patrolState

using Entity.Monster;
using Game.Path;
using Other;
using System;
using UnityEngine;

internal class PatrolState : FSMState
{
private Transform playerTransform;
private Animator m_animator;
private CharacterController m_controller;
public PathNode m_start;
public PathNode m_end;
private MonsterBase m_entity;
private PathNode[] m_path;
private PathNode[] m_backStartPath;

public float Speed;

public PatrolState(FSMSystem fsm, MonsterBase obj, PathNode path1, PathNode path2) : base(fsm)
{
m_entity = obj;
m_animator = obj.GetComponent<Animator>();
m_controller = obj.GetComponentInParent<CharacterController>();
stateID = StateID.Patrol;
playerTransform = GameObject.Find(“Player”).transform;

m_start = path1;
m_end = path2;
//找到两个巡逻点
m_path = PathManager.Instance.FindPath(m_start, m_end);

Speed = obj.MoveSpeed;
}

private enum Status
{
Front, Rotate, BackToPath, WarpToPath
}

private Status _status = Status.Front;

public override void DoBeforeEntering()
{
Speed = Mathf.Abs(Speed);
m_backStartPath = PathManager.Instance.FindPath(m_entity.LastPassNode, m_start);
if (m_backStartPath.Length == 0)
{
_status = Status.WarpToPath;
return;
}

bool flag = false;

//是否需要返回巡逻路径
if (Array.IndexOf(m_path, m_entity.LastPassNode) >= 0)//可能在巡逻路径中
{
foreach (var n in m_path)
foreach (var link in n.ConnectLinks)
if (link.NextNode == m_entity.SelectLink.NextNode && Array.IndexOf(m_path, link.NextNode) < 0)
{
_status = Status.BackToPath;
flag = true;
}
}
else
{
_status = Status.BackToPath;
flag = true;
}

if (flag)
{
var dir = (m_path[0].transform.position-m_entity.ParentTransform.position).normalized;
if (Vector3.Dot(m_entity.ParentTransform.forward, dir) < 0.0f)
Speed = -Mathf.Abs(Speed);
else
Speed = Mathf.Abs(Speed);
}
else
_status = Status.Front;
}

//巡逻
public override void Act(MonsterBase npc, Animator ani)
{
//单节点,不巡逻
if (_status == Status.Front && m_start != m_end)
{
float dist = Vector3.Distance(m_end.transform.position, npc.ParentTransform.position);

if (npc.LastPassNode != m_end && dist > 0.8f)
{
//var t = npc.transform.forward * 5.0f * Time.deltaTime;
m_entity.Move(Speed);
ani.SetFloat(“speed”, 1.0f);
}
else
{
if (npc.LastPassNode == m_end && dist <= 0.8f)
_status = Status.Rotate;
}
}
else if (_status == Status.Rotate)
{
ani.SetFloat(“speed”, 0.0f);
var tmp = m_end;
m_end = m_start;
m_start = tmp;
Speed = -Speed;
_status = Status.Front;
}
else if (_status == Status.BackToPath)
{
ani.SetFloat(“speed”, 1.0f);
npc.Move(Speed);
if (m_start == m_end)
{
if (Vector3.Distance(m_entity.ParentTransform.position, m_start.transform.position) < 0.5f)
{
_status = Status.Front;
ani.SetFloat(“speed”, 0.0f);
m_entity.Move(0);
}
}
else if (Vector3.Distance(m_entity.ParentTransform.position, m_path[0].GetLink(m_path[1]).CurveFunc(0.5f)) < 0.5f)
{
_status = Status.Front;
ani.SetFloat(“speed”, 0.0f);
m_entity.Move(0);
if (m_entity.LastPassNode == m_end)
{
var tmp = m_end;
m_end = m_start;
m_start = tmp;
}
}
}
else if (_status == Status.WarpToPath)
{
var p = Camera.main.WorldToScreenPoint(npc.ParentTransform.position);

if ((p.x < 0 || p.x > Screen.width) || (p.y < 0 || p.y > Screen.height))
{
npc.ParentTransform.position = m_start.transform.position;
_status = Status.Front;
}
}
}

public override void Reason(MonsterBase npc, Animator ani)
{
if (Vector3.Distance(playerTransform.position, npc.transform.position) < npc.SeePlayerDistance)
{
var o = npc.transform.position + new Vector3(0, 0.8f, 0);
var t = playerTransform.position + new Vector3(0, 0.5f, 0);
Ray lookRay = new Ray(o, (t – o).normalized);
RaycastHit hit;
if (Physics.Raycast(lookRay, out hit, 10, Layers.Player | Layers.Building))
{
if (hit.collider.gameObject.tag == “Player”)
{
Debug.DrawRay(npc.transform.position, npc.transform.forward, Color.red, 20f);
fsm.PerformTransition(Transition.SeePlayer);
ani.SetFloat(“speed”, 0.0f);
m_entity.Move(0);
}
}
}
}
}

 

Enemy:实体类,怪物身上,减少代码的复用性

using Game.Path;
using Skill.Context;
using Skill.Monster;
using UnityEngine;

namespace Entity.Monster
{
//internal只能在程序集中访问
internal class Enemy : MonsterBase
{
private SkillContext m_attContext;
private SkillContext m_skillContext;

protected override void Start()
{
m_attContext=SkillManager.RegisterSkill<MonsterAttAttack>();
m_skillContext = SkillManager.RegisterSkill<MonsterSkill1Attack>();

InitFSM();

base.Start();
}

private void InitFSM()
{
//entity的状态转移
FSMState patrolState = new PatrolState(FSM, this, StartNode, EndNode);
FSM.AddState(patrolState);
patrolState.AddTransition(Transition.SeePlayer, StateID.Chase);
FSMState chaseState = new ChaseState(FSM, this.gameObject);
FSM.AddState(chaseState);
chaseState.AddTransition(Transition.LostPlayer, StateID.Patrol);
FSMState attackState = new AttackState(FSM, this.gameObject,m_attContext,m_skillContext);
FSM.AddState(attackState);
chaseState.AddTransition(Transition.CanAttack, StateID.Attack);
attackState.AddTransition(Transition.SeePlayer, StateID.Chase);
attackState.AddTransition(Transition.LostPlayer, StateID.Patrol);
}
}
}

FlyState

using Entity;
using Entity.Monster;
using Other;
using Skill.Context;
using UnityEngine;

class
FlyAttackState : FSMState
{
private Status _status = Status.Attack_1;

private enum Status
{
Idle,
Attack_1,
Skill
}

private Vector3 tall = new Vector3(0, 1, 0);
private Transform playerTransform;
private Animator m_animator;
private float dir;
private CharacterController m_controller;

private SkillContext m_att;

public FlyAttackState(FSMSystem fsm, GameObject obj,SkillContext skill) : base(fsm)
{
m_animator = obj.GetComponent<Animator>();
m_controller = obj.GetComponentInParent<CharacterController>();
stateID = StateID.Attack;
playerTransform = GameObject.Find(“Player”).transform;

m_att = skill;
}

private float _timer = 0.0f;

public override void Act(MonsterBase npc, Animator ani)
{
npc.Move(0);
if (npc.SkillManager.GetActivateSkill() != null) return;

dir = Vector3.Distance(npc.transform.position, playerTransform.transform.position);

if (_status == Status.Attack_1)
{
if (dir < 3)
{
m_att.Use();
}
_status = Status.Idle;
}
else if (_status == Status.Skill)
{
if (dir < 2)
{
ani.SetTrigger(“skill”);
EntityBase monster = npc.GetComponent<EntityBase>();
//monster.TakeDamage(5);
}
}
else if (_status == Status.Idle)
{
if (_timer > npc.IdleTime)
{
_timer = 0.0f;
int r = Random.Range(0, 100);
if (r > 70)
_status = Status.Attack_1;
//else
// _status = Status.Skill;
}
_timer += Time.fixedDeltaTime;
}
}

public override void Reason(MonsterBase npc, Animator ani)
{
if (Vector3.Distance(playerTransform.position, npc.transform.position) > 5)
{
var o = npc.transform.position + new Vector3(0, 0.8f, 0);
var t = playerTransform.position + new Vector3(0, 0.5f, 0);
Ray lookRay = new Ray(o, (t – o).normalized);
//Ray lookRay = new Ray(npc.transform.position + tall, npc.transform.forward);
RaycastHit hit;
if (Physics.Raycast(lookRay, out hit, 10, Layers.Player | Layers.Building))
{
if (hit.collider.gameObject.tag == “Player”)
{
Debug.DrawRay(npc.transform.position + tall, npc.transform.forward, Color.red, 5f);
fsm.PerformTransition(Transition.SeePlayer);
//ani.SetFloat(“speed”, 1.0f);
//ani.SetBool(“Attack_1”, true);
m_att.Reset();
}
}
}
}
}

减少代码的

 

关于路径点的生成是我一同学自己造了轮子,自己写了一个路径在Unity中显示。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

%d 博主赞过: