What exactly belongs in my GameObject class?

Started by
15 comments, last by frob 8 years, 1 month ago


My initial thinking is that in order to draw, move, or detect collisions for the player you need access to the players location/size/rect.

Single Responsibility Principle still applies.

In most modern systems, a "game object" has the single responsibility of being a container.

They don't draw, they don't move, they don't animate, they don't collide, they don't have sprites. However, they might contain components which cause the container to move, or cause a mesh to animate, or provide a physics shape, or provide a physics response, or link to a mesh, or link to a sprite.

Commonly they will contain a transformation object, and it is so common that many systems turn the transform object into a requirement with its own accessor/mutator pair or maybe even allow direct access to the transform data member.

Many also have a name, but some systems do not. This also typically has an accessor/mutator pair so access is controlled.

Everything else is typically added as a component. A mesh component and a texture component have information about how it appears. A physics component has information about how it reacts with the physics system. A locomotion component allows for motion. An audio component has information about sounds it produces. An animation controller component controls animations. An effects component, a sprite or image panel component, a 3D text component, a timer component, whatever you need.

Sometimes components contain no data or code, they just serve as a marker. Usually you'll end up assigning some data to them eventually, so it works out to have them as full components. You might start with a simple marker component indicating something can be placed in inventories. (You may eventually add data like how much space it takes in the inventory, or if it is a player-only inventory or non-player inventory.)

This has a few potential drawbacks (especially when it comes to data locality) but with good engineering you can find ways that work for your system that minimize or avoid the issues you encounter.

Advertisement


And I'm assuming any member functions of PlayerRenderer, PlayerCollider, and PlayerMover would need to receive a Player object as an argument or perhaps the objects themselves need references to the Player?

Why should they need any data from the Player class? They should hold as much relevant data as necessary to perform their job within each instance of the class. If in some corner case you forsee them needing a reference to Player, maybe it would be appropriate to store a reference to Player as a class member too.

My initial thinking is that in order to draw, move, or detect collisions for the player you need access to the players location/size/rect.

Pass the minimum required information. For example, in one of my earlier projects, I had a Drawable class that simply had a Draw(Position& p) function (all game objects were of a fixed size). Each game object had a pointer to an instance of Drawable, and would call it as m_drawable->Draw(m_position);


And I'm assuming any member functions of PlayerRenderer, PlayerCollider, and PlayerMover would need to receive a Player object as an argument or perhaps the objects themselves need references to the Player?

Why should they need any data from the Player class? They should hold as much relevant data as necessary to perform their job within each instance of the class. If in some corner case you forsee them needing a reference to Player, maybe it would be appropriate to store a reference to Player as a class member too.

My initial thinking is that in order to draw, move, or detect collisions for the player you need access to the players location/size/rect.

Pass the minimum required information. For example, in one of my earlier projects, I had a Drawable class that simply had a Draw(Position& p) function (all game objects were of a fixed size). Each game object had a pointer to an instance of Drawable, and would call it as m_drawable->Draw(m_position);

And what about components that need access to other components? Is that an appropriate approach? For example, I have an input component that changes the direction of the player when certain keys are pressed. But when the direction of the player is changed I need a way to signal for the proper sprite to be drawn. So in this case my player input component would need access to the player rendering component?


And what about components that need access to other components? Is that an appropriate approach? For example, I have an input component that changes the direction of the player when certain keys are pressed. But when the direction of the player is changed I need a way to signal for the proper sprite to be drawn. So in this case my player input component would need access to the player rendering component?

It seems likely that the input component will need to touch several of the player's components. In that case, it might be appropriate for the input component to have a reference to the player, or to take a reference to the player as an argument when run (IE m_input->process(this) ).
If the number of components that the input component will need to touch is small (4 or less), it might be just as appropriate to pass just these components.

I don't typically treat input as a component. Unless this is a multiplayer game, input has a global scope, and I find it works well to treat it as such. Even in multiplayer games, it's likely that input will have effects beyond the players' game objects, so I would still probably treat it as a global thing.

Often input is evaluated and converted into events, and sometimes persisted for a short time for systems to query. The events are broadcast and responded to by whatever systems need to consume them.

This lets you send "input" from any source: from the player at the keyboard, from multiple keyboards, from keypads, from joysticks, from one mouse, from multiple mice, from touch events, from the network, from a tutorial script, from an instant replay system, or from an AI, or whatever else you come up with.

Such systems also make it easier to map players to various controllers, or to allow players to map different keys or buttons or directions as they see fit.

So for instance, if the user clicks the left arrow key I would add any relevant events to the given events queue (which exists globally). So in this case, a "player-move-left" event would be added to the queue and any necessary state changes/processing would be applied? If input is handled on the global scope would it be more appropriate to skip the events all together do something such as (if key pressed do following) instead of (if key pressed add event to queue) then (if event in queue do following)?

So for instance, if the user clicks the left arrow key I would add any relevant events to the given events queue (which exists globally). So in this case, a "player-move-left" event would be added to the queue and any necessary state changes/processing would be applied?

Commonly done, yes.

There is often a controller mapping that takes place as well. You probably don't care what button they pushed, if they use the arrow keys or the keypad or wasd or ijkl or an xbox controller. You don't care if it was a human or the game's AI or a network player. All you care is the "player-move-left" event needs to be processed.

A wise designer I worked with gave sage advice: Write the game manual first.

Figure out what all the commands are going to be for all the game, then assign keys in a way that is easy to explain and makes for a short, clear, concise game manual.

Commonly you've got directional arrows, a button that means "main action/ok/select", a button that means "cancel/back", a button that means "secondary action/jump". Games may have other buttons beyond those functions.

On a keyboard with a standard WASD configuration you'll have a 'jump' button, an 'attack/slash/shoot' button, a 'select/use/activate' button, a 'cancel/back' button, and 2-3 other quick commands.

Think about how you will describe the game to players. Well designed games typically have extremely simple control mappings.

If input is handled on the global scope would it be more appropriate to skip the events all together do something such as (if key pressed do following) instead of (if key pressed add event to queue) then (if event in queue do following)?

Probably not, but with all the variations on what games do, you can probably build a case where it makes sense.

Usually input is initially handled on a global scope. Events are broadcast or handled on a system-by-system basis. Sometimes they register as listeners with a priority level, the highest active priority gets it first and other systems get the event following a chain of command pattern, dispatching the message to children then reporting back when the system is done. Systems may have return a result to continue processing or terminate processing.

The code to convert input to events is often a giant if/else tree:
somecollection events;
if(keycode == config.upkey) {
  events.add(message.UpPressed);
} else if(keycode == config.downkey) {
  events.add(message.DownPressed);
} else ...
Sometimes you'll want to have a key generate multiple event types when you need to hack a system into the game or when a command gets merged, so it is often good to write a system that handles generating an arbitrary number of events per button press.

As for sending them out you can likely use an existing message broadcast system. Typically they look something like this:
foreach(handler in GetHandlersForEventByPriority(e)) {
  if(handler.ProcessEvent(e)) {
    break;
  }
}

This topic is closed to new replies.

Advertisement