Sign in to follow this  
xynapse

The Game Logic - how do you handle that?

Recommended Posts

Guys,
what are the approaches used to handle the game/level logic, which prevent you from falling into a spaghetti code?

I have a player in 3d isometric adventure game where you escape from a set of rooms.
Each room is different and sometimes you will need to go back, to pickup an item which is needed in your current ara ( you pickup a wrench in room 3, which you will use in room 6 to open up the safe ).

[url="http://youtu.be/TmiTJPjdTzU"]http://youtu.be/TmiTJPjdTzU[/url]


Let's imagine a simple level :

[img]http://extraordinaire.pl/0.jpg[/img]

[color=#00ffff]Player[/color]
[color=#ff0000]Doors[/color]
[color=#ff0000]Key[/color]



Ok, now that is easy aye?You can make this easily with a single BOOLean like:

[CODE]
// PSEUDOCODE //


bool bHasKey = false;


// Event handler
case ITEM_PICKEDUP:
{
if(item == key)
{
bHasKey = true;
}
}
break;


case PLAYER_COLLIDES_DOORS:
if(bHasKey == true)
{
pDoors->Open();
pKey->Destroy();
}
break;
[/CODE]


It's easy but i would like to avoid this kind of coding game logic - as it turns very quickly, that if you handle 10 rooms per level in one game state, and you have 5 levels, your game state class for level handling will grow into a piece of if/else/switch/case stuff that you'll debug continously scratching your head and asking - why is this here, and that there..?

Let's go to another theoretical room.
So, this time player has tu pull 3 switches in specific order - which in return opens the door for 5 seconds.

[color=#000000][img]http://extraordinaire.pl/1.jpg[/img][/color]


[color=#00ffff]Player[/color]
[color=#0000ff]Switch[/color]
[color=#ff0000]Doors[/color]

[color=#000000]Now this level plus the previous one are slowly moving towards a mess - but as long as there are three to four rooms per level - you can handle it somehow.[/color]

[color=#000000]What if my Level (map) has 10 rooms, each one more and more complicated, and i have 5 levels ready - making 50 rooms in total.[/color]
This kind of coding would be a disaster sooner or later...


[color=#000000]How do you approach that ? single state machine per level ? ( how would you apply it to this model ? ) anything else?

I just can't figure out how to do it - my brain is empty after several days of decoding ima4.[/color]


[color=#000000]Thanks ![/color]

Share this post


Link to post
Share on other sites
Give doors a counter that gets incremented or decremented when certain things happen, and when the counter reaches a certain point, the door is open (and if the counter drops back below the certain point, the door shuts again).

[code]// PSEUDOCODE //
typedef unsigned int DoorID;
struct Door
{
DoorID doorID;
unsigned int counter;
unsigned int neededCounter;

bool IsDoorOpen() { return counter >= neededCounter; }
}
struct Key
{
DoorID doorID; //The door this key goes to.
bool playerHas; //True if the player has this key.
}
struct Switch
{
DoorID doorID; //The door this switch goes to.
bool activated;
bool switchDeactivates; //True if the switch automaticly de-activates itself after a certain amount of time.
uint32 timeLeft; //The amount of millaseconds before the switch de-activates itself.
uint32 durationBeforeReset; //The amount of millaseconds to set 'timeLeft' to, when the switch is activated.
}
struct Enemy
{
DoorID doorID; //The door that gets incremented when this enemy is killed.
int health;
bool isDead;
}
struct Level
{
std::vector<Door> doors;
std::vector<Key> keys;
std::vector<Switch> switches;
std::vector<Enemy> enemies;
}

// Event handler
case KEY_COLLIDED:
{
if(currentLevel.keys[key].playerHas == false)
{
currentLevel.keys[key].playerHas == true;
DoorID doorID = currentLevel.keys[key].doorID;

currentLevel.doors[doorID].counter++;
}
}
break;
case SWITCH_ACTIVATED:
{
if(currentLevel.switches[switch].activated == false)
{
currentLevel.switches[switch].activated == true;
if(currentLevel.switches[switch].switchDeactivates == true)
{
currentLevel.switches[switch].timeLeft = currentLevel.switches[switch].durationBeforeReset;
}

}
}
break;

case PLAYER_COLLIDES_DOORS:
if(pDoor.IsDoorOpen() == false)
{
//Don't let the player past if the door isn't open.
}
break;[/code]

If you're using C++, you should actually make the Door and Key and Enemy and Switch be classes, and handle their logic within the class itself, but I didn't want to deviate too far from what you already had.
Every door should be of the same struct or class, and the level should just contain a vector of all the doors, and a vector of all the keys, and a vector of all the enemies, and a vector of all the switches, etc...
If you understand polymorphism, and if it makes sense in your code arctitecture, you might even just have one vector with derived entity types.

That's at a basic level, but depending on your programming skill you might want to do something more advanced.
You could create programmable logic gates between your entities. See [url="http://www.gamedev.net/blog/515/entry-2131862-relationship-editing/"]here[/url] and [url="http://www.gamedev.net/blog/515/entry-2249169-new-buttons/"]here[/url] for one posssible design.
You could use a scripting engine to describe logic between entities.

Share this post


Link to post
Share on other sites
Complementing what Servant said and answering to this issue:
[quote]It's easy but i would like to avoid this kind of coding game logic - as it turns very quickly, that if you handle 10 rooms per level in one game state, and you have 5 levels, your game state class for level handling will grow into a piece of if/else/switch/case stuff that you'll debug continously scratching your head and asking - why is this here, and that there..?[/quote]

If you are working with OO you could use Polymorphism.
You could create a generic class named [b]Item. [/b]Then you would have Key, Switch, etc. as children of Item.
That way you could create a method in Item called Grab() and Act() that is overwritten by the children.
Then in your class you would only need a vector of Items that call Act() or Grab() given a collision.
At runtime the computer will know if this is just an Item or a child of Item and will call the method Act() accordingly.

A quick example:
[source lang="cpp"]class Item
{
public:
Item() {}
~Item() {}
virtual void act() {}
};

class Key : public Item
{
private:
int type;
bool equipped;
public:
Key();
~Key();
void act()
{
//open door of same type
}
};

//In your game somewhere
vector<Item*> items;
//
Key *goldenKey = new Key();
//
items.push_back(goldenKey);

//Event Scope
{
vector<Item*>::iterator iter;

iter = items.begin();
while(iter != items.end())
{
//if players collides with something and attack button is pressed
iter->act();
}
}
[/source] Edited by kuramayoko10

Share this post


Link to post
Share on other sites
@kuramayoko10, Servant of the Lord - yes my engine is OO, so polymorphism is a known practice.

Thanks to both of you for a bit of light on this.

I just need more sleep to finish up this title ;)

Edited by xynapse

Share this post


Link to post
Share on other sites
Hey Xynapse,

This ties in very closely to a tutorial (possibly series) I'm writing. Do you mind if I use your problem and possibly diagrams as an example? The tutorial is going to focus on the structure of a game, both the game engine components, as well as the game-play components. It'll address the design goals of each set and how you can engineer the game and engine for best performance & flexibility trade-offs.

Cheers!

Share this post


Link to post
Share on other sites
One possibility is to use an event system. There are different design patterns to implement event systems, two popular ones seem to be the [url="http://en.wikipedia.org/wiki/Observer_pattern"]observer pattern[/url] and the [url="http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern"]publish-subscribe pattern[/url]. Using this, you can create messages/events that get fired off when something happens, and have listeners/subscribers that consume these events. Here's a crude example (I'm sure someone could make many improvements on it):

[code]
struct Event
{
enum Type
{
DOOR_OPENED,
DOOR_CLOSED,
SWITCH_ACTIVATED,
SWITCH_DEACTIVATED
// ...whatever other types of messages there are...
} type;

std::string senderId;

struct DoorEvent
{
bool isOpen;
// ...whatever other data you need...
};

struct SwitchEvent
{
bool isActivated;
// ...whatever other data you need...
};

union
{
DoorEvent doorEvent;
SwitchEvent switchEvent;
};
};
/*
Then you have some options on how to set this up:
1. Let each object subscribe to events from another object
Example 1: Door A subscribes to events from switch X.
Door A also subscribes to events from the room (to
know if the player enters/leaves the room).
Example 2: Door A subscribes to *ONLY* SWITCH_ACTIVATED events
from switch X.
Door A also subscribes to *ONLY* DOOR_CLOSED events
from the room.
3. Let some system class manage events and objects can subscribe to
listen to events.
Example 1: Door A subscribes to the main event system class.
Switch X has a SWITCH_ACTIVATED event, which gets
posted to the main event system class, and then
distributed to all other subscribers
Example 2: Door A subscribes to the main event system class
and requests SWITCH_ACTIVED events from *ONLY*
objects with the ID of 'X' and additionally it
subscribes to *ALL* DOOR_CLOSED events.
With such a system, objects only have to care about themselves. You can
easily "chain" conditions together by how you craft your event handling.
Door A opening requires door B to be closed and switch X to be activated,
for example.
*/
[/code]

Another option is to use a finite state machine like you said, but you could break it down even further and do a finite state machine for each object in the room (where one object's state depends on the other objects' states, rather than thinking of the entire room's state as a whole).

This is where scripting languages become useful though. You can put this all in C++ code, but it requires you to recompile (and go through the level again) if you make changes to things.

Share this post


Link to post
Share on other sites
The "easy solution" you gave for the key and door example is not particularly good. It'll serve you well if your entire game consists of a single room with a single key, but won't work beyond that without getting very messy.

A better way of keeping track of something like that is to give the key a unique entity ID, and have the door need that specific entity ID to unlock. Then you can scatter multiple keys in a room with different ID's, but only one of them will open the door.

To support more advanced behaviour you can add an event driven system as suggested by cornstalks. Something like a pressure plate for example. If the pressure plate changes state it could send an event to the door to tell it to open or close. Levers, buttons or whatever else you can think of are just variations on this theme.

I'm not sure what sort of game you're trying to create, but it might be worth looking at the developer blogs for [url="http://www.grimrock.net/blog/"]Legend Of Grimrock[/url]. They discuss how they handle scripting of events like this and might provide some insight.

Share this post


Link to post
Share on other sites
Hey guys, sorry for a delay - but i was off for some time.

@JWalsh - sure thing, go on and let me know where can i read about this - i am very interested in this.

@Cornstalks && @Postie - i already have events driven system implemented, and yes this is a great solution - everything within a level can dispatch 'messages' to it's receivers and thanks to that i can react on any kind of events happening in the game. Just need to 'pack' it somehow into a generic solution, that i could use within all levels in the game...

Well i think that i could delegate that 'logic handling' to the script files instead of going in C++ - i think it would be better to separate that from the main game code, and just let scripting handle the logic 'somehow' - but from the other side, it will take time to prepare the scripting implementation - and that time could be used to code the logic handling within the game. It's my first public title, and i would like to avoid using 3rd party libs with licenses.

I am still interested how people handle logic in their games, not everyone has scripting implemented - so how do they handle their game logic clean and clear ?

I'll prepare a sketch for the first level and drop it in here so we can all see what kind of game we're talking about. Maybe that would be more useful than my YT post.


Thanks for any input guys, it is great we can share ideas. Edited by xynapse

Share this post


Link to post
Share on other sites
I would recommend scripting aswell. Here's an example of the logic for a level of the game I'm working on right now:

[CODE]
# A clock and an enemy counter
Clock clock
clock.start()
enemy_count = 0


# When the clock hits 10 seconds, spawn an enemy and increment the enemy count
on clock.istime(10s):
spawn_enemy
enemy_count ++

# When an enemy is killed, decrement the enemy count and if the count is now zero finish the mission

on enemy.health == 0:
enemy_count --
if enemy_count == 0:
global::pass_mission()

# Namespace
global:
def fail_mission():
# Update some stats here maybe
game::show_mission_retry()
def pass_mission():
# Update some stats here maybe
game::show_mission_success()

# If the satellite is killed before all the enemies then fail the mission
satellite:
health = 40
on Damage( x )
health = health - x
if health <= 0:
global.fail_mission()
[/CODE]

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this