作为一个饱经风霜的程序员,你一定早就习惯了游戏开发中的反复。记得刚来公司的时候就听师兄讲过一个故事:策划在国庆节前突然想模仿微信做一个红包系统,还没等他实现完毕,红包二期的案子就已经写好了。不巧,这个幸运的师兄碰巧要去同学聚会,就暂时没有做。等他请假2天回到公司的时候,惊奇地发现红包二期已经被推翻了,变成了红包三期,师兄长舒一口气,我想此刻他的内心是复杂的,甚至复杂到不能用代码表达~~

那我们的代码到底能不能经得起折腾呢?大概有两种类型的代码让我印象深刻:

1.复杂的继承

计算机专业出身的同学对面向对象编程一定不陌生,这基本上是所有学校的计算机必修课,其实它也对很多公司的游戏架构产生了深远的影响,在我们公司的代码库里,也有它的身影。在长期的维护中,这些本身设计良好的继承关系由于需求的变动不断改变着继承关系,最后的结果就是,出现了很深很深的继承,这样的代码很难维护,就像一个人很难一下说出自己应该如何称呼自己的爸爸的爸爸的爸爸的爸爸的儿子,一个需求的改变可能要求你理清这些类之间的关系,是要先调用父类的函数呢,还是先调用父类的父类的函数呢,还是重写它。你甚至会陷入一种旋涡,这样的设计很难让逻辑清晰。

2.单个文件包含多种功能以及特判

这个可以举一个实际的例子,游戏中有多种不同类型的战斗,在战斗的过程中,我需要显示血条、倒计时、连击数、方向盘、技能面板、伤害排行榜、暂停按钮、托管按钮等等等等。但是不巧,每一种战斗需要的内容是不一样的,比如排行榜只在组队模式下需要,而暂停按钮则不能在PK模式中显示,血条在PK模式要换另一种表现方式。一开始这对我们来说很简单,几个ifelse就可以轻松搞定,但是看看维护了一年之后的UI代码吧,3000行的代码和充斥着整个文件的20多种战斗的特判,没有人敢维护这块代码,因为它基本一改就会在别的关卡中出现BUG。

之前的代码大概是这个样子的,是不是感觉似曾相识呢。

  1. function GameBattleUI:init()
  2. if gameMode == A then
  3. hP:setVisible(false)
  4. skillBoard:setVisible(true)
  5. joystick:setVisible(true)
  6. skillBoard.skillA:setVisible(true)
  7. skillBoard.skillB:setVisible(false)
  8. --some code
  9. elseif gameMode == B then
  10. hP:setVisible(true)
  11. skillBoard:setVisible(true)
  12. joystick:setVisible(true)
  13. skillBoard.skillA:setVisible(true)
  14. skillBoard.skillB:setVisible(true)
  15. skillBoard.skillC:setVisible(true)
  16. elseif gameMode == C then
  17. --some code
  18. end
  19. skillBoard.skillA:addTouchEventListener(
  20. function (sender, eventType)
  21. if eventType == ccui.TouchEventType.ended then
  22. if gameMode == A then
  23. --do something
  24. elseif gameMode == B then
  25. --do something
  26. end
  27. end
  28. end
  29. )
  30. end
  31. function GameBattleUI:update()
  32. --和上面差不多
  33. if gameMode == A then
  34. elseif gameMode == B then
  35. elseif gameMode == C then
  36. end
  37. end

复制代码

面对上面的问题,明显我们应该做点什么。很不幸,当时老大把重构战斗UI的任务交给了我,都说做成事情的第一步就是开始动手,在和大家进行了简单的讨论后,我们得到了几个结论:

1.很多功能在不同的战斗中是相同的,比如方向盘,不管在哪都是上下左右

2.功能模块之间互相不需要交互,比如方向盘和血条技能什么的没有任何关系

3.我们需要它变得更灵活,可以很方便地在某种战斗中增加一个新的自定义UI类型

这仿佛就是在告诉我们:你快用组件啊,就像堆砌乐高积木一样去创造你的游戏世界!是的,用组件模式来重构真的再适合不过了。

大概是这个样子,ui_game_battle_normal是某种战斗的UI,我只需要创建一个空壳,然后把每个需要的组件添加进去就够了,对于ui_game_battle_city_pk这个界面,我只需要倒计时、方向盘和HP,那我就只把他们增加进去。

  1. function cls_uiGameBattleCity3PK:InitUI()
  2. local timeComponent = CreateComponentTime()
  3. self:AddComponent({
  4. component = timeComponent,
  5. zoder = 10,
  6. layerTag = data.battle.UI_TAG_BATTLE_UI_NORMAL
  7. })
  8. local playerHpComponent = CreateComponentPlayerHPUI()
  9. self:AddComponent({
  10. component = playerHpComponent,
  11. zoder = 10,
  12. layerTag = data.battle.UI_TAG_BATTLE_UI_NORMAL
  13. })
  14. local joystickComponent = CreateComponentJoystick()
  15. self:AddComponent({
  16. component = joystickComponent,
  17. zoder = 10,
  18. layerTag = data.battle.UI_TAG_BATTLE_UI_NORMAL
  19. })
  20. end

复制代码

addComponent和管理组件的方法写在一个基类ui_game_battle_base中,因为我们不关心QQ靓号买卖平台组件具体的内容,只需要对它进行初始化和更新就行了,以下代码中的OnUpdate由游戏引擎的时钟驱动,然后分别调用组件的更新。

  1. function ui_game_battle_base:AddComponent(args)
  2. local component = args.component
  3. local zoder     = args.zoder
  4. local layerTag  = args.layerTag
  5. local componentName = component.__cname
  6. local layer = self[tag_to_layer[layerTag]]
  7. if layer then
  8. layer:addChild(component, zoder)
  9. if self.ComponentList[componentName] ~= nil then
  10. self.ComponentList[componentName]:removeFromParent()
  11. end
  12. self.ComponentList[componentName] = component
  13. else
  14. echoError("[ui_game_battle_base][AddComponent] layer error" .. tostring(layerTag))
  15. end
  16. end
  17. function ui_game_battle_base:OnUpdate()
  18. for k,v in pairs(self.ComponentList) do
  19. if v.OnUpdate then
  20. v:OnUpdate()
  21. end
  22. end
  23. end

复制代码

如此这般的设计,使代码比原来灵活许多,逻辑也变得清晰,我们先实现一堆component,再用各种方式组合,形成类似ui_game_battle_normal这样的实体,不仅代码得到了解耦,以后策划再要增加一个新的功能也变得异常简单,要么是修改其中的一个组件,要么就是直接新建一个组件,然后把它add到实体中就可以了。重构完成后,看着简单的目录结构,内心觉得很舒服,LZ再也不怕策划改这块代码了!

不过除了让人舒服的特性,在实现的过程中也遇到了一些小麻烦。因为某种原因,倒计时模块需要通知血条模块进行变化,当时有点傻眼,组件之间到底应该怎么交互呢?之前的结构貌似完全没有考虑过这个问题,如果我们随便去获取其他组件,本来清晰的结构不是又要乱掉了吗?组件之间会互相依赖!fuck!

《游戏编程模式》中提出了3种解决方案,和之前公司分享时讲到的方法几乎完全相同,看来这是经过无数人实践得出的优质答案:

1.由容器储存公用的变量,组件间可以通过访问这个容器中的变量进行通信(缺点:可能有些容器中不需要这个变量,但是通用的容器还是要定义,浪费内存)

2.保存另一个组件的引用。如果我们确定两个组件之间有关系,可以在初始化的时候就传入需要的组件的引用。(缺点:容器之间的关系很可能变得很复杂,甚至初始化顺序都要有要求)

3.在容器中实现通用的消息机制,每个组件可以通过容器发送广播,感兴趣的组件自己监听对应的消息就可以了(缺点:事件太多之后,你会往返于事件之间,搞不清楚谁的更新依赖谁)

这3种方式各有优缺点,比如说一个对象的位置信息是很常见的,完全就可以使用第一种方式,把它定义在容器中,所有组件都可以访问。这时候后面两种就显得很不合适。具体要使用哪种方案要根据实际的需要进行分析,选取最优的。就像《游戏编程模式》中说到的:意料之外的是,没有哪个选择是最好的。你最终有可能将上述所说的三种方法都用到

最后的最后,发表一下对组合的感叹,这是一种非理性的感叹:组合是创造之魂,它符合世界本身的运行规律,就像质子、中子、电子组成分子,细胞组成人,各种不同的硬件组成了你的台式电脑。组合存在着无数的可能性,来来来,尝试把这两个家伙放在一起,看看会发生什么!

游戏编程技巧分析:策划变心太快?也许可以使用组合相关推荐

  1. c语言编程技巧分析,C语言难点及编程技巧分析

    摘要:近些年信息技术发展迅速,社会需要大量专业的计算机人才,C语言是学习计算机知识的专业理论课程,能够为以后的学习打下专业基础.但目前C语言学习受到各方面因素的影响,学习效果不是很理想,很多时候即使努 ...

  2. C 编程语言多少数学知识,编程,需要多少英语,数学知识?初中毕业直接修编程,会不会太快。学不进?...

    编程,需要多少英语,数学知识?初中毕业直接修编程,会不会太快.学不进?以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! 编 ...

  3. 现代计算机理论基础是什么_为什么旧游戏在现代计算机上运行得太快?

    现代计算机理论基础是什么 If you've ever tried to get a vintage computer game up and running on a modern system, ...

  4. java directdraw_《Windows游戏编程技巧大师》就DirectDraw而创建DirectDraw知识笔记

    1.DirectDraw 这可能是Directx中最重要的技术,由于它是2D图形赖以实现的渠道.也是Direct3D构建于其上的帧缓冲层. 2.DirectDraw是由非常多借口组成的.共同拥有5个接 ...

  5. Windows游戏编程大师技巧(一)

    第一章 无尽之旅 Windows编程是一场由来已久并还在进行着的战争.开始时,游戏程序拒绝Windows平 台,但正如Borg所言:"反对无效......",我也赞同这一观点.本章 ...

  6. Android 4游戏编程入门经典

    <Android 4游戏编程入门经典> 基本信息 原书名:Beginning Android 4 Games evelopment 作者: (美)Mario Zechner Robert ...

  7. 《Windows游戏编程大师技巧》(第二版)第11章

    第三部分:核心游戏编程   第11章 算法.数据结构.内存管理和多线程   第12章 人工智能   第13章 游戏物理   第14章 文字时代   第15章 综合运用:编写游戏! 第11章 算法.数据 ...

  8. 《Windows游戏编程大师技巧》(第二版)第2章

    因此几乎是一夜间,Windows 95就改变了整个计算机行业.的确,目前还有一些公司仍然在使用Windows 3.1(你能相信吗?),但是Windows 95使得基于Intel的PC成为除游戏之外的所 ...

  9. 《Windows游戏编程大师技巧》(第二版)第1章(上)

    第1章 学海无涯 "Oh, you want some too?!?" -Hudson, Aliens Windows 编程就像是一场由来已久并还在进行着的战争.尽管游戏程序员曾经 ...

最新文章

  1. ios移动输入框被软键盘遮挡
  2. 滑动验证码、文件上传、form、AJAX.....
  3. 卸载Macports,安装HomeBrew
  4. 利用T-SQL语句创建数据表
  5. IntelliJ IDEA 安装问题解决
  6. java中 queryparam_@PathParam 和 @QueryParam
  7. tensorflow 小于_TensorFlow做Sparse Machine Learning
  8. linux安装vnc4server,Ubuntu 18.04安装vnc4server
  9. 设计模式 笔记 解释器模式 Interpreter
  10. 众昂矿业:萤石行业发展四大趋势
  11. Matlab有趣代码
  12. 下载加速小妙招,我不允许你不知道
  13. NTFS文件系统文件删除对比
  14. 详解word2vec
  15. Android 蓝牙系统打开蓝牙源码分析(一)--- 全网最详细
  16. 荒野行动计算机高考题,叮咚,这里有一份荒野行动强迫症测试试卷请查收~
  17. [附源码]Python计算机毕业设计大学生社团管理系统
  18. 蓝牙耳机哪款好用?这些选购小技巧帮你选到更适合你的蓝牙耳机!
  19. 围棋学习18k到7k
  20. 右击计算机管理打开会闪退,win10应用商店为什么会闪退 win10应用商店出故障怎么修复...

热门文章

  1. 极力推荐5款我一直在使用的Chrome优秀插件!
  2. python 深度 视差 计算_OpenCV-Python教程:49.立体图像的深度图
  3. 多线程相关-ThreadPoolExecutor
  4. 钉钉小程序数据传递——子传父,父传子
  5. 从零开始学安全(三)●黑客常用的windows端口
  6. Python(七) 元组+集合+随机+string
  7. appium定位WebView页面元素
  8. 求助:如何获取ueditor的上传路径
  9. SQL Server 自增字段重置
  10. linux下51单片机开发解决方案