Jump to content
  • Advertisement

dave19067

Member
  • Content Count

    2
  • Joined

  • Last visited

Everything posted by dave19067

  1. dave19067

    Entities-Parts I: Game Objects

    Sorry for the late response.  I haven't dealt with a situation like that where I would need parts to be updated in a certain order.  In that case, you can probably modify the Entity class to use SortedMap instead of HashMap.  Another solution is to add another method to your physics part called preUpdate(), which can be called before the main update stage.
  2. 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: 0.0 0.0 0.01 0.03 200.0 Sword 25.0 50.0 CLOSE MONSTERS OFFENSIVE 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 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 parts; public EntityAdapted() { } public EntityAdapted(List parts) { this.parts = parts; } public List getParts() { return new ArrayList(parts); } } public class EntityAdapter extends XmlAdapter { @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. 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.
  3. dave19067

    Entities-Parts III: Serialization

    Hi DeVisscha,   I was actually considering JSON for this article.  JSON syntax is leaner and would fit this case well.  The only reasons I didn't go with JSON is because I have little experience with it and would require importing another 3rd party library because there isn't any support for it in Java EE 6.
  4. dave19067

    Entities-Parts II: Interactions

    Good question.  I don't have experience working on a large-scale game, but I think you can combine both approaches in a game/engine:   In the Entities-Parts implementation, components have access to other components if they're from the same entity.  Put update logic in a component if it only needs to reference it's member variables or components of it's parent entity, e.g. the RestorePart in the example.   If you have logic that requires references to components from multiple entities or knowledge of the outside world, probably best to put the logic in a high-level class such as a system.  In the example, the BattleSystem is used to handle A.I. and targeting logic because they require access to a list of entities.
  5. Background I. Game Objects II. Interactions (current) III. Serialization The previous article in the series, Game Objects, gave a broad overview of entities and parts, an implementation for the Entity and Part class, and a simple example showing a lopsided fight between a monster Entity and a helpless villager Entity. It was basic enough to demonstrate how to use the Entity and Part classes but it didn't address entity management or interaction in detail. In addition to addressing these topics, the example will be expanded so that the villager's friends will join the battle against a mob of monsters. The previous article focused more on how entities and parts were structured. In this article, I will focus on ways to handle interactions between high-level classes, entities, and parts. Some examples of interactions are A.I. to target enemies or support allies, spell effects such as healing and summoning, and dealing damage to another character. I will also introduce a new class to the Entity-Parts framework: EntityManager, and an event system. The implementation of the framework will be provided in Java and C++ w/boost. Handling Interactions Interactions between Parts of the same Entity Putting logic in parts is a good idea when you want to model interactions between parts of the same entity. For example, a part for health regeneration could simply get the health part of its parent entity and increase the health every update step. In the Entities-Parts framework you add logic to parts by overriding the initialize, update, or cleanup methods. Interactions between Multiple Entities Putting logic in high-level classes such as systems and managers is a good idea when you want to model interactions between entities. For example, an Entity such as a warrior will need a list of enemy entities to attack. Implementing all of this logic in parts is difficult because the parts would be responsible for finding references of enemy entities to damage. As seen in the previous article's MonsterControllerPart, it is difficult to pass in references to other entities to parts without issues appearing. A better approach is to have a high-level class, e.g. a BattleSystem, become responsible for finding enemy entities because they inherently keep references to all entities. Events Events are commonly used in programming and are not restricted to games. They allow objects to immediately perform actions when something interesting happens. For example, when a missle receives a collision event, its collision event handler method makes it explode. In this fashion, the missle doesn't have to check each frame if it collided with anything, only when the collision happened. Events can sometimes simplify interactions between multiple objects. Event managers allow an object to wait on a certain event to happen and then act upon it while being decoupled from objects publishing the event. An event manager is a centralized hub of event publication/subscription that allows entities or other systems to simply subscribe to the event manager instead of the individual objects who are publishing the events. Likewise, the publishers can just publish an event to the event manager and let the event manager do the work of notifying the subscribers of the event. For example, the entity manager listens for an event where a new entity is created. If a new entity is spawned, say by a summon spell, the entity manager receives the event from the event manager and adds the new entity. It doesn't have to contain a reference to the summon spell. RPG Battle Example (continued) Now that we've discussed a high-level overview of handling interactions between high-level classes, entities, and parts, let's continue the example from the previous article. The RPG Battle Example has been completely refactored to support a larger battle between two teams of characters: Monsters vs. Villagers. Each side will have a Meleer, Ranger, Flying Ranger, Support Mage, and Summoner. Monster names start with M. and villager names start with V. Here is output of a round of combat in the updated example. Each character's information is displayed as well as the action it took during the current simulation time: SIMULATION TIME: 3.0 M.Meleer1 is dead! M.Ranger1 - Health: 89.25 - Mana: 0.0 Attacking with Bow. 34.0 damage dealt to V.Ranger1 M.FlyingRanger1 - Health: 100.0 - Mana: 0.0 Attacking with Bow. 23.0 damage dealt to V.Ranger1 M.SupportMage1 - Health: 100.0 - Mana: 56.0 Casting Light Heal. Healed 30.0 health on M.Ranger1 M.Summoner1 - Health: 100.0 - Mana: 39.0 Attacking with Staff. 14.0 damage dealt to V.Ranger1 V.Ranger1 - Health: 28.25 - Mana: 0.0 Attacking with Bow. 29.0 damage dealt to M.Ranger1 V.FlyingRanger1 - Health: 100.0 - Mana: 0.0 Attacking with Bow. 21.0 damage dealt to M.Ranger1 V.SupportMage1 - Health: 100.0 - Mana: 34.0 Casting Light Heal. Healed 30.0 health on V.Ranger1 V.Summoner1 - Health: 100.0 - Mana: 39.0 Attacking with Staff. 12.0 damage dealt to M.Ranger1 Demon - Health: 100.0 - Mana: 0.0 Attacking with Claw. 28.0 damage dealt to V.Ranger1 Demon - Health: 100.0 - Mana: 0.0 Attacking with Claw. 21.0 damage dealt to M.Ranger1 Now, let's walk through key sections of the example code. Character Creation In the updated example, characters of differing roles are created using a CharacterFactory. The following is a helper method to create a base/classless character. Note the parts that are added. They provide an empty entity with attributes that all characters should have such as name, health, mana, stat restoration, alliance (Monster or Villager), and mentality (how the AI reacts to certain situations). private static Entity createBaseCharacter(String name, float health, float mana, Alliance alliance, Mentality mentality) { // create a character entity that has parts all characters should have Entity character = new Entity(); character.attach(new DescriptionPart(name)); character.attach(new HealthPart(health)); character.attach(new ManaPart(mana)); character.attach(new RestorePart(0.01f, 0.03f)); character.attach(new AlliancePart(alliance)); character.attach(new MentalityPart(mentality)); return character; } Then, there are methods for creating specific characters such as the flying ranger. The method to create the flying ranger calls createBaseCharacter method to create a base character with 100 health, 0 mana, and an Offensive Mentality that tells it to attack with its weapon and ignore defense or support. We add the equipment part with a bow weapon that does 15-35 damage and the flying part to make a base character a flying ranger. Note that weapons with an AttackRange of FAR can hit flying entities. public static Entity createFlyingRanger(String name, Alliance alliance) { Entity ranger = createBaseCharacter(name, 100, 0, alliance, Mentality.OFFENSIVE); Weapon bow = new Weapon("Bow", 15, 35, AttackRange.FAR); ranger.attach(new EquipmentPart(bow)); ranger.attach(new FlyingPart()); return ranger; } As you can see, it is relatively easy to create numerous character roles or change existing character roles through reuse of parts. Take a look at the other CharacterFactory methods to see how other RPG classes are created. Entity Management The EntityManager is a centralized class for entity retrieval, addition, and removal from the game world. In the example, the EntityManager keeps track of the characters battling eachother. The list of characters is encapsulated in the Entity Manager to prevent it from being accidentally altered or replaced. The game loop uses the entity manager to retrieve all the entities and update them. Then, update is called on the entityManager so that it updates its entity list according to recently created or removed entities. Main.java: for (Entity entity : entityManager.getAll()) { entity.update(delta); } entityManager.update(); Events To create a summoner with a summon spell, we need to find a way to notify the EntityManager that a new entity has been summoned so the EntityManager can add it to the battle. This can be accomplished with events. The EventManager is passed to the summon spell's use method and it calls the notify method on the EventManager to notify the EntitySystem to add the summoned Entity. In the entity manager's constructor, it called a method to listen for the EntityCreate event. The classes that make up the event are the EntityCreateEvent and the EntityCreateListener. I didn't create the original event manager class so I can't take credit for it. See Event Manager for the original implementation and details on creating event listener and event classes. Note: The C++ version of the EventManager works differently using function bindings instead of event listeners. The comments in the file 'EventManager.h' will show you how to use it. Summon spell: public class SummonSpell extends Spell { private Entity summon; public SummonSpell(String name, float cost, Entity summon) { super(name, cost); this.summon = summon; } public void use(EventManager eventManager) { HealthPart healthPart = summon.get(HealthPart.class); healthPart.setHealth(healthPart.getMaxHealth()); eventManager.notify(new EntityCreatedEvent(summon)); System.out.println("\tCasting " + name); } } Event for entity create: public class EntityCreateEvent implements Event { private Entity entity; public EntityCreateEvent(Entity entity) { this.entity = entity; } @Override public void notify(EntityCreateListener listener) { listener.create(entity); } } EventListener for entity created: public interface EntityCreateListener { public void create(final Entity entity); } Stat Restoration In the example, characters regenerate health each timestep. The RestorePart increases the health and mana of its parent Entity every time its update method is called. It interacts with the HealthPart and ManaPart and updates their state. public class RestorePart extends Part { private float healthRestoreRate; private float manaRestoreRate; public RestorePart(float healthRestoreRate, float manaRestoreRate) { this.healthRestoreRate = healthRestoreRate; this.manaRestoreRate = manaRestoreRate; } @Override public void update(float dt) { HealthPart healthPart = getEntity().get(HealthPart.class); float newHealth = calculateRestoredValue(healthPart.getMaxHealth(), healthPart.getHealth(), healthRestoreRate * dt); healthPart.setHealth(newHealth); ManaPart manaPart = getEntity().get(ManaPart.class); float newMana = calculateRestoredValue(manaPart.getMaxMana(), manaPart.getMana(), manaRestoreRate * dt); manaPart.setMana(newMana); } private float calculateRestoredValue(float maxValue, float currentValue, float restoreRate) { float manaRestoreAmount = maxValue * restoreRate; float maxManaRestoreAmount = Math.min(maxValue - currentValue, manaRestoreAmount); float newMana = currentValue + maxManaRestoreAmount; return newMana; } } Battle System The BattleSystem is where high-level interactions between entities are implemented, e.g. targeting and intelligence. It also contains rules of the game such as when an entity is considered dead. In the future, we might want to create an AI System to handle targeting and just have the Battle System control the rules of the game. But, for a simple example it's fine as it is. In the following code snippet of the BattleSystem, note that it is using each character's Mentality part to specify how it will act in the current turn. The BattleSystem also resolves the issue from the last example of providing potential targets for attacking and supporting. public void act(Entity actingCharacter, List characters) { Mentality mentality = actingCharacter.get(MentalityPart.class).getMentality(); if (mentality == Mentality.OFFENSIVE) { attemptAttack(actingCharacter, characters); } else if (mentality == Mentality.SUPPORT) { boolean healed = attemptHeal(actingCharacter, characters); if (!healed) { attemptAttack(actingCharacter, characters); } } else if (mentality == Mentality.SUMMON) { boolean summoned = attemptSummon(actingCharacter); if (!summoned) { attemptAttack(actingCharacter, characters); } } } In addition to managing AI, it contains the game rules, such as when a character should be removed from the game using the helper method isAlive and the EntityManager remove method. for (Entity character : characters) { if (!isAlive(character)) { entityManager.remove(character); System.out.println(character.get(DescriptionPart.class).getName() + " is dead!"); } Conclusion Handling interactions between entities can become complex in large-scale games. I hope this article was helpful in providing several approaches to interactions by using high-level classes, adding logic to parts, and using events. The first and second articles addressed the core of creating and using entities and parts. If you want, take a closer look at of the example code or change it to get a feel of how it manages interactions between entities. Though the CharacterFactory is fine for small and medium-sized games, it doesn't scale well for large games where potentially thousands of game object types exist. The next article, Serialization, will describe several approaches to mass creation of game object types using data files and serialization. Article Update Log 16 April 2014: Initial release
  6. dave19067

    Entities-Parts I: Game Objects

    SpiderJack - Thanks SpiderJack!  Like I said, I don't have a fixed timeframe for the next article because I'm carefully planning out what I want to talk about next.  I did make a lot of progress on the example code today so things are moving along smoothly.  I'm shooting to complete it by the end of the month.
  7. dave19067

    Entities-Parts I: Game Objects

    Sanka - Glad you like the article .  I can't say for sure when it will be out because I'm busy with work and school, but I hope by the end of this month.  If not, I'll add more content to the example.
  8. dave19067

    Entities-Parts I: Game Objects

    Good feedback...   This is a part of a series of articles.  This first article describes the implementation of the entity and component class I use which I tried to make as simple and flexible as possible, and serves as a practical introduction to anyone who is new to the entity-component pattern.   I also moved the sidenote about the more datadriven ECS approach in the "Example" section.   Josh -  I fleshed out the conclusion some more and made it more clear this is a part of an article series.  I agree the example is very basic, but I think it describes the base functionality of the entity and part class.  I will elaborate on the example in the next article when I talk about entity management and interaction, which would be too much to put in this article.   Buckeye - You're right.  The comparison shouldn't be in the "Explaining the Concept" section and should be more about the entity-component pattern.  I created a new section called "The Problem" which talks about traditional approaches to gameobject creation and renamed some things so it wouldn't seem like an attack on OO which I didn't intend it to be.  I added some things in "Explaining the Concept" that detail the entity-component pattern specifically.
  9. dave19067

    Entities-Parts I: Game Objects

    Thanks for the feedback guys,   I have added the C++ version in the 'article resource'.  I took the C++ code from one of my games and modified it to remove any game specific code and uses of the boost library.  Let me know if there are any bugs with it since I haven't had a chance to test it.   Madhed - Yes, the entities own and manage components.  I'll add a couple sentences about the relational database inspired model in the Usage Notes.  I'm not completely sure what the alternate implementation is, but I assume that it's getting entities and parts by integer IDs instead of pointers.
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!