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

本文介绍了游戏开发中常用的设计模式,包括观察者模式用于UI控件管理、动画管理和技能系统,单例模式应用于各种Manager和工厂类,迭代器模式在资源管理中的应用,访问者模式用于静态复杂结构的操作,以及外观模式实现平台无关性和资源接口。这些设计模式能提高代码复用性和系统结构的灵活性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

使用设计模式来提高程序库的重复利用性是大型程序项目开发必须的。但是在“四人帮”的设计模式概述中提到了23种标准设计模式,不但难以记住,而且有些设计模式更多的适用于应用程序开发,对游戏项目引擎设计并没有很多的利用价值。


观察者Observer

它将对象与对象之间创建一种依赖关系,当其中一个对象发生变化时,它会将这个变化通知给与其创建关系的对象中,实现自动化的通知更新。

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

  1. UI控件管理类。
    当我们的GUI控件都使用观察者模式后,那么用户的任何界面相关操作和改变都将会通知其关联对象—–我们的UI事件机。
  2. 动画管理器。
    很多时候我们在播放一个动画桢的时候,对其Frame有很大兴趣,此时我们设置一个FrameLister对象对其进行监视,获得我们关心的事件进行处理是必须的。
  3. 技能系统
    在这执行的过程中角色可能发生种种意外(主要是ARPG比较容易出现),常见的事件比如角色死亡、切换地图、打断技能等,如果此时还在执行某些效果,我们需要将其正确的处理掉。

    一种直观的办法是将处理的操作封装到一个角色对象上的一个函数中,通过调用这个函数来完成清理,各个模块需要清理的就把清理相关代码加到这个函数中来。但是这种做法并不理想,首先一堆需要清理的功能放到一个函数中肯定不好看,另外个人觉得写起来别扭也容易漏掉,对于多个事件我们还得封装到不同的函数里面去,坏处一堆堆谁试谁知道。

    而通过观察者模式来进行管理,把角色对象定义成一个Subject,而具体的效果流程定义为Observer,这样一些持续性效果在开始之前,把自己加入到Subject上的某个事件的观察者列表中,角色对象由于状态改变而发出事件通知的时候,只需执行Notity(event),即可广播给所有关心这个事件的Observer对象(调用该对象上的OnEvent),这样每个效果流程内部只需要实现OnEvent并且添加处理代码就完成了整个过程,要比前者简洁漂亮不少。

观察者伪代码:

// 被观察对象目标类
Class Subject
{
       // 对本目标绑定一个观察者 Attach( Observer );
       // 解除一个观察者的绑定   DeleteAttach( Observer );
       // 本目标发生改变了,通知所有的观察者,但没有传递改动了什么
       Notity()
       {
              For ( …遍历整个ObserverList …)
              { pObserver ->Update(); }
       }
// 对观察者暴露的接口,让观察者可获得本类有什么变动GetState();
}

// 观察者/监听者类
Class Observer
{
       // 暴露给对象目标类的函数,当监听的对象发生了变动,则它会调用本函数通知观察者
        Void Update ()
        {
            pSubject ->GetState();  // 获取监听对象发生了什么变化
            TODO:DisposeFun();  // 根据状态不同,给予不同的处理
        }
}

非程序语言描述:

A是B的好朋友,对B的行为非常关心。B要出门,此时A给了B一个警报器,告诉B说:“如果你有事,立刻按这个警报器告诉我。”。结果B在外面遇上了麻烦,按下警报器(Update()),A就知道B出了事,于是就调查一下B到底遇到了什么麻烦(GetState()),当知道B原来是因为被人打了,于是立刻进行处理DisposeFun(),派了一群手下帮B打架。
当然关心B的人可以不止一个,C,D可能也对A很关心,于是B这里保存一个所有关心它的人的链表,当遇到麻烦的时候,轮流给每个人一份通知。


单件模式Singleton

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

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

  1. 所有的Manger
    在大部分的流行引擎中都存在着它的影子,例如SoundManager, ParticeManager等。
  2. 大部分的工厂基类
    这一点在大部分引擎中还是见不到的,实际上,我们的父类工厂采用唯一实例的话,我们子类进行扩展时也会有很大方便。

单件模式伪代码:

Class Singleton
{
       Static MySingleton;       // 单件对象,全局唯一的。
       Static Instance(){ return MySingleton;}              // 对外暴露接口
}

迭代器Iterator

提供一个方法,对一个组合聚合对象内各个元素进行访问,同时又不暴露该对象类的内部表示。

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

迭代器伪代码:

//迭代器基类
template<class Item>
class Iterator
{
public:
    virtual void first() = 0;
    virtual void next() = 0;
    virtual Item* currentItem() = 0;
    virtual bool isDone() = 0;
    virtual ~Iterator() {}
};

template<class Item>
class ConcreteAggregate;

//迭代器的实现类
template<class Item>
class ConcreteIterator : public Iterator <Item>
{
    ConcreteAggregate<Item> * aggr;
    int cur;
public:
    ConcreteIterator(ConcreteAggregate<Item>*a) :aggr(a), cur(0) {}
    virtual void first(){cur = 0; }
    virtual void next()
    {
        if (cur < aggr->getLen())
            cur++;
    }
    virtual Item* currentItem()
    {
        if (cur < aggr->getLen())
            return &(*aggr)[cur];
        else
            return NULL;
    }
    virtual bool isDone() { return (cur >= aggr->getLen()); }
};

//容器基类
template<class Item>
class Aggregate
{
public:
    virtual Iterator<Item>* createIterator() = 0;
    virtual ~Aggregate() {}
};

//容器实现类
template<class Item>
class ConcreteAggregate :public Aggregate<Item>
{
    vector<Item >data;
public:
    ConcreteAggregate()
    {
        data.push_back(1);
        data.push_back(2);
        data.push_back(3);
    }
    virtual Iterator<Item>* createIterator() { return new ConcreteIterator<Item>(this); }
    Item& operator[](int index) { return data[index]; }
    int getLen() { return data.size(); }
};

void TestIterator()
{
    Aggregate<int> * aggr = new ConcreteAggregate<int>();
    Iterator<int> *it = aggr->createIterator();

    for (it->first();!it->isDone();it->next()) { cout << *(it->currentItem()) << endl; }
    delete it;
    it = NULL;
    delete aggr;
    aggr = NULL;
}

访问者模式Visitor

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

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

例如场景管理器中管理的场景节点,是非常繁多的,而且种类不一,例如有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); }
这样做的好处,我们免去了对重要函数的修正,Object.Hide(Visitor){}函数我们可以永久不变,但是坏处也是很明显的,因为将方法从对象集合结构中抽离出来,就意味着我们每增加一个元素,它必须继承于一个抽象的被访问者类,实现其全部函数,这个工作量很大。
所以,访问者是仅适合于一个装载不同对象的大容器,但同时又要求这个容器的元素节点不应当有大的变动时才使用。另外,废话一句,访问者破坏了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()
        {
           // 根据某种条件,底层自主的选择使用A或B的方法。用户无须关心底层实现
           If ( XXX )
           {
               ActualA->Fun();
           }
           Else
           {
                ActualB->Fun();
           }
    }   
};

// 实际的实现,不暴露给用户知道
Class ActualA
{
       Void Fun();
}

// 实际的实现,不暴露给用户知道
Class ActualB
{
       Void Fun();
}

抽象工厂模式AbstractFactory

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

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

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

工厂与单件,管理器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() ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值