游戏对象互相交谈

时间:2021-10-08 07:53:42

What is a good way of dealing with objects and having them talk to each other?

什么是处理对象和让他们彼此交谈的好方法?

Up until now all my games hobby/student have been small so this problem was generally solved in a rather ugly way, which lead to tight integration and circular dependencies. Which was fine for the size of projects I was doing.

到目前为止,我所有的游戏爱好/学生都很小,所以这个问题通常以一种相当丑陋的方式解决,这导致了紧密的集成和循环依赖。这对于我正在做的项目来说是没问题的。

However my projects have been getting bigger in size and complexity and now I want to start re-using code, and making my head a simpler place.

然而,我的项目越来越大,越来越复杂,现在我想开始重用代码,让我的头脑变得更简单。

The main problem I have is generally along the lines of Player needs to know about the Map and so does the Enemy, this has usually descended into setting lots of pointers and having lots of dependencies, and this becomes a mess quickly.

我遇到的主要问题是玩家需要知道地图的路线,敌人也一样,这通常会导致设置大量指针和依赖,这很快就会变得一团糟。

I have thought along the lines of a message style system. but I cant really see how this reduces the dependencies, as I would still be sending the pointers everywhere.

我按照消息样式系统的思路进行了思考。但我真的看不出这是如何减少依赖关系的,因为我仍然会到处发送指针。

PS: I guess this has been discussed before, but I don't know what its called just the need I have.

我想这是以前讨论过的,但我不知道它叫什么。

7 个解决方案

#1


41  

EDIT: Below I describe a basic event messaging system I have used over and over. And it occurred to me that both school projects are open source and on the web. You can find the second version of this messaging system (and quite a bit more) at http://sourceforge.net/projects/bpfat/ .. Enjoy, and read below for a more thorough description of the system!

编辑:下面是我反复使用的一个基本事件消息传递系统。我突然想到,这两个学校项目都是开源的,而且是在网络上。您可以在http://sourceforge.net/projects/bpfat/上找到这个消息传递系统的第二个版本(以及更多版本)。请欣赏,并阅读下面的内容,以更全面的描述系统!

I've written a generic messaging system and introduced it into a handful of games that have been released on the PSP as well as some enterprise level application software. The point of the messaging system is to pass only the data around that is needed for processing a message or event, depending on the terminology you want to use, so that objects do not have to know about each other.

我已经编写了一个通用的消息传递系统,并将它引入了一些已经在PSP上发布的游戏以及一些企业级应用软件中。消息传递系统的要点是仅传递处理消息或事件所需的数据,这取决于您希望使用的术语,这样对象就不必相互了解了。

A quick rundown of the list of objects used to accomplish this is something along the lines of:

快速列出用于实现此目的的对象列表,如下所示:

struct TEventMessage
{
    int _iMessageID;
}

class IEventMessagingSystem
{
    Post(int iMessageId);
    Post(int iMessageId, float fData);
    Post(int iMessageId, int iData);
    // ...
    Post(TMessageEvent * pMessage);
    Post(int iMessageId, void * pData);
}

typedef float(*IEventMessagingSystem::Callback)(TEventMessage * pMessage);

class CEventMessagingSystem
{
    Init       ();
    DNit       ();
    Exec       (float fElapsedTime);

    Post       (TEventMessage * oMessage);

    Register   (int iMessageId, IEventMessagingSystem* pObject, FObjectCallback* fpMethod);
    Unregister (int iMessageId, IEventMessagingSystem* pObject, FObjectCallback * fpMethod);
}

#define MSG_Startup            (1)
#define MSG_Shutdown           (2)
#define MSG_PlaySound          (3)
#define MSG_HandlePlayerInput  (4)
#define MSG_NetworkMessage     (5)
#define MSG_PlayerDied         (6)
#define MSG_BeginCombat        (7)
#define MSG_EndCombat          (8)

And now a bit of an explanation. The first object, TEventMessage, is the base object to represent data sent by the messaging system. By default it will always have the Id of the message being sent so if you want to make sure you have received a message you were expecting you can (Generally I only do that in debug).

现在来解释一下。第一个对象TEventMessage是表示消息传递系统发送的数据的基对象。默认情况下,它将始终具有正在发送的消息的Id,因此,如果您希望确保收到了预期的消息,那么您可以这样做(通常我只在debug中这样做)。

Next up is the Interface class that gives a generic object for the messaging system to use for casting while doing callbacks. Additionally this also provides an 'easy to use' interface for Post()ing different data types to the messaging system.

接下来是接口类,它为消息传递系统提供一个泛型对象,以便在执行回调时进行转换。此外,这还提供了一个“易于使用”的接口,用于将不同的数据类型发送到消息传递系统。

After that we have our Callback typedef, Simply put it expects an object of the type of the interface class and will pass along a TEventMessage pointer... Optionally you can make the parameter const but I've used trickle up processing before for things like stack debugging and such of the messaging system.

在我们有了回调类型def之后,只需将它预期为接口类类型的对象,并传递一个TEventMessage指针…您可以随意地设置参数const,但是我以前在堆栈调试和消息系统之类的事情上使用过渐进式处理。

Last and at the core is the CEventMessagingSystem object. This object contains an array of callback object stacks (or linked lists or queues or however you want to store the data). The callback objects, not shown above, need to maintain (and are uniquely defined by) a pointer to the object as well as the method to call on that object. When you Register() you add an entry on the object stack under the message id's array position. When you Unregister() you remove that entry.

最后,核心是CEventMessagingSystem对象。这个对象包含一个回调对象堆栈数组(或链接列表或队列,或者您希望存储数据的任何方式)。没有在上面显示的回调对象,需要维护(并且惟一地由)指向该对象的指针以及调用该对象的方法。注册()时,在消息id的数组位置下,在对象堆栈上添加一个条目。当您注销()时,您将删除该条目。

That is basically it. Now this does have the stipulation that everything needs to know about the IEventMessagingSystem and the TEventMessage object... but this object should Not be changing that often and only passes the parts of information that are vital to the logic dictated by the event being called. This way a player doesn't need to know about the map or the enemy directly for sending events off to it. A managed object can call an API to a larger system also, without needing to know anything about it.

基本上是这样。现在,它确实有这样的规定:关于IEventMessagingSystem和TEventMessage对象,一切都需要知道……但是这个对象不应该经常更改,而应该只传递对被调用事件所决定的逻辑至关重要的信息部分。这样玩家就不需要知道地图或敌人直接向它发送事件。一个受管对象也可以调用一个API到一个更大的系统,而不需要知道它的任何信息。

For example: When an enemy dies you want it to play a sound effect. Assuming you have a sound manager that inherits the IEventMessagingSystem interface, you would set up a callback for the messaging system that would accept a TEventMessagePlaySoundEffect or something of that ilk. The Sound Manager would then register this callback when sound effects are enabled (or unregister the callback when you want to mute all sound effects for easy on/off abilities). Next, you would have the enemy object also inherit from the IEventMessagingSystem, put together a TEventMessagePlaySoundEffect object (would need the MSG_PlaySound for its Message ID and then the ID of the sound effect to play, be it an int ID or the name of the sound effect) and simply call Post(&oEventMessagePlaySoundEffect).

例如:当一个敌人死了,你想要它播放声音效果。假设您有一个继承IEventMessagingSystem接口的声音管理器,您将为消息系统设置一个回调,该回调将接受TEventMessagePlaySoundEffect或类似的东西。当声音效果被激活时,声音管理器会注册这个回调(当你想要关闭所有声音效果时,当你想要关闭所有的声音效果时)。接下来,您还将敌人对象继承IEventMessagingSystem,放在一起TEventMessagePlaySoundEffect对象(需要MSG_PlaySound的消息ID,然后声音效果的ID,int ID或声音效果)的名称和简单地调用Post(&oEventMessagePlaySoundEffect)。

Now this is just a very simple design with no implementation. If you have immediate execution then you have no need to buffer the TEventMessage objects (What I used mostly in console games). If you are in a multi-threaded environment then this is a very well defined way for objects and systems running in separate threads to talk to each other, but you will want to preserve the TEventMessage objects so the data is available when processing.

这只是一个非常简单的设计,没有实现。如果您立即执行,那么您就不需要缓冲TEventMessage对象(我主要使用的是控制台游戏)。如果您在多线程环境中,那么对于运行在独立线程中的对象和系统来说,这是一种定义良好的相互通信方式,但是您希望保留TEventMessage对象,以便在处理时数据可用。

Another alteration is for objects that only ever need to Post() data, you can create a static set of methods in the IEventMessagingSystem so they do not have to inherit from them (That is used for ease of access and callback abilities, not -directly- needed for Post() calls).

另一个改变是,对于那些只需要发布()数据的对象,您可以在IEventMessagingSystem中创建一组静态方法,这样它们就不必从它们继承(这用于访问和回调功能,而不是Post()调用所需的直接方法)。

For all the people who mention MVC, it is a very good pattern, but you can implement it in so many different manners and at different levels. The current project I am working on professionally is an MVC setup about 3 times over, there is the global MVC of the entire application and then design wise each M V and C also is a self-contained MVC pattern. So what I have tried to do here is explain how to make a C that is generic enough to handle just about any type of M without the need to get into a View...

对于所有提到MVC的人来说,这是一种非常好的模式,但是您可以以许多不同的方式在不同的级别实现它。目前我正在从事的专业项目是一个MVC设置大约3倍,有整个应用的全局MVC,然后设计智慧每个mv和C也是一个自包含的MVC模式。因此,我在这里尝试做的是解释如何使C具有足够的通用性来处理几乎任何类型的M,而不需要进入视图……

For example, an object when it 'dies' might want to play a sound effect.. You would make a struct for the Sound System like TEventMessageSoundEffect that inherits from the TEventMessage and adds in a sound effect ID (Be it a preloaded Int, or the name of the sfx file, however they are tracked in your system). Then all the object just needs to put together a TEventMessageSoundEffect object with the appropriate Death noise and call Post(&oEventMessageSoundEffect); object.. Assuming the sound is not muted (what you would want to Unregister the Sound Managers.

例如,当一个对象“死”时,它可能想要播放一个声音效果。您可以为像TEventMessageSoundEffect这样的声音系统创建一个结构体,该结构体继承自TEventMessage并添加一个声音效果ID(它可以是预加载的Int,也可以是sfx文件的名称,但是它们在您的系统中会被跟踪)。然后,所有的对象只需将teventmessagesounect对象与适当的死亡噪声组合在一起并调用Post(& & oeventmessagesounect);对象. .假设声音不是无声的(你想要取消声音管理器的注册)。

EDIT: To clarify this a bit in regards to the comment below: Any object to send or receive a message just needs to know about the IEventMessagingSystem interface, and this is the only object the EventMessagingSystem needs to know of all the other objects. This is what gives you the detachment. Any object who wants to receive a message simply Register(MSG, Object, Callback)s for it. Then when an object calls Post(MSG,Data) it sends that to the EventMessagingSystem via the interface it knows about, the EMS will then notify each registered object of the event. You could do a MSG_PlayerDied that other systems handle, or the player can call MSG_PlaySound, MSG_Respawn, etc to let things listening for those messages to act upon them. Think of the Post(MSG,Data) as an abstracted API to the different systems within a game engine.

编辑:为了澄清下面的注释:任何要发送或接收消息的对象都只需要知道IEventMessagingSystem接口,这是EventMessagingSystem需要知道的惟一对象。这就是你的超然。任何想要接收消息的对象只需为其注册(MSG、object、Callback)s。然后,当一个对象调用Post(MSG,Data)时,它通过它知道的接口将其发送到EventMessagingSystem,然后EMS将通知每个已注册的事件对象。您可以执行其他系统处理的msg_playerdies,或者播放器可以调用MSG_PlaySound、MSG_Respawn等来让监听这些消息的东西对它们进行响应。可以将Post(MSG,Data)看作是游戏引擎中不同系统的抽象API。

Oh! One other thing that was pointed out to me. The system I describe above fits the Observer pattern in the other answer given. So if you want a more general description to make mine make a bit more sense, that is a short article that gives it a good description.

哦!还有一件事我已经说过了。我所描述的系统在给出的另一个答案中符合观察者模式。所以如果你想要一个更一般化的描述来让我的文章更有意义,那是一篇简短的文章,给了它一个很好的描述。

Hope this helps and Enjoy!

希望这对您有所帮助和享受!

#2


15  

the generic solutions for communication between objects avoiding tight coupling:

避免紧密耦合的对象之间通信的通用解决方案:

  1. Mediator pattern
  2. 调停者模式
  3. Observer pattern
  4. 观察者模式

#3


4  

This probably does not only apply to game classes but to classes in the general sense. the MVC (model-view-controller) pattern together with your suggested message pump is all you need.

这可能不仅适用于游戏类,还适用于一般意义上的类。您只需要MVC(模型-视图-控制器)模式和建议的消息泵。

"Enemy" and "Player" will probably fit into the Model part of MVC, it does not matter much, but the rule of thumb is have all models and views interact via the controller. So, you would want to keep references (better than pointers) to (almost) all other class instances from this 'controller' class, let's name it ControlDispatcher. Add a message pump to it (varies depending on what platform you are coding for), instantiate it firstly (before any other classes and have the other objects part of it) or lastly (and have the other objects stored as references in ControlDispatcher).

"Enemy"和"Player"很可能是MVC的Model部分,这并不重要,但是经验法则是所有的模型和视图都通过controller进行交互。因此,您需要将引用(比指针更好)保存到(几乎)来自这个“控制器”类的所有其他类实例,让我们将它命名为ControlDispatcher。向它添加一个消息泵(根据您所编码的平台的不同而有所不同),首先(在任何其他类之前)实例化它(在其他类之前有其他的对象),或者最后(并将其他对象存储为ControlDispatcher中的引用)。

Of course, the ControlDispatcher class will probably have to be split down further into more specialized controllers just to keep the code per file at around 700-800 lines (this is the limit for me at least) and it may even have more threads pumping and processing messages depending on your needs.

当然,ControlDispatcher类可能需要进一步降低分割成更专门的控制器只是为了保持每个文件大约在700 - 800行代码(至少这是我的极限),它甚至可能有更多的线程将根据您的需要和处理消息。

Cheers

干杯

#4


3  

Here is a neat event system written for C++11 you can use. It uses templates and smart pointers as well as lambdas for the delegates. It's very flexible. Below you will also find an example. Email me at info@fortmax.se if you have questions about this.

这是为c++ 11编写的一个简洁的事件系统,您可以使用它。它使用模板和智能指针以及委托的lambdas。这是非常灵活的。下面您还将看到一个示例。在info@fortmax电子邮件我。如果你有什么问题的话。

What these classes gives you is a way to send events with arbitrary data attached to them and an easy way to directly bind functions that accept already converted argument types that the system casts and checks for correct conversion prior to calling your delegate.

这些类提供给您的是一种发送带有任意数据的事件的方法,以及一种直接绑定接受已转换参数类型的函数的简单方法,系统在调用委托之前对这些参数类型进行强制转换并检查是否正确转换。

Basically, every event is derived from IEventData class (you can call it IEvent if you want). Each "frame" you call ProcessEvents() at which point the event system loops through all the delegates and calls the delegates that have been supplied by other systems that have subscribed to each event type. Anyone can pick which events they would like to subscribe to, as each event type has a unique ID. You can also use lambdas to subscribe to events like this: AddListener(MyEvent::ID(), [&](shared_ptr ev){ do your thing }..

基本上,每个事件都是从IEventData类派生出来的(如果需要,可以将其称为IEvent)。您调用ProcessEvents()的每个“框架”,此时事件系统循环遍历所有委托,并调用已订阅每个事件类型的其他系统提供的委托。任何人都可以选择他们想订阅的事件,因为每个事件类型都有一个唯一的ID。

Anyway, here is the class with all the implementation:

不管怎样,这是一个实现了所有实现的类:

#pragma once

#include <list>
#include <memory>
#include <map>
#include <vector>
#include <functional>

class IEventData {
public:
    typedef size_t id_t; 
    virtual id_t GetID() = 0; 
}; 

typedef std::shared_ptr<IEventData> IEventDataPtr; 
typedef std::function<void(IEventDataPtr&)> EventDelegate; 

class IEventManager {
public:
    virtual bool AddListener(IEventData::id_t id, EventDelegate proc) = 0;
    virtual bool RemoveListener(IEventData::id_t id, EventDelegate proc) = 0; 
    virtual void QueueEvent(IEventDataPtr ev) = 0; 
    virtual void ProcessEvents() = 0; 
}; 


#define DECLARE_EVENT(type) \
    static IEventData::id_t ID(){ \
        return reinterpret_cast<IEventData::id_t>(&ID); \
    } \
    IEventData::id_t GetID() override { \
        return ID(); \
    }\

class EventManager : public IEventManager {
public:
    typedef std::list<EventDelegate> EventDelegateList; 

    ~EventManager(){
    } 
    //! Adds a listener to the event. The listener should invalidate itself when it needs to be removed. 
    virtual bool AddListener(IEventData::id_t id, EventDelegate proc) override; 

    //! Removes the specified delegate from the list
    virtual bool RemoveListener(IEventData::id_t id, EventDelegate proc) override; 

    //! Queues an event to be processed during the next update
    virtual void QueueEvent(IEventDataPtr ev) override; 

    //! Processes all events
    virtual void ProcessEvents() override; 
private:
    std::list<std::shared_ptr<IEventData>> mEventQueue; 
    std::map<IEventData::id_t, EventDelegateList> mEventListeners; 

}; 

//! Helper class that automatically handles removal of individual event listeners registered using OnEvent() member function upon destruction of an object derived from this class. 
class EventListener {
public:
    //! Template function that also converts the event into the right data type before calling the event listener. 
    template<class T>
    bool OnEvent(std::function<void(std::shared_ptr<T>)> proc){
        return OnEvent(T::ID(), [&, proc](IEventDataPtr data){
            auto ev = std::dynamic_pointer_cast<T>(data); 
            if(ev) proc(ev); 
        }); 
    }
protected:
    typedef std::pair<IEventData::id_t, EventDelegate> _EvPair; 
    EventListener(std::weak_ptr<IEventManager> mgr):_els_mEventManager(mgr){

    }
    virtual ~EventListener(){
        if(_els_mEventManager.expired()) return; 
        auto em = _els_mEventManager.lock(); 
        for(auto i : _els_mLocalEvents){
            em->RemoveListener(i.first, i.second); 
        }
    }

    bool OnEvent(IEventData::id_t id, EventDelegate proc){
        if(_els_mEventManager.expired()) return false; 
        auto em = _els_mEventManager.lock(); 
        if(em->AddListener(id, proc)){
            _els_mLocalEvents.push_back(_EvPair(id, proc)); 
        }
    }
private:
    std::weak_ptr<IEventManager> _els_mEventManager; 
    std::vector<_EvPair>        _els_mLocalEvents; 
    //std::vector<_DynEvPair> mDynamicLocalEvents; 
}; 

And the Cpp file:

和Cpp文件:

#include "Events.hpp"

using namespace std; 

bool EventManager::AddListener(IEventData::id_t id, EventDelegate proc){
    auto i = mEventListeners.find(id); 
    if(i == mEventListeners.end()){
        mEventListeners[id] = list<EventDelegate>(); 
    }
    auto &list = mEventListeners[id]; 
    for(auto i = list.begin(); i != list.end(); i++){
        EventDelegate &func = *i; 
        if(func.target<EventDelegate>() == proc.target<EventDelegate>()) 
            return false; 
    }
    list.push_back(proc); 
}

bool EventManager::RemoveListener(IEventData::id_t id, EventDelegate proc){
    auto j = mEventListeners.find(id); 
    if(j == mEventListeners.end()) return false; 
    auto &list = j->second; 
    for(auto i = list.begin(); i != list.end(); ++i){
        EventDelegate &func = *i; 
        if(func.target<EventDelegate>() == proc.target<EventDelegate>()) {
            list.erase(i); 
            return true; 
        }
    }
    return false; 
}

void EventManager::QueueEvent(IEventDataPtr ev) {
    mEventQueue.push_back(ev); 
}

void EventManager::ProcessEvents(){
    size_t count = mEventQueue.size(); 
    for(auto it = mEventQueue.begin(); it != mEventQueue.end(); ++it){
        printf("Processing event..\n"); 
        if(!count) break; 
        auto &i = *it; 
        auto listeners = mEventListeners.find(i->GetID()); 
        if(listeners != mEventListeners.end()){
            // Call listeners
            for(auto l : listeners->second){
                l(i); 
            }
        }
        // remove event
        it = mEventQueue.erase(it); 
        count--; 
    }
}

I use an EventListener class for the sake of convenience as base class for any class that would like to listen to events. If you derive your listening class from this class and supply it with your event manager, you can use the very convenient function OnEvent(..) to register your events. And the base class will automatically unsubscribe your derived class from all events when it is destroyed. This is very convenient since forgetting to remove a delegate from event manager when your class is destroyed will almost certainly cause your program to crash.

为了方便起见,我使用EventListener类作为任何希望侦听事件的类的基类。如果您从这个类派生听力类并将其提供给事件管理器,您可以使用非常方便的函数OnEvent(..)来注册事件。基类将在派生类被销毁时自动从所有事件中取消订阅派生类。这非常方便,因为当您的类被销毁时,忘记删除事件管理器的委托几乎肯定会导致程序崩溃。

A neat way to get a unique type id for an event by simply declaring a static function in the class and then casting it's address into an int. Since every class will have this method on different addresses, it can be used for unique identification of class events. You can also cast typename() to an int to get a unique id if you want. There are different ways to do this.

通过简单地在类中声明一个静态函数,然后将其地址转换为int类型,获得事件的唯一类型id的一种简单方法。您还可以将typename()转换为int类型,以便在需要时获得唯一id。有不同的方法。

So here is an example on how to use this:

这里有一个如何使用的例子:

#include <functional>
#include <memory>
#include <stdio.h>
#include <list>
#include <map>

#include "Events.hpp"
#include "Events.cpp"

using namespace std; 

class DisplayTextEvent : public IEventData {
public:
    DECLARE_EVENT(DisplayTextEvent); 

    DisplayTextEvent(const string &text){
        mStr = text; 
    }
    ~DisplayTextEvent(){
        printf("Deleted event data\n"); 
    }
    const string &GetText(){
        return mStr; 
    }
private:
    string mStr; 
}; 

class Emitter { 
public:
    Emitter(shared_ptr<IEventManager> em){
        mEmgr = em; 
    }
    void EmitEvent(){
        mEmgr->QueueEvent(shared_ptr<IEventData>(
            new DisplayTextEvent("Hello World!"))); 
    }
private:
    shared_ptr<IEventManager> mEmgr; 
}; 

class Receiver : public EventListener{
public:
    Receiver(shared_ptr<IEventManager> em) : EventListener(em){
        mEmgr = em; 

        OnEvent<DisplayTextEvent>([&](shared_ptr<DisplayTextEvent> data){
            printf("It's working: %s\n", data->GetText().c_str()); 
        }); 
    }
    ~Receiver(){
        mEmgr->RemoveListener(DisplayTextEvent::ID(), std::bind(&Receiver::OnExampleEvent, this, placeholders::_1)); 
    }
    void OnExampleEvent(IEventDataPtr &data){
        auto ev = dynamic_pointer_cast<DisplayTextEvent>(data); 
        if(!ev) return; 
        printf("Received event: %s\n", ev->GetText().c_str()); 
    }
private:
    shared_ptr<IEventManager> mEmgr; 
}; 

int main(){
    auto emgr = shared_ptr<IEventManager>(new EventManager()); 


    Emitter emit(emgr); 
    {
        Receiver receive(emgr); 

        emit.EmitEvent(); 
        emgr->ProcessEvents(); 
    }
    emit.EmitEvent(); 
    emgr->ProcessEvents(); 
    emgr = 0; 

    return 0; 
}

#5


0  

Be careful with "a message style system", it probably depends on implementation, but usually you would loose static type checking, and can then make some errors very difficult to debug. Note that calling object's methods it is already a message-like system.

注意“消息样式系统”,它可能依赖于实现,但是通常您会松散地进行静态类型检查,然后会使一些错误很难调试。注意,调用对象的方法已经是一个类似消息的系统。

Probably you are simply missing some levels of abstraction, for example for navigation a Player could use a Navigator instead of knowing all about the Map itself. You also say that this has usually descended into setting lots of pointers, what are those pointers? Probably, you are giving them to a wrong abstraction?.. Making objects know about others directly, without going through interfaces and intermediates, is a straight way to getting a tightly coupled design.

可能您只是缺少了一些抽象级别,例如,对于导航,玩家可以使用导航器,而不需要了解地图本身。你还说这通常会导致很多指针,这些指针是什么?也许,你给了他们一个错误的抽象?让对象直接了解其他对象,而不经过接口和中间层,是获得紧密耦合设计的直接方法。

#6


0  

Messaging is definitely a great way to go, but messaging systems can have a lot of differences. If you want to keep your classes nice and clean, write them to be ignorant of a messaging system and instead have them take dependencies on something simple like a 'ILocationService' which can then be implemented to publish/request information from things like the Map class. While you'll end up with more classes, they'll be small, simple and encourage clean design.

消息传递肯定是一种很好的方式,但是消息传递系统可能有很多不同之处。如果你想让你的类保持整洁,就把它们写成对消息传递系统一无所知,让它们依赖于一些简单的东西,比如“ILocationService”,然后可以实现它来发布/请求来自Map类的信息。虽然您最终将得到更多的类,但是它们将是小的、简单的,并且鼓励干净的设计。

Messaging is about more than just decoupling, it also lets you move towards a more asynchronous, concurrent and reactive architecture. Patterns of Enterprise Integration by Gregor Hophe is a great book that talks about good messaging patterns. Erlang OTP or Scala's implementation of the Actor Pattern have provided me with a lot of guidance.

消息传递不仅仅是一种解耦,它还允许您转向更异步、并发和反应性的体系结构。Gregor Hophe的《企业集成模式》是一本关于良好消息传递模式的好书。Erlang OTP或Scala的Actor模式实现为我提供了很多指导。

#7


-1  

@kellogs suggestion of MVC is valid, and used in a few games, though its much more common in web apps and frameworks. It might be overkill and too much for this.

@kellogs关于MVC的建议是有效的,在一些游戏中也可以使用,但在web应用程序和框架中却更为常见。这可能有点过头了。

I would rethink your design, why does the Player need to talk to Enemies? Couldn't they both inherit from an Actor class? Why do Actors need to talk to the Map?

我会重新考虑你的设计,为什么玩家需要和敌人对话?他们不能都继承一个演员类吗?为什么演员需要跟地图说话?

As I read what I wrote it starts to fit into an MVC framework...I have obviously done too much rails work lately. However, I would be willing to bet, they only need to know things like, they are colliding with another Actor, and they have a position, which should be relative to the Map anyhow.

当我读到我所写的内容时,它开始适合于MVC框架……我最近显然做了太多的rails工作。但是,我敢打赌,他们只需要知道一些事情,比如,他们和另一个演员发生碰撞,他们有一个位置,这个位置应该和地图相关。

Here is an implementation of Asteroids that I worked on. You're game may be, and probably is, complex.

这是我研究的小行星的实现。你的游戏可能是,也可能是,复杂的。

#1


41  

EDIT: Below I describe a basic event messaging system I have used over and over. And it occurred to me that both school projects are open source and on the web. You can find the second version of this messaging system (and quite a bit more) at http://sourceforge.net/projects/bpfat/ .. Enjoy, and read below for a more thorough description of the system!

编辑:下面是我反复使用的一个基本事件消息传递系统。我突然想到,这两个学校项目都是开源的,而且是在网络上。您可以在http://sourceforge.net/projects/bpfat/上找到这个消息传递系统的第二个版本(以及更多版本)。请欣赏,并阅读下面的内容,以更全面的描述系统!

I've written a generic messaging system and introduced it into a handful of games that have been released on the PSP as well as some enterprise level application software. The point of the messaging system is to pass only the data around that is needed for processing a message or event, depending on the terminology you want to use, so that objects do not have to know about each other.

我已经编写了一个通用的消息传递系统,并将它引入了一些已经在PSP上发布的游戏以及一些企业级应用软件中。消息传递系统的要点是仅传递处理消息或事件所需的数据,这取决于您希望使用的术语,这样对象就不必相互了解了。

A quick rundown of the list of objects used to accomplish this is something along the lines of:

快速列出用于实现此目的的对象列表,如下所示:

struct TEventMessage
{
    int _iMessageID;
}

class IEventMessagingSystem
{
    Post(int iMessageId);
    Post(int iMessageId, float fData);
    Post(int iMessageId, int iData);
    // ...
    Post(TMessageEvent * pMessage);
    Post(int iMessageId, void * pData);
}

typedef float(*IEventMessagingSystem::Callback)(TEventMessage * pMessage);

class CEventMessagingSystem
{
    Init       ();
    DNit       ();
    Exec       (float fElapsedTime);

    Post       (TEventMessage * oMessage);

    Register   (int iMessageId, IEventMessagingSystem* pObject, FObjectCallback* fpMethod);
    Unregister (int iMessageId, IEventMessagingSystem* pObject, FObjectCallback * fpMethod);
}

#define MSG_Startup            (1)
#define MSG_Shutdown           (2)
#define MSG_PlaySound          (3)
#define MSG_HandlePlayerInput  (4)
#define MSG_NetworkMessage     (5)
#define MSG_PlayerDied         (6)
#define MSG_BeginCombat        (7)
#define MSG_EndCombat          (8)

And now a bit of an explanation. The first object, TEventMessage, is the base object to represent data sent by the messaging system. By default it will always have the Id of the message being sent so if you want to make sure you have received a message you were expecting you can (Generally I only do that in debug).

现在来解释一下。第一个对象TEventMessage是表示消息传递系统发送的数据的基对象。默认情况下,它将始终具有正在发送的消息的Id,因此,如果您希望确保收到了预期的消息,那么您可以这样做(通常我只在debug中这样做)。

Next up is the Interface class that gives a generic object for the messaging system to use for casting while doing callbacks. Additionally this also provides an 'easy to use' interface for Post()ing different data types to the messaging system.

接下来是接口类,它为消息传递系统提供一个泛型对象,以便在执行回调时进行转换。此外,这还提供了一个“易于使用”的接口,用于将不同的数据类型发送到消息传递系统。

After that we have our Callback typedef, Simply put it expects an object of the type of the interface class and will pass along a TEventMessage pointer... Optionally you can make the parameter const but I've used trickle up processing before for things like stack debugging and such of the messaging system.

在我们有了回调类型def之后,只需将它预期为接口类类型的对象,并传递一个TEventMessage指针…您可以随意地设置参数const,但是我以前在堆栈调试和消息系统之类的事情上使用过渐进式处理。

Last and at the core is the CEventMessagingSystem object. This object contains an array of callback object stacks (or linked lists or queues or however you want to store the data). The callback objects, not shown above, need to maintain (and are uniquely defined by) a pointer to the object as well as the method to call on that object. When you Register() you add an entry on the object stack under the message id's array position. When you Unregister() you remove that entry.

最后,核心是CEventMessagingSystem对象。这个对象包含一个回调对象堆栈数组(或链接列表或队列,或者您希望存储数据的任何方式)。没有在上面显示的回调对象,需要维护(并且惟一地由)指向该对象的指针以及调用该对象的方法。注册()时,在消息id的数组位置下,在对象堆栈上添加一个条目。当您注销()时,您将删除该条目。

That is basically it. Now this does have the stipulation that everything needs to know about the IEventMessagingSystem and the TEventMessage object... but this object should Not be changing that often and only passes the parts of information that are vital to the logic dictated by the event being called. This way a player doesn't need to know about the map or the enemy directly for sending events off to it. A managed object can call an API to a larger system also, without needing to know anything about it.

基本上是这样。现在,它确实有这样的规定:关于IEventMessagingSystem和TEventMessage对象,一切都需要知道……但是这个对象不应该经常更改,而应该只传递对被调用事件所决定的逻辑至关重要的信息部分。这样玩家就不需要知道地图或敌人直接向它发送事件。一个受管对象也可以调用一个API到一个更大的系统,而不需要知道它的任何信息。

For example: When an enemy dies you want it to play a sound effect. Assuming you have a sound manager that inherits the IEventMessagingSystem interface, you would set up a callback for the messaging system that would accept a TEventMessagePlaySoundEffect or something of that ilk. The Sound Manager would then register this callback when sound effects are enabled (or unregister the callback when you want to mute all sound effects for easy on/off abilities). Next, you would have the enemy object also inherit from the IEventMessagingSystem, put together a TEventMessagePlaySoundEffect object (would need the MSG_PlaySound for its Message ID and then the ID of the sound effect to play, be it an int ID or the name of the sound effect) and simply call Post(&oEventMessagePlaySoundEffect).

例如:当一个敌人死了,你想要它播放声音效果。假设您有一个继承IEventMessagingSystem接口的声音管理器,您将为消息系统设置一个回调,该回调将接受TEventMessagePlaySoundEffect或类似的东西。当声音效果被激活时,声音管理器会注册这个回调(当你想要关闭所有声音效果时,当你想要关闭所有的声音效果时)。接下来,您还将敌人对象继承IEventMessagingSystem,放在一起TEventMessagePlaySoundEffect对象(需要MSG_PlaySound的消息ID,然后声音效果的ID,int ID或声音效果)的名称和简单地调用Post(&oEventMessagePlaySoundEffect)。

Now this is just a very simple design with no implementation. If you have immediate execution then you have no need to buffer the TEventMessage objects (What I used mostly in console games). If you are in a multi-threaded environment then this is a very well defined way for objects and systems running in separate threads to talk to each other, but you will want to preserve the TEventMessage objects so the data is available when processing.

这只是一个非常简单的设计,没有实现。如果您立即执行,那么您就不需要缓冲TEventMessage对象(我主要使用的是控制台游戏)。如果您在多线程环境中,那么对于运行在独立线程中的对象和系统来说,这是一种定义良好的相互通信方式,但是您希望保留TEventMessage对象,以便在处理时数据可用。

Another alteration is for objects that only ever need to Post() data, you can create a static set of methods in the IEventMessagingSystem so they do not have to inherit from them (That is used for ease of access and callback abilities, not -directly- needed for Post() calls).

另一个改变是,对于那些只需要发布()数据的对象,您可以在IEventMessagingSystem中创建一组静态方法,这样它们就不必从它们继承(这用于访问和回调功能,而不是Post()调用所需的直接方法)。

For all the people who mention MVC, it is a very good pattern, but you can implement it in so many different manners and at different levels. The current project I am working on professionally is an MVC setup about 3 times over, there is the global MVC of the entire application and then design wise each M V and C also is a self-contained MVC pattern. So what I have tried to do here is explain how to make a C that is generic enough to handle just about any type of M without the need to get into a View...

对于所有提到MVC的人来说,这是一种非常好的模式,但是您可以以许多不同的方式在不同的级别实现它。目前我正在从事的专业项目是一个MVC设置大约3倍,有整个应用的全局MVC,然后设计智慧每个mv和C也是一个自包含的MVC模式。因此,我在这里尝试做的是解释如何使C具有足够的通用性来处理几乎任何类型的M,而不需要进入视图……

For example, an object when it 'dies' might want to play a sound effect.. You would make a struct for the Sound System like TEventMessageSoundEffect that inherits from the TEventMessage and adds in a sound effect ID (Be it a preloaded Int, or the name of the sfx file, however they are tracked in your system). Then all the object just needs to put together a TEventMessageSoundEffect object with the appropriate Death noise and call Post(&oEventMessageSoundEffect); object.. Assuming the sound is not muted (what you would want to Unregister the Sound Managers.

例如,当一个对象“死”时,它可能想要播放一个声音效果。您可以为像TEventMessageSoundEffect这样的声音系统创建一个结构体,该结构体继承自TEventMessage并添加一个声音效果ID(它可以是预加载的Int,也可以是sfx文件的名称,但是它们在您的系统中会被跟踪)。然后,所有的对象只需将teventmessagesounect对象与适当的死亡噪声组合在一起并调用Post(& & oeventmessagesounect);对象. .假设声音不是无声的(你想要取消声音管理器的注册)。

EDIT: To clarify this a bit in regards to the comment below: Any object to send or receive a message just needs to know about the IEventMessagingSystem interface, and this is the only object the EventMessagingSystem needs to know of all the other objects. This is what gives you the detachment. Any object who wants to receive a message simply Register(MSG, Object, Callback)s for it. Then when an object calls Post(MSG,Data) it sends that to the EventMessagingSystem via the interface it knows about, the EMS will then notify each registered object of the event. You could do a MSG_PlayerDied that other systems handle, or the player can call MSG_PlaySound, MSG_Respawn, etc to let things listening for those messages to act upon them. Think of the Post(MSG,Data) as an abstracted API to the different systems within a game engine.

编辑:为了澄清下面的注释:任何要发送或接收消息的对象都只需要知道IEventMessagingSystem接口,这是EventMessagingSystem需要知道的惟一对象。这就是你的超然。任何想要接收消息的对象只需为其注册(MSG、object、Callback)s。然后,当一个对象调用Post(MSG,Data)时,它通过它知道的接口将其发送到EventMessagingSystem,然后EMS将通知每个已注册的事件对象。您可以执行其他系统处理的msg_playerdies,或者播放器可以调用MSG_PlaySound、MSG_Respawn等来让监听这些消息的东西对它们进行响应。可以将Post(MSG,Data)看作是游戏引擎中不同系统的抽象API。

Oh! One other thing that was pointed out to me. The system I describe above fits the Observer pattern in the other answer given. So if you want a more general description to make mine make a bit more sense, that is a short article that gives it a good description.

哦!还有一件事我已经说过了。我所描述的系统在给出的另一个答案中符合观察者模式。所以如果你想要一个更一般化的描述来让我的文章更有意义,那是一篇简短的文章,给了它一个很好的描述。

Hope this helps and Enjoy!

希望这对您有所帮助和享受!

#2


15  

the generic solutions for communication between objects avoiding tight coupling:

避免紧密耦合的对象之间通信的通用解决方案:

  1. Mediator pattern
  2. 调停者模式
  3. Observer pattern
  4. 观察者模式

#3


4  

This probably does not only apply to game classes but to classes in the general sense. the MVC (model-view-controller) pattern together with your suggested message pump is all you need.

这可能不仅适用于游戏类,还适用于一般意义上的类。您只需要MVC(模型-视图-控制器)模式和建议的消息泵。

"Enemy" and "Player" will probably fit into the Model part of MVC, it does not matter much, but the rule of thumb is have all models and views interact via the controller. So, you would want to keep references (better than pointers) to (almost) all other class instances from this 'controller' class, let's name it ControlDispatcher. Add a message pump to it (varies depending on what platform you are coding for), instantiate it firstly (before any other classes and have the other objects part of it) or lastly (and have the other objects stored as references in ControlDispatcher).

"Enemy"和"Player"很可能是MVC的Model部分,这并不重要,但是经验法则是所有的模型和视图都通过controller进行交互。因此,您需要将引用(比指针更好)保存到(几乎)来自这个“控制器”类的所有其他类实例,让我们将它命名为ControlDispatcher。向它添加一个消息泵(根据您所编码的平台的不同而有所不同),首先(在任何其他类之前)实例化它(在其他类之前有其他的对象),或者最后(并将其他对象存储为ControlDispatcher中的引用)。

Of course, the ControlDispatcher class will probably have to be split down further into more specialized controllers just to keep the code per file at around 700-800 lines (this is the limit for me at least) and it may even have more threads pumping and processing messages depending on your needs.

当然,ControlDispatcher类可能需要进一步降低分割成更专门的控制器只是为了保持每个文件大约在700 - 800行代码(至少这是我的极限),它甚至可能有更多的线程将根据您的需要和处理消息。

Cheers

干杯

#4


3  

Here is a neat event system written for C++11 you can use. It uses templates and smart pointers as well as lambdas for the delegates. It's very flexible. Below you will also find an example. Email me at info@fortmax.se if you have questions about this.

这是为c++ 11编写的一个简洁的事件系统,您可以使用它。它使用模板和智能指针以及委托的lambdas。这是非常灵活的。下面您还将看到一个示例。在info@fortmax电子邮件我。如果你有什么问题的话。

What these classes gives you is a way to send events with arbitrary data attached to them and an easy way to directly bind functions that accept already converted argument types that the system casts and checks for correct conversion prior to calling your delegate.

这些类提供给您的是一种发送带有任意数据的事件的方法,以及一种直接绑定接受已转换参数类型的函数的简单方法,系统在调用委托之前对这些参数类型进行强制转换并检查是否正确转换。

Basically, every event is derived from IEventData class (you can call it IEvent if you want). Each "frame" you call ProcessEvents() at which point the event system loops through all the delegates and calls the delegates that have been supplied by other systems that have subscribed to each event type. Anyone can pick which events they would like to subscribe to, as each event type has a unique ID. You can also use lambdas to subscribe to events like this: AddListener(MyEvent::ID(), [&](shared_ptr ev){ do your thing }..

基本上,每个事件都是从IEventData类派生出来的(如果需要,可以将其称为IEvent)。您调用ProcessEvents()的每个“框架”,此时事件系统循环遍历所有委托,并调用已订阅每个事件类型的其他系统提供的委托。任何人都可以选择他们想订阅的事件,因为每个事件类型都有一个唯一的ID。

Anyway, here is the class with all the implementation:

不管怎样,这是一个实现了所有实现的类:

#pragma once

#include <list>
#include <memory>
#include <map>
#include <vector>
#include <functional>

class IEventData {
public:
    typedef size_t id_t; 
    virtual id_t GetID() = 0; 
}; 

typedef std::shared_ptr<IEventData> IEventDataPtr; 
typedef std::function<void(IEventDataPtr&)> EventDelegate; 

class IEventManager {
public:
    virtual bool AddListener(IEventData::id_t id, EventDelegate proc) = 0;
    virtual bool RemoveListener(IEventData::id_t id, EventDelegate proc) = 0; 
    virtual void QueueEvent(IEventDataPtr ev) = 0; 
    virtual void ProcessEvents() = 0; 
}; 


#define DECLARE_EVENT(type) \
    static IEventData::id_t ID(){ \
        return reinterpret_cast<IEventData::id_t>(&ID); \
    } \
    IEventData::id_t GetID() override { \
        return ID(); \
    }\

class EventManager : public IEventManager {
public:
    typedef std::list<EventDelegate> EventDelegateList; 

    ~EventManager(){
    } 
    //! Adds a listener to the event. The listener should invalidate itself when it needs to be removed. 
    virtual bool AddListener(IEventData::id_t id, EventDelegate proc) override; 

    //! Removes the specified delegate from the list
    virtual bool RemoveListener(IEventData::id_t id, EventDelegate proc) override; 

    //! Queues an event to be processed during the next update
    virtual void QueueEvent(IEventDataPtr ev) override; 

    //! Processes all events
    virtual void ProcessEvents() override; 
private:
    std::list<std::shared_ptr<IEventData>> mEventQueue; 
    std::map<IEventData::id_t, EventDelegateList> mEventListeners; 

}; 

//! Helper class that automatically handles removal of individual event listeners registered using OnEvent() member function upon destruction of an object derived from this class. 
class EventListener {
public:
    //! Template function that also converts the event into the right data type before calling the event listener. 
    template<class T>
    bool OnEvent(std::function<void(std::shared_ptr<T>)> proc){
        return OnEvent(T::ID(), [&, proc](IEventDataPtr data){
            auto ev = std::dynamic_pointer_cast<T>(data); 
            if(ev) proc(ev); 
        }); 
    }
protected:
    typedef std::pair<IEventData::id_t, EventDelegate> _EvPair; 
    EventListener(std::weak_ptr<IEventManager> mgr):_els_mEventManager(mgr){

    }
    virtual ~EventListener(){
        if(_els_mEventManager.expired()) return; 
        auto em = _els_mEventManager.lock(); 
        for(auto i : _els_mLocalEvents){
            em->RemoveListener(i.first, i.second); 
        }
    }

    bool OnEvent(IEventData::id_t id, EventDelegate proc){
        if(_els_mEventManager.expired()) return false; 
        auto em = _els_mEventManager.lock(); 
        if(em->AddListener(id, proc)){
            _els_mLocalEvents.push_back(_EvPair(id, proc)); 
        }
    }
private:
    std::weak_ptr<IEventManager> _els_mEventManager; 
    std::vector<_EvPair>        _els_mLocalEvents; 
    //std::vector<_DynEvPair> mDynamicLocalEvents; 
}; 

And the Cpp file:

和Cpp文件:

#include "Events.hpp"

using namespace std; 

bool EventManager::AddListener(IEventData::id_t id, EventDelegate proc){
    auto i = mEventListeners.find(id); 
    if(i == mEventListeners.end()){
        mEventListeners[id] = list<EventDelegate>(); 
    }
    auto &list = mEventListeners[id]; 
    for(auto i = list.begin(); i != list.end(); i++){
        EventDelegate &func = *i; 
        if(func.target<EventDelegate>() == proc.target<EventDelegate>()) 
            return false; 
    }
    list.push_back(proc); 
}

bool EventManager::RemoveListener(IEventData::id_t id, EventDelegate proc){
    auto j = mEventListeners.find(id); 
    if(j == mEventListeners.end()) return false; 
    auto &list = j->second; 
    for(auto i = list.begin(); i != list.end(); ++i){
        EventDelegate &func = *i; 
        if(func.target<EventDelegate>() == proc.target<EventDelegate>()) {
            list.erase(i); 
            return true; 
        }
    }
    return false; 
}

void EventManager::QueueEvent(IEventDataPtr ev) {
    mEventQueue.push_back(ev); 
}

void EventManager::ProcessEvents(){
    size_t count = mEventQueue.size(); 
    for(auto it = mEventQueue.begin(); it != mEventQueue.end(); ++it){
        printf("Processing event..\n"); 
        if(!count) break; 
        auto &i = *it; 
        auto listeners = mEventListeners.find(i->GetID()); 
        if(listeners != mEventListeners.end()){
            // Call listeners
            for(auto l : listeners->second){
                l(i); 
            }
        }
        // remove event
        it = mEventQueue.erase(it); 
        count--; 
    }
}

I use an EventListener class for the sake of convenience as base class for any class that would like to listen to events. If you derive your listening class from this class and supply it with your event manager, you can use the very convenient function OnEvent(..) to register your events. And the base class will automatically unsubscribe your derived class from all events when it is destroyed. This is very convenient since forgetting to remove a delegate from event manager when your class is destroyed will almost certainly cause your program to crash.

为了方便起见,我使用EventListener类作为任何希望侦听事件的类的基类。如果您从这个类派生听力类并将其提供给事件管理器,您可以使用非常方便的函数OnEvent(..)来注册事件。基类将在派生类被销毁时自动从所有事件中取消订阅派生类。这非常方便,因为当您的类被销毁时,忘记删除事件管理器的委托几乎肯定会导致程序崩溃。

A neat way to get a unique type id for an event by simply declaring a static function in the class and then casting it's address into an int. Since every class will have this method on different addresses, it can be used for unique identification of class events. You can also cast typename() to an int to get a unique id if you want. There are different ways to do this.

通过简单地在类中声明一个静态函数,然后将其地址转换为int类型,获得事件的唯一类型id的一种简单方法。您还可以将typename()转换为int类型,以便在需要时获得唯一id。有不同的方法。

So here is an example on how to use this:

这里有一个如何使用的例子:

#include <functional>
#include <memory>
#include <stdio.h>
#include <list>
#include <map>

#include "Events.hpp"
#include "Events.cpp"

using namespace std; 

class DisplayTextEvent : public IEventData {
public:
    DECLARE_EVENT(DisplayTextEvent); 

    DisplayTextEvent(const string &text){
        mStr = text; 
    }
    ~DisplayTextEvent(){
        printf("Deleted event data\n"); 
    }
    const string &GetText(){
        return mStr; 
    }
private:
    string mStr; 
}; 

class Emitter { 
public:
    Emitter(shared_ptr<IEventManager> em){
        mEmgr = em; 
    }
    void EmitEvent(){
        mEmgr->QueueEvent(shared_ptr<IEventData>(
            new DisplayTextEvent("Hello World!"))); 
    }
private:
    shared_ptr<IEventManager> mEmgr; 
}; 

class Receiver : public EventListener{
public:
    Receiver(shared_ptr<IEventManager> em) : EventListener(em){
        mEmgr = em; 

        OnEvent<DisplayTextEvent>([&](shared_ptr<DisplayTextEvent> data){
            printf("It's working: %s\n", data->GetText().c_str()); 
        }); 
    }
    ~Receiver(){
        mEmgr->RemoveListener(DisplayTextEvent::ID(), std::bind(&Receiver::OnExampleEvent, this, placeholders::_1)); 
    }
    void OnExampleEvent(IEventDataPtr &data){
        auto ev = dynamic_pointer_cast<DisplayTextEvent>(data); 
        if(!ev) return; 
        printf("Received event: %s\n", ev->GetText().c_str()); 
    }
private:
    shared_ptr<IEventManager> mEmgr; 
}; 

int main(){
    auto emgr = shared_ptr<IEventManager>(new EventManager()); 


    Emitter emit(emgr); 
    {
        Receiver receive(emgr); 

        emit.EmitEvent(); 
        emgr->ProcessEvents(); 
    }
    emit.EmitEvent(); 
    emgr->ProcessEvents(); 
    emgr = 0; 

    return 0; 
}

#5


0  

Be careful with "a message style system", it probably depends on implementation, but usually you would loose static type checking, and can then make some errors very difficult to debug. Note that calling object's methods it is already a message-like system.

注意“消息样式系统”,它可能依赖于实现,但是通常您会松散地进行静态类型检查,然后会使一些错误很难调试。注意,调用对象的方法已经是一个类似消息的系统。

Probably you are simply missing some levels of abstraction, for example for navigation a Player could use a Navigator instead of knowing all about the Map itself. You also say that this has usually descended into setting lots of pointers, what are those pointers? Probably, you are giving them to a wrong abstraction?.. Making objects know about others directly, without going through interfaces and intermediates, is a straight way to getting a tightly coupled design.

可能您只是缺少了一些抽象级别,例如,对于导航,玩家可以使用导航器,而不需要了解地图本身。你还说这通常会导致很多指针,这些指针是什么?也许,你给了他们一个错误的抽象?让对象直接了解其他对象,而不经过接口和中间层,是获得紧密耦合设计的直接方法。

#6


0  

Messaging is definitely a great way to go, but messaging systems can have a lot of differences. If you want to keep your classes nice and clean, write them to be ignorant of a messaging system and instead have them take dependencies on something simple like a 'ILocationService' which can then be implemented to publish/request information from things like the Map class. While you'll end up with more classes, they'll be small, simple and encourage clean design.

消息传递肯定是一种很好的方式,但是消息传递系统可能有很多不同之处。如果你想让你的类保持整洁,就把它们写成对消息传递系统一无所知,让它们依赖于一些简单的东西,比如“ILocationService”,然后可以实现它来发布/请求来自Map类的信息。虽然您最终将得到更多的类,但是它们将是小的、简单的,并且鼓励干净的设计。

Messaging is about more than just decoupling, it also lets you move towards a more asynchronous, concurrent and reactive architecture. Patterns of Enterprise Integration by Gregor Hophe is a great book that talks about good messaging patterns. Erlang OTP or Scala's implementation of the Actor Pattern have provided me with a lot of guidance.

消息传递不仅仅是一种解耦,它还允许您转向更异步、并发和反应性的体系结构。Gregor Hophe的《企业集成模式》是一本关于良好消息传递模式的好书。Erlang OTP或Scala的Actor模式实现为我提供了很多指导。

#7


-1  

@kellogs suggestion of MVC is valid, and used in a few games, though its much more common in web apps and frameworks. It might be overkill and too much for this.

@kellogs关于MVC的建议是有效的,在一些游戏中也可以使用,但在web应用程序和框架中却更为常见。这可能有点过头了。

I would rethink your design, why does the Player need to talk to Enemies? Couldn't they both inherit from an Actor class? Why do Actors need to talk to the Map?

我会重新考虑你的设计,为什么玩家需要和敌人对话?他们不能都继承一个演员类吗?为什么演员需要跟地图说话?

As I read what I wrote it starts to fit into an MVC framework...I have obviously done too much rails work lately. However, I would be willing to bet, they only need to know things like, they are colliding with another Actor, and they have a position, which should be relative to the Map anyhow.

当我读到我所写的内容时,它开始适合于MVC框架……我最近显然做了太多的rails工作。但是,我敢打赌,他们只需要知道一些事情,比如,他们和另一个演员发生碰撞,他们有一个位置,这个位置应该和地图相关。

Here is an implementation of Asteroids that I worked on. You're game may be, and probably is, complex.

这是我研究的小行星的实现。你的游戏可能是,也可能是,复杂的。