c++ and DirectX game engine

I am currently working on a game engine using c++ and direct3D and I need help to design a class to manage the entities present on the scene.

The class should be something like this:
1
2
3
4
5
6
7
8
9
10
11
12
class Entity
{
public:
    Entity() {}
    ~Entity() {}

    float3 GetPos() const { return pos; }
    //...
private:
    float3 pos;
    //...
}


The vertices and indices data of each entity is handled somewhere else and we do not need to worry about it.
Last edited on
I need help to design a class to manage the entities present on the scene. The class should be something like this: [...]

You'd have to specifically explain what you're trying to do to get a decent answer. One way to do this is to describe the lifetime of a typical entity, from creation to destruction. How is it created, where is it stored, how and why is it used, and when and why is it destroyed?

Right now, at best we can guess about what you're trying to do. The only thing that can be said for certain is that every entity has a position. Presumably it has a rotation orientation too.

Try not to write code which does nothing. For instance, empty constructors and destructors are a waste of time and space, since they're provided by default. Similarly, if a class imposes no additional invariants on the state of its member data, making that member data private is a waste of time and space; writing the trivial getter and setter pair is too.
1
2
3
4
5
struct entity 
{
  float3 position; 
  float4 orientation; // quaternion
};
Last edited on
Similarly, if a class imposes no additional invariants on the state of its member data, making that member data private is a waste of time and space; writing the trivial getter and setter pair is too.
I will provide an alternative view to mbozzi's. In C++, setting the visibility of a member is a one way street: making it more visible is much easier than making it less visible. In other words, one you've made a member public and written all your code assuming it's public, if you later on find that you need to make it protected or private for whatever reason (e.g. to impose more invariants on its state) it can be very difficult if not impossible.
IMO you should consider very carefully whether it's worthwhile to make a data member public. Later on when you understand your problem better you can make the member public if you want for very little cost (maybe some inconsistencies in the code), while trivial setters and getters hurt very little.

I would have a different opinion of C++ allowed C#-like properties.
@helios, I agree with you, to a point.

I used to always write get/set pairs, but I quit because I realized that I almost never benefited significantly from the extra flexibility. YMMV, naturally, depending on the size of your project and the particular struct and stuff.
Last edited on
If you find it's a chore to write them, perhaps the preprocessor can help there:
1
2
3
#define DEFINE_GETTER_SETTER(x) \
    const decltype(x) &get_##x() const{ return this->x; } \
    void set_##x(decltype(x) &&value){ this->x = std::move(value); } 
It does make debugging a bit trickier sometimes, though.
While typing isn't my favorite thing to do, that's not the real issue.

Consider x.position vs. x.get_position():
The semantics of the former is obvious, but the function call is opaque: we can't tell if it throws, how long it will take, or whether or not the call initiates side-effects.

The argument "maybe I'll need to add an invariant later" isn't a sufficient justification for this burden in general, in my opinion.
Last edited on
The point of getters/setters is precisely that those details are hidden away. The function will behave as-if it was just returning the value of an internal state, while doing anything necessary to accomplish that. As long as the caller doesn't care what that is, it doesn't need to know anything else to use the interface.
If it does need to care, presumably the interface will provide some other means to get finer control.

Using your argument I could justify making all aspects of the implementation part of the interface. The client code could then be fully aware of exactly what effects each interaction with the class would have, and be designed accordingly. Needless to say, you would never be able to change any part of that class once you defined it, since it would have maximum coupling with all its users.
There's a reason why standard containers only specify minimum complexity requirements for their implementations, rather than specific algorithms and data structures.
Last edited on
speaking of standard containers, would not one of the STL containers solve the OP issue of tracking what is in the scene? A vector or list or whatever of pointers to the objects, perhaps? The objects themselves presumably know where they are in space, so can just peel it off the objects directly (assuming they all have a top level class with common attributes).
Last edited on
It depends on what OP wants to do with the entities, how they want to search for them, etc. Often, an octree is more efficient than a linear sequence.

But OP's question was more about designing the class itself, not where to store it.
I think I misread it... I saw "I have objects, and want to manage them"
The point of getters/setters is precisely that those details are hidden away. The function will behave as-if it was just returning the value of an internal state, while doing anything necessary to accomplish that. As long as the caller doesn't care what that is, it doesn't need to know anything else to use the interface.

If the caller is interested in writing a program that's correct, those details need to be available. If I call a function, I have to take a trip to the documentation. But If I access a member, it's clear what I'm getting.

Using your argument I could justify making all aspects of the implementation part of the interface.

No. This argument is specifically against pass-through interfaces, boilerplate code that does nothing but obscure things for no immediate benefit. One would only expose the implementation if it is trivial and relevant to the model.

For instance, if there's a position member, and having a position is an important trait of the thing being modeled (the entity), then the position member should likely be public. But any class member subject to a class invariant shouldn't be exposed, because than the invariant can't be guaranteed by the class.

Further, only a tiny minority of code needs to be concerned with forwards-compatibility. Most interfaces are only be used in a limited context as a local abstraction, and it's not hard to change a local interface. It only becomes challenging when the interface is used everywhere.

While a get/set pair on an API boundary is quite acceptable for reasons of forwards compatibility, it doesn't change that adding two unnecessary member functions incurs a cost every single time. For this reason, adding them everywhere is a bad idea, because the imagined benefit of future flexibility doesn't pay off frequently enough. You Ain't Gonna Need It, and all.
Last edited on
You guys are bike shedding.

But then again, this question is a bikeshed.

Nobody knows what OP wants and needs are.

An entity is a complex thing, and the one that OP posted is a perfectly functional but limited example, meanwhile there are 1000 other and more complex ways of doing it.

Also you are silly if you plan on writing an engine and expecting people to actually USE it.

Make the game idea first then write an engine around it (honestly when I was in school trying to make overly ambitious 2d games, I didn't even know I was writing my own engine that whole time), or more specifically for a 3d game, use an existing engine, be it a standalone all in one IDE+3d modeler+Asset Store engine, or an open source github engine possibly from a tutorial that you have full control over and figuring out how things work under the hood.

It sounds cheesy to use a engine like unity or unreal, but you have absolutely no idea how difficult it is to create something that is truly and definitively "good" or even "playable" if you try to hand write a 3d engine.

There is a fully functional game in a tutorial for a book called game coding complete 4th edition, and the code inside the book is marvelous, flexbible, deep and clean and jumps into tons of different areas and has like 30 000~ lines of code. I spent like 2-3 years studying it in depth and using bits a pieces of it, but I never played the game, I saw a youtube video on it (there was only one, I tried so hard to find more), but when I did get to compiling it and trying it out, it played like absolute crap, and it surprises me because it looked like code that would be used for the sims to me (at that time) but it cant handle 3 AIs at the same time because they all spawn in the same spot of fly off the stage, and the game hitches every time you try to shoot because it loads a file to spawn it (even though the file is cached in a zip). And this engine is made by 2 people, one was Mike McShaffry an important member of origin who worked on ultima 7 through ultima 10 and went to Looking Glass Studios, and David "Rez" Graham who worked on The Sims Medieval and a many other games. Really surprising how much talent they have, yet this is the best they can do after 3 books (Mike did the first 3). But I digress, the engine is 100% for engine logic and readability, and expects you to write the code for the game itself and many things could be fixed quite easily (but comprehension if the most difficult task).

But even though I learnt a lot from my experience, that's 2-3 years of nogaims. And I feel less competent in making a game than a person who just goes directly with something like unity or game maker studios. As of fact, whats stopping you from writing an engine in those engines? Sure you cant write all the "engine logic", but nothings wrong with a "game logic engine", which you just load files to do your bidding and the engine does the rest.
If the caller is interested in writing a program that's correct, those details need to be available.
Are you really going to argue that if you encountered this interface
1
2
3
4
class MyInterface{
public:
    virtual const std::string &get_name() const = 0;
};
you'd be unable to write a function that uses it correctly unless you read how the instance implements it?

This argument is specifically against pass-through interfaces, boilerplate code that does nothing but obscure things for no immediate benefit.
You argument was "these two pieces of code do the exact same thing, but one of them more fully defines its semantics, therefore it's better". Yes, you applied it to the specific case of thin wrappers, but can you explain why it couldn't be applied to the more general case of general encapsulation and data hiding? If having more thoroughly-defined semantics is inherently better, then unconditionally and fully exposing the implementation must be the perfect solution.

While a get/set pair on an API boundary is quite acceptable for reasons of forwards compatibility, it doesn't change that adding two unnecessary member functions incurs a cost every single time.
A small value multiplied by a large value may still be a small value. I will concede that the payoff of getters and setters is unusual, but if you want to argue against them, you need to make the case that the combined cost of adding them is greater than the benefit they add, and/or the risk of not adding them when you did need them after all.
Are you really going to argue that if you encountered this interface [...] you'd be unable to write a function that uses it correctly unless you read how the instance implements it?

Consider the following implementation:
1
2
3
4
5
6
7
8
9
class Derived: public MyInterface 
{
  public:
    const std::string &get_name() const override
    { 
      static std::string name = some_function(); 
      return name; 
    }
}

Just by looking at the interface, I can't say whether or not that function provides exception-safety guarantees.
I can't tell you whether or not there are side-effects. I can't tell you if I can call it from multiple threads. If I care about any of these things, I have to go look it up. In general one can't just use a function blindly and ignore everything that could go wrong.

In any event, if a derived class might implement a non-trivial override, this wouldn't satisfy the criteria as a trivial interface.

Your argument was "these two pieces of code do the exact same thing, but one of them more fully defines its semantics, therefore it's better"

My argument is "Choice A introduces a member-function which provides no immediate value. Choice B doesn't, but still accomplishes the same thing; Choice B more fully defines the semantics, therefore it's better."

Can you explain why [your argument] couldn't be applied to the more general case of general encapsulation and data hiding? If having more thoroughly-defined semantics is inherently better, then unconditionally and fully exposing the implementation must be the perfect solution.

Generally, I might claim that no abstraction is always better than one that doesn't provide any current value. But it is not clear that no abstraction is better than one which does provide value.

For example, member functions which maintain a class invariant are a valuable abstraction, because it prevents clients from having to remember and maintain those invariants themselves. Of course a client which is accessing the implementation directly assumes that burden.

In 2002 Joel Spolsky wrote about a "law of leaky abstractions", which supports this case pretty well. His idea is that every abstraction can be made to expose its underlying implementation, at least to some extent. When this happens, the programmer needs to care about it.
https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/

I will concede that the payoff of getters and setters is unusual, but if you want to argue against them, you need to make the case that the combined cost of adding them is greater than the benefit they add, and/or the risk of not adding them when you did need them after all.

Indeed I do, but I'm not sure this can be quantified, so we're stuck debating ;).
My suggestion would be to continue to add getters and setters, but only judiciously where you feel a payoff is likely. Otherwise you're paying for something you don't need.
Last edited on
Topic archived. No new replies allowed.