Jump to content
  • Advertisement
Sign in to follow this  
japro

Object specs and creation, what is the "standard approach"?

This topic is 2991 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi, maybe this question is trivial, but I wanted to ask anyway. This is one of those things where I could simply come up with a solution, but since almost all games have to do something like this, there must be some sort of canonical solution. What "structure" do you use to store data so it's accessible by the functions/objects that need it? Usually you'll have some files containing textures, mesh, object stats (hitpoints, movement speed...) etc. and these need to be loaded and stored somewhere in the program, so that my factory/spawnpoint can create a soldier object. Is there simply a singleton with a map/hashmap so you can do something like:
Entity *newSoldier = Creator::instance()->create("soldier");
?

Share this post


Link to post
Share on other sites
Advertisement
Well there's no reason the factory has to be a singleton. Instead it could be:
Entity *newSoldier = pWorld->pFactory->create("soldier");
...but yeah, I usually have a map of names (or hashes of names) to function pointers. Then you use the name of the entity you want to look up the creation function, and call it.

[EDIT]
Another simple technique is to pass some kind of 'key/value' data structure to the creation function. There's many more efficient ways of doing things, but this is very KISS:
typedef std::map<std::string, std::string> KeyValue;
typedef Entity* (*CreationFunction)( const KeyValue& );

class Factory
{
public:
void AddFunc( const std::string& name, CreationFunction pfn )
{
m_Table[name] = pfn;
}
Entity* Create( const std::string& name, const KeyValue& data )
{
CreationFunction pfn = m_Table[name];
if( pfn )
return pfn( data );
else
return 0;
}
private:
std::map<std::string, CreationFunction> m_Table;
};
Then you might use it like:
Entity* MakeSoldier( const KeyValue& data )
{
return new Soldier( data["name"] );
}

class World
{
public:
World()
{
factory.AddFunc( "Soldier", &MakeSoldier );
}
Factory factory;
};

World world;

KeyValue data;
data["name"] = "BadGuy_01";
Entity* pSoldier = world.factory.Create( Soldier, data );


If the soldier needs to create other things by itself, then (without resorting to singletons) when you create it you can pass it a pointer to the world or the factory. Perhaps the signature of the function pointer could be:
typedef Entity* (*CreationFunction)( World*, const KeyValue& );
so that every entity can access the world.

Share this post


Link to post
Share on other sites
It's super common to have a global single instance "application" class. You can put all the globals you like in this class and feel like you're not using globals :).

It's true though, they're not globals but in actuality, they're globals.

class Application
{
public:
Application& Instance( ) { static Application app; return app; }

Resource& Font( FontIndex:t index ) { return fontResources[ index ]; }
Resource& ExplosionDecals( DecalIndex::t index ) { return explosionDecals[ index ]; }

private:
Application( );

Resource fontResources[ FontIndex::FONT_COUNT ];
Resource explosionDecals[ DecalIndex::DECAL_COUNT ];
};

Share this post


Link to post
Share on other sites
There isn't really a canonic way of doing this. There are as many ways of doing it as there are programmers. But I would recommend against a singleton. Use globals if you must, but even that's not necessary. Dependency injection is not that hard. And for heaven's sake, don't load all your assets from one class. Look at the Single Responsibility Principle link in my sig.

Share this post


Link to post
Share on other sites
Hmm so, depency injection would look like this:


class Entity {

};

class EntityCreator {
virtual Entity* create();
};

class Bullet : public Entity {
int damage;
};

class BulletCreator : public EntityCreator {
Entity* create()
{
Bullet* b = new Bullet;
b->damage = damage;
return b;
}
int damage;
};

class Ship : public Entity {
void fire()
{
World.addEntity(bulletc->create());
}

int hitpoints;
EntityCreator *bulletc;
};

class ShipCreator : public EntityCreator {
Entity* create()
{
Ship* s = new Ship;
s->bulletc = bulletc;
s->hitpoints = hitpoints;
return s;
}
int hitpoints;
EntityCreator * bulletc
};

int main()
{
//load some data

BulletCreator *smallBulletCreator = new BulletCreator;
smallBulletCreator->damage = <some loaded value>;

BulletCreator *bigBulletCreator = new BulletCreator;
bigBulletCreator->damage = <some other loaded value>;

ShipCreator *smallShipCreator = new ShipCreator;
smallShipCreator->bulletc = smallBulletCreator;
smallShipCreator->hitpoints = <even more data>;

//...

Entity *fighter = smallShipCreator->create();
Entity *battlecruiser = bigShipCreator->create();

//...
}




So, every object has pointers to "factory classes" for every object it possibly has to create, right?

Share this post


Link to post
Share on other sites
Um... more like this, at least in the external interface:

struct EntityCreator{
Entity* CreateEntity(string typename, EntityInitData initdata){
//initdata is a generic struct for passing data to the constructor
//of any entity. could just be a string
return new Entity of the right type, with initdata
}
};

struct Ship: Entity{
EntityCreator* entity_Creator;
//This is the dependency injection part
//EntityCreator is the dependency, injected into the Ship
Ship(EntityCreator* entity_Creator)
:entity_creator(entity_creator) //save the creator to use later
{
//other initialization stuff...
}
void ShootBullet(Vec2 position, Vec2 velocity){
Entity* newbullet = entity_creator->CreateEntity("bullet", MakeBulletInitData(position, velocity));
World.addEntity(newbullet);
//ship could just as easily make some other entity
}
};


At least that's what I meant. That doesn't necessarily mean it's right [grin]. I use a very similar system for my entity controllers (entities are constructed directly by the World, since they're only one class). I just use a string as my init-data. In theory, it's capable of encoding any amount of information you could want.

You might want to use different factory classes for entities after all, if you need them to return pointers of the derived types. Depending on how complicated the process of creating an entity is, you might want to use a different class for each entity type under the covers of EntityCreator; in this case they need not share a base class.

Share this post


Link to post
Share on other sites
Hmm, but when I pass the same "build everything"-Object to every Entity, that can possibly create other objects (which most can since even bullets want to create a "explosion"-object on destruction) why don't I make that object a global or singelton in the first place?

Share this post


Link to post
Share on other sites
It's up to you and your requirements. I've seen both methods and I've never understood the need to create specialized creator classes vs having a single unified creator factory. The specialized creator classes lead to more code, more classes and more one off logic for entity creation vs a single unified factory which can reuse common logic.

Usually entities are composites of many logical and elemental components. These descriptors are defined in script or some external data file. The factory queries this data file to retrieve the descriptor. Using a valid descriptor it goes on to enumerate the object fully (ie it's logical comportment, physical, graphical, etc..) and then returns back a fully enumerated and instantiated object.

Sometimes people want to avoid the overhead of parsing and creating these objects so they use a cache or a clone method to duplicate a prototypical object.

I suggest keep it simple, use singletons if you need too (there are issues with them but I doubt you'll ever run into them for your project).

Good Luck!

-ddn

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!