Storing and Loading External Level Data and Information in XML


Have you ever accidentally hardcoded information into your program, to later realize it would be easier to not have to compile for small changes?

Storing information in XML allows you an easy way to change parts of your program, without needing to re-compile.

You can store entire levels, character definitions, and directions to artwork from xml.

This enables you to create a fully functioning game or application, and make it easy for yourself or other people to change the content of your program.

This tutorial just shows you how to create an xml file, put information in it, load it using actionscript, and run the program based off the content of the xml.

We will be creating a small “side scrolling” game. It is by no means complete, but should show how you can store game information in XML.

Creating your xml file

The first thing you need to do is create an xml file. If you don’t have a current FlashDevelop Project you are working in, you’ll need to create one.

Next, right click on your ‘src’ folder, and go to ‘Add->New XML File’ and name it ‘data.xml’ and click ‘OK'(you can also create a new text file in explorer, and rename it to .xml).

FlashDevelop sets up a basic xml file:

Your ‘root node’ has already been created, named ‘data’. You’ll notice that the syntax in xml is very simmilar to html, except that you can declare whatever tags and attributes you want. XML is just a way of organizing your data.

Now we’ll create a couple “basic levels”, and define where some of our art work is located.


	
		
						
				
					space background
					800
					600
					0x000000
					false
				
				
					Star Outline
					610
					0xffb400
				
				
				
					Star
					600
					0xFF0000
				
				
					tiny star1
					2
					0xFFFFFF
				
				
					tiny star1
					2
					0xFFFFFF
				
				
				
					Moon1
					100
					0x3388CC
				
				
					Moon2
					100
					0x3388CC
				
				
				
					ground
					800
					50
					0x006666
					true
				
				
					floating block
					50
					50
					0x006666
					true
				
			
			
			
		
	
	
		
			lilguy.png
			100
			10
		
	

Here, I just tried to decide on things from my “game” that I would want to store in xml.
I made an xml tag for “shapes” to go under, and then defined circles and rectangles, based on where I’d want to place them.

These make up an outerspace solar system – a giant red star, a few blue moons close by, and a few stars in the background. I also added two blocks to be used as interactive blocks to stand on.

I also added a “starting position” for out hero to start in our level.

I also created a tag for entities- so we can put enemies, allies, ect in here, but for now I just put the hero player, with a path/ filename to an image I created, and a tag for the health and speed of the character.
To load images externally, they must be in your ‘bin’ folder(or sub-folder) for loading using the ‘Loader’ class, or in your ‘bin’ folder if using an embed tag.

You’ll notice that for some tags I either created a sub tag, or an attribute. It’s really up to your discretion – I could have put filename and speed as attributes on the entity tag, like I did with the name, or I could have made the name attribute a sub-tag/ node.
The only difference is just how you refer to them in your actionscript, as you’ll see in the next part.

Creating Actionscript for loading

Now we need to setup our actionscript to load in the xml, load in any images defined in the xml, and create and setup the level.


package 
{
	import flash.display.Bitmap;
	import flash.display.Loader;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.geom.Rectangle;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.ui.Keyboard;
	
	/**
	 * ...
	 * @author Chris
	 */
	public class Main extends Sprite 
	{
		public var data:XML;
		
		public var images:Array;
		public var blocks:Array;
		public var loaded_images:int;
		public var hero:Bitmap;
		public var hero_speed:Number;
		
		public var level_loaded:Boolean;
		
		public function Main():void 
		{
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		private function init(e:Event = null):void 
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);
			// entry point
			
			images = new Array();
			blocks = new Array();
			loaded_images = 0;
			level_loaded = false;
			hero_speed = 1;
			
			var loader:URLLoader = new URLLoader(new URLRequest("data.xml"));
			loader.addEventListener(Event.COMPLETE, DataLoaded);
			addEventListener(Event.ENTER_FRAME, RunGame);
			
			stage.addEventListener(KeyboardEvent.KEY_DOWN, MoveHero);
			//loader.load();
		}
		public function DataLoaded(e:Event):void
		{
			data = new XML(e.target.data);
			trace("loaded data");
			
			LoadImages();
			//CreateLevel(0);
		}
		public function LoadImages():void
		{
			//check if all images loaded, and create level if it is
			if (loaded_images == data.entities.children().length())
			{
				trace("loading complete, images.length = " + images.length);
				CreateLevel(0);
				return;
			}
			
			var loader:Loader = new Loader()
			loader.load(new URLRequest(data.entities.children()[loaded_images].filename));
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, StoreImage);
		}
		public function StoreImage(e:Event):void
		{
			var bitmap:Bitmap = Bitmap(e.target.content);
			images.push( { name:data.entities.children()[loaded_images].@name, image:bitmap.bitmapData } );
			loaded_images++;
			LoadImages();
		}
		public function CreateLevel(level:int):void
		{
			this.graphics.clear();
			
			for (var i:String in data.levels.children()[level].shapes.children())
			{
				var shape:XML = data.levels.children()[level].shapes.children()[i];

				if (shape.@type == "circle")
				{
					this.graphics.beginFill(shape.color);
					this.graphics.drawCircle(shape.@x, shape.@y, shape.radius);
					this.graphics.endFill();
				}
				else if (shape.@type == "rectangle")
				{
					this.graphics.beginFill(shape.color);
					this.graphics.drawRect(shape.@x, shape.@y, shape.width, shape.height);
					this.graphics.endFill();
					if (shape.is_ground == "true")
						blocks.push(new Rectangle(shape.@x, shape.@y, shape.width, shape.height));
				}
			}
			
			for (var j:String in data.entities.children())
			{
				var entity:XML = data.entities.children()[j];
				if (entity.@name == "bobby")
				{
					hero_speed = Number(entity.speed);
					for (var k:String in images)
						if (images[k].name == "bobby")
						{
							hero = new Bitmap(images[k].image);
							this.addChild(hero);
							
						}
				}
			}
			level_loaded = true;
			hero.x = data.levels.children()[level].starting_point.@x;
			hero.y = data.levels.children()[level].starting_point.@y;
		}
		public function RunGame(e:Event):void
		{
			if (!level_loaded)
				return;
				
			var hero_on_ground:Boolean = false;
			for (var i:String in blocks)
			{
				if (hero.y + hero.height > blocks[i].y && hero.x > blocks[i].x && hero.x + hero.width < blocks[i].x + blocks[i].width)
				{
					hero.y = blocks[i].y - hero.height+1;
					hero_on_ground = true;
				}
			}
			
			if (!hero_on_ground)
				hero.y+=10;
		}
		public function MoveHero(e:KeyboardEvent):void
		{
			if (hero == null)
				return;
			if (e.keyCode == Keyboard.LEFT)
				hero.x -= hero_speed;
			else if(e.keyCode == Keyboard.RIGHT)
				hero.x += hero_speed;
				
			if (hero.y < 0)
				hero.y = 0;
			if (hero.x + hero.width > 800)
				hero.x = 800 - hero.width;
			
		}
		
	}
	
}

Line 46-47: The first thing we have to do is setup our ‘loader’ class in the ‘init’ function, to begin loading our xml file, and run the function to then begin loading any images from the xml file.

Line 53: Once the xml is loaded, we call the ‘DataLoaded’ function to convert the loaded xml into our xml object, and call the ‘LoadImages’ function to attempt to load the first (if any) images from xml.

Line 61-74: In the ‘LoadImages’ we first check if their are any images left to load, and if not, we go to loading the rest of the images, and adding them to our ‘images’ array.

Line 82: If we’re finished loading images, then we go to the function ‘CreateLevel’, and load the first defined level.

Line 86: We add graphics for each of our shapes, and add any rectangles that have ‘is_ground’ attribute as true to our ‘blocks’ array, which we can use as a basic “ground”.

Next we search for and create the hero, if he exists.

Line 121: At that point, the “level_loaded” variable is set to true, and the ‘RunGame’ function can now start updating.

Line 125-142: All this function does is move the hero to the ground, and stop him if the bottom left collides with a ‘block’ object.

Line 143: Lastly, I added function to move the hero back and forth, if he exists.

Lastly, I added function to move the hero back and forth, if he exists.

Conclusion

Now you should know how to load level, character, and other data from an xml file, as well as loading an image externally.

Where to go from here?

This is far from a bug-free complete game, and was created to mainly show how to load information from xml.

You can use these techniques in your own applications or games, or you can use this as a building block for a simple side-scroller. Creating proper collision detection for the hero and the blocks, making him jump, ect, shouldn’t take too long from this example.

Download the source Code and please leave me any comments or feedback you have.

Bookmark the permalink.

7 Comments

  1. Hey Chris, try to replace your code plz. The html entities > & are printed as a text literally, near the RunGame function.

    • Whoops, darn wordpress!

      I pasted it in again, and it seems to have fixed it. Will have to try to figure out a reliable method of pasting in code- &lt ;(<) and &gt ;(>) are automatically replaced sometimes too, but only happens sometimes, which is strange.

      Thanks for letting me know!

      • Yeah, darn wordpress !
        Now we can’t see the xml tags and the numbers of the lines on the code too !

        • Oy, I don’t know why it does that…. but hopefully it’s all good now – unless it changed back the greater than and equal signs! Thanks for seeing that!

          Let me know if you want any tutorials on something specific, and I’ll work on getting one up-

  2. Hi Chris,
    If there’s anyway you could explain the RunGame
    section of the actionscript code I’d really appreciate
    it.I just can’t get my head around what causes lilguy
    to jump over the block.When I mess with the code it
    seems to do the opposite of what I expect!. I’m very new
    to this stuff but I keep at it, so I want to understand
    things and find out how they function.
    Thank You,
    Mark Hubrouck

    • Hi Mark,

      the reason why the lil guy “jumps” over the box is bad collision detection 😉
      Mainly this line:
      if (hero.y + hero.height > blocks[i].y && hero.x > blocks[i].x && hero.x + hero.width < blocks[i].x + blocks[i].width) which says: "if(bottom of hero > top of block && the left side of the hero is greater then the left side of the block and hero’s right side is less than the block’s width)”

      and this:
      if (!hero_on_ground)
      hero.y+=10;

      so basically when he goes under the block in the air, the bottom of him is below the top of that block, so it assumes he is on the ground and sets him on top of that (so he “jumps” up on there when under it)

      A better way might be to use something called “the separating axis theorem” to see if two boxes intersect, Check all four corners of his box, pretend box boxes are circles and check when the distance between them is less than the sum of their radii from both centers, use flash’s built in collision detection(for two movie clips; movie_clip.hitTest(mc2), make the whole world tiled and just find out when he is on a collidable tile, or even box2d.

      I’ve mainly just used the simplified version of the separating axis theorem for my stuff

      • Hi Chris,
        Thank you for your speedy and helpful reply.
        And thanks for sharing your knowledge with people
        like me who have much to learn.

        Mark Hubrouck

Leave a Reply

Your email address will not be published. Required fields are marked *