Background
Download RPG Battle Example and Java version of Entities-Parts Framework
Download C++ version of Entities-Parts Framework
I. Game Objects
II. Interactions
III. Serialization (current)
The previous articles focused on entity structure and interaction. In the previous version of the RPG Battle Example, all of the code that defined the entity attributes was in the CharacterFactory
. For example, it contained code to set the health of the meleer character to 200 and add spells to the support mage. We now want to move entity data out of the code and into data files. By storing the entity data in files, we can conveniently modify them without compiling the code. In addition, the data files could be reused if the code was ported to another language. This will be the last article in the series and covers serialization.
For the purposes of this article, I chose XML and JAXB. If you aren't familiar with these technologies, I recommend googling about them as the article revolves heavily around them. Note that JAXB library refers to conversion between objects and data as marshalling, but this article will use the term serialization.
The advantages of XML are that it is a popular way to store data and is human-readable. JAXB is a powerful XML serialization framework packaged with Java EE 6 that uses annotations to mark serializable classes and fields. Using the annotations as hints, JAXB automatically de/serializes class instances and does much of the grunt work for us. The main drawback of JAXB is that it is slower to serialize/deserialize data compared to binary serialization frameworks such as Kryo and Java Serialization. In 1,000,000 runs of a serialization perfomance test, JAXB unmarshalling (a.k.a deserialization) took 249972ms, while Kryo took only 2557ms (Performance Comparison).
There are many viable ways to serialize/deserialize entities so I decoupled serialization code from general-purpose classes such as Entity
and Part
. This makes the code easy to modify if you want to switch to another serialization framework. Even if you decide to use another serialization framework, I hope this article gives you an idea of what issues or general approaches are associated with data serialization.
RPG Battle Example (continued)
The top of the article contains the download link for the RPG Battle Example. The RPG Battle Example has been updated to use JAXB serialization to load entities from files. The serialized files of the character entities are stored in the relative project path "data/characters/". Through the help of a program I created called EntityMaker.java
, I used the old character factory, now renamed to CharacterFactory_Old
, to serialize the entities to XML files. The following is the "meleer.xml" file:
<?xml version="1.0" encoding="UTF-8"?>
<entity>
<parts>
<part xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="manaPart">
<maxMana>0.0</maxMana>
<mana>0.0</mana>
</part>
<part xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="restorePart">
<healthRestoreRate>0.01</healthRestoreRate>
<manaRestoreRate>0.03</manaRestoreRate>
</part>
<part xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="healthPart">
<maxHealth>200.0</maxHealth>
</part>
<part xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="equipmentPart">
<weapon>
<name>Sword</name>
<minDamage>25.0</minDamage>
<maxDamage>50.0</maxDamage>
<attackRange>CLOSE</attackRange>
</weapon>
<spells/>
</part>
<part xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="descriptionPart">
<name></name>
</part>
<part xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="alliancePart">
<alliance>MONSTERS</alliance>
</part>
<part xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="mentalityPart">
<mentality>OFFENSIVE</mentality>
</part>
</parts>
</entity>
The XML contains elements that represent the entity and the individual parts. Notice that not all variables are stored. For example, the Entity
class has variables isInitialized
and isActive
that don't appear in the file above. The values for these variables can be determined at runtime so they don't need to be stored.
The attributes xmlns:xsi
and xsi:type
are needed by JAXB to deserialize the data to the necessary type. As you might imagine, it is very convenient to edit entities on the fly without compiling the whole program again. The human-readable XML format allows us to easily change entity behavior by updating existing part elements or add new part elements, e.g. a FlyingPart
element to the "meleer.xml" file. The CharacterFactory
from part II has been refactored to contain only one method instead of several methods to create each character.
The path to the XML file containing the serialized Entity
is passed into the createCharacter
method which converts the file to an Entity
. XmlUtils
is a helper class I created that serializes/deserializes between XML and Java objects. I will describe what the arguments to the read method represent later on in the article.
public class CharacterFactory {
/**
* Creates an character entity from a file path.
* @param path path to the serialized character definition
* @param name
* @param alliance
* @return new character
*/
public static Entity createCharacter(String path, String name, Alliance alliance) {
Entity character = XmlUtils.read(Paths.CHARACTERS + path, new EntityAdapter(), Bindings.BOUND_CLASSES, "bindings.xml");
character.get(DescriptionPart.class).setName(name);
character.get(AlliancePart.class).setAlliance(alliance);
return character;
}
}
In order to make a class recognized by JAXB for serialization, we add annotations such as @XmlRootElement
and @XmlElement
to the class. For example, the following classes EquipmentPart
and SummonSpell
contain annotations:
@XmlRootElement
public class EquipmentPart extends Part {
@XmlElement
private Weapon weapon;
@XmlElementWrapper
@XmlElement(name = "spell")
private List<Spell> spells;
...
@XmlRootElement
public class SummonSpell extends Spell {
@XmlJavaTypeAdapter(EntityAdapter.class)
@XmlElement
private Entity summon;
...
In case you don't know already, here are what the annotations mean:
@XmlRootElement - Creates a root element for this class.
@XmlAccessorType(XmlAccessType.NONE) - Defines whether properties, fields, or neither should be automatically serialized. The XmlAccessType.NONE argument means that by default, variables and properties will not be serialized unless they have the @XmlElement annotation.
@XmlElement(name = "spell") - This annotation defines fields or properties that should be serialized. The argument name = "spell" says that each Spell object in the list of spells should be wrapped in the tags.
@XmlElementWrapper - This wraps all of the individual elements in a tags.
@XmlJavaTypeAdapter(EntityAdapter.class) - The Entity field will be serialized and deserialized using the specified XML adapter passed in as the argument.
Obstacles
Ideally, it'd be nice to add annotations to our classes and just let our serialization framework do the rest of the work without any more effort from us. But often there are obstacles with serialization, such as classes that we don't want to or can't add annotations to. The following sections describe solutions for these issues and may be a little confusing because it goes into more advanced usage of JAXB: XML Adapters and Bindings.
XML Adapters
Since the classes Entity
and Part
can be reused in multiple games, we want to avoid adding JAXB annotations to these classes or modifying them to fit a specific purpose such as serialization. However, de/serializing unmodifiable classes requires some workarounds which I'll describe.
The first step to making Entity
serializable is creating an XmlAdapter
to convert Entity
to a serializable class. We add two new classes, the serializable class EntityAdapted
and the adapter EntityAdapter
which is derived from the JAXB class XmlAdapter
.
The EntityAdapted
class contains the fields from Entity
that need to be serialized such as parts and contains JAXB annotations. The EntityAdapter
class converts between the unserializable form, Entity
, and the serializable form, EntityAdapted
. EntityAdapter
is referenced in SummonSpell
because SummonSpell
contains a reference to an Entity
and is also used in the CharacterFactory.createCharacter
method.
@XmlRootElement(name = "entity")
public class EntityAdapted {
@XmlElementWrapper
@XmlElement(name = "part")
private List<Part> parts;
public EntityAdapted() {
}
public EntityAdapted(List<Part> parts) {
this.parts = parts;
}
public List<Part> getParts() {
return new ArrayList<Part>(parts);
}
}
public class EntityAdapter extends XmlAdapter<EntityAdapted, Entity> {
@Override
public EntityAdapted marshal(Entity entity) throws Exception {
EntityAdapted entityAdapted = new EntityAdapted(entity.getAll());
return entityAdapted;
}
@Override
public Entity unmarshal(EntityAdapted entityAdapted) throws Exception {
Entity entity = new Entity();
for (Part part : entityAdapted.getParts()) {
entity.attach(part);
}
return entity;
}
}
Bindings
We would like to add the @XmlTransient
annotation to Part
because we don't want to store any fields in that class. There is a way to add JAXB annotations to a class without modifying the class. If you noticed, "eclipselink.jar" was added to the project. This is a 3rd party library that allows JAXB annotations to be added to unmodifiable classes by defining the annotations in an XML file. This is what the bindings.xml file looks like and you'll notice that it contains an element to make Part
xml-transient.
<?xml version="1.0"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" package-name="entitypart.epf">
<java-types>
<java-type name="Part" xml-transient="true"/>
</java-types>
</xml-bindings>
When serializing a list of an abstract type, e.g. the parts in the EntityAdapted
class, the serializer needs to know what subtypes of Part
could exist in the list. As you saw in the createCharacter
method of the CharacterFactory
, you'll see that Bindings.BOUND_CLASSES
is passed in as an argument to XmlUtils.read
. This static list contains the classes that JAXB needs to know in order to serialize the list of parts with the data in the subclasses of Part
.
public class Bindings {
/**
* Required for serializing list of base types to derived types, e.g. when a list of parts is serialized, binding
* the health part class to the serialization will allow health parts in the list to be serialized correctly.
*/
public static Class<?>[] BOUND_CLASSES = new Class<?>[] {
HealSpell.class,
SummonSpell.class,
AlliancePart.class,
DescriptionPart.class,
EquipmentPart.class,
FlyingPart.class,
HealthPart.class,
ManaPart.class,
MentalityPart.class,
RestorePart.class,
TimedDeathPart.class
};
}
In the entityparts.parts
package, there is a file called "jaxb.properties". This file must be added to a package of any class included in BOUND_CLASSES
above. See JAXBContext for more information.
Final Notes
The article described the basics of using JAXB to serialize entities and parts. Also, some of the more advanced features of JAXB such as XMLAdapter were used to overcome obstacles such as unmodifiable classes.
In addition to JAXB, I recommend taking a look at these serialization frameworks:
SimpleXML (Java) - An easy-to-use, lightweight alternative to JAXB. If you're developing an Android app, I recommend this over JAXB. Otherwise, you need to include the 9 megabyte JAXB .jar with your app (see JAXB and Android Issue). The SimpleXML .jar file is much smaller, weighing in at less than 400kb.
I haven't used any of these libraries, but they are the most recommended from what I've researched:
JSONP (Java) - JSON is a human-readable format that also holds some advantages over XML such as having leaner syntax. There is currently no native JSON support in Java EE 6, but this library will be included in Java EE 7.
Kryo (Java) - According to the performance comparison (Performance Comparison), it is much faster than JAXB. I'll probably use it in a future project. The downside is it doesn't produce human-readable files, so you can't edit them in a text editor.
Protobuffer (C++) - A highly recommended serialization framework for C++ developed by Google.
Article Update Log
25 May 2014: Initial draft.
Hi, first of all, good article! :)
One thing though:
I really wonder why the choice for XML has been made?
Wouldn't JSON be better in this particular situation, because you are describing objects while XML is more useful for layout purposes? I'm not criticizing but just wondering...