Advertisement

I suck at using at creating/using classes

Started by August 08, 2024 10:04 PM
4 comments, last by elonwick3525 3 weeks, 2 days ago

I'm a complete newbie so sorry if this is completely dumb. I'm working on a game engine and I opted to go with an inheritance hierarchy for my scene code I have a SceneElement as the root class and anything that goes into a scene such as a model, camera, light source, skybox etc inherit it

class SceneElement {};
class Model : public SceneElement, public Transformable, public Renderable {};
class Camera : public SceneElement, public Transformable, public Updatable {};
class AudioSource : public SceneElement, public Transformable {};
class LightSource : public SceneElement, public Transformable {};

To me this makes sense a Model is a SceneElement and through the editor I can add an instance to the scene, but when it comes to actually use this code to make a game it starts to fall apart for example if I wanted to make a Player well it doesn't really fit into this is-a relationship I mean a model might be used to visually represent the player, but I feel like that's more of a has-a relationship the player has a model and can use input to move it around.

I don't think this would count as composition, but instead of having the Player inherit from SceneElement or some other class would it be practical to just reference the elements it needs or is mandatory that game objects fit into the engine

class Player {
void update() {
// use input module to move model around
}
private:
Model& model; // reference to some model in the scene
}

edit: another thing I realized is perhaps I could utilize mixin or interfaces? similar to how Model inherits Renderable

Inheritance like that can be made to work, but it is very awkward and brittle. They exist, but typically require expert-level interface design to work well, and implementations are typically limited only to the specific game being made. Get it wrong and implementation becomes a nightmare.

Generally prefer composition over inheritance.

In games the more typical approach with composition is to use an Entity Component System, or ECS. Elements of the scene would be entities, and they would be composed of any number of components that you create. A light source could be a component, an audio source could be a component, a model could be a component. A camera could be a component.

All the components would implement a common interface. A SceneElement could contain any number (0 or more) components. It might contain a model component, or might not, or might even have multiple models but only one is active. A SceneElement might contain a light source component if it emits light, and the light source component might be active or not if it is currently on or off. A SceneElement could have proximity trigger components, it could have audio components if it generates sound, it could have physics collision components if it interacts with physics, and more.

Consider downloading either Unreal or Unity and playing around with tutorials on them to learn how they implement their features. In Unity they're called MonoBehaviours attached to GameObjects. In Unreal they're called Components attached to Actors. They're not the only approach, the concepts are limited only by your imagination, but they're a common approach used by most of the marketplace.

Advertisement

I think you are overusing multiple inheritance. I mean, it's a good thing that C++ has multiple inheritance and a bad thing that Java et al don't, but I think I can count the number of times I actually used it on the fingers of one hand.

One thing you might consider is using a tree structure: each SceneElement can have any number of child SceneElements, which in turn can have more child elements. A player is-a SceneElement but has-a Model which also is-a SceneElement, as a child of the player. That's basically the technique used by Godot, and it is IMHO one of the best aspects of Godot. Far more flexible than a flat ECS structure. (This as an endorsement of Godot.)

I would avoid multiple inheritance when possible. Some thoughts:

  1. Looking at your class structure, it seems like SceneElement and Transformable could be combined. Is it possible that a SE is not going to be transformable?
  2. LightSource isn't updatable. That makes sense if it's static, but what if you want to change it later?
  3. Camera, Audio, and LightSource aren't renderable. That makes sense too, but it might be helpful if they were when you're debugging.

You could have all the classes derive from one simple base class. Assuming LightSource inherits from BaseClass and you really don't want the LightSouce position to be updateable:

virtual void BaseClass::SetPosition(VECTOR NewPosition) {m_Position = NewPosition;}
void LightSource::SetPosition(VECTOR  NewPosition) override {assert(false);}

The default is to have everything updatable. LightSource will override updating routine and if you do try to update it, you'll get an error and see your mistake right away.

The same logic could apply for a Model or Camera class:

virtual void BaseClass::Render() = 0; 			    //Impossible to draw a BaseClass
void Model::Render() override {MyModel.Render();}	//Draw a mesh stored in 'MyModel'
void Camera::Render() override { if (bDebug==true){CameraMesh.Render();})	//Draw IFF debugging 

-Scott

P.S. This wasn't a dumb question; inhertiance can be tricky until you get the hang of it. If inheritance isn't making your life easier, you should rethink your design.

Your approach makes sense, and it's a common challenge in game engine design. Instead of having `Player` inherit from `SceneElement`, which might not be appropriate, you can use composition effectively.

Here's a brief suggestion:

**Composition Over Inheritance:**
You’re right that `Player` is more of a "has-a" relationship with `Model` rather than an "is-a" relationship. It's practical to have `Player` hold a reference to `Model` (or any other components it needs) rather than inheriting from `SceneElement`. This way, `Player` can manage its components without forcing a hierarchical relationship.

**Interfaces/Mixins:**
Using interfaces or mixins can help you maintain flexibility. For example, you could create interfaces like `ITransformable` and `IRenderable` that components can implement, allowing `Player` to work with any object that supports those interfaces without being tightly coupled.

Here's a refined example:

```cpp
class ITransformable {
// Transform-related methods
};

class IRenderable {
// Rendering-related methods
};

class Model : public ITransformable, public IRenderable {
// Model-specific methods
};

class Player {
public:
void update() {
// Use input module to move model around
}
private:
ITransformable& model; // Reference to a model or any other transformable object
};
```

This way, you avoid forcing all game objects to fit into the engine’s core hierarchy while still leveraging the flexibility of composition and interfaces.

Advertisement