Fall Fury: Part 5 - Creating Levels

Creating Levels

One of the goals set when developing FallFury was making the game extensible in regards to playable levels. Furthermore, I thought that it would make sense from the development and testing perspective to have levels as a completely separate entity that can be modified as necessary. For example, when a new power-up was introduced, I wanted to add an extra line in a level file and test it out. This was ultimately achieved by creating an XML-based level engine and in this article I will describe the level structure and design process.

Check out the video for this article at http://channel9.msdn.com/Series/FallFury/Part-5-Creating-Levels.  For a complete, offline version of this series, you may download a nicely formatted PDF of all the articles.

The First Steps

When I started working on the level engine concept, I began designing the potential XML file structure for the level and ended up with the following requirements:

  • Level Type Identifier – As different levels have different backgrounds and sound themes, there should be a way to mark a level type. There are currently four level types: dream, nightmare, space, and magic bean.
  • The Starting Character Descriptor – When the game starts, the teddy bear has some initial, basic properties, such as maximum health level, horizontal position, and velocity.
  • A Collection of Obstacles – For each level, obstacles are positioned differently, and it’s important to specify that. For some obstacles it might be desirable to disable the damage infliction component, while for others it might be good to maximize the damage caused by colliding with them. Also, there are multiple textures associated with different obstacle types, so I wanted to specify the obstacles to render regardless of the selected level type.
  • A Collection of Monsters – Obstacles are not the only component that can damage the bear during gameplay. There are also monsters that can pop up and shoot at the main character. Similar to the bear, monsters represent a living entity and have some specific properties, such as the initial health, damage, starting position, velocity, and type.
  • Buttons – These are the bonus point boosters in FallFury. The player collects as many of those as possible, and each of them should be individually positioned to form either a trail or a shape.
  • Power-Ups – With the basic set of abilities, the bear is able to get some bonuses such as a cape that will speed-up his descent or a bubble that will protect him from incoming shells.

The first build of the level engine integrated into FallFuryused percentage-based relative values to position elements on the screen. Although this seemed like a good idea at the time, it became problematic because

  • It required the level to be a fixed size, which restricted element addition and level extension.
  • It caused problems with obstacles that needed to be scaled and therefore had a non-standard size.
  • Small modifications were harder to make because minimal adjustments would throw off the relative position.

So, I switched to a pixel-based conditioning in which each position is relative to zero. With this in place, levels can be infinitely long (within the context of the machine’s rendering and memory capabilities)and extra elements can be more seamlessly added.

Additionally, levels need to be packaged together in individual sets, normally grouped by themes, without restriction. This is achieved with the help of an extra XML file,called core.xml, which keeps track of level tiers, and acts as a container that allows the developer to name and easily enable or disable specific levels .

The structure for the core.xml file looks like this:

<tiers>
  <tier name="GO, GO, TEDDY">
    <level name="mind travels" file="GoGoTeddy\mind.xml"></level>
    <level name="falling in" file="Nightmare\full_pilot.xml"></level>
    <level name="frontlines" file="Nightmare\the_beginning.xml"></level>
    <level name="the chase" file="Nightmare\chasing_monsters.xml"></level>
  </tier>
  <tier name="SECRET GARDEN">
    <level name="bean stalking" file="Garden\bean_stalking.xml"/>
    <level name="thorn apart" file="Garden\thorn_apart.xml"/>
  </tier>
  <!--<tier name="Obstacle ***TEST***">
    <level name="Nightmare 0" file="test\Obstacle\nightmare\0.xml" />
    <level name="Nightmare 1" file="test\Obstacle\nightmare\1.xml" />
    <level name="Bean 0" file="test\Obstacle\bean\0.xml" />
    <level name="Bean 1" file="test\Obstacle\bean\1.xml" />
    <level name="Dream 0" file="test\Obstacle\dream\0.xml" />
    <level name="Dream 1" file="test\Obstacle\dream\1.xml" />
  </tier>-->
  <!--<tier name="Death ***TEST***">
    <level name="Monster 0" file="test\death\0.xml" />
  </tier>-->
  <!--<tier name="Monster ***TEST***">
    <level name="0" file="test\Monsters\0.xml" />
    <level name="1" file="test\Monsters\1.xml" />
    <level name="2" file="test\Monsters\2.xml" />
    <level name="3" file="test\Monsters\3.xml" />
    <level name="4" file="test\Monsters\4.xml" />
    <level name="5" file="test\Monsters\5.xml" />
    <level name="6" file="test\Monsters\6.xml" />
    <level name="7" file="test\Monsters\7.xml" />
    <level name="8" file="test\Monsters\8.xml" />
    <level name="9" file="test\Monsters\9.xml" />
    <level name="10" file="test\Monsters\10.xml" />
  </tier>-->
  <!--<tier name="MEDALS *****TEST******">
    <level name="gold" file="test\Medals\gold.xml"></level>
    <level name="silver" file="test\Medals\silver.xml"></level>
    <level name="bronze" file="test\Medals\bronze.xml"></level>
  </tier>
  <tier name="Buttons ***TEST***">
    <level name="1" file="test\Buttons\single.xml" />
    <level name="Lots" file="test\Buttons\multiple.xml" />
  </tier>
  <tier name="Obstacles ***TEST***">
    <level name="Cape" file="test\PowerUps\0.xml" />
  </tier>-->
</tiers>

Tiers that are commented out are ignored and the included levels aren’t on the game list. Also, the paths indicated for each file attribute—for each individual tier—are relative to the game folder itself. There is no limit on the number of subfolders that can be included in the path. The above structure will render this level set:

clip_image002

The Level XML

Let’s now take a look at the layout of the level descriptor XML file:

<?xml version="1.0" encoding="utf-8" ?>
<level type="0">
  <meta score="0" buttonPrice="10"></meta>
  <bear maxHealth="100" startPosition="300" velocity="8.0" damage="11" criticalDamage="20" defaultAmmo="100" />
  <obstacles>
    <obstacle type="1" x="119" y="2300" inflictsDamage="true" healthDamage="5" rotation="3.14" scale="1" />
    <obstacle type="3" x="534.5" y="3000" inflictsDamage="true" healthDamage="5" rotation="0" scale="1" />
    <obstacle type="3" x="534.5" y="3300" inflictsDamage="true" healthDamage="5" rotation="0" scale="1" />
    <obstacle type="1" x="119" y="4200" inflictsDamage="true" healthDamage="5" rotation="3.14" scale="1" />
    <obstacle type="2" x="546" y="5000" inflictsDamage="true" healthDamage="5" rotation="0" scale="1" />
    <obstacle type="1" x="119" y="5800" inflictsDamage="true" healthDamage="5" rotation="3.14" scale="1" />
    <obstacle type="2" x="546" y="6600" inflictsDamage="true" healthDamage="5" rotation="0" scale="1" />
  </obstacles>
  <monsters>
    <monster lifetime="3000" scale=".2" velocityX="2" velocityY="2" type="0" x="460" y="19900" maxHealth="80" bonus="100" lives="0" damage="10" criticalDamage="8" defaultAmmo="50" />
    <monster lifetime="3000" scale=".2" velocityX="2" velocityY="2" type="1" x="460" y="25000" maxHealth="80" bonus="100" lives="0" damage="10" criticalDamage="8" defaultAmmo="50" />
    <monster lifetime="3000" scale=".2" velocityX="2" velocityY="2" type="2" x="460" y="34500" maxHealth="80" bonus="100" lives="0" damage="10" criticalDamage="8" defaultAmmo="50" />
    <monster lifetime="6000" scale=".4" velocityX="2" velocityY="2" type="3" x="460" y="41400" maxHealth="180" bonus="100" lives="0" damage="17" criticalDamage="8" defaultAmmo="50" />
  </monsters>
  <buttons>
    <button x="300" y="800" />
    <button x="360" y="800" />
    <button x="300" y="860" />
    <button x="360" y="860" />
    <button x="300" y="920" />
    <button x="360" y="920" />
    <button x="300" y="980" />
    <button x="360" y="980" />
  </buttons>
  <powerups>
    <powerup category="1" type="4" x="140" y="9200" effect="3" lifespan="4"></powerup>
    <powerup category="1" type="3" x="480" y="19800" effect="10" lifespan="6"></powerup>
    <powerup category="1" type="0" x="480" y="27500" effect="10" lifespan="6"></powerup>
    <powerup category="1" type="1" x="100" y="34000" effect="10" lifespan="6"></powerup>
    <powerup category="1" type="0" x="100" y="41200" effect="10" lifespan="6"></powerup>
  </powerups>
</level>

The opening level tag carries a type attribute. This is level theme flag. It can be set to one of the three four values:

  • 0 – The Nightmare Theme
  • 1 – The Magic Bean Theme
  • 2 – The Dream Theme
  • 3 – The Space Theme

You can see the design differences in the images below:

clip_image004

clip_image006

Nightmare

Magic Bean

 

clip_image008

clip_image010

Dream

Space

Remember, that obstacles and the level theme itself do not influence much other than the background and soundboard.

Once the type is specified, the meta tag brings up the score and buttonPrice attributes. If you for some reason want to create a level including an initial score, you can specify it here. And because buttons are fixed bonus assets that are all created equal, each of them carries a given bonus point weight. The score based on the collected buttons is calculated at the end of the game and relies on the value specified in the meta tag.

Obstacles

Next comes the obstacle collection, which is represented by the obstacles tag. This tag is required even if there are no obstacles on a given level. Simply use <obstacles /> as necessary. Each child node represents an instance of an obstacle that can be choosen from the following enum:

enum class ObstacleType
{
    OT_CLOUD = 0,
    OT_SPIKE_NIGHTMARE_LARGE = 1,
    OT_SPIKE_NIGHTMARE_MEDIUM = 2,
    OT_SPIKE_NIGHTMARE_SMALL = 3,
    OT_BEAN_A = 4,
    OT_BEAN_B = 5,
    OT_BEAN_C = 6,
    OT_BEAN_D = 7,
    OT_BEAN_E = 8,
    OT_SPACE_ROCKET = 9,
    OT_SPACE_COMET_A = 10,
    OT_SPACE_COMET_B = 11,
    OT_SPACE_SATELLITE = 12,
    OT_SPACE_UFO = 13,
    OT_SPACE_BALL = 14
};

Here is the complete table showing the appearance of each of them:

OT_CLOUD

clip_image012

  

OT_SPIKE_NIGHTMARE_LARGE

clip_image014

  

OT_SPIKE_NIGHTMARE_MEDIUM

clip_image016

  

OT_SPIKE_NIGHTMARE_SMALL

clip_image018

  

OT_BEAN_A

clip_image020

  

OT_BEAN_B

clip_image022

  

OT_BEAN_C

clip_image024

  

OT_BEAN_D

clip_image026

  

OT_BEAN_E

clip_image028

  

OT_SPACE_ROCKET

clip_image030

  

OT_SPACE_COMET_A

clip_image032

  

OT_SPACE_COMET_B

clip_image034

  

OT_SPACE_SATELLITE

clip_image036

  

OT_SPACE_UFO

clip_image038

  

OT_SPACE_BALL

clip_image040

No matter how the obstacles are positioned, they will either be located in the visible area or displaced outside the viewport and not displayed. The ultimate position is taken from the obstacle size and is relative to the center of the texture. For example, if an obstacle texture is 400- pixels wide, the X-relative position should be set to 200. If the position differs from the starting one, however, the obstacle texture is cut to include the area that fits in the 768 pixel wide playable zone visible. There are no restrictions regarding the Y position.

To help in level creation, some obstacles have pre-defined left and right margins. For example:

  • OT_BEAN_A: 269.5 (Left), 498.5 (right, with 3.14 rotation)
  • OT_BEAN_B: 128.5 (left), 638.5 (right, with 3.14 rotation)
  • OT_BEAN_C: 172 (left), 596 (right, with 3.14 rotation)
  • OT_BEAN_D: 188.5 (left), 579.5 (right, with 3.14 rotation)
  • OT_BEAN_E: 206.5 (left), 561.5 (right, with 3.14 rotation)

The inflictsDamage attribute determines whether the obstacle harms the main character. If it is set to false, the character will still make the sound of colliding with it but will not lose any health points. The primary use for this attribute is level testing.If it is set to true, the character will loose the amount of health points indicated by the healthDamage attribute.

The rotation and scale attributes can be used to flip and resize the texture as needed. Rotation is measured in radians, and the scale is a normalized value in which 1.0 represents 100% of the scale.

Monsters

As with obstacles, the monsters node should never be omitted from the file and should at least contain a placeholder: <monsters />. Unlike obstacles, however, monsters are dynamic and do not have fixed positions. Moreover, monsters have limited active time during gameplay. The first attribute,lifetime, determines the length of the fall during which the monster will be visible in the viewport. With the y attribute as the Y-based position at which the monster appears, at the y+lifetime position the monster simply flies away if not killed.

The scale attribute carries the same purpose as the one for the obstacle—normalized texture size relative to the size of the original image file. As such, the level designer does not have to worry about linking the width and height of the monster when resizing and can instead use a percentage-like value to scale the monster up or down, simultaneously modifying both the width and the height with zero stretching.

Nextup are velocityX and velocityY. These two attributes are used to set the motion velocity when the monster is already visible. Instead of being a static shooting entity, the enemy moves on a randomized zig-zag path at the bottom of the screen. The horizontal and vertical displacement—in pixels, per update cycle—is individually set through the values carried by the above-mentioned attributes. If necessary, this functionality can be disabled in the code-behind by assigning a fixed value for the vertical and horizontal movement for all monsters that are being loaded on a given level.

The monster type is an integer value that is translated in a value from the following enum (located in MonsterType.h):

enum class MonsterType
{
    MT_NIGHTMARE_A = 0,
    MT_NIGHTMARE_B = 1,
    MT_NIGHTMARE_C = 2,
    MT_MAGICBEAN_A = 3,
    MT_MAGICBEAN_B = 4,
    MT_MAGICBEAN_C = 5,
    MT_CANDYLAND_A = 6,
    MT_CANDYLAND_B = 7,
    MT_CANDYLAND_C = 8,
    MT_CANDYLAND_D = 9,
    MT_CANDYLAND_E = 10
};

Here is a table that shows the texture associated with each identifier:

MT_NIGHTMARE_A

clip_image042

  

MT_NIGHTMARE_B

clip_image044

  

MT_NIGHTMARE_C

clip_image046

  

MT_MAGICBEAN_A

clip_image048

  

MT_MAGICBEAN_B

clip_image050

  

MT_MAGICBEAN_C

clip_image052

  

MT_CANDYLAND_A

clip_image054

  

MT_CANDYLAND_B

clip_image056

  

MT_CANDYLAND_C

clip_image058

  

MT_CANDYLAND_D

clip_image060

  

MT_CANDYLAND_E

clip_image062

  

Each monster has three separate textures associated with it. The three textures are cycled inside the update loop for each monster entity when the monster becomes visible, creating the movement effect:

clip_image063

Each monster currently shoots only one type of ammo: a red plasma ball. Be aware, however, that there is a preprogrammed condition in which the last monster in the XML file collection is automatically considered the final boss. This means the scale, maxHealth and lifetime properties must be manually adjusted to reflect the effect. Without doing so, the last monster will, regardless of the XML setting, switch in-game to a triple fireball shot that inflicts three times the damage indicated by the damage attribute:

clip_image065

Each monster can have limited ammo as set by the defaultAmmo attribute. In the case that the ammo is exhausted before the monster expires, the monster will continue its motion at the bottom of the screen without inflicting direct damage to the main character.

Buttons

These are the least complex entities and only carry an X and a Y position. Given those coordinates, relative to the left margin of the visible area, a button texture is rendered:

clip_image067

Once picked-up, the button counter is increased by one and the meta-score incremented by the value set in the meta tag at the beginning of the level XML as long as the feature is activated in the code-behind.

Power-ups

To make the game more fun, there are bonus elements that can be picked up by the bear in order to enhance its performance or protection. These elements are declared in the <powerups/> collection. First and foremost, it is important to declare whether the power-up is positive or negative. In the current version of FallFury, only positive power-ups are included. Nonetheless, the harness for negative ones is already integrated in the parser. Therefore, the category attribute should be set to 1 if the power-up has a positive effect and 0 for a negative effect. This value will only have an effect over the sound played when the bonus is collected.

The power-up type can be one of the following (enum located in PoweupType.h):

enum class PowerupType
{
    HEALTH = 0,
    HELMET = 1,
    PARACHUTE = 2,
    BUBBLE = 3,
    CAPE = 4,
    AXE = 5,
    BOOMERANG = 6,
    HAMMER = 7,
    KNIFE = 8,
    PLASMA_BALL = 9,
    CIRCLE = 10
};

 

The table below shows the power-up texture appearance. Behaviors are already defined in the game and influenced by only the effect and lifespan (seconds) attributes:

HEALTH

Restores the character health, incremented by the value in effect. The lifespan attribute is ignored.

clip_image069

HELMET

Adds a helmet to the bear, setting the maximum health to the effect value. Active for the duration of lifespan.

clip_image071

PARACHUTE

Slows down the fall of the bear, setting the descent velocity to the effect value. Active for the duration of lifespan.

clip_image073

BUBBLE

Wraps the character in a protective bubble, setting the maximum health to the value of the effect attribute. Active for the duration of lifespan.

clip_image075

CAPE

Accelerates the descent by multiplying the velocity by the value of effect. Active for the duration of lifespan.

clip_image077

AXE

Sets the current character weapon to an axe. The damage is determined by the effect attribute and lifespan is ignored.

clip_image079

BOOMERANG

Sets the current character weapon to a boomerang. The damage is determined by the effect attribute and lifespan is ignored.

clip_image081

HAMMER

Sets the current character weapon to a hammer. The damage is determined by the effect attribute and lifespan is ignored.

clip_image083

KNIFE

Sets the current character weapon to a knife. The damage is determined by the effect attribute and lifespan is ignored.

clip_image085

PLASMA_BALL

Sets the current character weapon to a plasma ball. The damage is determined by the effect attribute and lifespan is ignored.

clip_image087

CIRCLE is a helper power-up that has no effect on the bear and is instead used as an additional texture overlay along with any other power-up in order to create a pulsating circle effect.

Conclusion

FallFury ships with a dozen of sample levels that showcase all of the elements described in the article. At the moment, a level editor is in the works, but it isn’t too complicated to build XML files manually. To do so, you need to consider the pixel-based locations and ensure that they’re all in the visible area—the game engine will automatically handle all other displacements and adjustments.

Tags:

Follow the Discussion

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.