• 游戏开发中的一些基本方法


    一.              检測对象变化的两种基本方式:

    学过《微机原理》的人应该都了解这两种方式

    1.       轮询

        1) 每帧轮询

        2) 定时轮询

        按业务需求和性能问题选择

     

    2.       中断(并不是硬件中断,而是软件的事件通知方式)

    两种模式:

        1)       观察者模式

        长处:① 扩展性强,事件发起接口不变,仅仅需添加事件类型

                     ② 仅仅通知对某件事有兴趣的对象,不会浪费性能

                     ③ 每种事件相应一种回调函数,对于回调函数,事件參数类型是固定的,MouseMove事件的參数类型肯定是EventMouseMove,不会是EventKeyDown

        缺点:① 须要动态维护观察者列表,维护不好可能会通知到不存在的对象

     

        形态A 独立观察者模式:事件回调列表由每一个被观察对象独立维护

            ①   可能也会附带一个全局回调列表。仅仅是起辅助作用

            ②   CEGUI使用的是这样的方式

        形态B 全局观察者模式:事件回调列表由全局消息中心统一维护

            ① 全局回调列表比較庞大,消息中心的负载可能非常大


        2)  固定接口广播模式

        长处:① 不须要维护观察者列表,从根节点往下广播到整棵树

        缺点:① 对某事件不感兴趣的对象也会被通知到,性能稍许有浪费

                     ② 仅仅有树上的节点才干被通知到。全部逻辑对象不一定都在树上

                (假设这棵树仅仅代表游戏中各业务流程,那么非业务流程的对象不能被通知到;假设这棵树代表游戏中全部逻辑对象,那么全部对象都能被通知到,可是代价就是这棵树可能有点庞大)

     

        形态A 特定接口模式:一种事件相应一个发起接口

            ①   对于每一个发起接口。事件參数类型是固定的

            ②   扩展性较差。添加事件类型须要添加接口

            ③   适合于事件类型较少的框架结构

     

        形态B 统一接口模式:全部事件相应同一个发起接口

            ①   扩展性强。事件发起接口不变。仅仅需添加事件类型

            ②   事件发起接口的參数类型是不固定的,响应时须要依据消息类型去推断

     

        形态C 分组接口模式:类型接近的事件作为一个组。相应同一个发起接口;比方鼠标事件有好几种,这几种事件使用同一个发起接口,可是这个接口不会发起键盘事件,会有还有一个接口负责

            ①   扩展性较强,同一组内事件发起接口不变,仅仅需添加事件类型

            ②   添加事件组须要添加发起接口

            ③   事件发起接口的參数类型是不固定的。响应时须要依据消息类型去推断

     

     

    两种调用方法:

        1)     马上模式:事件立马处理,保证逻辑顺序。调试链不会断裂,相当于SendMessage

        2)  间接模式:事件保存在事件列表中。下次更新处理。不一定能保证逻辑顺序,调试链断裂,相当于PostMessage

     

     

    轮询的逻辑耦合性强,代码easy变得混乱,C++代码中必须包括目标对象头文件。性能也不一定好。适用于比較简单的程序结构。

    中断的逻辑耦合性弱,代码结构相对简单、清晰。C++代码中不须要包括目标对象头文件,仅仅须要包括事件定义的头文件,仅仅在必要的时候触发对应逻辑。性能较好,适用于较复杂和扩展性要求较高的程序结构。

     

    总结:看情况使用。轮询适用于业务需求较少、比較简单的程序结构。

    假设业务需求较多,考虑扩展性,中断是比較好的选择。中断的两种方法也能够结合使用。

     

    举例:

    主角3分钟无操作后:

        1)  头顶名字加上暂离字样

        2)  聊天信息框显示你已进入暂离状态

    主角退出暂离状态时:

        1)  去掉暂离字样

        2)  聊天信息框显示你退出了暂离状态


    解决方式:

    使用轮询模式:

        1)  Character Manager须要不停去查询主角是否进入、退出暂离状态。须要保存主角上一次状态并与当前状态比較,造成轻微硬编码。

        2)  暂离不是常态,导致轻微性能浪费

        3)不须要为此设计回调列表、不须要维护回调列表。几句if语句就能够处理

     

    使用中断模式:

        1)仅仅在必要的时候调用对应逻辑,代码整洁非常多。复杂性也减少。

        2)须要为这么一个简单的功能设计并维护回调列表。杀鸡用牛刀


    怎样选择:

             假设对主角对象还有若干类似的观察需求,则能够使用中断模式,比方观察主角移动

             假设对主角对象仅有这么一个简单需求。则能够选择轮询模式


    根本原因:

             对象变量当前值是什么是比較easy获取的,可是变量的跃迁一般不太会通知给外部对象,并且业务上对变量的跃迁需求较少。所以设计上easy忽略这方面内容。

     

     

    .      对象引用的三种基本方式:

    1.  直接引用,直接保存目标对象指针

             长处:性能较好;对象不存在了立即就能够知道,由于崩溃了

             缺点:对象生命期同步处理不好,会导致野指针,程序崩溃。新手easy犯错

     

    2.  间接引用。保存目标对象句柄/ID,须要引用对象时(向Manager)查询

             长处:假设对象不存在,不会导致程序崩溃

             缺点:性能较差,每次须要查询取得对象指针

               对象不存在了,外部程序可能还不知道,程序不崩溃,可是行为可能不对,可能造成程序性能轻微浪费

     

    3.      引用计数,直接引用的改进方式

             长处:性能较好。即使不能处理好对象生命期,也不会导致野指针

             缺点:不能保证非常好的掌握对象生命期。有时候就是不想要这个对象了,却不知道谁还保持了对它的引用。easy导致内存被无故占用

     

    总结:看情况选择

     

     

    .      參数传递的两种基本方式:

    1. 直接引用,直接保存目标对象指针

             长处:性能较好。没有复制开销

             缺点:逻辑耦合性强,对象生命期同步不好,会导致野指针,程序崩溃

               包括了大量不必要的信息,代码easy变得混乱,C++代码中必须包括目标对象头文件,使用目标对象的地方须要了解目标对象的知识

     

    2. 信息复制,定义“传递信息的中间对象”,复制须要的信息

             长处:逻辑耦合性弱,仅仅复制须要的信息。即使不能处理好对象生命期,也不会导致野指针

               代码结构相对简单、清晰,C++代码中不须要包括目标对象头文件。仅仅须要包括“传递信息中间对象”定义的头文件

             缺点:性能较差。略微有点复制开销,能接受

     

    总结:信息量较少时推荐使用信息复制

     

    举例:

    右击NetPlayer弹出操作菜单:私聊、加好友、组队、查看等

    这是一个异步操作,先弹出操作菜单,再从菜单上点击button,也可能不选择


    解决方式:

    使用直接引用方式:

        1)  显示菜单的代码须要了解NetPlayer对象的知识,须要包括NetPlayer头文件

        2)  菜单弹出后点击button前。NetPlayer断线或离开视野。假设未被通知到,再点击button,程序崩溃

    使用信息复制方式:

        1)  将菜单显示和button操作须要的信息,比方idnamelevel等,从NetPlayer指针复制中间结构体,作为菜单的userdata,从而菜单回调函数与NetPlayer对象断开连接

        2)  显示菜单的代码不须要包括NetPlayer头文件

        3)  即使点击菜单button前NetPlayer消失,也不会导致程序崩溃。

    使用事件通知(中断模式)

        1)  NetPlayer消失的时候隐藏操作菜单,就不会导致程序崩溃了

        2)  能够跟‘信息复制方式’ 结合使用

     

     

    四.对象成员变量创建/销毁资源的两种方式:

    1)使用构造函数与析构函数

             ① 构造函数做默认初始化,并创建资源

             ② 析构函数销毁详细资源。不一定须要将成员变量设置回默认值

             ③ 构造函数无返回值,不能通过返回值知道资源是否创建成功

             ④ 适用于含有较少资源的简单对象、或者创建资源不太会失败的对象

     

    2)使用Init/CreateFini/Destroy函数

             ① 构造函数做默认初始化,能够将对象先创建出来。占好位置,激活时再调用Init

             ② Init函数负责详细资源创建,能够有返回值推断资源创建情况

             ③ Fini函数销毁详细资源。将成员变量设置回默认值,通常该函数能够重入

             ④ 析构函数中须要调用Fini函数,或者加断言推断资源是否已销毁

             ⑤ 对象所占用内存不须要又一次申请就能够再次使用

             ⑥ Init时推荐使用do{}while(0)结构:

    do{

             member1 = create_ member 1()

             if (!member1)

                       break;

     

             member2 = create_ member 2()

             if (!member2)

                       break;

     

             ……

    }while(0)

     

     

    五.对象使用前初始化两种方式:

    1)谁使用,谁初始化

             ① 用完以后。成员变量仍然保留了上一次对象状态

             ② 不能直接拿来用。必须先初始化为默认值

             ③ 适用于不太含有资源的较简单对象,用完不销毁可能有资源泄露

             ④ 适用于入口比較统一的程序结构

    2)谁使用,用完恢复

             ① 用完以后,成员变量恢复为默认值,不保留不论什么对象状态

             ② 能够直接拿来用。对象成员变量已经是默认值

             ③ 适用于须要新创建资源的地方。用完时须要销毁资源

             ④ 适用于出口比較统一的程序结构

     

    总结:UI编程常常使用到,界面打开与关闭时各控件的状态

     

     

    六.几种主要的容错问题:

    1. 重入问题

        1) 不可重入:button灰态、加断言等方式来保证函数不被反复调用

        2) 可重入:button使能可是有提示、容错处理来保证函数即使被反复调用也不会产生问题

    比方Init函数,资源创建。不能反复调用。资源创建前加断言推断资源指针是否为空

    比方Destroy函数,资源销毁,每份资源销毁前加推断指针是否为空,函数可重入

     

    2. 边界问题

        1) 内存边界检查

        2) 数值边界检查

    数值边界问题不一定是有害的。比方::GetTickCount()返回的当前时间与上一帧时间相减,假设当前值超出临界值,相减溢出,结果也是正确的。

            

    3.       子对象间一致性问题

        1)       利用信息冗余。降低子对象数量

        2)       利用断言推断子对象间的值一致

    比方定时器会有个计时用的浮点变量m_fCountTime。和一个表示使能状态的布尔变量m_bEnabled,某些情况下。m_fCountTime <= 0就表示m_bEnabled == falsem_fCountTime > 0就表示m_bEnabled == true,因此两者之间存在信息冗余,m_bEnabled能够被删除掉, 成员函数bool IsEnabled() { return m_bEnabled; }能够改成bool IsEnabled() { return m_fCountTime > 0}

     

    总结:利用断言检查可能错误发生的地方,比方逻辑能否够执行到switchdefault语句。在有Destroy成员函数的情况下,在析构函数中推断子对象指针是否为空(表示Destroy是否被外部程序调用到而且已经销毁资源)

     

     

    七.逻辑状态及其变化的解释




     

    1).   计算机中主要的逻辑状态就是01。这个地球人都知道,如图,低电平表示0,高电平表示1,比方一个bool值,false就是0,低电平,true就是非0。一般是1,高电平。

    2).  大部分人的问题在于不能理解上升沿和下降沿,事实上数电课上都讲过这些概念。

    上升沿就是电平从0变到1的瞬间。下降沿就是电平从1变到0的瞬间。

        当电平从0变到1的瞬间。上升沿是1。其他时候都是0。当电平从1变到0的时候。下降沿是1,其他时候都是0


    举例:

    1).  上升沿、下降沿用的最多的地方是输入设备编程。button从松开(0)状态,到按下(1),这一瞬间、这一帧,上升沿为1,之后上升沿立即变为0。尽管按键仍然保持按下(1)。反过来。按键松开的一瞬间、那一帧。下降沿为1。之后下降沿立即变为0

    2).  另外比方上面讲的样例,轮询主角状态变化。主角从非暂离切到暂离的一瞬间,才须要去加上“暂离”字样,并显示提示信息你已进入暂离状态。并不是须要在主角暂离后的每一帧都显示这个提示信息。

    主角从暂离切回非暂离状态,那一瞬间。才须要去掉“暂离”字样。

     

     

    程序出现复杂问题的处理方法:

    1).  差异比較法

    2).  控制变量法

         比方修电脑的时候,用好的电脑上的模块替换问题电脑上的模块,找出问题所在

    3).  排除法,屏蔽不相关代码

    4).  尝试恢复到一个没问题的版本号

    5).  更换測试环境

     

     

    版本号出现故障的处理方法:

    使用二分法:先恢复到一个老版本号,看问题是否正常,不正常就使用更老的版本号,正常就二分计算中间版本号号。递归使用此方法

     

     

    十.定位/查找代码的处理方法:

    确定代码切入点。污染源查找法,扩散查找

    假设量污染太大不能掌控,可能是切入点找的不正确

     

     

    十一. UI编程要点:

    1).  键盘快捷操作用的三个基本按键:EscEnterTab

    2).  异步操作状态下,控件使能状态的控制。特别是button

               辅助工具:cloud,用来模拟网络延迟

    3).  控件的输入焦点与选中状态。降低用户操作

    4).  文本输入框的初始值、有效字符及最大值控制

    5).  界面又一次打开时的初始状态

    6).  界面关闭时是否要恢复状态

     

    假设你不知道该做些什么。该怎么做。答案非常easy:參考标准。什么是标准?比方操作系统。比方暴雪的游戏等。暴雪在细节方面做的非常到位的。

     

    举例:比方邮件发送金币,弹出一个输入框。框显示的时候,输入焦点要定位在Editbox上,Editbox初始值0,自己主动选中文本0。这时候按数字键,文本框中的0直接被替换掉,假设按字母键。应该无响应或者错提示音,不能输入负值。假设输入超出角色身上全部金额。应该限制在最大金额或忽略输入。输入完直接按Enter。接受输入值,对话框消失;不想继续输入或者想取消的时候。直接按Esc键。对话框消失;假设按Tab键。当前焦点应该在EditboxOkCancel之间按顺序切换,焦点切在Ok上。按回车等同于点击Ok。焦点切在Cancel上。按回车等同于点击Cancel;不论什么情况下按Esc,都是Esc效果;假设鼠标点击底框,焦点切究竟框上,按Esc,也应该对应。

     

     

    十二.Yes/No对话框与Ok/Cancel对话框的差别

             YesOk的操作一般都是一样的,接受某个事情

             NoCancel有点差别,No代表拒绝,Cancel表示忽略、什么也不做

             YesNoOkCancel不论什么一个button按了。对话框都应该消失

     

    举例:比方游戏中有人向你发起组队邀请,假设弹出一个Yes/No对话框,点了Yes,接受组队,点了No。会向server发送拒绝消息,发起人会收到一个被拒绝的消息。假设弹出一个Ok/Cancel对话框。点了Ok,接受组队,点了Cancel,表示忽略请求,不会向server发送拒绝消息,这个邀请就此结束了。

     

    所以另一类对话框,Yes/No/Cancel都有。

     

    总结:使用的时候,应该考虑清楚当下是使用Yes/NoOk/Cancel,还是Yes/No/Cancel

     

     

    十三代码Check In的好习惯

    1)提交之前确定哪些是须要提交的文件。暂时文件、不相关的文件统统剔除

    2)提交之前再次比对每一个文件改动。确保每一行改动是正确且相关的

    3)按功能、分块提交

        假设Commit的时候发现这次改动中涉及到几个功能改动,则分多次提交。每次都是独立的功能;

        假设是同一功能可是量比較大,能够把底层支持、业务无关的功能先提交,再提交各业务功能

        假设一个文件里涉及到不同功能的改动,则备份文件。恢复不相关的代码行,一次仅仅提交一个功能改动,完了之后再从备份文件里恢复其他改动,如此重复,不要嫌麻烦,确保一次提交中仅仅涉及一个功能块。

     

    总结:1)和2)加起来是两段式提交。这些习惯各自是为了“降低垃圾文件”、“降低出错几率”、“版本号出问题后方便定位”

     

     

    十四优化

    1).  古话云:过早的优化是万恶之源

    2).  过晚的优化会让你极其痛苦

    3).  优化应该融入到你的血液其中,时刻想着要设计比較优化、简化的代码和流程

    4).  优化也是个不断调整、不断迭代的过程,是个不断改动设计的过程,一次性非常难做好

    5).  代码优化的四(五)个层级:架构级、(模块级)、算法级、代码级、汇编级

        代码级主要是指函数參数、返回值、循环、赋值表达式等。

        汇编级太依赖于CPU。不做重点。并行处理的汇编指令(MMX/SSE等)能够作为学习内容。

    6).  如今的编译器优化工作都做的非常好,我们仅仅须要做适当引导

     

     

    十五游戏程序设计的宗旨:稳定和正确、简洁、高效、可扩展性

    1). 稳定和正确是基本条件。没有什么比这更重要,程序宁可执行的慢一点,也要保证稳定和正确。错误的代码执行的再快。也没有意义

    2). 简洁的设计easy让人理解,比較直观。通常也会带来高效的副作用

    3). 高效的代码绝对能体现一个人编程水平。保证稳定和正确情况下的高效是令人观赏的;高效有时候跟简洁是对立面,看情况取舍。高效对程序猿的素养要求更高,在人力成本较高的情况下。通过提高硬件配置来改善程序执行效率也是可行的

    4). 游戏编程策划需求千变万化,你的设计要比策划需求快半步,考虑到策划将来可能会添加的需求,减少代码的改动成本。太先进的可扩展性不一定可取。由于可能用不到,也有可能是通过减少性能来换取的,属于设计过度;可扩展性有时候跟简洁也是对立面。

            

    代码是写给人看的。不是写给机器看的。设计思路一定要简单、直观、易懂、合理,少绕弯。杜绝奇葩思路,文档凝视清楚齐全,代码保持整洁,版式清楚。该去的统统去掉。方便同事维护、方便后人维护,潜规则和硬编码尽量少定制,必须有潜规则、硬编码的情况下必须凝视清楚。推測别人的潜规则成本较高、也easy出问题。

     

     

    十六游戏程序设计的若干技巧:

    1).  不论什么计算机问题。都能够通过添加一个间接的中间层来解决

    2).  降低模块间耦合(就像设计电路板时降低各组件间的连线)

    3).  对象的继承与组合(m*nm+n),用于防止子类数量爆炸

    4).  lazy computepre compute

    5).  查表(通经常使用于pre compute和提高程序效率、时空互换的典型)

    6).  时空互换

    7).  层级架构(树、图)vs 平坦架构(数组、链表)

    8).  分时复用

    9).  分布式

    10). ……


     

    PS:现实世界的设计往往都非常巧妙,小而简洁的设计中往往包括着大哲学,大道至简,并不一定须要懂得复杂高深的算法才干设计程序,毕竟那些出现的几率非常低。比方imageset,把小图片合并成大图片,原先老的显卡上不支持非2的幂的贴图,你一张界面底框即使800x600,也会占用1024x1024的内存,imageset把小图片合成在一起,即利用了原本浪费的内存,又降低了渲染时贴图切换的开销(dxSetTextureStage),一定程度上还添加了读取磁盘文件的效率,一举三得,非常巧妙。

     

  • 相关阅读:
    【转】 【技巧 】 数学难题大揭秘:减少计算错误的技术
    [转]Mathematical Induction --数学归纳法1
    Vector Calculus
    test latex1
    [转]架构蓝图--软件架构 "4+1" 视图模型
    What Is Mathematics?
    二项式展开
    游戏系统设计
    Golang游戏服务器与skynet的个人直观比较
    [转]透过 Linux 内核看无锁编程
  • 原文地址:https://www.cnblogs.com/zfyouxi/p/5138322.html
Copyright © 2020-2023  润新知