Sign in to follow this  
leiavoia

Responsible Way To Delete The Player?

Recommended Posts

As you can imagine, pretty much everything in a game ties into the object that represents the player: GUI, enemy AI, drawing routines, input, collision detection, etc. So what happens when the player dies? All these objects that have some kind of pointer/reference to the player are suddenly up a creek. What is a responsible way to clean up the millions of little wires that all connect to the player, (stop taking input, stop displaying stats, stop targeting the player, etc)? I have a generalized Observer system in place which i use to some degree. When the player dies, it notifies any observers of its death and the observers can do what they want with that information. However, not everything is an observer object. Should they be? Another thing i could do is simply make the player a global of some kind, perhaps a pointer wrapped for safety. I know a lot of you guys would scoff at such an idea, but it would be simple. This could also be generalized by asking "how can i shut down a major object in the program without leaving lots of loose ends or creating lots of overhead to handle it?" I could do this in many different ways, but i'm looking for other ideas and opinions. Thanks for yours.

Share this post


Link to post
Share on other sites
One option is to keep is as a handle; this adds a bit of overhead, but at least it's safe.

A common path to this would be to generate a handle from the object name (std::string), using some form of CRC32 or MD5 to generate an handle (unsigned int). Any object is thus accessed from the WorldManager using a handle-lookup into a hash-table or similar. Every time an object needs to find a game-object it would explicitly fetch from the world. This becomes more important once there's a lot of interconnectivity (not just object->player connectivity, but also object->object).

This is also really usefull if you're binding the system up to a scripting language (such as Python or LUA), where you want to be able to access objects easily by name, and where keeping pointers usually invalidates the sandbox encapsulation.

Hope that helps;

Allan

Share this post


Link to post
Share on other sites
It helps of course to first design around the possibility that the player or other major element dies, or won't exist.

So, 2 methods, which are not so clean, which I've at least sort of used.

1. Ingrain the loss of a major component into the error handling. As Odin suggests, make things handles [foo **] so that they can detect destruction of the major object. When they're next used, they'll detect it, and shutdown. A sort of anti-singleton.

2. Cascading. This is actually what I use currently for player loss in my current project. Generally, this sort of thing is good when there's a one way need-to-know. If the player dies, the player's empire is deleted [or a deletion call is placed on an event system or...] in the player's destructor. The empire then destroys the empire's units. The units then destroy their skills....

It'd be silly for a unit skill object to hold a handle/pointer/reference to the player object. This way, dependancies are handled fairly nicely.

Share this post


Link to post
Share on other sites
Defensive coding (using handles, checking the validity of the handle before continuing), is OK, as long as you still keep it in the error category (i.e. assert when the handle becomes invalid of the object with that handle is no more).

You still should try to de-allocate everything nicely. Usually, there should be a point in your program where you create a player, create the score object, create the HUD object, create the input object, ect... Then equally there should be a point where you delete the input object, delete the hud object, delete the score object, then delete the player (preferably in that order, the reverse order of creation).

So I guess, you just need a sort of global function "static void CPlayer::CreatePlayer(cHandle hPlayerHandle)" and a "static void CPlayer::DestroyPlayer(cHandle hPlayerHandle)". That should take care of most of them. Maybe not all of them if, for example, you have a message "Player %s say : Hello", but the player is no more so his name is not there. Since it's deep inside an object of little importance (an on-screen message queue), it would be pointless to track it down when the player dies and delete the queued messages relating to the player (you could still do it). Just ignore and remove the message if the player is invalid (defensive code).

Share this post


Link to post
Share on other sites
Why don't you just rely off booleans? You could just allow input for the player, if something is true, though when the player is no longer around, make it false, then have a few if statments guarding the action.

Share this post


Link to post
Share on other sites
Smart pointers. Specifically, I'd suggest boost smart pointers.

Example (edited for better clarity using OO):

using namespace boost;

struct game_instance
{
shared_ptr< player > player1;
shared_ptr< player > player2;
shared_ptr< enemy > enemy1;
shared_ptr< enemy > enemy2;
game_instance( void )
: player1( new player( "player1" ) )
, player2( new player( "player2" ) )
, enemy1( new enemy( "enemy1" ) )
, enemy2( new enemy( "enemy2" ) )
{
enemy1->target = player1;
enemy2->target = player2;
}
void kill_player_1( void )
{
player1.reset();
}
void respawn_player_1( void )
{
player1.reset( new player( "player1" ) );
}
//...
};

struct enemy
{
weak_ptr< player > target;
void do_ai_cycle( void )
{
if ( shared_ptr< player > target = this->target.lock() )
{
move_towards( target->position );
}
else //target dosn't exist anymore
{
find_new_target();
}
}
//...
};



edit: fixed and added to. The idea is that an object will be deleted when no more shared_ptr s point to it. A weak_ptr can point to an object pointed to by smart pointers, and if that object goes bye bye (aka no more shared pointers point to it) the .lock() method will return an empty shared pointer (making the if statement fail in the example).

As to why you create a shared_ptr using the lock method of a weak_ptr, the rationale is that it ensures the object won't be deleted (by running out of shared pointers) while you want to use it from a weak pointer. This isn't much of a concern unless you're multithreading, but it's not a hard practice to adopt (especially considering the boost library more or less forces you to - a good thing IMHO).

Some parts are missing but it should get the idea across.

Note that there's no delete statements in my code. This is OK, because the objects will get deleted automagically when no more smart pointers point to it.

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