Question

I tried to make the question title as generally applicable as possible, but I'm not certain I worded it so well. It was brought about by a very specific problem I'm having, and for the remainder of this question I'll just try to describe that.

I'm attempting to write a small game engine with graphics, physics, and collisions*. I am trying to implement each of these things separately; it is perfectly conceivable that I have a graphically displayed, physically moving object that can't collide (fire effects?) or a collision object that isn't rendered or governed by physics (invisible barrier). However, a large number of objects will need all three.

I'm pretty darn sure that I can isolate the three pieces almost completely in their own classes, with the one exception that they all need to reference the same position and angular orientation of their object.

Initially, I thought that I'd put each thing into its own class and just build each actual game object with multiple inheritance, but in light of the fact that they all need to share data I wasn't so sure that that would work.

Next option was having each object initialized with pointers to the appropriate position and orientation data, but I felt weird treating those like special members . . . I would like each class to act as ignorant as possible of the others, and I would never do the pointers if I were designing each class independently.

I could also let each class live with its own position data and have the combination class (via either friendship or getters/setters) force them all to coincide, but that also just didn't feel right.

I tried searching Google for information on what's typically done, but most everything I found was focussed on a specific part of the system and didn't say a lot about integrating everything. Am I just being too philosophical, or is there a better way to do this?

If it's important, I'm using C++.

Thanks.


* I know that implementing it myself is pointless, but it's all just for fun.

Was it helpful?

Solution

Services, not Superclasses

These things are not related objects, they are behavioral aspects of game objects that may come and go as the state of the game objects change; think about them as services, not superclasses. They're not well-suited to purely compositional elements either, as the in-game behavior will likely be strongly dependent on the state of the game objects over time.

Example

A 'ghost' object may be visible and in motion but can pass through solid objects but may not be able to pass through a magical barrier and also may inflict damage when it collides with living tissue. It can only be damaged by magical weapons.

Sorry

Game programming is hard, at this level mostly due modeling complexity. Hack around to get your own ideas (if just having fun) and/or study existing game engines to see what others have done (e.g. Unreal Engine's source code)

OTHER TIPS

You're naturally working your way towards entity-component systems.

enter image description here

The final step which is the hardest to take if you've been applying an OOP mindset for years, is to turn those shared components into raw data, but it is only then that you can really come to appreciate the true flexibility of the ECS. I actually stubbornly resisted doing that until a second iteration where I just made components into raw data.

The way to kind of counteract the feeling that this seems all wrong and a total violation of encapsulation and information hiding is to remember that typically only one or two systems will be accessing any given component and often just one system that actually transforms (mutates) that data. As a result it's actually not much harder, if any, to maintain intracomponent invariants. It also tends to make it easy to maintain broader, intercomponent invariants with coarse systems processing everything.

Entities are just containers of components (with one component type instance maximum per entity). Components are just raw data. Systems process entities/components and are the sole providers of functionality between these three. Systems are also generally decoupled from each other and typically do not call functions into each other. Most systems also resemble the loopy pattern of:

for each entity with these specific components:
    do something with the components

For instance, the render system above might be like this:

for each entity with pos/velocity and sprite components:
    render sprite component at pos

While the movement system might look like this:

for each entity with pos/velocity components:
    pos += velocity * time_elapsed
Licensed under: CC-BY-SA with attribution
scroll top