Jump to content

  • Log In with Google      Sign In   
  • Create Account


What's your preferred way of letting objects affect the game?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
20 replies to this topic

#1 Cornstalks   Crossbones+   -  Reputation: 6966

Like
3Likes
Like

Posted 16 February 2013 - 04:27 PM

I try to program as atomically as possible, following the Single Responsibility Principle as best I can (but I don't go extreme nazi on things, in case you panic). This works pretty well in a lot of my work.

 

But games are complicated. Objects interact with each other and the entire game's state regularly throughout the game. Units collide, one unit attacks another, a building makes units, a unit can create projectiles, and those projectiles can affect a large number of other units, etc. In other words, when programming this, you need a way for one object to be able to affect another object (or the game state itself, possibly by creating other units or projectiles). But it's not just a one-time case; many different units have different affects and interactions, which can result in messy code when trying to make all these possible interactions and events possible.

 

What's your preferred way of letting various objects (units) affect the game (by creating other units, buildings, projectiles, hurting/healing other units, etc.)?

 

I'm trying to think of some good ways to let these interactions occur that allows for (generally) clean code. My current solution is to have a Game class that actually runs a single game (by updating units (units update themselves as much as possible, but the Game tells them when to), enforcing game rules (so units can't walk up cliffs, for example), updating scores, etc.), and having each unit (or any other object that needs to interact with the Game itself) hold a reference to the Game to which it belongs. If a unit wants to do something that affects another unit or the Game, it calls the corresponding method on the Game object.

 

For example, if unit A wants to create a building at x, y, it calls Game's makeBuilding(owning_player, x, y), and the Game either creates the building, or it returns false indicating that was an illegal move (perhaps there's already a building there). Or, for another example, unit B wants to launch a rocket that's aimed at target_x, target_y. Unit B then calls Game's createProjectile(owning_player, unit_pos_x, unit_pos_y, target_x, target_y), and the Game creates the projectile and takes ownership of it. Or, another example, unit C performs a melee attack at position x, y in direction dir, so it calls Game's meleeAttack(owning_player, x, y, dir) and the Game can decide if another (enemy) unit was hit and deal damage as needed.

 

Is this a sane solution? Is there a better one? I'm afraid Game will grow into a monstrous class and have too much responsibility, but then again I'm not sure this can be avoided, and this solution will hopefully prevent a lot of spaghetti code and unnecessary coupling.

 

I want to try and explore as many (good) solutions as I can before I get too far down the road with this game I'm working on.


[ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

Sponsor:

#2 Khatharr   Crossbones+   -  Reputation: 2819

Like
0Likes
Like

Posted 16 February 2013 - 06:06 PM

This should be a cool thread. Looking forward to responses.


void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

#3 Tasche   Members   -  Reputation: 218

Like
0Likes
Like

Posted 16 February 2013 - 06:10 PM

though i basically went for the same system you are using in my own engine, so i cant say for sure that it is better, but in retrospect i wish i had implemented it as a event driven system, where game objects can register callbacks for an event. that way the interface stays 'clean', and you have a clearly defined connection point, namely the event message dispatcher/processing. also, when done properly, you get the 'only update when necessary' for free. needless to say, this variant is usually very performance effective (of course it always depends)

 

however, like i said, i haven't actually done it, so there could be some serious pitfalls i cant think of just now, i'm just learning this stuff myself.



#4 ApochPiQ   Moderators   -  Reputation: 14281

Like
5Likes
Like

Posted 16 February 2013 - 09:17 PM

The key realization is that representing a "game" or "unit" or "building" might still be violating SRP.

 

Yes, on some level, those are "single" groups of responsibility. But they're really just God Classes of slightly more tame dimensions.

 

 

At some point you have to decide how much complexity is actually too much; in some games, like PacMan, representing a single "character" in a class is totally fine; in, say, an RPG, it's a mistake. This is entirely situational and dependent on the nature of the game you're writing, which is why I resist the idea that there's value in a "preferred way" or some kind of ruleset for making design decisions absent the relevant contextual information.

 

On one extreme, I find no sin in writing a game like TicTacToe in a couple of modules/units of organization (classes, if your language of choice believes in them, are just one unit of organization).

 

At the other, let's look at how a rich RPG might be implemented. I would consider each of the following to merit its own unit of organization in a complex game:

 

  • Individual items

  • Groups of items - think bags, containers, etc.

  • Inventories

  • Health bars

  • Individual skills (swing sword, cast fireball)

  • Skill groupings (all skills granted to sword wielders)

  • Movement controller (can I walk? Run? Swim? Climb trees?)

  • High level action management (coordination between using items, skills, movement)

  • Combat engine (coordinates usage of skills/items and how they interact with health and movement)

  • Status engine (handles things like temporary status ailments, buffs, etc.)

 

Movement controllers interact with the physics system to determine where units move. The status engine might be consulted to see if we are paralyzed or slowed for some reason. Skill groupings may selectively permit certain skills in various contextual scenarios. Action management might be wired to player input or an AI system. And so on.

 

 

Component architectures tend to be a popular contemporary solution for building up complex logic from all this sort of decomposition of responsibilities. They're just one option, though; and not necessarily the best.



#5 thePyro_13   Members   -  Reputation: 629

Like
0Likes
Like

Posted 16 February 2013 - 09:24 PM

I've been implementing a Entity Component System and have found it to be very clean and easy to control.

Entity's store data. Controllers are attached to entities and add or alter the entities data(usually after reading it) they also handle events. Observers act on data changes and fire events.

I currently have an explicit controller, that takes the user input and changes the entitys state.
An implicit controller that takes movement states and turns it into actual movement.

An observer that watches entity movement and fires collision events.
and an observer that watches entity positions and animations and sends updated data to the renderer.

Such a system can require some intermediate techniques to set up, but has many advantages. Controllers and observers each only have one task, and so are very simple. Game data is cleanly split from the renderer(and both can be moved to different threads much easier).

#6 phantom   Moderators   -  Reputation: 6789

Like
2Likes
Like

Posted 17 February 2013 - 04:12 AM

Apoch has covered design nicely, but I think this is still worth calling out.

But games are complicated. Objects interact with each other and the entire game's state regularly throughout the game. Units collide, one unit attacks another, a building makes units, a unit can create projectiles, and those projectiles can affect a large number of other units, etc. In other words, when programming this, you need a way for one object to be able to affect another object (or the game state itself, possibly by creating other units or projectiles). But it's not just a one-time case; many different units have different affects and interactions, which can result in messy code when trying to make all these possible interactions and events possible.

The bold section, in particular the end, is key to this I feel; a single object in the game is not going to touch the whole game state. Not ever and certainly not directly. This is something you go on to basically say yourself with the list of interactions below and this is a key place to start when thinking about it - each object has a pre-defined interaction with the world. If you were going to make an RTS game, for example, your 'tank' super-object (super-object => collection of parts) isn't suddenly going to be marrying The Hulk any time soon ;)

If we take a closer look at one of the examples you gave, "a building makes units", as a typical factory in an RTS game.
While this might look complicated it isn't really, at the highest level the building does one thing 'creates a unit', at which point beyond throwing out a few notifications it is done.

Its output might simply be a message (in the generic sense, not a 'message system' sense) which says "I've made vehicle <type> at location <here> for <player>". Other systems would be listening for that event and respond accordingly; a player's unit collection might update and the unit spawner might create a new object so it can be rendered.

At which point the messages could ripple further afield (unit collection => UI to display message/icon; spawner to any AI and physics systems to insert it into their world view; and so on) but the key interaction is done.

As to how you expose this... well, there is the erstwhile mentioned 'message system' which you could route all the messages to - however this global routing might not be what you want. Your 'factory' super-object might contain a list of delegates/functions to call when certain events happen and systems 'subscribe' to them or you could abstract the whole thing and come up with a processing graph which represents spawning in data; this would require your super-class still outputs events but the hookup of those input and output pins becomes data driven and potentially modified per level.

The key thing, however, is that each unit has a predetermined level of interaction with the world so it can't mutate all the state itself nor does it have to deal with every possible thing that can happen. Even something as simple as 'collision' isn't really the units problem as it is a problem for the physics under the hood.

#7 Tasche   Members   -  Reputation: 218

Like
0Likes
Like

Posted 17 February 2013 - 01:46 PM

i didn't mention this in my first post, but i've actually moved to something in between 'every object can affect everything else (within its class)' and an 'event system', im using a command queue of sorts.

 

all objects interacting with the world issue a command to the queue (most prominently spawning of stuff, but also game logic triggers and such), and the command queue handles dispatching data to other objects that need it. so its basically an event system, but i will not shoehorn my collision into there, that seems performant enough, since i got a broadphase covering all objects anyway.

 

a nice side effect of the queue was that i was easily able to prioritize and limit events per game loop iteration.

even though a lot still gets calculated inside the object class itself, i managed to move most of it to the queue.

 

deriving new objects from my game object class has become so clean and easy now, wish i had done it from day one (well actually i did, but limited to spawning at first)

 

EDIT: seems from the answers sofar that event systems are the way to go....


Edited by Tasche, 17 February 2013 - 01:49 PM.


#8 AllEightUp   Moderators   -  Reputation: 4110

Like
0Likes
Like

Posted 18 February 2013 - 02:56 AM

I think Apoch and phantom cover the bases well but phantom brings up the real problem: how to trigger event and apply effect.  No offense meant but phantoms "clarification" was unfortunately lacking in "how" it works.

 

It's all in the lists...

 

Games are "turn based", just really damned fast turns.  So, your object wants to create a projectile, you create the new projectile and send a message to the games main loop saying "add this to the world".  You send a message because you want the creation of a new projectile "NOT" to effect current turn processing, the creation is the result of this turn and will affect the world next turn, "not" this turn.  (This delayed reaction solves ungodly numbers of bugs, just do it, really.)

 

Given we solved how to produce a projectile, how do you apply damage?  Same basic deal, the projectile says "I hit" and posts a message for "do damage to xxx" because I hit it.  Next frame xxx figures out if it is still alive after getting hit.

 

Or, xxx could post a "I did x damage to area" to the world object and the message can be applied to all objects in the area.  Those objects can be told to die or removed, depends how you want to deal with the details here.

 

The key thing to remember is that even though the game is realtime the rules are basically turn based.  If you try to apply rules interactively in a single frame, you are asking for all sorts of problems.  OMG, he didn't die 1/60th of a second faster really isn't something a player will notice and not integrating everything every loop makes for a much more simplified coding problem.  Network and all that, gets more complicated but still basically the same other than when to apply messages.



#9 darkhaven3   Members   -  Reputation: 160

Like
0Likes
Like

Posted 18 February 2013 - 11:11 AM

typedef unsigned long coord_t;
typedef unsigned char byte_t;
typedef signed short mobjtype_t;
 
typedef enum {
MOBJ_CREATE=0,
MOBJ_ACTIVE,
MOBJ_ATTACK,
MOBJ_PAIN,
MOBJ_DEATH,
maxmobjframes;
} mobjstates_e;
 
typedef struct {
mobjtype_t type;
frame_t* mobjframes[maxmobjframes];
coord_t mapx,mapy;
mobj_t* target;
} mobj_t;



mobj_t represents every possible object in the game world, including the player. mobj_t contains a list of pointers to a master "frame list" that defines things like what graphic should be displayed for this object at this frame, what the next and/or previous frames for the current one are, and a function pointer to describe what needs to be done by this object this frame. mobj_t frames only have access to certain functions in a master list which should, by convention, only interact with other mobj_t types, and what object that ends up being is dependent on mobj_t.target. Objects can do whatever the hell they want to each other after this point.


Edited by darkhaven3, 18 February 2013 - 11:12 AM.


#10 ApochPiQ   Moderators   -  Reputation: 14281

Like
1Likes
Like

Posted 18 February 2013 - 03:30 PM


mobj_t represents every possible object in the game world, including the player. mobj_t contains a list of pointers to a master "frame list" that defines things like what graphic should be displayed for this object at this frame, what the next and/or previous frames for the current one are, and a function pointer to describe what needs to be done by this object this frame. mobj_t frames only have access to certain functions in a master list which should, by convention, only interact with other mobj_t types, and what object that ends up being is dependent on mobj_t.target. Objects can do whatever the hell they want to each other after this point.

I can see this being a legitimate approach if and only if your game logic is heavily data-driven and validated by external tools prior to loading into the engine.

If you're hard-coding a non-trivial game in this style, it's going to turn into a Big Ball of Mud sooner rather than later.

Of course, as I said before, for a certain class of games (and more accurately, for a certain degree of simulation simplicity) that's totally fine.

I'd hate to see an RTS built that way, though ;-)

#11 darkhaven3   Members   -  Reputation: 160

Like
0Likes
Like

Posted 18 February 2013 - 03:46 PM

It's heavily implied by the design approach that I'm not going to just allow any frame to start calling functions designed to load a map or initialize SDL or anything, just by convention of how the function-pointer list that the frames reference works. I imagine it will work well enough for the sidescroller I intend to use it with where there's not a whole lot of elegance in object management required.

Example being: "Object A tries to move in the direction of object B. Do whatever the function to move this object says to do. I don't care to sanity-check the results; the function will resolve that on its own. The end."


Edited by darkhaven3, 18 February 2013 - 03:55 PM.


#12 phantom   Moderators   -  Reputation: 6789

Like
0Likes
Like

Posted 18 February 2013 - 04:27 PM


I can see this being a legitimate approach if and only if your game logic is heavily data-driven and validated by external tools prior to loading into the engine.

Really? All I can see is the many hundreds of ways this can crash and burn in a series of amusing ways... more so when you consider the follow up reply about not sanity checking the results... dry.png

#13 phantom   Moderators   -  Reputation: 6789

Like
0Likes
Like

Posted 18 February 2013 - 04:31 PM

No offense meant but phantoms "clarification" was unfortunately lacking in "how" it works.

heh, I was vague on purpose as I felt the general idea was more important than the precise details ;)

For the record I'd favour a hybrid of using scripts/graphs designed by a designer to drive things but allow that to push messages into the message system if needs be. (Such as wanting to kick a sound off and not want to directly tie the sound system into the scripting/graph system.)

#14 Telastyn   Crossbones+   -  Reputation: 3718

Like
0Likes
Like

Posted 18 February 2013 - 08:13 PM

What's your preferred way of letting various objects (units)
affect the game (by creating other units, buildings, projectiles,
hurting/healing other units, etc.)?

 

Directly. I've started in with component based entities, and letting isolated single responsibility components work directly against references (while ignoring what those references are, or how they are provided) is totally awesome. No event storms to hunt through, no message queues to interpret or parse, no bundles of void* or object to cast about.

 

Dependency resolution is done in one isolated place, and otherwise components go about their business: exposing functionality for other things to use/consume.



#15 Khatharr   Crossbones+   -  Reputation: 2819

Like
0Likes
Like

Posted 19 February 2013 - 07:13 PM

ApochPiQ, on 16 Feb 2013 - 19:25, said:
The key realization is that representing a "game" or "unit" or "building" might still be violating SRP.

Yes, on some level, those are "single" groups of responsibility. But they're really just God Classes of slightly more tame dimensions.

I'm curious about this. I understand what you're saying in terms of risk to SRP, but is a 'Game' class really necessarily a god class? If all of the components of 'Game' are properly abstracted it could be pretty simplistic, couldn't it?

You made the point that representing an RPG character with a single class is inappropriate. Are you saying that a character instance should be represented by more than one object, such as dividing character state between components that deal with the character or do you just mean that the attributes of the character should be further abstracted?
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

#16 ApochPiQ   Moderators   -  Reputation: 14281

Like
0Likes
Like

Posted 20 February 2013 - 07:21 PM

I'm not sure I follow the question, honestly.

I think you should organize your code so that individual units of organization have a single, clearly defined area of responsibility. I also think that's extremely context-sensitive, as I said before, and what makes sense for a small design may utterly backfire in a larger one - and vice versa.

There are no 100% applicable rules that govern every single design situation. You have to learn how to solve the problem at hand.

#17 Khatharr   Crossbones+   -  Reputation: 2819

Like
0Likes
Like

Posted 20 February 2013 - 08:16 PM

I'm just asking what you mean when you say classes like "game" or "unit" or "building" are mini-god classes.

My understanding of SRP is that a unit should do one thing and do it well. When I think of a 'Game' class I want to limit it to essentially being a container for the game's components that works like a composite for updating them. The Game class itself only holds the component system objects, of which there are few, and runs the main control loop, which simply updates the member objects in order. I consider this to be adhering to SRP. While it's true that nearly the entire program runs within the context of the Game class instance, the Game class itself is only performing one duty - playing host to the parts. What the parts themselves do I consider to be their own areas of responsibility, not that of 'Game'. Is this sensible?

Just interested in your point of view.

Edited by Khatharr, 20 February 2013 - 08:20 PM.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

#18 ApochPiQ   Moderators   -  Reputation: 14281

Like
0Likes
Like

Posted 20 February 2013 - 11:10 PM

I was referring to the OP. Nothing grand or sweeping. I don't mean to imply that any class called "Game" or "Unit" or "Building" is inherently bad, although it seems that's what you inferred, for which I apologize for my lack of clarity.

 

If you read through the rest of my posts in this thread, it should (I hope!) be abundantly clear that composing such a class from SRP-adherent constituents is perfectly fine with me.



#19 Khatharr   Crossbones+   -  Reputation: 2819

Like
0Likes
Like

Posted 21 February 2013 - 01:39 AM

Ah. Sorry. I was just sort of skimming before.


void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

#20 Sporniket   Members   -  Reputation: 263

Like
0Likes
Like

Posted 21 February 2013 - 09:58 AM

In my recent game projects, I can relate my design to a company : game entitys (player, asteroids, aliens, laser beams,...) are under the care of a manager (PlayerManager, FlyingObjectsManager, BulletManager,etc...), that are under the care of the Main manager (Game)

 

Basically, the main manager tells the other managers to do their stuff or redraw their stuff when it's time, and the others manager tell their entity to update or redraw, and manages their group of entities (e.g. the flyingObjectManager spawns asteroids and bonus and recycle them when they go out of the screen).

 

When their should be an interaction between entities from different manager, it is carried by the main manager either procedurally (e.g. in my game loop retrieve a collection of flying objects/bullets from the corresponding manager, and ask the player manager if anything collides with a player), either event-driven (e.g., a player entity has collided with a bonus, the score must be updated and the bonus entity must be hidden).

 

Of course, my games are simple, so this level of details is adequate. For bigger projects, (e.g. an ARPG in an open world), their would be more levels of management, like in a big company.


Edited by Sporniket, 21 February 2013 - 09:59 AM.

Space Zig-Zag, a casual game of skill for Android by Sporniket-Studio.com





Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS