Need advice on class responsibilities/information flow in game loop

Started by
7 comments, last by ExErvus 8 years, 5 months ago

Hello fellow gamedevs!

I'm currently working for about a year on a game in C++/SFML (i've worked with Lua and Java so far), however as the project grows larger and more complex i've began to question the way the basic class/info flow is handled, and i'd like to have some advice what, in your opinion, would be a good structure/good programming practice for my games class relations.

To narrow it a bit down, the actual game aspect of the game has one main "StateGame" class, which holds a gameloop method (executed every frame and recieving a deltatime float) aswell as pointers to other classes, such as a Player class, a Controls class (responsible for user input), etc
Below you can see both classes with only the for this discussion relevant attributes/methods

The StateGame class (of course very simplified)


class StateGame {
public:
 std::shared_ptr<Player> player;
 std::shared_ptr<Controls> controls;
 void gameLoop(float dt) {
   //handle controls every frame
   this->controls->handleInput(dt);
 };
}

aswell as a Controls class (also very simplified), which is supposed to handle the user Input


class Controls{
public:
 void handleInput(float dt);
}

The content of the player class isn't really important for this topic.

My question now is : What would be the best programming practice to let the controls class have access to the player class (to move it around, for example. I'd like to have the controls and the player object to be seperate classes).

So far i've figured out three possibilities:

A) Give controls class a direct pointer to the player class


class Controls{
public:
 std::shared_ptr<Player> player;
 void handleInput(float dt);
}

This would give the Controls class direct access to the Player class via


this->player

(fast performance, good), however at the same time it would create a seperate pointer to the player class (bad) instead of the StateGame class being soley responsible for it, and considering that other aspects also need to know info from the player class aswell (GUI for example, or enemies) this practice would require other classes to have direct pointers to the Player class aswell, it would result in the Player class being coupled with many other classes (bad). It would get even worse with other classes having pointers to eachother (all subclasses pointing to eachother, rather than letting the StateGame handle its children).

This is the method i've been using so far (first game in C++, possible overuse of pointers. It's such a luxury that you can just have everything point to eachother to have direct access.... but as i've written above, i fear that it could become too chaotic rolleyes.gif)

B) Give controls class indirect acces by "jumping" through StateGame class

Another possibility would be to give Controls a weak pointer to the StateGame class


class Controls{
 public:
 std::weak_ptr<StateGame> game;
 void handleInput(float dt);
}

and then access the player class via


this->game->player

which would result in no direct pointer between the Player and the Controls class and other classes (good), but it would require an extra detour jump through the StateGame class, which is of course slower than a direct access via member (not to mention that i'd have to .lock() the weak_ptr first).

C) Let StateGame pass a shared_ptr ( or const reference ) of the Player class to the Controls clas

This possibility would be to not let the Controls class have member pointers to any of those classes (neither Player nor StateGame) but rather let the StateGame class pass a shared_ptr (or const reference) everytime the Controls class is supposed to do something with the Player class (aka the StateGame class is responsible for delivering needed info to the member classes/subclasses)


class StateGame{
public:
 std::shared_ptr<Player> player;
 std::shared_ptr<Controls> controls;
 void gameLoop(float dt) {
   //handle controls every frame
   this->controls->handleInput(dt, this->player);
 };
}

and then let Controls accept a shared_ptr as additional parameter in the handleInput() method


class Controls{
 public:
 std::weak_ptr<StateGame> game;
 void handleInput(float dt, std::shared_ptr<Player> player);
}

which would result in the handieInput()-method having direct access to the player class by the pointer given by the parent StateGame class, the parent class StateGame having complete control over what it's child member classes get and it would eliminate the need of member pointers to the StateGame or Player class in the Controls class (good), however it would require the StateGame class to pass a shared_ptr (or a const reference to it) every frame and i'm not sure how much impact this has on the performance, compared to the other possibilities, or if it actually is better programming practice.

Basically the question is : Is it a better mix of practice and performance to have

a) classes have direct pointers to eachother, regardless where they are in the hierarchy and regardless how chaotic the endresult is (dozens of child classes pointing to eachother) or

b) member classes access other classes by taking a detour and jumping through their "superior class" (in this case : StateGame) or

c) should instead the "parent" class (StateGame) distribute the info (in this case : pointer to player) to its member classes (in this case Controls)?

a) and b) would mean that each frame the game would loop trough all member classes, which then request player info from their parent class (StateGame)
c) would mean that each frame the StateGame class would loop through all member classes and the parent would distribute all the info the children need

Which of these methods would you consider the best practice? Is there any better method of handling this?

Maybe the performance differences aren't that great, however if i program something i'd like to do it the best way, both performance-wise and in terms of programming practice, and i don't have any problem rewriting what i've written so far.

Thank you very much!

Advertisement

The way I have gone about this in my current project is to have the Player class use an input interface component. Specifically I have an IInputComponent that the player is given. When the IInputComponent is created, it is passed to the Controls system which maintains a reference to the IInputComponent and updates it every frame. The exact implementation of the IInputComponent that the Player class has determines how the input is handled.

This system has worked well for me so far. The Player class has no idea that there is a Controls system, all it knows is that it has a component that receives input. The Controls system also has no idea about Player classes, it just knows that there is some component out there in the sea of objects that needs input information.

To allow things like the IInputComponent to be sent to the Controls system, I have a ComponentFactory that is responsible for building components and registering them to relevant systems. This allows me to do something along the lines of:

Player.Components.Add(ComponentFactory.CreatePlayerInputComponent())

where PlayerInputComponent is a class that implements the IInputComponent interface.

As can be seen, I am using a Component-based design. If you don't want to do so, you could use this same approach by just having your Player class implement some kind of interface that the Controls class can work with.

So, this is roughly your option A, but it with some more clearly defined structure and hiding of implementation details. It does boil down to a bunch of different pointers, but try to keep things such as Controls and GUI from having to know everything about the Player class, and instead use interfaces. Interfaces really help you enforce contracts with your own design. If you give the GUI class a reference to a Player class, GUI could now change everything about the Player. If it instead uses some kind of interface, you can hide all the Player data that the GUI does not need (unless you do some fancy casting, which should be avoided), which helps a lot with maintaining a clear structure as the code grows.

I would think as that:

A game knows what a player entity is (and not the vice-versa).

The player doesn't know what a keyboard/mouse is; it is controlled by a script inside the game state/script system that verifies which input actions to fire.

Then, the player (an entity) has data such position and orientation, and you change them directly in the game state depending of the inputs.

Since you've mentioned the ECS approach, I can't say if my thoughts are still valid yet even if they're valid for an entity-actor based approach.

I'd go the static class route. Since you won't have multiple "controls" there's only 1 player performing input at all times, this would indicate a good section of code to use static and/or singleton structure. This would enable anyone to query the state of the control system without having to be directly passed a pointer to it, just don't allow anyone to modify the state of the control system, probably only the "Game" class or message loop.

If you look into sfml and/or Unity, they both do this.

As far as this stuff.

Basically the question is : Is it a better mix of practice and performance to have

a) classes have direct pointers to eachother, regardless where they are in the hierarchy and regardless how chaotic the endresult is (dozens of child classes pointing to eachother) or

b) member classes access other classes by taking a detour and jumping through their "superior class" (in this case : StateGame) or

c) should instead the "parent" class (StateGame) distribute the info (in this case : pointer to player) to its member classes (in this case Controls)?

a) and b) would mean that each frame the game would loop trough all member classes, which then request player info from their parent class (StateGame)
c) would mean that each frame the StateGame class would loop through all member classes and the parent would distribute all the info the children need

What you need to think about is simple. Do the objects you are creating via new, have a owner. For example, when a game loads a scene it loads the textures/meshes/audio files and whatever else at level load. This means that any of the game objects in the scene that use these assets are referencing something that will be guaranteed to exist. So the game objects could just have pointers to those assets as the system they are in guarantees their life, therefor passing an asset pointer to a game object is not a "tangled mess" but a very well structured program, and very efficient too!

However your game objects could be destroyed at any time, say by player interaction, or game play scripts. This means their lifetime is not determinate. If one game object wants to keep track of another that could be destroyed at any time. There must be some kind of system used such as a smart pointer (shared_ptr/weak_ptr, intrusive_ptr(boost)) or some other reference counting or memory management system.

In the system described above the Scene would load all assets, then load the game objects that use those assets. When the scene is destructed, it will do that in reverse automatically as per the standard. So you see you can guarantee therefor use naked pointers to assets within the scene.

The point is, what objects do you know for sure have certain lifetimes/ownership? Use this to your advantage.

If this post or signature was helpful and/or constructive please give rep.

// C++ Video tutorials

http://www.youtube.com/watch?v=Wo60USYV9Ik

// Easy to learn 2D Game Library c++

SFML2.2 Download http://www.sfml-dev.org/download.php

SFML2.2 Tutorials http://www.sfml-dev.org/tutorials/2.2/

// Excellent 2d physics library Box2D

http://box2d.org/about/

// SFML 2 book

http://www.amazon.com/gp/product/1849696845/ref=as_li_ss_tl?ie=UTF8&camp=1789&creative=390957&creativeASIN=1849696845&linkCode=as2&tag=gamer2creator-20


class StateGame{
public:
 Player player; //If 'StateGame' owns them, why do they need to be shared_ptrs?
 Controls controls;

  //WARNING: Because we are passing 'controls' a reference to 'player', make sure 'player' is created before 'controls'
  //(by putting 'player' higher up the list of StateGame member-variables).
  StateGame() : controls(player)
  {  }
};

class Controls {
 public:
 Player &player;

 Controls(Player &player) : player(player)
 {  }
};

This would give the Controls class direct access to the Player class via 'this->player' (fast performance, good)

You're optimizing prematurely and thus, in this case, optimizing the wrong thing.

The number of times 'Controls' will access the player in a single frame is less than a hundred. That's so ridiculously irrelevant as to not be worth mentioning. smile.png

This area of your game is almost certainly not a bottleneck, and unless your profiler is telling you otherwise, you should focus on code cleanliness/simplicity/clarity rather than performance in this area. Performance is important. But optimizing areas that don't need to be optimized is wasted effort that doesn't benefit you, the game, or the players.

however at the same time it would create a seperate pointer to the player class (bad)

Why is that bad? Pointers exist so that more than one (pointer/reference) variable can access the same location in memory. That's what they are for. wink.png

instead of the StateGame class being soley responsible for it


When people talk about "sole responsibility" they are talking about ownership of the memory's lifetime. That is to say, who is responsible for creating and freeing the memory.
This doesn't prevent other classes from accessing and potentially writing to that memory.

and considering that other aspects also need to know info from the player class aswell (GUI for example, or enemies)

Is this the player's stats, or the player's avatar? They don't need to be stored in the same class.

this practice would require other classes to have direct pointers to the Player class aswell

Yep. What is clearer: A piece of code accessing another piece of data that it needs, or a piece of code pretending to not access a piece of data, while still actually eventually accessing that piece of data through a round-about and harder to follow way? tongue.png

it would result in the Player class being coupled with many other classes (bad).

Nope. The Player class is only coupled to the other classes if the Player class knows about them.
In the scenario you described, the other classes would be coupled to Player, but not Player to the other classes.

And why shouldn't they be coupled? The other classes depend on Player. So their dependence should be clear (not hidden), and their access should be straightforward (not jumping through hoops).

It would get even worse with other classes having pointers to eachother (all subclasses pointing to eachother, rather than letting the StateGame handle its children).


Now this is the real issue: You don't want them to all know about each other - you only want them to know about the ones they need to know about. This is where the rubber meets the road, and there's no clean solution that I've personally found, though this is likely from me having a lack of professional experience. sad.png

One method is giving StateGame ownership of a separate class that manages lifetime and access to entities, and passing that class to the entities themselves so they can query for access to other entities. This kinda creates a cyclic reference: the entity-creator knows about (and owns) the entities, and the entities knows about (but doesn't own) the entity-creator.

Another method is events or callbacks (or signals-and-slots) to facilitate entity-to-entity communication, with StateGame or the entity-creator acting as a middleman for the communications.

Will Player always be the only object that accepts input? What about remote-controlled devices? Guided missiles? Menus?

There are then 2 thoughts of solving this approach (besides the ECS approach mentioned above, which is fine if your doing Components) which keeps the objects decoupled from each other

.

#1, Have the Control class send Input messages, and, which ever class wants to be controlled by the inputs will receive the messages. This allows proper separation between all the classes, and any object can be controlled by the input simply by registering for input messages. When the object's update is then called, it can parse the input messages it has received.

#2, Maybe it would be better to have the inputs put into an InputQueue, and, when you go to update your other classes, whichever object currently is being handled by the input will read from the InputQueue. This allows any object to be controlled by the input, and decouples the objects from each other.

There are other possible solution as well, but I wanted to share these 2.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)


I'd go the static class route. Since you won't have multiple "controls" there's only 1 player performing input at all times, this would indicate a good section of code to use static and/or singleton structure. This would enable anyone to query the state of the control system without having to be directly passed a pointer to it, just don't allow anyone to modify the state of the control system, probably only the "Game" class or message loop.

All you're doing is hiding dependencies. Don't use a singleton for this. It may appear to make your life easier, but all it does is hide poor design and impose needless limitations on things (it's going to make it difficult to write unit tests and such).


The StateGame class (of course very simplified)
class StateGame {
public:
std::shared_ptr<Player> player;
std::shared_ptr<Control> controls;
void gameLoop(float dt) {
//handle controls every frame
this->controls->handleInput(dt);
};
}

Why are player and controls shared_ptrs? Do they need to exist beyond the lifetime of StateGame? Object lifetime management is extremely important for games, and you should plan up front what the expectations are. Ideally, some class will be responsible for creating and owning the player and control objects, and ensuring they exist beyond the lifetime of anyone that needs them. In that case, they could be unique_ptrs instead.

shared_ptr definitely has its uses, but at times it can also be a bit of a code smell, i.e. "I don't fully understand the lifetime of my objects, so the safest thing to do is use shared_ptr". If that's the reasoning, shared_ptr will get you by, but cause problems in the long run.

Now as for the original question, we don't know all the details of what your Control class does, but let's assume it's responsible for receiving raw input and then making the player move. In that case, it does depend on Player, and it's best to call that out explicitly by providing the Control object with a reference to Player. Don't make it go through StateGame to get it - that adds a needless dependency of Control on StateGame. All Control wants is an object to move.

If you want, it is easy to make it a little more generic by having a IMovable interface that Player implements, and have Control only known about IMovable.

Something like:



class IMovable
{
public:
    virtual void MoveLeft() = 0;
    virtual void MoveRight() = 0;
};

class Player : public IMovable
{
public:
    virtual void MoveLeft() override
    {
        // do stuff
    }
    virtual void MoveRight()
    {
        // do stuff
    }
};

class Control
{
public:
    Control(IMovable &movable) : _movable(movable) {}

    void HandleInput(float dt)
    {
        float x = GetJoystickHorizontal();
        if (x < -0.1f)
        {
            _movable.MoveLeft();
        }
        else if (x > 0.1f)
        {
            _movable.MoveRight();
        }
    }

private:
    IMovable &_movable;
};

class StateGame
{
public:
    StateGame()
    {
        player = std::make_unique<Player>();
        controls = std::make_unique<Control>(*player);
    }

    void GameLoop(float dt) {
        //handle controls every frame
        controls->HandleInput(dt);
    };

private:
    std::unique_ptr<Control> controls;
    std::unique_ptr<Player> player;
};

This isn't anymore complex than any of your suggestions in the OP, but the dependencies are clear, and there is a lot of flexibility. It would be trivial to add multiple Control objects that control different IMovable objects (maybe for multiplayer), for instance.


#1, Have the Control class send Input messages, and, which ever class wants to be controlled by the inputs will receive the messages. This allows proper separation between all the classes, and any object can be controlled by the input simply by registering for input messages. When the object's update is then called, it can parse the input messages it has received.

In my opinion this is a bit of an implicit dependency of Player on Control. Player is basically saying "I know about input messages, let me register for some". Player may not know specifically about the Control class, but it clearly has some access to the outside world to let it register for messages. What if you end up having multiple Player objects? How do they cooperate so they aren't listening to the exact same messages? What if you're writing a test suite where you want to move the Player around and there aren't any conventional input messages to process? Or you want the game logic to take control of the player for a while?

I think it's best to move the "input -> action" processing outside of the Player object entirely.

I'm not really sure how valid your concerns are about the inputs handled via messaging.


In my opinion this is a bit of an implicit dependency of Player on Control. Player is basically saying "I know about input messages, let me register for some". Player may not know specifically about the Control class, but it clearly has some access to the outside world to let it register for messages.

Player only knows that, if it registers for input messages, it will get them, and it doesn't came where they come from. They could come from an Enemy class who has mind control ability, or it could come from a ControllerSImulator or tests, or it could come from a Network abstraction that issues input messages.


What if you end up having multiple Player objects? How do they cooperate so they aren't listening to the exact same messages?

In my example, I mentioned using homing rockets. In that case, the player class would need to be told to ignore input messages from the weapon class somehow. But, who's to say you wouldn't want 2 or more objects listening to input messages? A dual-player game on a single monitor comes to mind.


What if you're writing a test suite where you want to move the Player around and there aren't any conventional input messages to process? Or you want the game logic to take control of the player for a while?

Answered already.

Phil_t, your solution is a fine one, but it too has limitations. As I mentioned earlier, there are many solution to this, you just need to pick the one that you suits your style and how you like to do things. Often times there are multiple "right" solutions.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

The answer depends on the desired modularity of the engine and/or classes. As many have already said, a "Player" does not know about a "Controller". In your current setup, Your StateGame would be the mediator between the two. A solution as simple as an "Asset" structure which holds all related objects(The player, the controller for the player, ect...) would suffice. The StateGame would read input from the controller and effect the player accordingly through accessor functions.

Now how I would handle this(and how I do handle it) is creating and abiding by the rule that, indeed that controller does not need to know what it is controlling, it doesn't need to know anything except that it controls "stuff". My physics pipeline uses a "controller" class that basically holds a void* that is interpreted into a 12 byte x,y,z location. This frees it from strong data types as long as the consumer follows a few simple rules. In fact, my physics library is completely free from any dependency on any other game related tasks other than physics(because why should it be).

At this point, your Player and Controller are decoupled and now you can use your Controller for anything! You can use your controller to graph interpolated movements of fire balls(fireballs are not Players right?), or pass your controller to an AI class so that your AI flow can now inherently control enemies.

In the end your answer is really based on the size and complexity that you are shooting for.

This topic is closed to new replies.

Advertisement