About

I'm developing my first game, Age of Goblins. I develop this part time, and work at a "real" (read: paying) job full time. Age of Goblins is a three dimensional goblin empire building game. Inspired by Dwarf Fortress and Minecraft. Age of Goblins gives a player control over a small band of goblins in a cube-based sandbox world. The player can instruct the goblins to add or remove different types of cubes, build various structures, make elaborate traps, and craft a multitude of items.

Tuesday, February 5, 2013

Data Files: Entities

Following up on my last post about the data files used for materials. This post will tell you a bit about using data files for defining entities.

First off, it's important to know that Age of Goblins uses and entity/component framework for all the entities in game, similar to the one described here. The data files take advantage of that when defining entities. Basically, the data files for entities tells the entity component system which components to add and what values to give them.



Note that I'll switch between components and attributes, for the purpose of this post, these terms are interchangeable. My entity/component/systems framework is a entity/attribute/behavior framework.

Now, like everything in AoG right now, it's all under construction, so things are likely to change, but we can get a snapshot of what things are like right now. This will give you an idea of how things are defined. Let's look at the spike spitter definition for an example, I'll post the whole thing here, then talk about each section below. A lot of it should be somewhat self explanatory once you get the idea of what's going on:


ENTITY:"SpikeSpitter"
{
    Description="A semi-sentient plant that defends its self by shooting spiked barbs or swatting with its spiked mouth pod"
    CommonName="Spike spitter"
    PluralCommonName="Spike spitters"
    EntityTags=Plant
    ColonyTags=Selectable
    baseAttributes="Common"
 
    [Information] {
    }
 
    [AnimationState]{
        defaultmeshname="SpikeSpitter"
    }
 
    [Model]{
        MeshName="SpikeSpitter"
        TextureName="spikespitterTexture.png"
        distantdrawtype="NoRender"
        selectionType="DynamicBone"
    }
    [LifeRequirement]{
    }
 
    [Inventory]{
        MaxItems=20
        Allow="Spiked Barb"
        Disallow=ALL
    }
 
    [ZoneActivator] {
        Zones{
            ActivatorReactionZone{
                Zone{
                    Shape=Sphere
                    Radius=3.0:4.0
                }
                ActivationTypes=Foe
                ZoneReactions{
                    CombatZoneReaction{
                    }
                }
            }
        }
    }
 
    [ProduceHarvest] {  
        ProduceItems{
            ItemProduced{
                ItemName="Spiked Barb"
                TimeToProduce=12000:19000
                TimeToCollect=2000:5000
                WeightedChance=10
                ProductionActivity=Dawn,Day,Dusk
                ProductionResult=StoreStopWhenFull
                MinConditionToProduce=0.3
                MaximumPerCentadak=30
                Seasons=Sprungston,Smeltar
            }
            ItemProduced{
                ItemName="Spike plant seed"
                TimeToProduce=30000:120000
                TimeToCollect=200:500
                WeightedChance=2
                ProductionActivity=ALL
                ProductionResult=LaunchIntoWind
                MinConditionToProduce=0.0
                MaximumPerCentadak=50
                Seasons=Sprungston
            }
        }
    }
}
All of that creates this, a spike spitter soaking up some sun:




At the first level, we tell the parser we're reading an "ENTITY" and it's called  "SpikeSpitter". Then we have some common information that applies to all spike spitters. This information will be stored in the the Lexicon and allow us to reference this entity by its name. The script also tells us some display strings for the entity and some tags. "Selectable" means that this entity will be selectable with the mouse, so we can click on it for more information in game. Then things get interesting. "baseAttributes" is like an "include" statement in C++. It references a defined set of common attributes that I didn't feel like writing over and over again for each entity. CommonAttributeGroups can be defined for any combination of attributes. The `Common` group referenced in SpikeSpitter is not very interesting:


COMMONATTRIBUTEGROUP:"Common" {
    [Position]{
    }
}
At the moment, it just contains a position attribute. The neat thing about CommonAttributeGroups is that they can actually be used even when you have that attribute defined again. For example, I could define a CommonAttributeGroup that had an Inventory attribute defined like so:

COMMONATTRIBUTEGROUP:"Common" {
     [Inventory]{
            MaxItems=10
            Allow=ALL
            Disallow=ALL
        }
}
 Then, I could, later in the same entity, define the Inventory attribute again like so:

    [Inventory]{
            MaxItems=15
     }

It would be a valid definition and the final result would be a merged definition of the two. Basically, you can create CommonAttributeGroups and just override the values you want. Pretty neat.

If you remember, in the materials post, there was some materials that had some EntityBaseAttributes defined. These are it. For example, some of the materials used the "Slow Decay" common group. That's just a [TimeEffect] attribute that will be applied to the material entity when it's created.
COMMONATTRIBUTEGROUP:"Slow Decay" {
    [TimeEffect] {
    timeBetweenTicks=1000:1500
    startingTick=0:0
    maxTicks=1:1
    numberOfTicksPerIncrement=1:1
    effectType=Decay
    }
}
The TimeEffect attribute is one that changes the entity over time. In this case, when the timer reaches the end, the item will decay from the world. This means that organic materials won't last forever, goblins will need to collect the materials before they go bad. Some minerals have Very Slow Decay rates. So materials like soil will eventually decay away when they're in item form. This is like a little clump of dirt being washed/blown away over time.

Continuing on with the spike spitter definition, all the values like [Name] are naming an attribute. The entity component system will see those and know that when it creates an instance of this type of entity, it'll need to add an attribute of that type. Some of the attributes are just empty brackets. That's because they're either not implemented yet, or they don't require anything to be defined in the scripts. Like the Position attribute is empty because the position is defined at run time when the entity is created, not in the script beforehand.

The "ProduceHarvest" attribute is similar to the "HarvestItems" value found in the materials, except this one is actively producing materials. As you can tell from the description, the spike spitter is actively producing new barbs to spit and it's producing seeds to spawn. You can see that it won't have an unlimited number of spikes to spit out at passing goblins. It can only hold 20 spikes and takes time to produce new ones.

The "ZoneActivator" attribute is brand new. It defines the area in which this entity will "watch". The trigger is activated by any "Foe" and will result in the entity activating a "CombatReaction". As you can see below, with debug visuals turned on, the goblin has entered the spike spitters detection area.




As you might have noticed, most of the numerical values are defined as ranges. That's because this script isn't defining just one spike spitter, it's defining them all. When an entity instance is generated, it'll choose a random value between the defined values for that entities value. I wrote an answer on GDSE about this a while back that might give some more details too.

The entity component framework is what really gives this scripting its power. Being able to easily tack on one attribute or another is not only useful in game, it makes creating a script that does that considerably easier.

2 comments:

  1. Very informative. I like reading how others utilize an entity / component system. I also appreciate your helpful posts on gamedev.stackexchange.

    Though, I am curious how you create the scripts. Do you type them up manually for each new type of entity, or do you use some kind of custom entity-creation tool that generates the scripts for you? (Sorry if this question is already answered somewhere else on your blog. I haven't read through all your previous posts yet)

    ReplyDelete
    Replies
    1. Thanks. At the moment, the scripts are manually generated. I would like to create a drag-and-drop tool for defining entities in the future, but for now it's easy enough to copy, paste, and modify for creating a new entity. Additionally, you'll see in one of my other posts I can "include" common component sets to make scripting faster. Good question. Thanks for your interest.

      Delete