游戏开发中常用的设计模式

使用设计模式来提高程序库的重复利用性是大型程序项目开发必须的。但是在“四人帮”的设计模式概述中提到了23种标准设计模式,不但难以记住,而且有些设计模式更多的适用于应用程序开发,对游戏项目引擎设计并没有很多的利用价值。根据经验,精挑细选后,笃志在这里记录一些自认为有利用价值的设计模式,以便之后自己设计时使用。

 

一:观察者Observer

观察者的设计意图和作用是 它将对象与对象之间创建一种依赖关系,当其中一个对象发生变化时,它会将这个变化通知给与其创建关系的对象中,实现自动化的通知更新

 

游戏中观察者的适用环境有

1UI控件管理类。当我们的GUI控件都使用观察者模式后,那么用户的任何界面相关操作和改变都将会通知其关联对象-----我们的UI事件机。

2:动画管理器。很多时候我们在播放一个动画桢的时候,对其Frame有很大兴趣,此时我们设置一个FrameLister对象对其进行监视,获得我们关心的事件进行处理是必须的。

 

观察者伪代码:

//-------------------------------------------------------------------------------------------------------

// 被观察对象目标类

Class Subject

{

       // 对本目标绑定一个观察者 Attach( Observer );

       // 解除一个观察者的绑定   DeleteAttach( Observer );

       // 本目标发生改变了,通知所有的观察者,但没有传递改动了什么

       Notity()

       {

              For ( …遍历整个ObserverList …)

              { pObserver ->Update(); }

       }

// 对观察者暴露的接口,让观察者可获得本类有什么变动GetState();

}

//-------------------------------------------------------------------------------------------------------

// 观察者/监听者类

Class Observer

{

       // 暴露给对象目标类的函数,当监听的对象发生了变动,则它会调用本函数通知观察者

       Void Update ()

       {

              pSubject ->GetState();  // 获取监听对象发生了什么变化

              TODODisposeFun();  // 根据状态不同,给予不同的处理

       }

}

//-------------------------------------------------------------------------------------------------------

 

非程序语言描述

AB的好朋友,对B的行为非常关心。B要出门,此时A给了B一个警报器(Notity),告诉B说:“如果你有事,立刻按这个警报器告诉我。”。结果B在外面遇上了麻烦,按下警报器(Notity),B就知道A出了事,于是就调查一下B到底遇到了什么麻烦(Update()中调用GetState()),当知道B原来是因为被人打了,于是立刻进行处理DisposeFun(),派了一群手下帮B打架。

当然关心B的人可以不止一个,CD可能也对B很关心,于是B这里保存一个所有关心它的人的链表,当遇到麻烦的时候,轮流给每个人一份通知。

 

二:单件模式Singleton

单件模式的设计意图和作用是    保证一个类仅有一个实例,并且,仅提供一个访问它的全局访问点。

 

游戏中适用于单件模式的有

1:所有的Manger。在大部分的流行引擎中都存在着它的影子,例如SoundManager, ParticleManager等。

2:大部分的工厂基类。这一点在大部分引擎中还是见不到的,实际上,我们的父类工厂采用唯一实例的话,我们子类进行扩展时也会有很大方便。

 

单件模式伪代码

//-------------------------------------------------------------------------------------------------------

Class Singleton

{

       Static MySingleton;       // 单件对象,全局唯一的。

       Static Instance(){ return MySingleton;}              // 对外暴露接口

}

//-------------------------------------------------------------------------------------------------------

 

三:迭代器Iterator

迭代器设计意图和作用是    提供一个方法,对一个组合聚合对象内各个元素进行访问,同时又不暴露该对象类的内部表示。

 

游戏中适用于迭代器模式的有    因为STL的流行,这个设计已经广为人知了,我们对任何形式的资源通一管理时,不免会将其聚合起来,或者List,或者Vector,我们都需要一个对其进行访问的工具,迭代器无疑是一个利器。

 

迭代器伪代码

//-------------------------------------------------------------------------------------------------------

// 迭代器基类

 Class Iterator

{

              Virtual First();              

              Virtual Next();

              Virtual End();

              Virtual CurrentItem();    // 返回当前Item信息

}

//-------------------------------------------------------------------------------------------------------

// 聚合体的基类

Class ItemAggregate

{

              Virtual CreateIterator();  // 创建访问自身的一个迭代器

}

//-------------------------------------------------------------------------------------------------------

// 实例化的项目聚合体

 Class InstanceItemAggregate : public ItemAggregate

{

              CreateIterator(){ return new InstanceIterator(this); }

}

//-------------------------------------------------------------------------------------------------------

 

四:访问者模式Visitor:

访问者设计意图和作用是    当我们希望对一个结构对象添加一个功能时,我们能够在不影响结构的前提下,定义一个新的对其元素的操作。(实际上,我们只是把对该元素的操作分割给每个元素自身类中实现了而已)

 

游戏中适用于访问者模式的有    任何一个比较静态的复杂结构类中都适合采用一份访问者。这里的“比较静态的复杂结构类”意思是,该结构类中元素繁多且种类复杂,且对应的操作较多,但类很少进行变化,我们就能够将,对这个结构类元素的操作独立出来,避免污染这些元素对象。

       1:例如场景管理器中管理的场景节点,是非常繁多的,而且种类不一,例如有Ogre中的Root, Irrchit中就把摄象机,灯光,Mesh,公告版,声音都做为一种场景节点,每个节点类型是不同的,虽然大家都有共通的Paint(),Hide()等方法,但方法的实现形式是不同的,当我们外界调用时需要统一接口,那么我们很可能需要需要这样的代码

       Hide( Object )

       { if (Object == Mesh) HideMesh();  if (Object == Light) HideLight();  …  }

此时若我们需要增加一个Object新的类型对象,我们就不得不对该函数进行修正。而我们可以这样做,让Mesh,Light他们都继承于Object,他们都实现一个函数Hide(),那么就变成

       Mesh::Hide( Visitor ) { Visitor.Hide (Mesh); }

       Light::Hide(Visitor ){ Visitor.Hide (Light); }

我们在调用时只需要Object.Hide(Visitor){ return Visitor.Hide(Object/*(this)代替*/); }

 

这样做的好处,我们免去了对重要函数的修正Object.Hide(Visitor){}函数我们可以永久不变,但是坏处也是很明显的,因为将方法从对象集合结构中抽离出来,就意味着我们每增加一个元素,它必须继承于一个抽象的被访问者类,实现其全部函数,这个工作量很大。(#add,以及添加一个访问者对该新增加元素的访问方法

所以,访问者是仅适合于一个装载不同对象的大容器,但同时又要求这个容器的元素节点不应当有大的变动时才使用。另外,废话一句,访问者破坏了OO思想的。

 

 访问者伪代码

//-------------------------------------------------------------------------------------------------------

//  访问者基类

Class Visitor

{

              Virtual VisitElement( A ){ … };             // 访问的每个对象都要写这样一个方法

              Virtual VisitElement( B ){ … };

}

 

// 访问者实例A

Class VisitorA

{

       VisitElement( A ){ … };         // 实际的处理函数

       VisitElement( B ){ … };        // 实际的处理函数

}

 

// 访问者实例B

Class VisitorB

{

       VisitElement( A ){ … };         // 实际的处理函数

       VisitElement( B ){ … };        // 实际的处理函数

}

 

 

// 被访问者基类

Class Element

{

       Virtual Accept( Visitor );        // 接受访问者

}

 

// 被访问者实例A

Class ElementA

{

       Accecpt( Visitor v ){ v-> VisitElement(this); };    // 调用注册到访问者中的处理函数

}

 

// 被访问者实例B

Class ElementB

{

       Accecpt( Visitor v ){ v-> VisitElement(this); };    // 调用注册到访问者中的处理函数

}

 

//-------------------------------------------------------------------------------------------------------

 

五:外观模式Façade

外观模式的设计意图和作用是   将用户接触的表层和内部子集的实现分离开发。实际上,这个模式是个纸老虎,之后我们看伪代码立刻就会发现,这个模式实在用的太频繁了。

 

游戏中需要使用外观模式的地方是   这个非常多了,举几个比较重要的。

       1:实现平台无关性。跨平台跨库的函数调用。

       2:同一个接口去读取不同的资源。

       3:硬件自动识别处理系统。

 

外观模式伪代码

//-------------------------------------------------------------------------------------------------------

       // 用户使用的接口类

       Class Interface

{

// 暴露出来的函数接口函数,有且仅有一个,但内部实现是调用了两个类

       Void InterfaceFun()

       {

       // 根据某种条件,底层自主的选择使用AB的方法。用户无须关心底层实现

              if ( XXX )

              {

                     ActualA->Fun();

              }

             else

              {

                     ActualB->Fun();

              }

       };   

}

 

// 实际的实现,不暴露给用户知道

Class ActualA

{

       Void Fun();

}

 

// 实际的实现,不暴露给用户知道

Class ActualB

{

       Void Fun();

}

 

怎么样,纸老虎吧,看起来很高深摸测的命名而已。

//-------------------------------------------------------------------------------------------------------

 

六:抽象工厂模式AbstractFactory

抽象工厂的设计意图和作用是    封装出一个接口,这个接口负责创建一系列互相关联的对象,但用户在使用接口时不需要指定对象所在的具体的类。从中文命名也很容易明白它是进行批量生产的一个生产工厂的作用。

 

游戏中使用抽象工厂的地方有    基本上任何有批量的同类形式的子件地方就会有工厂的存在。(补充一句:下面代码中的ConcreteFactory1实例工厂就是工厂,而抽象工厂仅仅是工厂的一个抽象层而已。)

1:例如,在音频方面,一个音频的抽象工厂派生出不同的工厂,有音乐工厂,音效工厂。音效工厂中又有一个创建3D音效节点的方法,一个创建普通音效节点的方法。最终用户只需要SoundFactory->Create3DNode( pFileName );就可以创建一个节点了。

2:场景对象。

3:渲染对象。

4:等等……

工厂与单件,管理器Manager关系一定是非常紧密的。

 

抽象工厂伪代码

//-------------------------------------------------------------------------------------------------------

       class AbstractProductA {}; // 抽象的产品A基类
  class AbstractProductB {}; //抽象的产品B基类

 

// 抽象工厂基类
       class AbstractFactory
  
{
  
public:
   virtual AbstractProductA* CreateProductA() = 0 ;// 创建
ProductA
   virtual AbstractProductB* CreateProductB() = 0 ;// 创建
ProductB
       } ;

 

  class ProductA1 : public AbstractProductA {};    // 产品A的实例1
  class ProductA2 : public AbstractProductA {};    // 产品A的实例2

  class ProductB1 : public AbstractProductB {};    // 产品B的实例1
  class ProductB2 : public AbstractProductB {};    // 产品B的实例2

 

// 实例工厂1

      class ConcreteFactory1 : public AbstractFactory
  
{
    
virtual AbstractProductA* CreateProductA() { return new ProductA1() ; }
    
virtual AbstractProductB* CreateProductB() { return new ProductB1() ; }
       static ConcreteFactory1* Instance() { }        // 实例工厂尽量使用单件模式

  } ;

 

// 实例工厂2

class ConcreteFactory2 : public AbstractFactory
{
    
virtual AbstractProductA* CreateProductA() { return new ProductA2() ; }
    
virtual AbstractProductB* CreateProductB() { return new ProductB2() ; }
    static ConcreteFactory2* Instance() {}  
       // 实例工厂尽量使用单件模式
} ;

//-------------------------------------------------------------------------------------------------------

客户端代码

Void main()
{
       AbstractFactory *pFactory1 = ConcreteFactory1::Instance() ;
  
AbstractProductA *pProductA1 = pFactory1->CreateProductA() ;
  
AbstractProductB *pProductB1 = pFactory1->CreateProductB() ;
  
AbstractFactory *pFactory2 = ConcreteFactory2::Instance() ;
  
AbstractProductA *pProductA2 = pFactory2->CreateProductA() ;
  
AbstractProductB *pProductB2 = pFactory2->CreateProductB() ;
}

//-------------------------------------------------------------------------------------------------------

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 设计模式游戏与完美开发 PDF 是两个不同的东西,其设计模式游戏是一本介绍常用设计模式的书籍,而完美开发 PDF 是一种开发技巧与知识总结的文档。 设计模式游戏适合那些想更深入了解设计模式的软件工程师、程序设计师或学习设计模式的学生。这本书详细介绍了23种常见的设计模式,并通过游戏方式帮助读者更好地理解和掌握这些模式的应用。设计模式可以提高代码的可读性、可维护性和可扩展性,是面向对象编程的重要思想。 完美开发 PDF 则适合那些想提升自己开发技巧和知识的人员。这份文档详细总结了一些软件开发的注意事项和技巧,如代码规范、调试技巧、内存管理、测试等。这些知识和技巧可以帮助开发人员写出高质量、高效率的代码,提高开发效率和质量。 总的来说,这两个东西都是非常有价值的,但需要针对自己的需求选择合适的。设计模式游戏可以帮助读者掌握常用设计模式,提高自己的编程思维和能力,而完美开发 PDF 则可以帮助读者提高开发能力和效率,写出更加高质量的代码。 ### 回答2: 设计模式游戏是一个基于游戏框架的学习设计模式的互动式教程。该游戏涵盖了常见的设计模式,通过解决问题,并用代码实现来学习设计模式游戏的角色扮演和动画效果增加了游戏的趣味性和互动性,从而更容易留下深刻的理解。设计模式游戏是一个非常有效的学习工具,使得学习设计模式更加生动和有趣。 而完美开发 pdf 则是一本以实践为主的技术书籍,它详细介绍了软件开发的各个环节,包括需求分析、设计、代码实现、测试、维护等。该书以项目驱动的方式,给出了概念性介绍和实际的代码实现,使得读者可以了解到整个软件开发流程,并获得实际的代码编写技巧和实践经验。完美开发 pdf 是一本非常实用的技术书籍,适合那些想要学习软件开发和完整项目开发的人员。 两者相比较,设计模式游戏更注重提高学习者的兴趣和理解,而完美开发 pdf 更注重实际操作和项目实践,两者均是非常有效的学习工具和实践指南,适合不同层次的软件工程师和学习者。 ### 回答3: 设计模式游戏是一款面向软件开发者的教育游戏,其目的是通过互动式的游戏流程,让学习者深入了解和掌握设计模式的原理、应用和实现。通过该游戏的教育模式,学习者可在不断试错和调整的过程,逐渐领会设计模式的实际意义和作用,有效提升自身的软件设计和开发能力。相比传统的讲授式教育模式,设计模式游戏更注重学习者的实践和探索,提升学员的参与度和自主学习能力。 而完美开发 pdf 则是一本软件开发者必备的技术读物,它以设计模式为基础,深入解析了软件开发的各个方面,涉及到的知识点非常全面而深入。它主要从实践的角度出发,以案例为驱动,通过实例和代码的演示,让读者系统地了解和掌握软件设计和开发的常见问题和解决方案。作为一本非常经典的开发读物,完美开发 pdf 具有指导性和启发性,可让软件开发者在实践逐渐成长和完善自身的工作和技能。 因此,通过设计模式游戏与完美开发 pdf 的学习,软件开发者可在掌握设计模式的同时,更全面地了解和掌握软件开发的各个方面,从而在实践不断成长和提升自身的技术水平。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值