In the previous post, we discussed the topic of XNA and Level Validation.
Now, let's build on this information to discuss Component Based Design.
Note: this post includes complete code sample on CodePlex.
Download code sample here.
Component Based Design
Component Based Design is a common approach to build game objects that are flexible, maintainable and scalable: each component encapsulates a set of related functions, or data, so that additional game objects can be created without any extra code.
In game development, component based object data is typically stored in XML files. The logic used to parse XML and build game objects can be complex and error prone; consequently, system tests can
be used to validate all game objects before fully integrating into the game.
Example
As an example, let’s revise the Spacewar starter kit to demonstrate XNA and Component Based Design.
First, create a system that can parse XML files and build proxy objects for game consumption:
public class ProxyObjectBuilder
{
public void BuildProxyObjects(String folder, String txtFile);
public void BuildProxyObjects(String folder, IList<String> xmlFiles);
public ProxyObject GetProxyObject(String name);
}
Next, create a component that can be used to build custom game objects at run-time:
public class GameObjectBuilder
{
public T BuildGameObject<T>(String assetName) where T : GameObject;
public GameObject BuildGameObject(String assetName);
public GameObject BuildGameObject(ProxyObject proxyObject);
}
Sample
The following code sample refactors the starter kit to integrate an unlimited number of spaceships using data driven design and validates each spaceship through system tests.
First, define an XML file that stores an unlimited number of spaceships available; each spaceship defines:
- 3D model, ship class and ship skin
- Position, rotation, flight boundary
- Thrust power and bleed velocity
|
<Assets>
<Asset name="PencilOneSkinOne" type="Spaceship">
<ModelComponent id="100" shipClass="PencilOne" modelName="p1_pencil" textureName="pencil_p1_diff_v1" />
<PhysicsComponent id="200" modelScale="0.02" rotateScale="0.02" position="0,0,0" rotation="90,0,0" boundLeft="-400" boundRight="400" boundTop="-250" boundBottom="250" />
<ThrustComponent id="300" textureName="thrust_stripSmall" thrustFrame="12.0" thrustConstant="12" thrustMaximum="29" thrustFactor="1.0" thrustPower="100" bleedVelocity="0.3" />
</Asset>
Also, define an XML file that stores an unlimited number of weapons available to each spaceship:
<Assets>
<Asset name="RocketTwo" type="Weapon">
<ModelComponent id="100" modelName="p2_rocket" textureName="p2_rocket" />
<PhysicsComponent id="200" modelScale="0.02" rotation="90,0,0" boundLeft="-400" boundRight="400" boundTop="-250" boundBottom="250" />
<WeaponComponent id="400" cost="3000 " lifetime="1.5" maximum="1" burst="1" acceleration="300" damage="5" />
</Asset>
</Assets>
Finally, write system tests to validate all data before fully integrating into the game.
Note: an IoC Container will be used to construct all components used throughout.
PROXY OBJECT BUILDER TESTS
[TestFixture]
public class ProxyObjectBuilderTests
{
// System under test.
private ProxyObjectBuilder proxyObjectBuilder;
[SetUp]
public void SetUp()
{
proxyObjectBuilder = IoCContainer.Resolve<ProxyObjectBuilder>();
}
[Test]
public void BuildProxyObjectsSpaceshipsTest()
{
String path = GetPath();
String[] xmlFiles = new[] { "Spaceships.xml" };
proxyObjectBuilder.BuildProxyObjects(path, xmlFiles);
Assert.AreEqual(18, proxyObjectBuilder.ProxyObjectManager.ProxyObjects.Count);
}
}
GAME OBJECT BUILDER TESTS
[TestFixture]
public class GameObjectBuilderTests
{
// System under test.
private GameObjectBuilder gameObjectBuilder;
[SetUp]
public void SetUp()
{
gameObjectBuilder = IoCContainer.Resolve<GameObjectBuilder>();
}
[Test]
public void BuildGameObjectSpaceshipComponentTest()
{
String path = GetPath();
String[] xmlFiles = new[] { "Spaceships.xml" };
gameObjectBuilder.ProxyObjectBuilder.BuildProxyObjects(path, xmlFiles);
String assetName = "WedgeTwoSkinOne";
Spaceship spaceship = gameObjectBuilder.BuildGameObject<Spaceship>(assetName);
var component = spaceship.GetComponent<ModelComponent>();
Assert.AreEqual(ShipClass.WedgeTwo, component.ShipClass);
Assert.AreEqual("p2_wedge", component.ModelName);
Assert.AreEqual("wedge_p2_diff_v1", component.TextureName);
}
}
Download code sample here.
Summary
The revised Spacewar starter kit demonstrates how to integrate an unlimited number of spaceships using data driven design: simply update the XML file to add more spaceships without constant need to recompile and validate all data quickly and efficiently through system tests.
In conclusion, data driven design does seem to have the potential to scale using XNA:
Consequently, there is an opportunity to integrate more complex game code and data using XNA and
data driven design techniques.