Scalable Data Driven design for RPG

Started by
6 comments, last by CrazyCdn 5 years, 6 months ago

Hello!

I'm currently developing a top-down RPG styled game with an Entity Component System architecture and, as the game grows in features, so does my game entities, that is, item models, enemy prototypes, etc. Those definitions are currently in JSON files but at the end of the day I still have long factory classes that read from those files and populate the entities with their respective components and properties in the most naive way.

Reading through a presentation about Techniques and Strategies for Data-driven design in Game Development (slides 80–93) (warning: big pdf file) there is this "prototyping approach" where you can build up each game entity from multiple prototypes. I find this really interesting, however, the presentation doesn't mention any implementation details and I'm totally in the dark. I don't know how powerful should this system be. By the way, I'm using Java and LibGDX's engine. My first idea is making a robust prototype-instancing factory where, given a JSON file, it will be able to return an entity populated with its corresponding components. For example:
 

Enemies.json


{
	"skeleton" : {
		"id" : 0,
		"components" : {
			"HealthComponent" : {
				"totalHealth" : 100
			}, "TextureComponent" : {
				"pathToTexture" : "assets/skeleton.png" 
			}
		}
	}
}

 

If I wanted to instantiate a Skeleton entity, I would read it's prototype, iterate over it's components and somehow I would instantiate them correctly.

With this approach I have the following issues:

  1. It will most likely involve using Java Reflection to instance entities from a JSON file. This is a topic that I know little about and will probably end up in dark magic code.
  2. Some instances properties can't be prototyped and will have to be passed as parameters to the factory. For example, when creating an enemy entity, an (x, y) position will have to be provided. Suddenly creating instances is not so straight forward.
  3. How powerful should this system be? Should it have this "recursive" behavior where you can extend a prototype with an other and so on?
  4. This sounds a little bit like dependency injection. Am I reinventing the wheel? Is there anything done already I can make us of?

Even though it's still in it's infancy, here is a short demo (under one minute) of my game.


Thank you!

Advertisement

Extendable prototypes are useful - Enemy->Undead->Skeleton for example. Just to not have to write so much. Using combining prototypes may be useful, but also kind of doubles the whole component thing - might well be superfluous.
Creating entities from prototypes does not mandate passing parameters to whatever actually creates them. At whatever point you create an entity, you know why and what entity is to be created. You might as well just return one from the factory without passing anything other than the proto-id, and set whatever you need on the returned entity in whatever place requested it. Lets say you populate a dungeon with enemies - that generator-thing knows "right now i wanna place a skelly at X,Y" - so have it request a skeleton and have it set the proper position. 

If you have serialization for your entities, human readable that is (so you can manually adjust the file), you could just use that for "prototypes". create an "empty" skeleton, save it, and use that when somebody request a fresh skeleton.
 

Another way might be to have prototypes and init-data, prototypes have an init-function that accepts init-data to set up that specific type of entity. So you can decouple specific inits away from the factory to some place more appropriate.

For me, my entity is just an ID and a bitset of each component(s) it has, set to 1.  When I create an entity from data I pass it to the factory class.  Each component system (that does work on the component(s), creates, updates, destroys, etc) registers itself with the factory.  I pass the data on to the general factory which parses the data and passes on the requested data to each component system to initialize that component with.  I'm a C/C++ programmer so no idea what Java's reflection system does but you should be able to implement something similar to what I described quite easily.  Especially with text (json) data.  <rant>Though I hate json/xml and just create my own format.  No need for a bloated library of features I will never use and took me about an hour to code up and test/verify</rant>.

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

You could start with a basic implementation where component factories are registered by name in a map. When creating your entities, iterate over the "components" object and use the key to look up the factory in the map. Then pass the corresponding value to that factory so it can instantiate the component with the proper data. As there's nothing with this approach specific to components or entities, it can be reused and extended to support just about any structured, nested data model. This technique is commonly used in serialization libraries, where you frequently need to reconstruct runtime data from information received over the network, or some other external source.

22 minutes ago, Zipster said:

You could start with a basic implementation where component factories are registered by name in a map. When creating your entities, iterate over the "components" object and use the key to look up the factory in the map. Then pass the corresponding value to that factory so it can instantiate the component with the proper data. As there's nothing with this approach specific to components or entities, it can be reused and extended to support just about any structured, nested data model. This technique is commonly used in serialization libraries, where you frequently need to reconstruct runtime data from information received over the network, or some other external source.

You could also pass all the json data onto each component generation system and let it parse out it's data itself if you want to go very simple.  So every component system gets the json data and scans it for any related components it handles.  Wastes a little processing time but super simple to implement too.

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

Thank you all very much for your replies, they were really helpful. I will go ahead and try two ideas you guys proposed here: one is – given a prototype – allowing each component to initialize itself and the other one is having a map of component factories. At first sight, the first option seems to be easier and more straight forward than the later but at the expense of having "prototype instantiation" code all across my components, something the second option seems to avoid.

I will follow up with you guys as soon as I have something solid.

Thanks again!

Well the components typically are only data.  So the system that does work on them (however you do that, like an Update() function) will also have the Create( const std::uint64_t entity_id, const std::string&& data ) function which will extract the data from the string you pass in (typically the whole json block you're concerned about).  That's what I meant anyway.  YMMV :)

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

This topic is closed to new replies.

Advertisement