0
\$\begingroup\$

I have a GameObject class, which contains Components, like:

  • Renderer
  • Camera
  • Behaviour
  • Rigidbody

First I inherited all of them from Component which has an Update() method, and it is called every frame on every game objects' every component.

But then I noticed, that** Renderer and Camera's Update method is unused**. (There are classes like RenderData & CameraData which are isolated and made from primitives, and upon creation of these Components, these Data's pointer is sent to the GraphicsEngine which will process them every frame.)

So having an unused method is bad. So I created a DynamicComponent class, which inherits from Component, and Behaviour & Rigidbody classes are inherited from it.

I moved the Update() from Component to DynamicComponent, and I have now 2 containers at gameobjects:

  • Components (for components which are inherited directly from Component)

  • Dynamic Components (for components which are inherited from D.Comp.)

And every frame I only call DynamicComponents' Update().

Now I'm having another issue. There is the Behaviour class, where should be a Start() method, which is called on every Behaviour when the scene is fully initalized.

That means that I should have a Components, D.Components and also a Behaviours container at game objects.

This looks very bad to me.

Having a Component class, with Start(), Update(), etc is very simple and makes a very clean code, but this means that only a few derived classes implement its virtual methods.

What should I do?

\$\endgroup\$
1
\$\begingroup\$

Basically I'm with truthastup. I'm kind of a fan of the so called ECS pattern, which separate data and logic for Component and System. I think it's the pattern is good at making the engine architecture modular and flexible. I've also implemented a version of my own but it hasn't been used in commercial game, though.

On the other hand, the architecture you're using is more like Unreal and Unity engine, which is, lets say more conventional, but it's also easy to use. So follow this architecture is totally fine, there are many users of Unreal and Unity, which proves it can work, myself is also developing game by Unreal engine right now.

Anyway, the point is, I think to have so many layers of inheritance of Component is kind of shooting itself in the foot. The origin of Component based design is to let composition over inheritance and make things easier. So I'd say to have only one base class of Component and design a set of common virtual functions (Start(), Update() etc.) is very good to go.

You mentioned the problem that only a few derived classes implement some virtual functions, I think it's totally fine, since virtual functions are not always needed to be implemented. They are virtual because they "maybe" overridden, but not necessarily.

If your game engine is to be released for the public some day, then I'd strongly recommend to stick with a single Component base class and have all possible virtual functions in it, which is following the Unity way. Comparing Unity and Unreal's component based design, I'd say Unity is totally a winner, their architecture is clearer and easier to learn and use, and is more "component like". But Unreal's code base is far more older than Unity's, so maybe it's unfair/pointless to compare them, though.

\$\endgroup\$
  • 2
    \$\begingroup\$ I don't think Unity's base component class has a Start/Update etc method as you describe. I say this because you don't write "override" when declaring your own implementation of them. So I think Unity looks for methods with those names using reflection, which also explains how it can call them even when they're not public. Once it's done that, it can put only scripts that implement eg. Update in its batch of instances to update each frame, skipping any function call overhead for scripts that don't need an Update method. You could get a similar effect with Interfaces, eg. "IUpdatable" \$\endgroup\$ – DMGregory Jan 10 '17 at 7:47
  • \$\begingroup\$ @DMGregory Yes you are correct, those method are not virtual and not overridden, and actually I should have referred to MonoBehaviour instead of Component, since Unity's user defined component inherit from that. It's just that OP is using C++ so I tried to explain it in a C++ way, I apologize for the inaccuracy. So the point is that, in my opinion, the pre-defined sets of methods (Start(), Update() etc.) are working just like a pre-defined Component base class and a set of virtual functions. But yes, they are different implementation. \$\endgroup\$ – Marson Mao Jan 10 '17 at 10:48
  • 1
    \$\begingroup\$ With the virtual approach you still have to call the function — and if the function returns immediately with a no-op, so be it. That strikes me as an important difference — I often work on games where half the items in the scene don't need per-frame updates (or are split between Update and FixedUpdate / LateUpdate etc), so trying to call every exposed virtual method on all of them could result in a not-insignificant amount of function call overhead that gets zero desired work done, compared to keeping a collection of only the instances that need [a particular] update called. \$\endgroup\$ – DMGregory Jan 10 '17 at 11:50
  • \$\begingroup\$ I see, that makes sense. Have you done any profiling of such overhead? And I'm not sure how large the scale your game is, if there are thousands of objects active on the scene then it maybe matters (just a guess). Also 2 things: I'm not sure how do do this in C++, since Unity is using C# after all; and I'm also not sure if an empty virtual function could get optimized away by C++ compiler, I'll do some research and post back. \$\endgroup\$ – Marson Mao Jan 11 '17 at 2:08
2
\$\begingroup\$

Having a Component class, with Start(), Update(), etc is very simple and makes a very clean code

I'm not sure this is true. In my code, Components are simple data structures without any functionality. They just keep some info about this GameObj.

For instance:

class GraphicsComponent: public Component{
    public:
    unsigned int gameobj_id;
    float posx, posy, posz;
    float scaling;
    std::string material_name;
}

My Systems have Init(), Update(), etc, and will update the Components during those functions. in Engine::StartRunning() I call every System's Init() function. Then, when I start my gameloop, I call every System's Update() function over and over again until someone tells me to stop.

class GraphicsSystem: public System{
public:
    void Init(){
        // *set up initial GameObj components* //
    }
    void Update(float delta_time){
        HandleSystemMessagesAndInput();
        // *do stuff with components* //
    }

    std::vector<GraphicsComponent> gfx_comps;
};

Different Systems will keep 'their data' about the GameObjs (it's all inside that Component), but they will have functions implementing their functionality. For example, PhysicsSystem will have a PhysicsSystem::HandleCollisions( PhysicsComponent *phys_comp_a, PhysicsComponent *phys_comp_b) function.

I still keep a global container of GameObjs for convenience, but that's kind of taboo. However, I find it useful to hang a GameObj's Components on a GameObj struct for intra-System bailouts. In reality I only really use this during Component add/remove (each System does its own Component adds/removes) and I would warn you against thrashing the cache by doing it too often.

P.S. This link has some good discussion about the tradeoffs in using different approaches: http://gameprogrammingpatterns.com/component.html

\$\endgroup\$
  • \$\begingroup\$ Hey does your System get noticed when any Component on any Entity is added/removed? And does the System has something like a component bit masks? Since your architecture looks like artemis framework so I'm curious if you implement the same idea. \$\endgroup\$ – Marson Mao Jan 10 '17 at 2:00
  • \$\begingroup\$ I have a Msg Bus-type system (not a System, no Init/Update or anything) and a global PostMsg(MsgBuilder) function. Everything that wants to read Msgs (including most Systems) has a MsgSubscriber. Each of my Systems (usually onInit) registers MsgHandler command-pattern-objects to MsgSubscriber::RegisterHandler(new MsgHandler). On the call to MsgSubscriber::HandleMessages(), each handler is called by looping through them and calling handler->execute(Msg*) on each one (Systems usually call HandleMessages first thing onUpdate). Systems then hear about/react to Msgs (including GameObjSpawned). \$\endgroup\$ – truthastup Jan 10 '17 at 4:23
  • \$\begingroup\$ Though I hang/retrieve Components on GameObjs using a 64-bit hash of std::string Component::family_name, I don't use masks for Systems to build dependent Components. My GameObjs have an AttributesContainer and a PrototypeNameHash (basically a general AttributesContainer) on them. My System::onMsgGameObjSpawn() functions usually call a ThisComponentFactoryClass, which calls up the GameObj by its ID, reads its Attributes, and then figures out how to build ThisComponent based on this info. I don't know much about the Artemis Framework, actually. Is it like this? \$\endgroup\$ – truthastup Jan 10 '17 at 4:25
  • \$\begingroup\$ I think it's not like what you described, but I can't fully explain the artemis thing right now, I'll post back tomorrow! \$\endgroup\$ – Marson Mao Jan 10 '17 at 10:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.