如何重构这个代码层次结构(与德米特法则相关)

发布于 2024-07-11 22:34:58 字数 613 浏览 7 评论 0 原文

我有一个游戏引擎,将物理模拟与游戏对象功能分开。 因此,我有一个物理体的纯虚拟类,

class Body

我将从中导出物理模拟的各种实现。 我的游戏对象类看起来像这样

class GameObject {
public:
   // ...
private:
   Body *m_pBody;
};

,我可以插入该特定游戏所需的任何实现。 但当我只有一个 GameObject 时,我可能需要访问所有 Body 函数。 所以我发现自己写了很多东西,就像

Vector GameObject::GetPosition() const { return m_pBody->GetPosition(); }

我很想把它们都刮掉一样,只是做类似的事情,

pObject->GetBody()->GetPosition();

但这似乎是错误的(即违反了德米特定律)。 另外,它只是将冗长的实现从实现推向了使用。 所以我正在寻找一种不同的方法来做到这一点。

I've got a game engine where I'm splitting off the physics simulation from the game object functionality. So I've got a pure virtual class for a physical body

class Body

from which I'll be deriving various implementations of a physics simulation. My game object class then looks like

class GameObject {
public:
   // ...
private:
   Body *m_pBody;
};

and I can plug in whatever implementation I need for that particular game. But I may need access to all of the Body functions when I've only got a GameObject. So I've found myself writing tons of things like

Vector GameObject::GetPosition() const { return m_pBody->GetPosition(); }

I'm tempted to scratch all of them and just do stuff like

pObject->GetBody()->GetPosition();

but this seems wrong (i.e. violates the Law of Demeter). Plus, it simply pushes the verbosity from the implementation to the usage. So I'm looking for a different way of doing this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

如痴如狂 2024-07-18 22:34:58

得墨忒耳定律的想法是,您的 GameObject 不应该具有诸如 GetPosition() 之类的函数。 相反,它应该具有 MoveForward(int)TurnLeft() 函数,可以在内部调用 GetPosition() (以及其他函数)。 本质上,它们将一种界面转换为另一种界面。

如果您的逻辑需要 GetPosition() 函数,那么将其转换为 Ates Goral 接口是有意义的。 否则,您将需要重新思考为什么要如此深入地抓住一个对象来调用其子对象的方法。

The idea of the law of Demeter is that your GameObject isn't supposed to have functions like GetPosition(). Instead it's supposed to have MoveForward(int) or TurnLeft() functions that may call GetPosition() (along with other functions) internally. Essentially they translate one interface into another.

If your logic requires a GetPosition() function, then it makes sense turn that into an interface a la Ates Goral. Otherwise you'll need to rethink why you're grabbing so deeply into an object to call methods on its subobjects.

动次打次papapa 2024-07-18 22:34:58

您可以采取的一种方法是将 Body 接口拆分为多个接口,每个接口都有不同的用途,并仅向 GameObject 授予它必须公开的接口的所有权。

class Positionable;
class Movable;
class Collidable;
//etc.

具体的 Body 实现可能会实现所有接口,但只需要公开其位置的 GameObject 只会引用(通过依赖注入)一个 Positionable界面:

class BodyA : public Positionable, Movable, Collidable {
    // ...
};

class GameObjectA {
private:
    Positionable *m_p;
public:
    GameObjectA(Positionable *p) { m_p = p; }
    Positionable *getPosition() { return m_p; }
};

BodyA bodyA;
GameObjectA objA(&bodyA);

objA->getPosition()->getX();

One approach you could take is to split the Body interface into multiple interfaces, each with a different purpose and give GameObject ownership of only the interfaces that it would have to expose.

class Positionable;
class Movable;
class Collidable;
//etc.

The concrete Body implementations would probably implement all interfaces but a GameObject that only needs to expose its position would only reference (through dependency injection) a Positionable interface:

class BodyA : public Positionable, Movable, Collidable {
    // ...
};

class GameObjectA {
private:
    Positionable *m_p;
public:
    GameObjectA(Positionable *p) { m_p = p; }
    Positionable *getPosition() { return m_p; }
};

BodyA bodyA;
GameObjectA objA(&bodyA);

objA->getPosition()->getX();
醉酒的小男人 2024-07-18 22:34:58

游戏层次结构不应涉及大量继承。 我无法向您指出任何网页,但这是我从多个来源(尤其是游戏宝石系列)收集到的感觉。

您可以拥有诸如“ship->tie_fighter”、“ship->x_wing”之类的层次结构。 但不是 PlaysSound->tie_fighter。 您的 tie_fighter 类应该由它需要代表自身的对象组成。 物理部分、图形部分等。您应该提供一个最小的接口来与游戏对象交互。 在引擎或物理部件中实现尽可能多的物理逻辑。

通过这种方法,您的游戏对象将成为更基本的游戏组件的集合。

综上所述,您将希望能够在游戏事件期间设置游戏对象的物理状态。 因此,您最终会遇到您所描述的设置各个状态部分的问题。 这只是令人讨厌,但这是我迄今为止找到的最好的解决方案。

我最近尝试使用 Box2D 的想法来制作更高级别的状态函数。 有一个函数 SetXForm 用于设置位置等。另一个函数 SetDXForm 用于设置速度和角速度。 这些函数将代理对象作为代表物理状态各个部分的参数。 使用这样的方法,您可以减少设置状态所需的方法数量,但最终您可能仍然会实现更细粒度的方法,并且代理对象将比跳过这些方法节省更多的工作就几种方法而言。

所以,我并没有提供太多帮助。 这更多的是对之前答案的反驳。

总之,我建议您坚持使用多种方法。 游戏对象和物理对象之间可能并不总是存在简单的一对一关系。 我们遇到了这样的情况,让一个游戏对象代表爆炸中的所有粒子要简单得多。 如果我们屈服并只暴露一个主体指针,我们将无法简化问题。

Game hierarchies should not involve a lot of inheritance. I can't point you to any web pages, but that is the feeling I've gather from the several sources, most notably the game gem series.

You can have hierarchies like ship->tie_fighter, ship->x_wing. But not PlaysSound->tie_fighter. Your tie_fighter class should be composed of the objects it needs to represent itself. A physics part, a graphics part, etc. You should provide a minimal interface for interacting with your game objects. Implement as much physics logic in the engine or in the physic piece.

With this approach your game objects become collections of more basic game components.

All that said, you will want to be able to set a game objects physical state during game events. So you'll end up with problem you described for setting the various pieces of state. It's just icky but that is best solution I've found so far.

I've recently tried to make higher level state functions, using ideas from Box2D. Have a function SetXForm for setting positions etc. Another for SetDXForm for velocities and angular velocity. These functions take proxy objects as parameters that represent the various parts of the physical state. Using methods like these you could reduce the number of methods you'd need to set state but in the end you'd probably still end up implementing the finer grained ones, and the proxy objects would be more work than you would save by skipping out on a few methods.

So, I didn't help that much. This was more a rebuttal of the previous answer.

In summary, I would recommend you stick with the many method approach. There may not always be a simple one to 1 relationship between game objects and physic objects. We ran into that where it was much simpler to have one game object represent all of the particles from an explosion. If we had given in and just exposed a body pointer, we would not have been able to simplify the problem.

撩心不撩汉 2024-07-18 22:34:58

我是否正确理解您将某些事物的物理特性与其游戏表现分开?

即,你会看到这样的东西:

class CompanionCube
{
    private:
        Body* m_pPhysicsBody;
};

如果是这样,那我就觉得不对劲了。 从技术上讲,您的“GameObject”一个物理对象,因此它应该派生自 Body。

听起来您正在计划交换物理模型,这就是您尝试通过聚合来完成此操作的原因,如果是这样的话,我会问:“您计划在运行时或编译时交换物理类型吗? ?”。

如果编译时间是你的答案,我将从 Body 派生你的游戏对象,并使 Body 成为你想要的默认物理体的 typedef。

如果是运行时,您必须编写一个“Body”类来在内部进行切换,如果您的目标是尝试不同的物理效果,这可能不是一个坏主意。

或者,您可能会发现根据游戏对象的类型(水、刚体等),Body 有不同的“父”类,因此您可以在推导中明确这一点。

无论如何,我不会再胡言乱语了,因为这个答案是基于大量的猜测。 ;)如果我的说法有误,请告诉我,我会删除我的答案。

Do I understand correctly that you're separating the physics of something from it's game representation?

i.e, would you see something like this:

class CompanionCube
{
    private:
        Body* m_pPhysicsBody;
};

?

If so, that smells wrong to me. Technically your 'GameObject' is a physics object, so it should derive from Body.

It sounds like you're planning on swapping physics models around and that's why you're attempting to do it via aggregation, and if that's the case, I'd ask: "Do you plan on swapping physics types at runtime, or compile time?".

If compile time is your answer, I'd derive your game objects from Body, and make Body a typedef to whichever physics body you want to have be the default.

If it's runtime, you'd have to write a 'Body' class that does that switching internally, which might not be a bad idea if your goal is to play around with different physics.

Alternatively, you'll probably find you'll have different 'parent' classes for Body depending on the type of game object (water, rigid body, etc), so you could just make that explicit in your derivation.

Anyhow, I'll stop rambling since this answer is based on a lot of guesswork. ;) Let me know if I'm off base, and I'll delete my answer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文