Data driving your games?

Do you data drive your games? How?

I’ve been experimenting with this myself, primarily using Spring beans to make available prototype instances of game objects which can be instanced by simply requesting a instance of “whateverGameObject” on demand.
But I’ve reached a point where I don’t think that’s a good way of doing it. Although Spring is powerful, I feel like it’s really not supposed to be used in this manner, defining data! Also, it seems to be extremely slow at loading up all the bean definition for an entire RTS game, containing maybe 40-50 XML bean files.

Going to something like Xstream, or maybe a in-memory h2 database, might be something worth exploring. But I wanted to hear how others are doing this before I spend a lot of time on this. Not much data on this topic on the interweb. :\

Currently, data driving my game is driving me mad! (ehe, I just had to add that one ;))

I pretty heavily data drive my games, I used to use Cas’ resource system but now I’ve got Rebirth which is an evolution of the same core ideas. It’s kind of like a mix between a DI framework and an XML->Object loading system, with an emphasis on nice, minimal xml.

Here’s a peek inside some of Albion’s xml to define a Dark Imp monster:



	<!-- Tag mappings -->
	<map name="BasicBrain" class="albion.brains.BasicBrainTemplate" />
	<map name="CharacterClass" class="albion.character.CharacterClass" />
	<!-- ... -->
	
	<decoder class="albion.character.Attributes" />
	
	<!-- Items implement and Item interface -->
	<HealthVial name="healthVial.item" itemName="Health vial" image="HealthVial.image" hpGain="100" mpGain="0" />
	<HealthVial name="manaVial.item" itemName="Mana vial" image="ManaVial.image" hpGain="0" mpGain="100" />
	<GoldCoins name="goldCoins.item" image="SmallCoins.image" count="10" />
	<BasicWeapon name="startingSword.item" itemName="Basic sword" image="ShortPlainSword.image"
				type="Bladed"
				attackPower="4"
				modifiers="bell2[-2, -2]"
	/>

	<!-- Character classes -->
	<CharacterClass name="warrior.class" className="Warrior" icon0="MeleeAttackCursor.image" icon1="DefendCursor.image"
				defaultAttributes="STR7 DEX6 CON5 INTEL3 WILL4 CHA5"
				equipableWeaponType="Bladed"
				hasMagic="false"
				magicScaleFactor="0.0"
				detectionSkillMultiplier="1.0"
				defaultInventory="startingSword.item, healthVial.item"
				defaultMaleNames="Bowie, Scythe, Vyse, Drachma, Ramirez, Higins, Minsc, Kurth, Bartok "
				defaultFemaleNames="Mazzy, Nalia, Josella, Petra, Rosalind, Miyako, Tifa, Scarlet"
				>
		<description>
			Warriors are skilled melee fighters trained to fight at the front of battle. They are comfortable with any non-ranged weapon and may even fight bare handed.
			As strong physical atheletes, a good warrior will have high strength and dexterity to deal damage and a high constitution in order to withstand it.
			Warriors may later specialise further and become skilled swordsmen, or become inspirational leaders, or focus on raw physical combat.
		</description>
	</CharacterClass>
	
	<!-- BasicBrain implements Brain interface -->
	<BasicBrain name="imp.brain" willFlee="true" hpFleeThreshold="0.25" />

	<!-- Dark imp is a regular imp tinted towards dark blue/green -->
	<AvatarSheet name="darkImp.sheet" sheet="Enemies/Imp.png" face="" tint="rgb(50, 140, 200)" />

	<!-- Spawn template for a dark imp -->
	<CharacterTemplate name="darkImp.character"
							characterName="Dark Imp"
							characterClass="warrior.class"
							attributes="STR1 DEX1 CON8 INTEL1 WILL1 CHA1"	<!-- Custom decoder translates this into an Attributes object -->
							sheet="darkImp.sheet"
							brain="imp.brain">
		<dropTable>
			<DropTable>
				<oneOf>
					<drop item="basicWeapon.drop" probability="1" />
					<drop item="manaVial.item" probability="1" />
					<drop item="goldCoins.item" probability="1" />
					<drop item="" probability="2" />
				</oneOf>
			</DropTable>
		</dropTable>
	</CharacterTemplate>

There’s very little code on the java side since almost all of that xml is automatically parsed via reflection. The important bits are:

  1. Actual objects of the correct sub-type (like BasicBrain, or HealthVial) are created, but in game they’re just handled via a Brain or Item interface. That’s the DI side of things, and means that I can easily configure a SmartImp by swapping in a new brain without changing any of the code. I assume you’re doing something similar to this with Spring.

  2. All the top-level objects are named so they can be referenced in other objects. Ie. “warrior.class” is shared between most melee monsters and can be chosen by the player as a character class too.

  3. Decoders mean it’s dead easy to insert custom syntax into attribute and have them spit out objects (like the strength/dexterity/etc. attributes).

  4. Not shown, but you can derive one definition from another. That means I can have superToughDarkImp.character derive from darkImp.character and only redefine it to have higher strength. Everything else gets automatically inherited.

  5. The whole thing reloads and reparses automatically, so you can tweek the text/attributes/probabilities and see them reflected instantly in game without a restart. That’s really, really handy when you’re tweeking tiles and images.

It’s really powerful when you get your head around it, as I can define whole new items/characters/behaviours/etc. with just new data, or insert new subtypes just by adding the class and references it in the xml. The core of the game stays entirely unaware of all of the subtypes and only has to deal with a few core interfaces.

Hope that’s useful. :slight_smile:

Er, what he said :slight_smile: All our games work in exactly the same way. I expect the XML is probably even completely compatible too, but he’s got a clever system for dynamically updating things at runtime.

Cas :slight_smile:

Hey, thanks.

Seems like a fine system, but doesn’t fit my requirements.

It’s not general purpose enough. I would need to define classes for the types of units I have in my game. That’s not good data-driven approach. In my codebase I only have one GameObject class and then just components. There seems to be a lot of glue-related code in your Rebirth system, and it enforces a certain design upon you. If you wanted to swap from Rebirth to e.g. a H2 database, you’d be in trouble. My system is far from being good, and that’s why I’m here, but at least it’s very easy to change from e.g. Spring to whatever else (at least there’s nothing in there enforcing Spring upon you, I can still compose my gameobjects using a hardcoded factory).

What I am looking for is a way to define a gameobject, the components it uses, and its properties. Here’s what I have in Spring XML for one of my gameobjects:


	<bean id="pegasus" class="com.gamadu.gcom.gocs.GameObject" scope="prototype">
		<property name="objectId" value="PEGASUS" />
		<property name="group" value="units" />
		<property name="radius" value="45" />

		<property name="components">
			<list>
				<bean class="com.gamadu.gcom.gocs.components.bounds.CircleBounds">
					<property name="radius" value="45" />
				</bean>

				<bean class="com.gamadu.gcom.gocs.components.fogofwar.RevealRadius">
					<property name="radius" value="600" />
				</bean>

				<bean class="com.gamadu.gcom.gocs.components.selecting.ImageHighlightSelectable">
					<property name="highlightImage">
						<bean class="org.newdawn.slick.Image">
							<constructor-arg index="0" value="races/terrans/pegasus_highlight.png" />
						</bean>
					</property>
				</bean>

				<bean class="com.gamadu.gcom.gocs.components.exhaust.MultipleExhaust">
					<property name="onlyWhenMoving" value="true" />
					<property name="exhaustEmitters">
						<list>
							<bean class="com.gamadu.gcom.gocs.components.exhaust.ExhaustEmitter">
								<property name="name" value="Thrust" />
								<property name="offsetX" value="-35" />
								<property name="offsetY" value="-28" />
							</bean>
							<bean class="com.gamadu.gcom.gocs.components.exhaust.ExhaustEmitter">
								<property name="name" value="Thrust" />
								<property name="offsetX" value="-35" />
								<property name="offsetY" value="28" />
							</bean>
						</list>
					</property>
				</bean>

				<bean class="com.gamadu.gcom.gocs.components.shield.StandardShield">
					<property name="image">
						<bean class="org.newdawn.slick.Image">
							<constructor-arg index="0" value="shields.png" />
						</bean>
					</property>
					<property name="hitImage">
						<bean class="org.newdawn.slick.Image">
							<constructor-arg index="0" value="shieldHit.png" />
						</bean>
					</property>
				</bean>

				<bean class="com.gamadu.gcom.gocs.components.movement.RealisticShipMovement">
					<property name="mass" value="50" />
					<property name="thrust" value="0.005" />
					<property name="maximumSpeed" value="0.06" />
					<property name="surfaceFriction" value="0.10" />
					<property name="turnFactor" value="0.1" />
				</bean>

				<bean class="com.gamadu.gcom.gocs.components.health.RegenerativeHealth">
					<property name="health" value="4500" />
					<property name="maximumHealth" value="4500" />
					<property name="regenerationIncrement" value="0.055" />
				</bean>

				<bean class="com.gamadu.gcom.gocs.components.highlighting.NameHighlighting">
				</bean>

				<bean class="com.gamadu.gcom.gocs.components.visuals.ImageVisuals">
					<property name="image">
						<bean class="org.newdawn.slick.Image">
							<constructor-arg index="0" value="races/terrans/pegasus.png" />
						</bean>
					</property>
				</bean>

				<bean class="com.gamadu.gcom.gocs.components.collision.TagCollidable" />

				<bean class="com.gamadu.gcom.gocs.components.spawn.DestroyedSpawner">
					<property name="associatePlayer" value="false" />
					<property name="spawnObjectNames" value="unitExplosionLarge,disperseProtomatter,disperseProtomatter,disperseProtomatter,disperseProtomatter" />
				</bean>

				<bean class="com.gamadu.gcom.gocs.components.targeting.TotallyTargetable" />

				<bean class="com.gamadu.gcom.gocs.components.targeting.NearestTargeter">
					<property name="targetingRange" value="700" />
				</bean>

				<bean class="com.gamadu.gcom.gocs.components.weapons.CompositionWeapons">
					<property name="weaponControllers">
						<util:list>
							<bean class="com.gamadu.gcom.gocs.components.weapons.IntervalShootingWeaponController">
								<property name="shootInterval" value="2000" />
								<property name="weapons">
									<util:list>
										<bean class="com.gamadu.gcom.gocs.components.weapons.Shooter">
											<property name="offsetX" value="41" />
											<property name="offsetY" value="-22" />
											<property name="armamentName" value="blastBeam" />
										</bean>
										<bean class="com.gamadu.gcom.gocs.components.weapons.Shooter">
											<property name="offsetX" value="41" />
											<property name="offsetY" value="22" />
											<property name="armamentName" value="blastBeam" />
										</bean>
									</util:list>
								</property>
							</bean>
						</util:list>
					</property>
				</bean>
			</list>
		</property>
	</bean>

Then I simply request a new bean called “pegasus” from the beanFactory instance. I write all the components so they can be used for DI. There’s really no “glue” code.

Although this is flexible, and convenient, and enables me to create rather complex gameobjects, it’s really a rape of the IoC concept. Loading time has increased dramatically during development, as I need to load up all the bean definitions when I load the game world, and it takes Spring some time to go through nearly 100 bean definitions in 70+ XML files. We’re planning on perhaps 200 types of gameobjects.

Spring is supposed to resolve dependencies in your application, and is very good at that, but it’s not supposed to store the data for your application. Clearly I didn’t anticipate the project growing so much.

Maybe I will need to design my own system to manage this stuff. :-\ Doesn’t seem to be many other alternatives.

Not really, you could translate that Spring XML to Rebirth without any major problems. You don’t have to define a new class for every type of unit either - in fact Albion doesn’t do that either. All creatures (which includes the human controlled players) are just Characters. It’s semi-component based (in that the body, character class, brain and several other important bits can be swapped in and out, but it’s not a full blown component system. Mostly because I haven’t yet felt the need for a full component system.

For a laugh, I decided to translate your Spring XML and see how it’d look:


<map name="GameObject" class="com.gamadu.gcom.gocs.GameObject" />
<map name="CircleBounds" class="com.gamadu.gcom.gocs.components.bounds.CircleBounds" />
<map name="RevealRadius" class="com.gamadu.gcom.gocs.components.fogofwar.RevealRadius" />
<map name="ImageHighlightSelectable" class="com.gamadu.gcom.gocs.components.selecting.ImageHighlightSelectable" />
<map name="MultipleExhaust" class="com.gamadu.gcom.gocs.components.exhaust.MultipleExhaust" />
<map name="ExhaustEmitter" class="com.gamadu.gcom.gocs.components.exhaust.ExhaustEmitter"/>
<map name="StandardSheild" class="com.gamadu.gcom.gocs.components.shield.StandardShield" />
<map name="RealisticShipMovement" class="com.gamadu.gcom.gocs.components.movement.RealisticShipMovement" />
<map name="RegenerativeHealth" class="com.gamadu.gcom.gocs.components.health.RegenerativeHealth" />
<map name="NameHighlighting" class="com.gamadu.gcom.gocs.components.highlighting.NameHighlighting" />
<map name="ImageVisuals" class="com.gamadu.gcom.gocs.components.visuals.ImageVisuals" />
<map name="TagCollidable" class="com.gamadu.gcom.gocs.components.collision.TagCollidable" />
<map name="DestroyedSpawner" class="com.gamadu.gcom.gocs.components.spawn.DestroyedSpawner" />
<map name="TotallyTargetable" class="com.gamadu.gcom.gocs.components.targeting.TotallyTargetable" />
<map name="NearestTargeter" class="com.gamadu.gcom.gocs.components.targeting.NearestTargeter">
<map name="CompositionWeapons" class="com.gamadu.gcom.gocs.components.weapons.CompositionWeapons">
<map name="IntervalShootingWeaponController" class="com.gamadu.gcom.gocs.components.weapons.IntervalShootingWeaponController">
<map name="Shooter" class="com.gamadu.gcom.gocs.components.weapons.Shooter">

<decoder class="SlickImageDecoder" />

<GameObject name="BEGASUS"
			group="units"
			radius="45">
		<components>
			<CircleBounds radius="45" />
			<RevealRadius radius="600" />
			<ImageHighlightSelectable highlightImage="races/terrans/pegasus_highlight.png" />
			
			<MultipleExhaust onlyWhenMoving="true">
				<exhaustEmitters>
					<ExhaustEmitter name="Thrust" offsetX="-35" offsetY="-28" />
					<ExhaustEmitter name="Thrust" offsetX="-35" offsetY="+28" />
				</exhaustEmitters>
			</MultipleExhaust>

			<StandardSheild image="sheilds.png" hitImage="sheildHit.png" />
			
			<RealisticShipMovement mass="50" thrust="0.005" maximumSpeed="0.06" surfaceFriction="0.10" turnFactor="0.1" />
			
			<RegenerativeHealth health="4500" maximumHealth="4500" regenerationIncrement="0.055" />
			
			<NameHighlighting />

			<TagCollidable />
			
			<DestroyedSpawner associatePlayer="false" spawnObjectNames="unitExplosionLarge,disperseProtomatter,disperseProtomatter,disperseProtomatter,disperseProtomatter" />
			
			<TotallyTargetable />
			
			<NearestTargeter targetingRange="700" />
			
			<CompositionWeapons>
				<weaponControllers>
					<IntervalShootingWeaponControllers shootInterval="2000">
						<weapons>
							<Shooter offsetX="41" offsetY="-22" armamentName="blastBeam" />
							<Shooter offsetX="41" offsetY="+22" armamentName="blastBeam" />
						</Weapons>
					</IntervalShootingWeaponControllers>
				</weaponControllers>
			</CompositionWeapons>
		</components>
</GameObject>

And the Java side:


public GameObject extends AutoParsable
{
	private String units;
	private int radius;
	private Components[] components;
}

public CircleBounds extends AutoParsable
{
	private int radius;
}

private CompositionWeapons extends AutoParsable
{
	private WeaponControllers[] weaponControllers;
}

private IntervalShootingWeaponControllers extends AutoParsable
{
	private int shootInterval;
	private Weapons[] weapons;
}

Note that’s not simplified Java code, that’s all you need - everything gets parsed into attributes automagically with the correct types. The only extra thing you’d have to do is add a decoder (~5 line class) to decode your Slick images and you’re done.

Edit: And to answer the inevitable complaint - you don’t have to inherit from AutoParsable either, you can implement the Resource interface instead (which just consists of getName() and setName()) if you need to derive from a specific type.

Looks interesting. Will digest this…

Persist is a simple no nonsense persistence framework. http://code.google.com/p/persist/

I use this for my games along with basic XML encoding.