已关闭。此问题为opinion-based。目前不接受回答。
**要改进此问题吗?**更新此问题,以便editing this post可以使用事实和引文来回答。
4年前关闭。
Improve this question的
什么是处理对象并让它们彼此交谈的好方法?
到目前为止,我所有的游戏爱好/学生都很小,所以这个问题通常是以一种相当丑陋的方式解决的,这导致了紧密的集成和循环依赖。这对于我正在做的项目的规模来说很好。
然而,我的项目越来越大,越来越复杂,现在我想开始重用代码,让我的头脑变得更简单。
我遇到的主要问题通常是沿着Player
的行需要知道Map
,Enemy
也需要知道,这通常会导致设置大量指针并具有大量依赖关系,这很快就会变得一团糟。
我已经沿着消息样式系统的思路进行了思考,但我真的看不出这是如何减少依赖性的,因为我仍然会到处发送指针。
PS:我想这之前已经讨论过了,但我不知道这叫什么,只是我有需要。
7条答案
按热度按时间nbysray51#
编辑:下面我描述了一个基本的事件消息传递系统,我已经使用了一遍又一遍。我突然想到,这两个学校的项目都是开源的,在网络上。你可以在http://sourceforge.net/projects/bpfat/找到这个消息传递系统的第二个版本(以及更多)。享受,并阅读下面的系统更全面的描述!
我已经写了一个通用的消息传递系统,并将其引入到一些PSP上发布的游戏以及一些企业级应用软件中。消息传递系统的要点是只传递处理消息或事件所需的数据,这取决于您想要使用的术语,因此对象不必知道彼此。
用于完成此任务的对象列表的快速摘要是沿着以下行的:
字符串
第一个对象TEventMessage是表示消息系统发送的数据的基本对象。默认情况下,它总是具有正在发送的消息的ID,所以如果你想确保你收到了你期望的消息,你可以(通常我只在调试中这样做)。
接下来是Interface类,它为消息传递系统提供了一个通用对象,用于在执行回调时进行强制转换。此外,它还提供了一个“易于使用”的接口,用于将不同的数据类型Post()发送到消息传递系统。
之后,我们有了回调typedef,简单地说,它需要一个接口类类型的对象,并将传递沿着一个TEventMessage指针...可选地,你可以使参数const,但我以前使用过涓流处理,用于堆栈调试等消息传递系统。
最后也是最核心的是CEventMessagingSystem对象。(或链表或队列,或者您希望存储数据的任何方式)。回调对象,上面没有显示,需要保持(并且由)一个指向对象的指针以及在该对象上调用的方法唯一定义。你在对象栈中的消息id的数组位置下添加一个条目。当你Unregister()的时候,你删除了那个条目。
基本上就是这样。现在这确实有一个规定,即一切都需要知道关于IEventMessagingSystem和TEventMessage对象的信息...但是这个对象不应该经常改变,并且只传递对被调用的事件所指示的逻辑至关重要的信息部分。这样,玩家就不需要知道Map或敌人的信息就可以直接向它发送事件。一个托管对象也可以将API调用到更大的系统,而不需要了解它。
举例来说,您可以:当敌人死亡时,你希望它播放声音效果。假设你有一个继承IEventMessagingSystem接口的声音管理器,您将为消息传递系统设置一个回调,该回调将接受TEventMessagePlaySoundEffect或类似的东西。然后,声音管理器将在启用声音效果时注册此回调(或者当你想静音所有的声音效果以方便打开/关闭功能时取消注册回调)。接下来,你将让敌人对象也从IEventMessagingSystem继承,将TEventMessagePlaySoundEffect对象放在一起(需要MSG_PlaySound作为其消息ID,然后是要播放的声音效果的ID,可以是int ID或声音效果的名称),并简单地调用Post(&oEventMessagePlaySoundEffect)。
这只是一个非常简单的设计,没有实现。(我主要在控制台游戏中使用)。如果你在多线程环境中,那么这是一种定义良好的方式,可以让运行在不同线程中的对象和系统相互通信,但您可能希望保留TEventMessage对象,以便在处理时数据可用。
另一个改变是对于只需要Post()数据的对象,您可以在IEventMessagingSystem中创建一组静态方法,这样它们就不必继承它们(这是为了方便访问和回调功能,而不是Post()调用直接需要的)。
对于所有提到MVC的人来说,这是一个非常好的模式,但是你可以用很多不同的方式和在不同的级别实现它。我目前正在专业工作的项目是一个MVC设置大约3倍,有整个应用程序的全局MVC,然后设计明智的每个MV和C也是一个自包含MVC模式。所以我在这里试图做的是解释如何使一个C足够通用,可以处理几乎任何类型的M,而不需要进入一个视图。
例如,一个对象在“死亡”时可能想要播放一个声音效果。您可以为Sound System创建一个结构体,如TEventMessageSoundEffect,它继承自TEventMessage并添加一个声音效果ID(无论是预加载的Int,还是sfx文件的名称,无论它们在您的系统中如何跟踪)然后所有对象只需要将TEventMessageSoundEffect对象与适当的Death噪声放在一起并调用Post(&oEventMessageSoundEffect); object..假设声音未静音(您希望取消注册声音管理器。
编辑:为了澄清这一点,关于下面的评论:任何发送或接收消息的对象只需要知道IEventMessagingSystem接口,这是EventMessagingSystem需要知道的所有其他对象中唯一的对象。这就是给你的分离。任何想要接收消息的对象只需注册(MSG,Object,Callback)s。然后当一个对象调用Post(MSG,Data)它通过它知道的接口将其发送到EventMessagingSystem,EMS然后将通知每个注册的事件对象。您可以执行其他系统处理的MSG_PlayerDied,或者玩家可以调用MSG_PlaySound,MSG_Respawn等来让监听这些消息的事物对它们进行操作。可以将Post(MSG,Data)看作是游戏引擎中不同系统的抽象API。
哦,我的天啊!还有一件事是向我指出的。我上面描述的系统符合另一个答案中的观察者模式。所以如果你想要一个更一般的描述,使我的更有意义一点,这是一个简短的文章,给它一个很好的描述。
mrfwxfqh2#
避免紧耦合的对象间通信的通用解决方案:
gr8qqesn3#
这里有一个为C++11编写的整洁的事件系统,你可以使用。它使用模板和智能指针以及代理的指针。它非常灵活。下面你还可以找到一个例子。
这些类为您提供了一种发送带有任意数据的事件的方法,以及一种直接绑定函数的简单方法,这些函数接受已转换的参数类型,系统在调用委托之前强制转换并检查正确的转换。
基本上,每个事件都派生自IEventData类(如果你愿意,你可以称之为IEvent)。你调用ProcessEvents()的每一个“帧”,在这个点上,事件系统循环通过所有的委托,并调用已经订阅每个事件类型的其他系统提供的委托。任何人都可以选择他们想要订阅的事件,因为每个事件类型都有一个唯一的ID。您还可以使用AddIdas订阅这样的事件:AddIdas(MyEvent::ID(),[&](shared_ptr ev){ do your thing }.
无论如何,这里是所有实现的类:
字符串
Cpp文件:
型
为了方便起见,我使用了一个EventNode类作为任何想要监听事件的类的基类。如果你从这个类派生你的监听类,并将它提供给你的事件管理器,您可以使用非常方便的OnEvent函数(..)注册你的事件。当你的派生类被销毁时,基类会自动取消订阅你的派生类的所有事件。这是非常方便的,因为当你的类被销毁时,忘记从事件管理器中删除一个委托几乎肯定会导致你的程序崩溃。
一种简单的方法来获得一个事件的唯一类型id,只需在类中声明一个静态函数,然后将它的地址转换为int。由于每个类在不同的地址上都有这个方法,它可以用于唯一标识类事件。如果你愿意,你也可以将typeObject()转换为int来获得一个唯一的id。有不同的方法来做到这一点。
这里有一个关于如何使用它的例子:
型
x8diyxa74#
这可能不仅适用于游戏类,也适用于一般意义上的类。MVC(模型-视图-控制器)模式以及您建议的消息泵是您所需要的。
“Enemy”和“Player”可能适合MVC的Model部分,这并不重要,但经验法则是所有模型和视图都通过控制器进行交互。(比指针更好)指向这个“控制器”类的(几乎)所有其他类示例,我们将其命名为ControlDispatcher。向其添加消息泵(取决于您编码的平台),首先示例化它(在任何其他类之前,并将其他对象作为它的一部分)或最后示例化它(并将其他对象作为引用存储在ControlDispatcher中)。
当然,ControlDispatcher类可能必须进一步拆分为更专门的控制器,以保持每个文件的代码在700-800行左右(至少对我来说是限制),它甚至可能有更多的线程泵和处理消息,这取决于你的需要。
zfciruhq5#
小心“消息风格的系统”,它可能依赖于实现,但通常你会放松静态类型检查,然后可能会产生一些很难调试的错误。请注意,调用对象的方法它已经是一个类似消息的系统。
也许你只是缺少了一些抽象层次,例如,对于导航,Player可以使用Navigator而不是知道Map本身的所有信息。你还说
this has usually descended into setting lots of pointers
,那些指针是什么?也许,你把它们给了一个错误的抽象?..让对象直接知道其他对象,而不通过接口和中间体,是获得紧耦合设计的直接方法。e4eetjau6#
消息传递绝对是一种很好的方式,但是消息传递系统可能有很多不同之处。如果你想保持你的类漂亮和干净,那么就把它们写得不知道消息传递系统,而是让它们依赖于一些简单的东西,比如“ILocationService”,然后可以实现它来发布/请求来自Map类之类的东西的信息。虽然你最终会有更多的类,它们会很小,很简单,并鼓励干净的设计。
消息传递不仅仅是解耦,它还可以让你转向一个更加异步,并发和React式的架构。Gregor Hophe的《企业集成模式》是一本谈论好的消息传递模式的好书。Erlang OTP或Scala对Actor Pattern的实现为我提供了很多指导。
tquggr8v7#
@kellogs对MVC的建议是有效的,并在一些游戏中使用,尽管它在Web应用程序和框架中更常见。这可能是矫枉过正,太多了。
我会重新考虑你的设计,为什么玩家需要和敌人对话?他们不能都继承Actor类吗?为什么Actor需要和Map对话?
当我读我写的东西时,它开始适合MVC框架.
这是我开发的Asteroids的一个实现。你的游戏可能很复杂,也可能是很复杂的。