今天晚上跑步太晚了,回去已经11:30了
她不喜欢跑步,只喜欢和我压马路,如果我和她说:今晚出去转转吧,她就很开心的跟上来.
如果我换衣服换鞋,手机放桌上,是的.我出去跑步不带手机.她就朝这瞄一眼,然后自己抱着毛绒玩具,打开ipad,看<人世间>了.也有的时候看<鸡毛飞上天>,她不喜欢大部分脑残国产剧,只看几个口碑不错的.
回来了之后,洗完澡,再把餐桌收拾一下.切点水果,热杯牛奶给她.已经快12点了.
敲门,进她房间之后,看她已经洗完澡,刷过牙了,躺着了,夜灯柔柔的亮着,满天星.我问她,水果和牛奶还吃吗?
她说:不了,我想睡觉了.说完就盖好被子,然后又说,把灯关一下.
于是,我就把水果和牛奶拿到我的房间, 想了想,还是到书房吧,好久没做游戏了.
打开电脑,准备思索一番敲代码的时候,她抱着公仔走来.流氓兔,是的,她巨喜欢流氓兔
还记得第一次见她的时候,她兴高采烈的和我介绍,这是眼镜盒上的,这是包包上的,
然后好像在自言自语的说:迷死它了迷死它了.
她往我腿上一坐,装作很困的样子,然后圈主我的脖子,此时,流氓兔的兔毛一直在刺激我的鼻子,我好想打喷嚏.
我拍拍她的背,摸了摸她的头发,她也转头看向电脑,看到了电脑上一个AI模型,她问:这个是什么?
我回答说,这是个AI,我正在想用什么方法去控制它
她又问:都有什么方法可以控制它?这个好丑啊.
我哈哈一笑,说:一般的,在游戏开发中,用于驱动AI的只有两种常用的,一种是状态机,一种就是行为树.状态机呢,就是在合适的时候,去转换AI的状态,行为树呢就是上面许多”叶子”节点,通过执行不同的节点来达到驱动AI.
她说:哦,那你打算要用哪一种?
我说:这个要看后面的复杂程度,如果这个AI的功能单一,状态机就完全够用,如果后续功能越来越多,维护起来的话状态机肯定没有行为树方便的.现在打算先用状态机.
她说:状态机不是复杂了就不好维护了吗?为什么还要用它呀
我说:在开发的时候,并不是觉得哪个厉害就用哪个,而是哪个更适合就用哪个,目前我想到的AI没什么功能,所以先用状态机
她问:现在有那几个功能啊?
我说:有Idle , Chase ,Attack, Move 这几个写在代码里就是这样的:
1//状态枚举
2public enum ENUM_AI_State
3{
4 Idle = 0,
5 Chase, //追击敌人
6 Attack, //攻击
7 Move //移动
8}
她疑惑的问我,你是不是少写了下面的等于啊?
因为看起来下面的几种状态应该是给个值的,比如:Chase = 1
我笑着答:不用,枚举类型它会根据上面的值自动为下面的变量赋值的,自动加1.
她又问:就是根据这几个状态,去执行不同的AI行为吗?
我说:是的,而且因为有许多不同AI的情况下,我们需要…
写父类吧.她开心的说
我回答她:哈哈,是的
真开心,她永远都能知道我是怎么想的.
我飞快的写下一个状态机抽象父类,因为在这个里面不需要父类具体实现方法.所有的方法都要子类去实现.
1public abstract class ICharacter
2{
3 //默认状态
4 protected ENUM_AI_State m_AiState = ENUM_AI_State.Idle;
5 //移动相关
6 protected const float MOVE_CHECK_DIST = 1.5f;
7 protected bool m_bOnMove = false;
8 //是否有攻击目标位置
9 protected bool m_bSetAttackPostion = false;
10 protected Vector3 m_AttackPostion;
11
12 //追击的对象
13 protected bool m_BoChase = false;
14 protected ICharacter m_ChaseTarget = null;
15 protected const float CHASE_CHECK_DIST = 2.0f;
16
17 //攻击的对象 因为是AI 对战,所以攻击的目标也是AI
18 protected ICharacter m_AttackTarget = null;
19
20 //更新
21 public abstract void UpdateAI(List< ICharacter >Targets);
22
23 //...
24}
她问:就是根据枚举里面的变化去切换吗?
我说:是的,但是目前我们还不能这么做,因为直接去实现接口方法的话,必然要通过一系列的判断.稍微好点的方法就是switch()了.所以,我们还需要写一个角色的父类,实现抽象类里面的方法,然后再供给子类用,她又问:写起来是什么样的,很多吗?
我说,我给你写一个看看吧:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ISoldier : ICharacter
{
public override void UpdateAI(List<ICharacter> Targets)
{
switch (m_AiState)
{
case ENUM_AI_State.Idle:
break;
case ENUM_AI_State.Chase:
break;
case ENUM_AI_State.Attack:
break;
case ENUM_AI_State.Move:
break;
default:
break;
}
}
}
她问:难道就是这样就好了吗?
我回答:当然不是,当AI在Idle模式的时候,需要在里面判断一些特定的条件,到其他3个状态,比如,它看到了敌人,需要在Idle的分支中,先把状态改为Chase,然后要追到敌人的时候,状态再改为Attack.
唔,她皱皱眉头,说道:这样岂不是状态越多,里面就越乱了.
我说,是的,还需要再优化一下.
她问:又要重构?
我说:是的,一般的,有点重构癖好的,都是拿switch改类,就是每个状态都改成一个类.并且为它们写一个总的父类.
她问:你也要这样吗?
我说:不是,虽然我很想,在这之前,我们还需要一个能感知到状态切换的一个类,也就是说,我们需要一个状态的拥有者,通过它 来切换状态.
她有点迷惑,说:好绕啊
于是我和她说:那我们做个简单的好不好
她点点头.
我说,我们先写一个接口吧,这个接口就执行AI的控制:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IState
{
void Execute(AIController ac);
}
还有一个总控制器FSM:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FSM : MonoBehaviour
{
public IState currentState { get; private set; }
public bool isCanMoveToCamera;
public bool isCanMoveToMission;
public bool isCanMoveToExcut;
public AIController ac;
// Use this for initialization
void Start()
{
}//End of Start
public void ChangeState(IState newState)
{
currentState = newState;
}
// Update is called once per frame
void Update()
{
if (currentState != null && ac != null)
{
currentState.Execute(ac);
}
}//End of Update
}
我对她说,这次简单点,就做3个模式,移动到相机,移动到任务点,和执行任务
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class _MoveCameraState : IState {
public void Execute (AIController ac)
{
if (ac.fsm.isCanMoveToCamera) {
ac.MoveToCamera ();
} else {
ac .fsm .ChangeState (new _MoveMissionState());
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class _MoveMissionState : IState
{
public void Execute(AIController ac)
{
if (ac.fsm.isCanMoveToMission)
{
ac.MoveToExcut();
}
else
{
ac.fsm.ChangeState(new _MoveExcutState());
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class _MoveExcutState : IState
{
public void Execute(AIController ac)
{
if (ac.fsm.isCanMoveToExcut)
{
ac.MoveToExcut();
}
else
{
ac.fsm.ChangeState(new _MoveCameraState());
}
}
}
她问:这几个这么像,不能写成一个类扩展吗?
我哈哈大笑,当然不行啦,这是单独的状态,虽然我们这次写的是简单的.但是也不能这么做啊,没有意义的.
再把AIController写完
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AIController : MonoBehaviour
{
public FSM fsm;
// Use this for initialization
void Start()
{
fsm = GetComponent<FSM>();
fsm.ac = this;
//初始化一个默认的状态机
fsm.ChangeState(new _MoveCameraState());
}//End of Start
public void MoveToCamera()
{
Debug.Log("移动到相机");
}
public void MoveToMission()
{
Debug.Log("移动到任务点");
}
public void MoveToExcut()
{
Debug.Log("移动到执行点");
}
}
然后把FSM与AIContrller挂载在物体上,通过修改bool值来达到状态切换:
然后我运行一下,点击修改了几次bool值:
我说这样就行啦!
她唔的应了一声,这孩子,确实是困了,她八爪鱼一样趴在我的身上.我把她送到她的房间,放好再盖上被子,对她说一声晚安.
…END…