面向对象设计播放器和AI控制单元.dynamic_cast

OOP Design Player and AI Controlled Unit. dynamic_cast?

本文关键字:dynamic cast 单元 控制 播放器 AI 面向对象设计      更新时间:2023-10-16

假设我有一个Actor类型,它可以是我的游戏关卡上的任何可放置对象。我还有Unit,它是Actor的子类。单位可以是玩家,也可以是AI控制的英雄。

Unit也有两个子类:Player(玩家)和Hero (AI控制的单位)。在单位类中,会有移动信息,旋转,以及其他玩家和英雄都需要的一般设置。在子类中,AI将拥有与玩家不同且更多的功能。

现在我面临着以下问题:

一个函数只接受Actor作为参数(例如OnOverlap(Actor a*))但是OnOverlap()只有当它是一个单位职业(英雄或玩家)时才应该做一些事情。因此,我需要一些像instanceof()从Java在c++。

一个解决方案是要么使用dynamic_cast,但我不确定这是否是一个好主意的性能。或者使用virtual,但是当Hero拥有比Player更多的功能时,这将不起作用。

或者我应该尝试一个全新的面向对象设计?

我想说dynamic_cast是代码气味。从本质上讲,这并不一定是坏事,但这表明你的设计可能出了问题。

OOP中的一个重要概念是多态性:对象的行为取决于它们的类型而不同,这种行为被封装在中,隐藏在接口后面。如果你显式地检查一个对象的类型来改变你想要应用到它的逻辑,那么你就违反了多态性。

现在虚拟方法也不是那么好,它们确实会产生运行时成本,并且有时会给表带来太多的复杂性。c++使virtual方法成为例外,而不是默认的,我相信正是出于这些原因。

您应该知道的一件事是,虚拟方法只是一种多态性,称为动态多态性。还有另一种方法可以根据对运行时影响较小的类型获得不同的行为:静态多态性,即模板元编程。但这在复杂性方面并没有真正的帮助。

在这种情况下,你真正应该做的是分开对待不同的东西。你可能想在Units上有这个OnOverlap()方法是有原因的:假设你只在Units上做碰撞检查,而不是所有的Actors。然后维护一个单独的Units列表,并使OnOverlap()方法在Unit类上是非虚拟的。这种思维方式往往是关键。

这种技术称为运行时类型信息(RTTI),检查dynamic_cast的输出是否为NULL是一种有效的实现。

另一种方法是#include <typeinfo>,并使用typeid(*a).name()来获取a的类型的字符串名称。

需要使用dynamic_cast 通常(尽管并不总是)表明您存在设计缺陷。在这种情况下,我认为你需要。

如果不进一步评论您所描述的体系结构的总体设计,我想说使用虚拟方法是正确的方法。让OnOverlap接受它的actor并在actor上调用OnOverlapped方法,也许给它一个指向执行初始OnOverlap方法的指针(从你的问题中不清楚那是什么(*))。

void X::OnOverlap (Actor * actor) {
  actor->OnOverlapped(this);
}

OnOverlapped本身是虚的。这允许您在actor单元的情况下"做正确的事情",但在其他地方不做任何事情或其他一些默认行为。您是正确的,在这种情况下,这意味着您只能使用Actor的公共API。您必须将任何需要Player的附加方法的内容移到OnOverlappedPlayer实现中。如果您真的认为由于某些原因不能这样做,那么您可能应该考虑在更高级别上的替代设计(并且可能需要编辑您的问题以提供有关您的体系结构的更多细节)。

(*)我最初读到你的问题让我认为OnOverlap已经不是Actor的方法了;那是不相关事件的一部分。如果Actor部分,您可能会对一种称为双重分派的模式感兴趣,这种模式可用于这种"冲突处理"类型的问题,并且也是一种使用动态分派(虚拟)的技术。

这些函数是由我的引擎预定义的,其中参数必须而是父函数"Actor"。所以如果任何Actor与网格,函数被调用。在这种情况下,我看不到其他的解决方案,而不是获取Actor对象的实例来知道它是否存在玩别的东西

Actor基类中的简单虚函数可以是noop。在单位类中覆盖它,如果需要的话,可以在英雄和玩家类中更专门化它。非单元Actor将调用空方法。比RTTI和instanceof习惯用法干净。

可以有一个默认的基本实现,期望被专门化的后代覆盖。这就是抽象基类的意义所在,除非在这种情况下,您提供的是一个什么都不做的默认实现。这个想法是,你正在调度"DoCollision"消息(c++中的虚函数)。您不关心哪些类处理它或如何处理它。只要对象有DoCollision编译时"接口",并且你正确地分派了它。