3D Game Programming and Design Patterns

3D Game Programming and Design Patterns

Foreword

This is the Zhongshan University School of Science and Computer Data 2019 3D game programming and design asked him to blog, is a summary of the main design patterns learning courses a semester, and since it is a personal experience Unity3D programming.
All the code has been uploaded to the project among github, welcome to visit.
github profile: https://starashzero.github.io
3D game programming and design of the home page: https://starashzero.github.io/3DGameDesign

What design patterns will be used?

There are many existing design patterns, we recommend reading Alan Shalloway, written R. Trott James Design Patterns Explained , the book also has a Chinese version of "design patterns to resolve."
The book easy to understand, no concept too complicated, and some of the basic words will patiently explain, very suitable for beginners to learn.
Next, I summarize some of the main courses involved in the design mode, and although the course but did not mention the game program used to model and example usage scenarios

  • Composite Pattern-- combination mode
    combination mode (Composite Pattern), also known as part of the overall pattern, for a group of similar objects as a single object. Combination pattern based on a combination of the object tree structure, a partially used and the overall level. This type of design pattern belongs structural model, it creates a tree structure of the target group.
    This model created a class contains its own group of objects. This class provides a way to modify the same object group. (Rookie tutorial)
    combined mode is very common Unity programming design patterns, or almost never leave it in an object-oriented programming, after all, will be similar to what is classified as a Class unity process is our "common sense."
    Here Insert Picture Description
    For example, in Unity system, we used Component belongs combined mode, whether or MonoBehavior TransForm belong Component.
    Another example
    Here Insert Picture Description
    CCSequenceAction, CCMoveToAction belong SSAction, because they are the type of action
  • Strategy Pattern——策略模式
    在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
    在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。(菜鸟教程)
    策略模式总结起来就是以“以不变,应万变”,不变的是接口,变化的是实现。策略模式与组合模式比较相似,我认为其差异在于组合模式是以类型来进行封装,而策略模式是以功能来进行封装,当然很多时候这两种设计模式同时存在。
    Here Insert Picture Description
    例如上图的IActionManager、CCActionManager、PhysisActionManager,从类型角度上来看,这是组合模式,因为后两者都属于动作管理类;从功能角度上来看,这是策略模式,后两者代表着不同的动作管理策略。也就是说,组合模式看重的ActionManager,而策略模式看重的是CC与Physis,前者封装相似,后者封装变化。
  • Facade Pattern——门面/外观模式
    外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
    这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。(菜鸟教程)
    又是一个用于封装的设计模式,门面模式的目的是封装整个复杂的系统,而对外只提供简单的接口,对使用者隐藏实现细节并方便其使用。
    Here Insert Picture Description
    还是之前的动作管理的例子,要完成动作管理的功能,需要多个类的协作,但是用户(XXSceneController)只需要值得CCActionManager即可,实现细节不需要被了解,这样大大降低了用户的使用难度。
  • Adapter Pattern——适配器模式
    适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
    这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。(菜鸟教程)
    Adapter模式的目的就是将原本不兼容的器件通过适配器使其能够兼容系统,<<设计模式解析>>中与Facade模式进行了一些比较认为Facade模式是简化系统的接口,而Adapter模式是将一种接口转变为另一种接口。
    Here Insert Picture Description
    还是以动作分离为例,在前面我提到,CCActionManager、PhysisActionManager和IActionManager之间是Strategy模式和Composite模式,那么CCFlyAction、PhysisFlyAction和IActionManager之间,或者说SSAction的子类和IActionManager接口之间是什么模式呢?
    没错,就是Adapter模式。无论是CCFlyAction还是PhysisAction,它们与IActionManager接口本身是不兼容的,不能直接将它们接上去,而CCActionManager和PhysisActionManager就成为了Adapter。CCActionManager、PhysisActionManager实现IActionManager接口并且调用CCFlyAction、PhysisAction的方法,从而实现了两者的适配。
  • 动作分离
    动作分离并不是一个传统的设计模式,而是在老师课件中提出的,即将游戏中的动作单独划分出来进行实现。
    Here Insert Picture Description
    动作分离模式中主要有三个接口需要实现SSAction、ISSActionCallback、SSActionManager。SSAction封装动作的实现,SSActionManager封装动作的管理,ISSActionCallback实现回调(当然,后面有更合适的设计模式来实现回调)
  • Singleton Pattern——单例模式
    这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
    这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。(菜鸟教程)
    单例模式的主要目的是能够快速的获得单一实例,实例可以自己实现创建方法,也可以由他人创建但是可以被共享获取。
    在Unity游戏编程中,可以通过以下方法生成Singleton类
    public class Singleton<T> : MonoBehaviour where T: MonoBehaviour
    {
        protected static T instance;
    
        public static T Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = (T)FindObjectOfType(typeof(T));
                    if (instance == null)
                    {
                        Debug.LogError("An instance of " + typeof(T) + " is needed in the scene, but there is none");
                    }
                }
                return instance;
            }
        }
    }
    
    通过这个单实例类可以比较方便的获得Component实例,当然前提是这个Component提前被添加到对象上。
  • Factory Pattern——工厂模式
    这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
    在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。(菜鸟教程)
    工厂模式简而言之就是统一管理一个对象的创建与回收,在游戏编程当中经常能够用到
    Here Insert Picture Description
    以UFO游戏为例,UFO的产生与回收可以由DiskFactory的工厂对象来统一管理。RoundController不需要了解diskGameObject的创建与销毁方法,而将这些工作交给DiskFactory来做,这样大大提高了获取对象的效率。
  • Observer Pattern——观察者模式
    当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。(菜鸟教程)
    观察者模式是实现依赖反转的解决办法。在之前的动作分离中,我们使用了一个ISSActionCallback来进行回调,这种方式可以被称为代理模式。这种方式在1对1或者确定数量的1对多上有不错的效果,但是对于不确定数量的一对多的回调上就束手无策了,同时这种方式需要在被调用者中主动设置调用者的回调接口,也很不方便,而观察者模式提供了解决方案。
    Here Insert Picture Description
    如上图中,GameCharator实现了AttackRound的接口,同时attack-interested实现AttackHandle接口,attack-interested将AttackHandle加入到AttackRound的listener中实现观察,这样当GameCharactor向AttackRound发送事件的时候,attack-interested就能接收并处理。
    在C#中提供了这种模式的实现
    // publisher
    public delegate void AttackAction(Object sender, string info);
    public static event AttackAction OnAttackAction;
    //subscriber  
    void OnEnable(){
        XXEventManager.AttackeAction += Teleport;
    }
    void OnDisable(){
        XXEventManager.AttackeAction -= Teleport;
    }
    
    可以看到观察者模式类似于代理模式的扩展

游戏编程中常用的设计模式就总结到这了

程序框图有用吗?一定要用吗?

程序框图有用,但不一定要用。
程序框图作用主要有两个,一是在游戏编程开始前将自己的想法与设计通过框图记下来,而是通过框图来向他人介绍自己的程序。
因此程序框图不是必须的。如果在心中能够清晰的记下自己的计划,就不需要使用框图,因为需求是变化的,在编程的过程中可能有很多细节并不能提前想到。同时如果并不需要使用框图来介绍程序,也就不会用到框图。
但是我的建议是最好能够使用,尤其是写博客进行介绍时。

程序框图一般而言只要掌握类图即可,最初的时候可以跟着现有的类图画
Here Insert Picture Description

设计模式与3D游戏编程实例

我就以之前的Unity巡逻兵游戏-与怪兽战斗!为例子吧,来谈谈我是怎么设计游戏编程的。
在游戏编程前,如果完全不进行设计然后埋头苦干,很容易出现结构混乱、思路不清的问题,而设计的基础是要先找出需求。对于需求,我们可以遵循亚历山大的设计原则:

  • 每次一个
  • 背景优先

现在回到巡逻兵游戏上,我现在要做一个能够进行战斗、怪兽自动巡逻与追击的即时游戏,有哪些需求呢?

  1. 地图对象,游戏场景的基础
  2. 玩家对象,玩家操作的承载者
  3. 怪兽对象,敌人的承载者
  4. 游戏需要导演
  5. 游戏需要能够获得玩家的操作
  6. 地图需要能够探测玩家的位置
  7. 地图需要能够将玩家的位置告诉怪兽
  8. 玩家不在自己区域时,怪兽能够巡逻
  9. 玩家在自己区域时,怪兽能够追击
  10. 玩家进入攻击范围时,怪兽能够攻击
  11. 怪兽或许要有一些动画
  12. 怪兽有一些状态(受伤等)
  13. 怪兽可以被批量生成和回收
  14. 玩家需要能够移动
  15. 玩家能够攻击
  16. 玩家能够跳跃
  17. 玩家有一些状态(受伤等)
  18. 玩家或许要有一些动画

还有什么需求?可以不用担心落下什么需求,需求是可能变化的,在编程过程中可以进行扩展,因此设计模式很重要。
在需求中,1、2、3、11、16可以不通过编程来完成,因此我们掠过他们,得到新需求

  1. 游戏需要导演
  2. 游戏需要能够获得玩家的操作
  3. 地图需要能够探测玩家的位置
  4. 地图需要能够将玩家的位置告诉怪兽
  5. 玩家不在自己区域时,怪兽能够巡逻
  6. 玩家在自己区域时,怪兽能够追击
  7. 玩家进入攻击范围时,怪兽能够攻击
  8. 怪兽有一些状态(受伤等)
  9. 怪兽可以被批量生成和回收
  10. 玩家需要能够移动
  11. 玩家能够攻击
  12. 玩家能够跳跃
  13. 玩家有一些状态(受伤等)
  • 需求1、2
    在课程中有关于导演类和交互模块的框架,我们可以直接拿来进行使用
    Here Insert Picture Description
  • 需求3、4
    对于需求3,我们可以设置一个类用于探测玩家的位置
    对于需求4,可以用到Observer模式,将用户位置的事件发布给观察者
    Here Insert Picture Description
  • 需求5、6、10
    显然,这三个需求与运动有关,显然我们可以用到动作分离,单独编写动作部分
    Here Insert Picture Description
    由于各个需求的动作要求不同,SSAction使用Strategy模式Composite模式,将不同的动作进行封装
    Here Insert Picture Description
    使用Adapter模式将SSAction适配IActionManager接口
    Here Insert Picture Description
    这里可能会有疑问,不使用这些模式进行设计,简单的进行动作分离(即只是将动作分出来写),也可以实现,而且好像结构更简单?
    是的,实际上如果按照之前Adapter模式,一个SSAction的子类型(FollowPlayerAction、MovePlayerAction、MoveMonsterAction)应该对应一个ActionManager,我没有这样做的原因就是这样会使得程序没必要的复杂,我将ActionManager作为SSAction的Adapter而不是其子类型的Adapter,因此这还是Adapter模式
    假如完全不使用设计模式,将各个动作直接与接口相连,那么结构就会变得更简约,但是这个时候对系统进行扩展会非常麻烦,很可能需要对已有的代码进行大量的修改。
    事实上,如果用一个SSAction的子类来对应一个ActionManager,那么当增加一个动作需求(SSAction子类)的时候,可以完全做到对原有代码零修改,但是结构更复杂,这就涉及到取舍了。
    For example, I now Adapter usage, if a SSAction increase different types of (perhaps called DDAction), you can modify the source code zero, if the increase SSAction subclass, you need minor modifications to the original code.
  • 7,8,9 needs
    to be able to mass production and recovery of monsters, naturally think of using the factory pattern
    , but there are some monster own behavior and state, such as assault, injury, etc., and therefore need to increase the number of managers to deal with these events as monsters, monsters may also need to publish some of the events ( the Observer mode )
    previously mentioned figures will be released position, and monsters to get this information, you need to subscribe.
    Many of the above logic, but for the director does not need so much class in terms of content, how do? The Facade pattern on rafts handy.
    Combined at the above design:
    Here Insert Picture Description
  • 11,12,13 demand
    analogy monster practices need to set some managers to deal with the player's event, through Observer Mode released a number of events (players injured, death, score, etc.)
    Here Insert Picture Description

All needs are being taken into account, overall as follows:
Here Insert Picture Description
the game framework to design complete. After make good use of design patterns, even if the program as many as 22 categories, but the structure is still clear and strong expansion, the next task is to follow the plan step by step to write code.

Published 34 original articles · won praise 8 · views 2733

Guess you like

Origin blog.csdn.net/qq_20549085/article/details/104070481