思考并回答以下问题:
- 需要首先定义一个枚举,列举所有的AI状态,为什么?
- FSM这三个词代表的意思并没有多大的技术含量。FSM有两种实现方式。1.switch case;2.状态模式。明确这一点怎么理解?
- 状态模式就是新增状态接口类和几个具体状态类。
本章涵盖:
- 角色的AI
- 状态模式
- 使用状态模式实现角色AI
- 角色AI的实现
- 实现说明
- 使用状态模式的优点
- 角色AI执行流程
- 状态模式面对变化时
- 结论
角色的AI
在前面几个章节中,我们将《P级阵地》的角色属性、装备武器、武器攻击流程进行了说明。本章我们将把重点放在如何能让角色在场景上“根据战场情况来移动或攻击”。
游戏开始时,玩家会先决定由哪一个兵营产生角色,而角色在经过一段时间的训练后,就会出现在战场上,负责守护阵地防止被敌方角色占领。同时,画面的右方会出现敌方角色,并且不断地朝玩家阵地前进,他们的目的是“占领玩家阵地”。当双方角色在地图上遭遇时会相互攻击,这时候,玩家角色要击退敌人,而敌人角色则是努力突破防线。在过程中,玩家无法参与指挥任何一只角色,任由他们自动决定要如何行动。
在玩家不能参与操作角色的情况下,双方角色要如何自动攻击和防守呢?一般会使用所谓的“人工智能”(AI)来实现这一目标。或许读者会认为“人工智能”是一门很高深的技术,其实不然,它不像字义表面那么复杂,有时候它也可以用很简单的方式来实现。
在实现前先分析一下游戏需求,列出双方阵营的行为模式:
- 玩家阵营角色出现在战场时,原地不动,之后:
- 当侦测到敌方阵营角色在“侦测范围”内时,往敌方角色移动。
- 当角色抵达“武器可攻击的距离”时,使用武器攻击对手。
- 当对手阵亡时,寻找下一个目标。
- 当没有敌方阵营角色可以被找到,就停在原地不动。
- 敌方阵营角色出现在战场时,往阵地中央前进,之后:
- 当侦测到玩家阵营角色在“侦测范围”内时,往玩家角色移动。
- 当角色到达“武器可攻击的距离”时,使用武器攻击对手。
- 当对手阵亡时,寻找下一个目标。
- 当没有玩家阵营角色可以被找到,就往阵地中央目标前进。
通过上述的分析,可以得知,双方阵营的角色都有4个条件作为判断的依据,而这些条件都可以改变角色的行为(状态)。
例如,原本一出现在场景上的玩家角色A,其状态为“闲置状态(Idle)”。而进入闲置(Idle)状态的单位A,会不断地侦测它的“视野范围”内是否有可攻击的目标(敌方阵营单位)。此时,敌方角色B出现在场景中,并且会往阵地中央前进,如图1所示。
图1 闲置状态
当角色B进入单位A的“视野范围”内时,单位A即进入“追击状态(Chase)”并往单位B方向移动,如图2所示。
图2 追击状态
当单位A追击B到达武器的“射程距离”内时,即进入“攻击状态(Attack)”并使用武器攻击单位B,如图3所示。
图3 攻击状态
在经过一番交火之后,当单位B阵亡时,且单位A又回到“闲置状态(Idle)”,则寻找下一个可攻击的单位,如图4所示。
图4 恢复闲置状态
所以单位A是在不同的状态之间进行切换,因此,实现时可以使用“有限状态机”来完成上述的需求。“有限状态机”通常用来说明系统在几个“状态”之间进行转换,可以用图5来表示。
图5 玩家阵营角色的AI转换状态机
敌方阵营角色的AI转换则可用如图6所示的状态图来表示。
图6 敌对阵营角色的AI转换状态机
“有限状态机”用于游戏的AI开发时,并不是特别复杂的技术或理论,只需要应用者定义好几个“状态”,并且将每个状态的“转换规则”定义好,就可以使用“有限状态机”来完成AI的功能。
在《P级阵地》开始实现时,可以使用C#的枚举(enum)功能,将所有可能的状态列举出来,并且在角色类ICharacter中增加一个AI状态的属性。另外,也将在各状态下使用到的参数一并定义进去:
Listing1 角色AI的第一次实现
1 | // AI状态 |
因为游戏的需求,两个阵营角色的行为有如下差异:
- 玩家阵营:没有目标时,设为闲置状态(Idle),并留在原地。
- 敌方阵营:没有目标时,设为移动状态(Move),并向攻击的目标前进。
所以,将AI更新方法UpdateAI声明为抽象方法,分别由玩家阵营类ISoldier和敌方阵营类IEnemy两个子类重新实现。以下是ISoldier的实现:
Listing2 Soldier实现AI状态转换
1 | public class ISoldier : ICharacter |
以下是IEnemy的实现:
Listing3 Enemy角色实现AI状态转换
1 | public class IEnemy : ICharacter |
两个类都在UpdateAI方法中实现了“条件判断”和“有限状态机”的切换。但因为两个阵营对于没有目标时的需求不同,所以在闲置状态(ENUM_AI_State.Idle)和移动状态(ENUM_AI_State.Move)的处理方式不太一样,其他状态大部分是差不多的。
在游戏场景的转换--状态模式中说明《P级阵地》转换场景的功能时曾提及,“有限状态机”使用switch case来实现时会有一些缺点。所以,在第一次的实现范例中,也同样出现了类似的缺点:
- 1.只要增加一个状态,则所有switch(state)的程序代码都需要增加对应的程序代码。
- 2.与每一个状态有关的对象和参数都必须被保留在同一个类中,当这些对象与参数被多个状态共享时,可能会产生混淆,不太容易了解是由哪个状态设置的。
- 3.两个类的UpdateAI方法都过于冗长,不易了解和调试。或许可以将两个类中重复的程序代码重构为父类的方法来共享,但这样一来,又会造成父类ICharacter也过于庞大。
同样地,既然能使用“有限状态机”来实现角色的AI功能,那么就可以使用状态模式来解决上述缺点。
状态模式
有限状态机最简单的实现方式,就是使用switch case来实现。故而以往很容易看到,一个类方法中被一大串的switch case占据。有重构习惯的程序设计师会想办法让每一个case下的程序代码能够写到类方法中,但对于“状态转换”和“不共享参数的保护”也会是个麻烦的地方。善用状态模式可以让有限状态机变得不那么复杂。
GoF对状态模式的详细说明,已经在游戏场景的转换--状态模式中完整介绍过了,在此还是将结构图和角色说明列出,如图7所示。
图7 采用状态模式实现时的类结构图
参与者的说明如下:
- Context(状态拥有者)
- 是有一个具有“状态”属性的类,可以制订相关的接口,让外界能够得知状态的改变或通过操作改变状态。
- 有状态属性的类,例如:游戏角色有潜行、攻击、施法等状态;好友有上线、脱机、忙碌等状态;GoF使用TCP连接为例,有已连接、等待连接、断线等状态。
- 会有一个ConcreteState[X]子类的对象为其成员,用来代表当前的状态。
- State(状态接口类)
- 制定状态的接口,负责规范Context(状态拥有者)在特定状态下要表现的行为。
- ConcreteState(具体状态类)
- 继承自State(状态接口类)。
- 实现Context(状态拥有者)在特定状态下该有的行为。例如,实现角色在潜行状态时该有的行动变缓、3D模型要半透明、不能被敌方角色察觉等行为。
程序代码的实现部分在游戏场景的转换--状态模式中有详细说明,在此不再列出。
使用状态模式实现角色AI
就如之前提到的,状态模式是游戏程序设计中被应用最频繁的一个模式,而游戏程序设计师的新手第一次学习“有限状态机”的场合,多半是应用在AI的实现上。游戏程序设计书籍多半是以switch case作为入门的实现方式。当程序设计师了解有限状态机和状态模式的关联之后,想要转换到运用模式来实现,就不会那么困难了。
角色AI的实现
在开始运用状态模式时,先将《P级阵地》中的AI功能从角色类中独立出来。所以,先声明一个角色AI抽象类ICharacterAI,而继承它的SoldierAI和EnemyAI则分别代表玩家角色和敌方角色的AI。ICharacterAI类中拥有一个代表当前状态的IAIState类对象,IAIState的子类们分别代表角色当前的状态。类结构图如图8所示。
图8 采用状态模式实现游戏角色AI的类结构图
参与者的说明如下:
- IAIState:角色的AI状态,定义《P级阵地》中角色AI操作时所需的接口。
- AttackAIState、ChaseAIState、IdleAIState、MoveAIState:分别代表角色AI的状态:攻击(Attack)、追击(Chase)、闲置(Idle)、移动(Move)等状态,并负责实现角色在各自状态下应该有的游戏行为和判断。这些状态都可以设置给双方阵营角色。
- ICharacterAI:双方阵营角色的AI接口,定义游戏所需的AI方法,并实现相关AI操作。类的定义中,拥有代表当前AI状态的IAIState类对象,也负责执行角色AI状态的切换。
- SoldierAI、EnemyAI:ICharacterAI的子类,由于游戏设计要求双方阵营在AI行为上有不同的表现,所以将不同的行为表现在不同的子类中实现。
实现说明
AI状态接口IAIState,定义了在不同的AI状态下共同的操作接口:
Listing4 AI状态接口(IAIState.cs)
1 | public abstract class IAIState |
IAIState定义中的ICharacterAI类对象引用m_CharacterAI,主要指向AI状态的拥有者,通过该对象引用可以要求角色更换当前的AI状态。《P级阵地》一共实现了4个主要AI状态,分别为攻击(Attack)、追击(Chase)、闲置(Idle)、移动(Move),这些状态是双方阵营都可以使用到的。但因为双方阵营在闲置(Idle)状态下有不同的行为表现,这一部分的实现方式会在闲置状态类IdleAIState中进行判断:
Listing5 闲置状态(IdleAIState.cs)
1 | public class IdleAIState : IAIState |
闲置状态中利用“是否设置了攻击目标”,也就是m_bSetAttackPosition这个属性被设置与否,来决定角色在闲置状态下会不会转换为移动状态。而当前只有EnemyAI会通过调用SetAttackPosition“设置要攻击的目标”方法来启用这个功能,这方法主要是通知敌方阵营角色,在没有目标可攻击时,向阵地中心的方向前进。
Update是闲置状态的更新方法,它会从参数传递进来的目标中挑选一个最近的作为攻击目标。当攻击目标存在时,会先判断目标是否在武器可攻击的距离内,如果是在可攻击的距离内,则将角色更换为攻击状态,并攻击该目标:
Listing6 攻击状态(AttackAIState.cs)
1 | public class AttackAIState : IAIState |
攻击状态类会将攻击目标记录下来,并在更新方法Update中进行攻击。但如果目标角色已经阵亡或不存在时,则切换为闲置状态。另外,当目标角色的距离大于武器可攻击的范围时,则将AI状态改为追击状态,并将追击的目标设置给追击状态类:
Listing7 追击状态(ChaseAIState.cs)
1 | public class ChaseAIState : IAIState |
记录好追击的目标之后,追击状态类会在更新方法Update中持续地让角色往目标前进,直到目标进入武器可攻击的范围内时,转换为攻击状态(Attack)。但如果目标角色距离太远而超出追击范围(CHASE_CHECK_DIST)时,或者目标阵亡被删除时,就会转为闲置状态。
转为闲置状态的角色,会根据有没有设置“攻击位置”来决定是否要往“攻击位置”移动。若要往“攻击位置”移动,则会将状态转换为移动状态(Move),并将目标位置设置给移动状态类:
Listing8 移动的目标状态(MoveAIState.cs)
1 | public class MoveAIState : IAIState |
移动状态类记录攻击位置后,在更新方法Update中让角色往“攻击位置”移动。其间如果发现有可攻击的目标出现,就马上转为闲置状态,由闲置状态类来决定要攻击目标还是追击目标。当角色到达“攻击位置”,通知角色AI类ICharacterAI执行“占领阵地”,之后将自己设置为阵亡,实现目标。
而角色AI类ICharacterAI中,拥有一个AI状态对象引用,上述范例中所有状态的切换都需要通过该对象来进行(ICharacterAI类在本小节的最后还会进行一些修改):
Listing9 角色AI类(ICharacterAI.cs)
1 | public abstract class ICharacterAI |
更换AI状态方法ChangeAIState,除了会记录新的AI状态对象,也将自己的对象引用设置给新的AI状态对象。此外,还提供与游戏角色AI功能实现时所需要的操作方法。双方角色分别继承ICharacterAI后,实现各自的阵营AI:
Listing10 玩家阵营角色AI(SoldierAI.cs)
1 | public class SoldierAI : ICharacterAI |
Listing11 敌方角色AI(EnemyAI.cs)
1 | public class EnemyAI : ICharacterAI |
最后,将原本在角色类中旧的AI实现程序代码删除,增加一个ICharacterAI类对象作为执行角色AI功能的对象,并提供必要的操作方法:
Listing12 角色接口(ICharacter.cs)
1 | public abstract class ICharacterAI |
使用状态模式的优点
游戏角色的AI有时并不难,使用有限状态机即可完成。而有限状态机最适合运用状态模式来实现,并具有以下优点:
减少错误发生及降低维护的难度
不使用switch(m_AiState)来实现AI功能,可以减少新增AI状态时,因为没有检查到所有switch()程序代码而造成的错误,也让原本庞大的AI更新方法大为缩减,有利于后续的维护。
状态执行环境单一化
与每一个AI状态有关的对象及参数,都分别被包含在一个AI状态类下,所以可以清楚地了解每一个AI状态执行时,需要使用的对象及搭配的类。另外,与其他类使用的对象分开,也可以减少错误设置发生的机会。
角色AI执行流程
角色AI的执行流程图如图9所示。下面的流程图显示出,某一角色从“闲置状态”中发现可攻击目标后,转换为“追击状态”。在“追击状态”下,执行向目标移动的功能,并在武器可攻击的范围内转换为“攻击状态”,最后则是在“攻击状态”下攻击目标。
图9 角色AI的执行流程图
状态模式面对变化时
就在某一天,策划又来找小程了…
策划:“小程。”
小程:“又有什么需求想要更改啊?”
策划:“是这样的,我最近测试时突然觉得,玩家角色在阵地里站着等下一波敌人出现时,傻傻地站在原地,有点奇怪。”
小程:“嗯…是有那么点呆呆的感觉。”
策划:“是吧?你也这样觉得。那我们来改一下好了,玩家阵营角色在‘没有可攻击目标时’加入一个‘守卫状态’,这样你觉得如何,就像这张新的状态图(如图10所示)一样。”
图10 增加了“守卫状态”的新状态图
小程:“那玩家阵营角色在‘守卫状态’时,要执行什么功能吗?”
策划:“我想一下…那就到处走走吧。”
小程想了一下,在当前角色AI以状态模式实现的情况下,增加一个状态并不是太困难的任务。所以,小程新增了一个“守卫状态类”:
Listing13 守卫状态(GuardAIState.cs)
1 | public class GuardAIState : IAIState |
在“守卫状态”的角色,会不断地向随机位置移动。但是发现攻击目标出现时,就会马上转换为“闲置状态”,让闲置状态决定是要追击还是攻击目标。
完成守卫状态类后,再修改原来的“闲置状态”类,让“没有设置攻击目标”的角色,能转换成“守卫状态”:
Listing14 闲置状态(IdleAIState.cs)
1 | public class IdleAIState : IAIState |
小程在完成修改后,评估了一下:新增了一个类GuardAIState及修改了原有的闲置状类IdleAIState,对原有架构并未造成太大的变化。包含执行完单元测试(UnitTest),花费了不到1小时,所以在现有的状态模式设计基础下,对于这个游戏需求的修改,可以说是有效率的。
结论
使用状态模式可以清楚地了解单一状态执行时的环境,减少因新增状态而需要大量修改现有程序代码的维护成本。
而在《P级阵地》中,只规划了4个状态来实现游戏角色的攻击等实现需求,但对于较复杂的AI行为,可能会产生过多的“状态类”,而造成大量类产出的问题,这算是其中的缺点。不过,先前已经提到过:与传统使用switch(state_code)的实现方式相比,使用状态模式对于项目后续的长期维护上,仍是较具优势的。
与其他模式的合作
在角色的组装-建造者模式中,《P级阵地》将使用建造者模式(Builder)来负责游戏中角色对象的产生,当角色产生时,需要设置该角色使用的AI类和状态,这部分会由各阵营的建造者(Builder)来完成。
这也是另一个桥接模式(Bridge)的范例
如果读者再仔细分析一下XXXX节的角色AI类结构图及实现程序代码,以及XXXX节增加的“守卫状态”的修改方式,就可以理解,角色AI类(ICharacterAI)与AI状态类(IAIState)两者之间其实是采用桥接模式(Bridge)进行连接的。
角色AI类(ICharacterAI)是“抽象类”,定义了与AI有关的行为和操作,它的子类只负责增加不同的“抽象类”,如玩家角色AI(SoldierAI)和敌方角色AI (EnemyAI),而AI状态类(IAIState)是“实现类”,负责实现AI的行为和状态之间的转换(使用State来实现)。所以,当项目需要增加“守卫状态”时,不会影响到角色AI类(ICharacterAI)群组;当项目再新增一个角色AI类(ICharacterAI)时,也一定不会影响到现有的AI状态类(IAIState)群组。
其他应用方式
奇幻类型的角色扮演游戏(RPG)中,常有设置目标遭到法术攻击后会呈现的“特殊状态”,例如:
- 冰冻:角色不能移动,有特效出现。
- 晕眩:角色不能移动,会有眩晕动作。
- 变身:角色变成另一种形体,会在场上乱走动。
这些特殊状态,都可以使用状态模式来实现,但限制是,只能同时存在一个状态。
角色类
经过前面几章的介绍后,《P级阵地》角色类ICharacter的功能就大致完成了,角色架构如图1所示。
图11 角色架构1
在图1中,包含了与角色功能相关的类:
- 角色属性类ICharacterAttr:记录角色当前的最高生命值、攻击力,并负责计算攻击流程中所需要的属性。
- 武器类IWeapon:角色可以装备的武器。
- 角色AI类ICharacterAI:负责角色在游戏中攻击和防守等自动行为。
另外图1中还包含一些与Unity3D引擎有关的几个组件:
- UnityEngine.GameObject:负责角色在游戏中的3D模型数据,通过该对象引用可以设置Unity3D相关的功能,而有关实际创建出角色3D模型数据,将在下一个阶段进行说明。
- UnityEngine.AudioSource:负责播放角色在游戏进行中发出的音效。
- UnityEngine.NavMeshAgent:负责角色在场景中的自动寻路功能。以往,游戏中如果实现自动寻路功能,多半要自行开发寻路(Path Finding)算法(A*,Dijkstra……)。不过Unity3D引擎已经内置了不错的寻路系统,可节省许多开发时间。
角色类ICharacter算是《P级阵地》的重要类之一,但要让其实际运行起来,还需要一些系统进行协助,如图2所示。
图12 角色与其他系统
- 游戏角色管理系统CharacterSystem:管理游戏中双方阵营所产生的角色;并通过它的定期更新功能,让角色AI系统可以运行并产生自动化行为(攻击、防守)。
- 游戏角色生产和组装功能:一个游戏角色包含了3个游戏系统组件和Unity3D引擎相关的对象。所以在角色组装系统CharacterBuilderSystem中,会经过一定的步骤和流程,将这些组件产生并设置给一个角色。此外,将Unity3D引擎中的模型从资源目录下加载,并放入场景中也有一定的步骤。关于角色的组装,将在之后几章进行详细说明。
- 兵营系统与关卡系统:玩家通过兵营系统CampSystem产生玩家阵营的角色来防守阵地。而关卡系统StageSystem则是按照设置,不断地产生敌方角色来进攻玩家的阵地。
游戏角色管理系统
游戏角色管理系统CharacterSystem,在《P级阵地》中负责管理角色类ICharacter的对象。它是游戏主要类--外观模式中提到的一个“游戏系统IGameSystem”,它的类对象会在PBaseDefenseGame类中被定义和初始化,并在PBaseDefenseGame类的定期更新方法Update中被更新。
所谓的游戏角色“管理”指的是,角色管理系统CharacterSystem类会将当前游戏产生的角色类对象“记录”下来,并提供接口让客户端可以新增、删除、获取这些被记录的角色对象。而此处所称的“记录”则是使用C#的容器类List来完成。通过记录管理这些对象,让游戏系统可以有效地进行角色更新、数据查询、资源释放等操作。最重要的是,游戏中的角色之所以能够自动攻击和防守,就是由游戏角色管理系统CharacterSystem来执行的。
游戏角色管理系统CharacterSystem的类定义中,先定义的两个List容器类,分别来记录玩家角色和敌方角色:1
2
3
4
5
6
7// 管理创建出来的角色
public class CharacterSystem : IGameSystem
{
private List<ICharacter> m_Soldiers = new List<ICharacter>();
private List<ICharacter> m_Enemys = new List<ICharacter>();
...
}
并且提供与这两个容器相关的“管理”功能,包含新增、删除等方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72// 管理容器的相关方法
// 增加Soldier
public void AddSoldier( ISoldier theSoldier)
{
m_Soldiers.Add( theSoldier );
}
// 删除Soldier
public void RemoveSoldier( ISoldier theSoldier)
{
m_Soldiers.Remove( theSoldier );
}
// 增加Enemy
public void AddEnemy( IEnemy theEnemy)
{
m_Enemys.Add( theEnemy );
}
// 删除Enemy
public void RemoveEnemy( IEnemy theEnemy)
{
m_Enemys.Remove( theEnemy );
}
// 删除角色
public void RemoveCharacter()
{
// 删除掉可以删除的角色
RemoveCharacter( m_Soldiers, m_Enemys, ENUM_GameEvent.SoldierKilled );
RemoveCharacter( m_Enemys, m_Soldiers, ENUM_GameEvent.EnemyKilled);
}
// 删除角色
public void RemoveCharacter(List<ICharacter> Characters, List<ICharacter> Opponents, ENUM_GameEvent emEvent)
{
// 分別获取可以删除和存活的角色
List<ICharacter> CanRemoves = new List<ICharacter>();
foreach( ICharacter Character in Characters)
{
// 是否阵亡
if( Character.IsKilled() == false)
continue;
// 是否确认过阵亡事件
if( Character.CheckKilledEvent()==false)
m_PBDGame.NotifyGameEvent( emEvent,Character );
// 是否可以删除
if( Character.CanRemove())
CanRemoves.Add (Character);
}
// 删除
foreach( ICharacter CanRemove in CanRemoves)
{
// 通知对手删除
foreach(ICharacter Opponent in Opponents)
Opponent.RemoveAITarget( CanRemove );
// 释放资源并删除
CanRemove.Release();
Characters.Remove( CanRemove );
}
}
// Enemy数量
public int GetEnemyCount()
{
return m_Enemys.Count;
}
游戏角色管理系统的定期更新中,会先让所有角色进行更新,再进行角色AI的功能更新:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34// 系统定期更新
// 更新
public override void Update()
{
UpdateCharacter();
UpdateAI(); // 更新AI
}
// 更新角色
private void UpdateCharacter()
{
foreach( ICharacter Character in m_Soldiers)
Character.Update();
foreach( ICharacter Character in m_Enemys)
Character.Update();
}
// 更新AI
private void UpdateAI()
{
// 分別更新两个群组的AI
UpdateAI(m_Soldiers, m_Enemys );
UpdateAI(m_Enemys, m_Soldiers );
// 删除角色
RemoveCharacter();
}
// 更新AI
private void UpdateAI( List<ICharacter> Characters, List<ICharacter> Targets )
{
foreach( ICharacter Character in Characters)
Character.UpdateAI( Targets );
}
这里的“更新”并不是指Unity3D引擎MonoBehaviour中的Update方法,而是进行我们为开发需求所设计的“游戏系统”更新。就像在游戏的主循环-Game-Loop中提到的“单一的游戏系统”,对于《P级阵地》来说,游戏角色管理系统CharacterSystem和角色AI就是“单一的游戏系统”,所以必须通过之前设计的Game Loop机制来定期更新,并使它们运行:
“通过Game Loop,开发者可以为游戏系统定期更新功能,因为这个游戏系统类,不想通过继承MonoBehaviour且挂入某一个Unity游戏对象(GameObject)的方式,来拥有定期更新的功能。”
以角色AI功能为例,在UpdateAI的方法中,会分别更新两个阵营群组的AI方法。在更新每一个单位角色AI时(UpdateAI),都会将敌对阵营的全部角色以参数的方式传入。这样一来,每个角色在AI状态更新时,就会有全部的敌对角色可以引用,之后就可以从这些敌对角色中找出可攻击或追击的目标,接着完成AI状态的转换或维持现状。通过下面的流程图(如图13所示)就能了解整体系统的运行方式。
图13 系统流程图
在后续的阶段中,我们将继续介绍“游戏角色的生产和组装功能”以及“兵营系统与关卡系统”,也将介绍在一般实现时会遇到的问题,并提出使用设计模式的解决方法。