游戏开发这个世界太广阔了,这篇文章中,我只在程序实现的抽象逻辑上,开一个口子进行一些肤浅的阐述。 当我去设计一个游戏,一个玩法,包含规则,游戏对象等等一系列的游戏系统,用纸可以写下来,用嘴可以说出来,但是当我想要用程序去实现这一堆东西的时候,就有些无从下手。 比如我们大家都会下五子棋,它有一个明确的规则“一人一字交替放置棋子,率先5颗连成一线获胜”,在现实世界中,我们不用考虑五子棋的硬件配置,拿一张白纸,画上横纵格子,两人分别执笔就可以完成一场五子棋的游戏,或者是使用正规的五子棋盘和棋子。无论用什么玩,要实现“玩”是很简单的。 但是在做一个五子棋视频游戏的时候,那就需要把“棋子”“棋盘”这些东西用软件去实现, 进一步,作为抽象概念的“游戏规则”“胜负判断”也需要软件去实现,这就需要更多的“用计算机思维去思考”。 于是我花了一些时间,凭借有限的本科计算机基础以及一些自我东拼西凑的自学,总结了一下游戏框架的一个通用设计方案。 一.随便开个头 首先阐述一下大的抽象概念,这里也不用被“抽象”二字吓到了,什么是抽象,就是归纳总结出来的结论之类的东西,就像把自行车,飞机,汽车都抽象为“交通工具”一样,这里归纳总结一下这些玩意的共性,于是“交通工具”这一个抽象概念就诞生了。 视频游戏程序框架,我认为她(she)就是一个循环(Loop)过程性,“开始游戏——杀敌/解密/闯关/…——游戏胜利/游戏失败——开始游戏”,对吧,关键字,循环(Loop)的,过程的。 进一步对此概念进行细化,衍生出GameState(游戏状态)和GameController(游戏/规则控制器),至于为什么会有这两个概念?我是参考了UE4提供的GameState类和GameMode类,同时也参考了前辈们用Flash写的游戏逻辑,至此我总结为GS和GC两大块,至于更高级的学术阐述,受限于我学识水平目前还做不到。 二.GameState GameState就是对游戏状态的描述,最基本的两个,GamePlay(开始游戏)GameOver(游戏结束),这里GameOver不能理解成玩家平时看见的GameOver,玩家平时看见的GameOver大多表示游戏失败,而在这里,这个概念只是表示游戏结束,因为无论你游戏胜利还是游戏失败,游戏都结束了(GameOver)。 在此之上,可以进行扩展,例如现在游戏都会有主菜单,也有暂停,或者有多人模式,游戏设置等等,于是我们可以在GameState中添加MainMenu,GamePause,MultiPlay,GameOption等等。 使用GameState进行游戏状态的管理有什么用?最重要的就是给你一个清晰的编程实现游戏的思路,或者说给你一个入手点,至于方便管理游戏进程,方便后续游戏程序功能性扩展,降低游戏程序各系统耦合性等等,实在太多了。 至于GameState如何使用?这东西就是用来切换的,作为标识游戏目前状态的一个Flag。 之前在阅读《游戏人工智能编程案例精粹》这本书中,其中详解了FSM(有限状态机)在游戏AI的应用和扩展,以此联想,对于GameState的处理使用FSM岂不是再合适不过了。《lua游戏开发实践指南》中提到“对于Singleton,无论什么情况下,只要它们提供方便就使用它们”,虽然这句话说的太满,不太符合中国人的思维,也不符合辩证法(笑),但也某种程度上表明Singleton的实用性和广泛性。所以更进一步,使用Singleton(单例模式)的FSM处理GameState,以我来看是极好的。 当然,以上是抽象概念的阐述,最终用到游戏程序设计上,还得按照一些编程语言的语法和特性来实现,这里,我也给出了一份Unity的C#的原型代码,可以当作伪代码吧。 Unity的C#的原型代码: publicclassS_GameState : MonoBehaviour { publicenumGameState { GamePlay, GamePause, GameOver, GameReady, GameInit } publicstaticS_GameState Instance; privateGameState m_GameState; void Awake() { Instance = this; } publicGameState GetGameState() { returnm_GameState; } publicvoid ToGamePlay() { m_GameState = GameState.GamePlay; } publicvoid ToGamePause() { m_GameState = GameState.GamePause; } publicvoid ToGameOver() { m_GameState = GameState.GameOver; } publicvoid ToGameInit() { m_GameState = GameState.GameInit; } } 补记: 至于FSM和Singleton,请教谷歌老师是一个很好方法,不过,这里我稍微阐述一些FSM的应用,FSM早些年用于游戏AI的构建,毕竟游戏AI不同于科研领域的AI,游戏AI不是为了让玩家无法战胜而设立的,基本上游戏AI就是一套规律的集合,比如《黑暗之魂3》的第一个BOSS——古达,其中一个规律就是半血之后会变身,变身时候有动画,不会攻击,玩家可以很安逸的砍空一条体力,然后全身而退,这个规律,打几百遍古达都不会变。 回到原来话题,FSM广泛应用于游戏AI的地位现在基本被行为树取代了,然后FSM现在就专职做起了角色动画的管理,比如第三人称玩家角色的各种动画切换,UE4和Unity都使用FSM进行动画状态切换的管理。 三.GameController 说完了GameState,就该说一下GameController了 GameController就是用来管理游戏规则的了,比如开始游戏了,要生成玩家角色,生成敌人,生成地图等等;玩家按了ESC,要暂停游戏了;玩家通关了,妙极,GameOver; 所以GameController比起GameState要更加具体,基本上GameController就是要处理GameState切换之后一系列工作,依旧是五子棋,比如GameState从MainMenu切换到了GamePlay,那么GameController就要加载一个载入画面,载入结束后,消掉载入画面,同时显示一个棋盘等待。当然,OnLoading也可以作为一个GameState,至于要不要这个OnLoading的GameState,就归到弹性选项里面好了(笑)。 这里就需要一个游戏状态的检测,如果游戏某一时刻切换了状态,那么作为GameController就得做点事情了,所以要快!准!狠! 于是在一个独立线程里面整个一while(true)来不断循环判断是否游戏状态进行了切换 当然GameController也可以使用一个Singleton来实现,我认为是不错的,不仅可以异步执行,而且很快,充分满足快准狠的需要,而且分离了游戏的渲染线程和逻辑线程,并行优化/劣化好像很不错(笑)——对于一般的小型游戏,多线程可能会杀鸡用牛刀,反而劣化的游戏性能。 不过现在游戏引擎都体统了一个每帧调用的函数,所以将游戏状态检测放在里面也是很好的,以下,提供一个Unity的C#代码,以供参考。 Unity的C#的原型代码: void Awake() { Instance = this; } void Start() { m_StartWait = newWaitForSeconds(m_StartDelay); m_EndWait = newWaitForSeconds(m_EndDelay); StartCoroutine(GameStateOperator()); } IEnumeratorGameStateOperator() { while(true) { switch(S_GameState.Instance.GetGameState()) { caseS_GameState.GameState.GameInit: GameInit(); break; caseS_GameState.GameState.GameReady: GameReady(); break; caseS_GameState.GameState.GamePlay: GamePlay(); break; caseS_GameState.GameState.GamePause: GamePause(); break; caseS_GameState.GameState.GameOver: GameOver(); break; } yieldreturnnull; } } 补记: 这里使用了Unity的伪线程——协程,方便使用多线程进行参考,同时也为了让代码结构清晰一些。 四.状态切换 GameState提供了游戏状态来切换,GameController提供了状态切换后的工作处理,那么问题来了,什么玩意儿来切换GameState呢? 自然是游戏对象了,GameObejct,玩家角色,Npc,某个子弹,某些触发器,键盘操作等等。回到上面看看GameState的切换函数使用了public,也是这个原因,反正都是static了,稍微裸一点,我觉得颇为不错(笑)。 回到原来话题,比如玩家角色也许有一个HP的属性,当HP==0时,GameOver,调用一下GameState的状态切换函数,将状态切换称GameOver,这时,GameController检测到了GameState切换了,开始做出响应,很完美。 当然GameController也可以用一用状态切换,比如一些类似关卡倒计时计算需要写在GameController里面,当倒计时为0是,游戏结束,这时候就需要GameController调用切换函数了。同样的,对于玩家角色的HP检测放到GameController里面也未尝不可,不过这显然不符合OOP的设计原则,这么明显的增加耦合性,软件工程老师要气死。但是我就是要气死他(笑)。 补记: 对于游戏对象的分析和设计,我觉得需要综合OOP思想和设计原则,虽然设计主流游戏类型,大多有原型了,比如UE4就很人性化,提供了各种类型游戏原型,这里UML之类工具使用起来也是极好的。当然不能忘了UI的设计,UI是要契合游戏程序的,有效利用GameState是很好的入口点。想要更多了解关于游戏设计和培训者方面知识,可以进官网https://youxi.hxsd.com/查看
|