Quantcast
Channel: GameDev.net
Viewing all articles
Browse latest Browse all 17825

Entities-Parts III: Serialization

$
0
0

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.

There are many viable ways to serialize/deserialize entities. 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. Why XML and JAXB? 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.

Note that JAXB library refers to conversion between objects and data as marshalling, but this article will use the term serialization. The main drawback of JAXB is that it is slower to serialize/deserialize data compared to other frameworks such as Kryo and Java Serialization (Performance Comparison). 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 <spell></spell> tags.

@XmlElementWrapper - This wraps all of the individual <spell></spell> elements in a <spells></spells> 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.

Viewing all articles
Browse latest Browse all 17825

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>