Chapter 10. XML and External Data

XML and External Data

In this last chapter of the book, we're going to look at how to load and save game data using some of AS3.0's built-in tools. We'll cover these topics:

  • Local shared objects

  • Loading and saving files using FileReference

  • Using XML to store and load game levels

  • Loading files at runtime using URLLoader

  • Flash Player security issues

These are very general techniques that have a wide application in games. You can use them along with any of the other techniques we've covered in this book.

At the end of this chapter, I'll give you a few suggestions on where to go from here to take your study of game design further.

Local shared objects

The simplest way to save game data with AS3.0 is in something called a local shared object. This is an AS3.0 Object, like any other object you've used in your code. The only difference is that a local shared object is stored outside the SWF, in a separate file on the user's hard drive. That means that even after the SWF is closed, the shared object still exists. When the user starts the game again, you can load the object and use its data to resume the game from where it was saved.

Creating and loading shared objects

Creating a local shared object is simple. First, import the SharedObject class from the flash.net package.

import flash.net.SharedObject;

Next, create the shared object with the SharedObject class.

var sharedObject:SharedObject =  SharedObject.getLocal("savedData");

The getLocal method creates a shared object on the user's hard drive called savedData. If savedData has already been created in a previous session, the code loads it into the program and stores its data in the new sharedObject variable.

The sharedObject that was created has a data object property.

sharedObject.data

You can assign any variables or values to the data object that you need. Do this by creating new properties on the data object, and assign values to those new properties.

sharedObject.data.name  = "Player Name";
sharedObject.data.score  = gameScore;
sharedObject.data.levelArray  = mapArray;

These properties are dynamic. Any values that you assign to the data object will become your saved information.

The shared object will be saved to the user's local hard drive automatically when the user closes the SWF window. However, you can also force the shared object to be saved at any time by using the colorfully named flush method.

_sharedObject.flush();

Now the data is saved to the hard drive.

To load the shared object, use the getLocal method. The getLocal method takes one argument, which is the name of the shared object you want to load. It should be the same name that you supplied in getLocal 's argument when you created the shared object.

sharedObject = SharedObject.getLocal("savedData");

You can now access all the data properties that you created earlier.

sharedObject.data.name
sharedObject.data.score
sharedObject.data.levelArray

This is useful for resuming a game from a save point.

Where on the hard drive is the shared object actually saved? One limitation of using local shared objects is that the user or developer has no control over the save location. Flash Player saves the shared object in a folder that it knows can't be exploited to breach the operating system's security—someplace that Flash Player deems safe. The exact save location depends on the user's operating system and can change depending on whether the SWF file is being executed directly or run in a browser. Flash Player saves the file with the .sol file name extension, in a folder with a random name, so that a malicious SWF can't try to guess the folder name.

On Windows, you can look for .sol files here:

C:Documents and SettingsusernameApplication DataMacromediaFlash Player
#SharedObjects[a folder with a random name]

On Mac OS X, look here:

/User/username/Library/Preferences/Macromedia/Flash Player/#SharedObject s[a folder with a random name]

Note

If you're building games for the Web, you'll almost certainly need to store game data online. With Adobe's Flash Media Server, you can also use a remote shared object to save data online. For more information about remote shared objects, see the SharedObjects entry in Adobe's online ActionScript 3.0 Language and Components Reference. Also, a great introduction to this topic is Foundation Flex for Developers: Data-Driven Applications with PHP, ASP.NET, ColdFusion, and LCDS by Sas Jacobs with Koen De Weggheleire (friends of ED, 2007).

Let's look at a practical example of how to use local shared objects.

Using shared objects

In the chapter's source files, you'll find a folder called LocalSharedObjects, which contains a simple example of how to use a shared object to save and load game data. Run the SWF, and you'll see input and output text fields with Save and Load buttons. Type something into the input field and click the Save button, as shown in Figure 10-1.

Close the SWF, and then launch it again. Click the Load button, and you'll see the text that you saved has been copied into the output text field, as shown in Figure 10-2.

Enter some text and click the Save button to save it.

Figure 10.1. Enter some text and click the Save button to save it.

Click the Load button to load the text you saved previously.

Figure 10.2. Click the Load button to load the text you saved previously.

The following is the entire application class that creates the buttons and text fields, and loads and saves the data. (You'll find the code for the EasyButton class that creates the buttons in the com.friendsofed.utils package.)

package
{
  import flash.net.SharedObject;
  import flash.text.*;
  import flash.display.Sprite;
  import flash.events.MouseEvent;
  import com.friendsofed.utils.StatusBox;
  import com.friendsofed.utils.EasyButton;

  [SWF(backgroundColor="0xFFFFFF", frameRate="30",
  width="550", height="400")]
public class LocalSharedObjects extends Sprite
  {
//Create the shared object.
//This creates a "savedData" object that can
//contain any saved values.
    private var _sharedObject:SharedObject
      = SharedObject.getLocal("savedData");

//Text labels
    private var _inputLabel:TextField = new TextField();
    private var _outputLabel:TextField = new TextField();

//Input and output text fields
    private var _input:TextField = new TextField();
    private var _output:TextField = new TextField();

//Buttons
    private var _saveButton:EasyButton
      = new EasyButton("Save", 10, 40, 21);
    private var _loadButton:EasyButton
      = new EasyButton("Load", 10, 40, 21);
    private var _clearButton:EasyButton
      = new EasyButton("Clear input", 10, 80, 21);

//Status box
    private var _status:StatusBox;

    public function LocalSharedObjects():void
    {
      _status = new StatusBox("LOCAL SHARED OBJECTS");
      addChild(_status);

      //Input label
      addChild(_inputLabel);
      _inputLabel.x = 10;
      _inputLabel.y = 50;
      _inputLabel.text = "Enter a value:";

      //Input text field
      addChild(_input);
      _input.x = 10;
      _input.y = 70;
      _input.width = 100;
      _input.height = 15;
      _input.border = true;
      _input.background = true;
      _input.type = TextFieldType.INPUT;
//Output label
      addChild(_outputLabel);
      _outputLabel.x = 10;
      _outputLabel.y = _input.y + 30;
      _outputLabel.text = "Output:";

      //Output text field
      addChild(_output);
      _output.x = 10;
      _output.y = _outputLabel.y + 20;
      _output.width = 300;
      _output.height = 100;
      _output.multiline = true;
      _output.wordWrap = true;
      _output.border = true;
      _output.background = true;

      //Add and position the buttons
      addChild(_saveButton);
      _saveButton.y = _input.y;
      _saveButton.x = _input.x + _input.width + 20;

      addChild(_loadButton);
      _loadButton.y = _saveButton.y;
      _loadButton.x = _saveButton.x + _saveButton.width + 10;

      addChild(_clearButton);
      _clearButton.y = _loadButton.y;
      _clearButton.x = _loadButton.x + _loadButton.width + 10;

      //Button listeners
      _clearButton.addEventListener
        (MouseEvent.CLICK, clearHandler);
      _saveButton.addEventListener
        (MouseEvent.CLICK, saveHandler);
      _loadButton.addEventListener
        (MouseEvent.CLICK, loadHandler);
    }

    private function clearHandler(event:MouseEvent):void
    {
      _input.text = "";
    }

    private function saveHandler(event:MouseEvent):void
    {
      //Save the input text in the shared object
      _sharedObject.data.savedInput = _input.text;
//Write the data to a local file
      _sharedObject.flush();

      //Confirm the save in the output box
      _output.appendText("Input text saved" + "
");
    }

    private function loadHandler(event:MouseEvent):void
    {
      //Load the shared object. This loads a "savedData" object that can
      //contain any saved values.
      _sharedObject = SharedObject.getLocal("savedData");
      _output.appendText("SharedObject loaded" + "

");
      _output.appendText
        ("loaded value: " + _sharedObject.data.savedInput + "

");
    }
  }
}

When the class is initialized, the shared object is created.

private var _sharedObject:SharedObject
  = SharedObject.getLocal("savedData");

This creates an .sol file on the user's local drive called savedData. If there's already an existing SOL file called savedData, it loads it into the _sharedObject variable.

When the Save button is clicked, it calls the saveHandler that does the job of saving the text from the input field to the SOL file. First, it copies the value of the input field's text property to the shared object's data object.

_sharedObject.data.savedInput = _input.text;

It stores the value in a property called savedInput, which is a dynamic property that is created on the data object. The data object is dynamic, so you can create any new properties on it when you need to and give them any names you choose. You can save any kind of values in properties in the data object, including XML objects.

The shared object saves the SOL file containing the new data by calling its flush method.

_sharedObject.flush();

The data will now be saved to the user's hard drive.

Flash Player will automatically save the .sol file when the SWF is closed, so calling flush is optional in most cases. As long as the information you want to save has been copied to the shared object's data property, it will be saved when the SWF quits. Use flush only if you want to force the data to be saved at a particular time, such as with a button click.

The shared object loads the data with the getLocal method when the user clicks the Load button.

_sharedObject = SharedObject.getLocal("savedData");

The savedInput property that was saved previously is then displayed in the output text field using the TextField class's appendText method.

_output.appendText
   ("loaded value: " + _sharedObject.data.savedInput  + "

");

Using appendText is a faster way of adding text in a text field than by doing it with the increment operator, like this:

textFieldObject.text += "new text to add to the text field";

The SharedObject also has a clear method, which clears all the shared object's data and deletes the .sol file from the disk. Its size property tells you the size, in bytes, of the shared object.

Note

SharedObject has a few more specialized properties, mostly relating to remote shared objects, that you'll find described in the SharedObject entry in Adobe's online ActionScript 3.0 Language and Component Reference.

Limitations of local shared objects

Adobe gives SWF files very limited local file access. This is to maximize security, so that Flash developers can't use SWF files to write and distribute worms, viruses, and spyware. In addition to Flash Player determining the save location, there are a few more limitations to using shared objects:

  • By default, Flash Player limits the maximum amount of storage for SOL files to 100KB. However, users can change this in the Flash Player settings to prevent any SOL files from being saved at all. This is something you have no control over.

  • Flash Player manages SOL files, and it can delete them (without informing anyone) if the user runs out of allocated storage space.

  • The user can choose to block SWF files from specific domains from saving SOL files by using the Flash Player Settings Manager.

  • The SWF file needs to be at least 215 by 138 pixels so that Flash Player has enough room to display the dialog box that prompts users to increase their storage, if necessary.

You'll need to decide whether these limitations are a deal-breaker for the kind of game you're building. For many small, pick-up-and-play Flash games, local shared objects are a quick and efficient solution for saving noncritical persistent game data. However, because of these limitations, you should avoid using local shared objects for any mission-critical game data.

Note

If you need more reliability, for storing data locally, consider designing your game using Adobe Integrated Runtime (AIR). AIR is a specialized framework for AS3.0 that allows you build desktop applications with deep access to the operating system. It requires the user to download and install the AIR runtime, but doesn't tie your hands as SWF files do when it comes to local storage. For more information about AIR, visit the website at http://www.adobe.com/products/air/. The Essential Guide to Flash CS4 AIR Development by Marco Casario (friends of ED, 2008) is an excellent book on the subject that will show you how to build desktop apps using AIR and AS3.0.

There is one more option for local file storage. AS3.0 has a class called FileReference that makes it possible for a user to load and save a data file in any location in the local hard drive, without using AIR.

Loading and saving files to a specific location

FileReference opens a file browser window whenever a file needs to be loaded or saved. The file browser is launched by the operating system, so it isn't limited by Flash Player's security. Because the loading and saving is done by the operating system, and the user can choose which file to open, Flash Player can safely wash its hands of any responsibility for possible security breaches.

A disadvantage to this approach is that your game can't discreetly save data in the background to a directory of your choosing. The user will need to take the initiative to load and save the game data. Is that asking too much from the user? It might be, but you'll need to decide based on any given project requirements.

For example, you might find FileReference useful if you are making a puzzle game where users can upload their own images into the game. You could also use FileReference to create a game level editor, either for yourself to edit levels quickly or for users to make their own levels and save them.

In the chapter's source files, you'll find an example in a folder called UsingFileReference. Click the Load button, and a file browser window will open. Select the map.xml file from the sampleFiles folder. It will be loaded into a text field labeled XML Data, as shown in Figure 10-3. map.xml is game level map formatted as XML, which is a format that AS3.0 can easily read.

Note

If you haven't used XML files before, for now, you can think of them as overachieving text files. We'll take a detailed look at XML and how to use it to create files like map.xml a little later in this chapter.

Load an XML file, make some changes to it, and save it to any location.

Figure 10.3. Load an XML file, make some changes to it, and save it to any location.

Make some changes to the level data, and then click the Save button. A file browser opens, and you're prompted to save the XML file in any location with any name you like. This demonstrates how, unlike with SharedObject, the user has control over exactly which file is opened, where it's saved, and what it's called.

The two new classes used to make this happen are FileReference and FileFilter, both of which are part of the flash.net package.

import flash.net.FileReference;
import flash.net.FileFilter;

The application class initializes a new FileReference object like this:

private var _fileReference:FileReference = new FileReference();

Let's look at how to use FileReference objects to load and save an XML file.

Loading the file

When you click the Load button, the loadHandler lets you browse for the data file. In this example, the file is an XML file.

private function loadHandler(event:MouseEvent):void
{
  //Create a FileFilter to prevent the user from
  //opening any file type except XML files
  var fileFilter:FileFilter
    = new FileFilter("XML Documents", "*.xml;");
//Allow the user to browse for the new file.
  //Pass the fileFilter as an argument
  _fileReference.browse([fileFilter]);

  //FileReference SELECT listener. This is fired when the
  //user selects the correct file
  _fileReference.addEventListener(Event.SELECT, selectHandler);
}

You can make sure that the user can open only a specific type of file by using a FileFilter object. In this example, the FileFilter prevents the user from opening any files except XML files. All other types of files will be dimmed in the file browser.

var fileFilter:FileFilter
  = new FileFilter("XML Documents", "*.xml;");

The first argument, "XML Documents", is just a description. This description is displayed in the title bar of Windows and Linux file browsers. The second argument, "*.xml;", is the type of file you want to open. In this case, it's any file with an .xml extension. To allow the user to open more than one type of file, add more extension names and separate them with semicolons, like this:

var fileFilter:FileFilter = new FileFilter
   ("Documents", "*.xml; *.doc; *.txt; *.pdf" );

Of course, your AS3.0 code will need to be able to interpret any file you open. AS3.0 can read XML files natively, which is why they're used in this example.

Next, the code calls the _fileReference object's browse method and passes the fileFilter as an argument.

_fileReference.browse([fileFilter]);

This is what launches the operating system's file browser and dims the names of all files except those with an .xml extension. The code then attaches a listener that calls the selectHandler when the file has been selected by the user in the file browser.

_fileReference.addEventListener(Event.SELECT, selectHandler);

The selectHandler does the job of loading the file into the SWF with FileReference 's load method. It calls the loadCompleteHandler when it's finished.

private function selectHandler(event:Event):void
{
  //Remove this listener
  _fileReference.removeEventListener(Event.SELECT, selectHandler);

  //Add a listener to check whether the file has
  //successfully loaded
  _fileReference.addEventListener
    (Event.COMPLETE, loadCompleteHandler);
//Load the file
  _fileReference.load();
}

The loadCompleteHandler reads the loaded data, casts it as an XML object, and copies it into the text field so that the file data is visible on the stage

private function loadCompleteHandler(event:Event):void
{
  //Remove this listener
  _fileReference.removeEventListener
    (Event.COMPLETE, loadCompleteHandler);

  //Store the FileReference's data in the XML object
  var xmlData:XML = XML(_fileReference.data);

  //Display the XML object in the text field
  _textField.text = xmlData;
}

FileReference objects have a data property that contains the loaded data. The following directive reads the loaded data and interprets it as XML.

_xmlData = XML(_fileReference.data);

Finally, xmlData, the loaded XML file, is displayed in the text field on the stage.

_textField.text = xmlData;

Note

This same technique works for images as well. In this chapter's source files, you'll find the LoadingImages folder. Run the SWF, and use the Loa button to load any image file onto the stage. The code loads the images with the help of the Loader and LoaderInfo classes. Check the comments in the source code for more details.

Saving the file

When the XML file is loaded into the text field, you can make any changes to it that you like. To allow editing directly in the text field, the text field's selectable property must be set to true, and its type needs to be set to INPUT.

_textField.selectable = true;
_textField.type = TextFieldType.INPUT;

Once you've made some changes, click the Save button. The Save button's listener calls the saveHandler. The saveHandler creates a new FileReference object that saves the data from the text field as an XML file. A file browser window prompts the user to choose a file name and the directory location to save the new file.

private function saveHandler(event:MouseEvent):void
{
  //Create a FileReference object for the save data
  var saveFile:FileReference = new FileReference();

  //Add an optional listener that confirms that the file has
   //been saved
  saveFile.addEventListener
    (Event.COMPLETE, saveCompleteHandler);

  //Create a new temporary XML object to store the
  //changed data in the text field
  var newXML:XML = XML(_textField.text);

  //Save the changed XML data with the name "newMap.xml"
  saveFile.save(newXML, "newMap.xml");
}

private function saveCompleteHandler(event:Event):void
{
  trace("Save completed");
}

First, a new FileReference object is created called saveFile. Its job will be to save the changed data in the text field.

var saveFile:FileReference = new FileReference();

The saveFile object adds an Event.COMPLETE listener that is called when the file is actually saved.

saveFile.addEventListener
    (Event.COMPLETE, saveCompleteHandler);

In this example, it just traces as "Save completed," but you could use it to trigger some other code. This is optional.

Next, we need to create a new temporary XML object that contains the text from the text field.

var newXML:XML = XML(_textField.text);

The saveFile object can then use its save method to initiate the file save.

saveFile.save(newXML, "newMap.xml");

This directive opens a new file browser window. The browser will allow you save a file called newMap.xml anywhere you like on your hard drive. You can also change the name of the file if you wish.

This is a simple example, but is the basis for creating something more complex, like a game level editor using XML data.

Note

The basic technique described here can also be used to modify and save user-loaded images. Adobe has a detailed tutorial by H. Paul Robertson on how to crop and save such images at the Adobe Developer Connection website (http://www.adobe.com/devnet/flash/quickstart/filereference_class_as3/). It includes two specialized classes that will encode the final modified images as PNG or JPEG files.

Understanding XML

In this chapter, we're going to take a detailed look at AS3.0's powerful built-in tools for reading and writing XML. When you become comfortable working with XML, you may find that it's the primary way in which you store and manage your game's data. Knowing how to work with XML is an essential skill for any game designer.

XML stands for Extensible Markup Language. This language is a way of describing data so that computers can understand it. XML is useful for game designers because it allows you to keep your game data file completely separate from the rest of your code.

You can load XML files into your game at runtime. This means you can tweak, change, and add to all of the game's data without needing to recompile the SWF file or change your AS3.0 code. You can load XML files into the game from a local directory or over a network from any server on Earth.

XML is a standardized data format that's used as a common interface for sharing data between virtually all software and database platforms. Even though AS3.0 might be a completely different language from PHP, C#, or Java, you can be sure that any data stored as XML can be read by all of them. Storing and sharing game data as XML ensures maximum compatibility. It's also easily readable by humans and fun to learn.

The building blocks of XML

Imagine that we're creating a multiple-choice quiz game that looks like Figure 10-4. The quiz presents three questions with three possible answers, one of which is the correct response. Wouldn't it be useful if our program could actually understand what all those sections of the quiz represent—the questions, the answers, and even which are the correct answers?

XML can be used to explain the meaning of data in a document.

Figure 10.4. XML can be used to explain the meaning of data in a document.

XML can help you structure data so that's easily readable by a computer. The basic concept in XML is that every important item in your data is described by a tag. XML tags are just like HTML tags. They're descriptive words, surrounded by opening and closing angle brackets. You can use any tag names you like to describe your data.

Here's an XML tag that could be used to describe which part of the quiz is a question:

<question>

This tells the computer than anything that follows is part of the quiz's question.

Each tag must have a matching closing tag, so that the computer knows where the item ends. Closing tags have the same name as the opening tag, but are preceded by a forward slash, like this:

</question>

The opening and closing tags work together to describe the information they contain. Here's how we could format the first question of the quiz as XML:

<question>What was the first video game?</question>

Because the text is surrounded by opening and closing question tags, the computer will know that any data within them will be a question.

A matching pair of XML tags, and the data it contains, is known as an element in XML's terminology. This is an element:

<question>...</question>

Each individual part of the element is known as a node. For example, a single opening <question> tag can be called the question node. The text between the tags is called the text node.

<question>This text between the element tags is the text node</question>

XML can also contain extra information about the data that your program might need. This extra information is sometimes called metadata. For example, let's say that in our quiz game, we want to assign 10 points to the correct answer. We can do this by including an attribute in the tag. Here's what the tag for the answer might look like with this new attribute:

<answerC points="10" >Cathode Ray Tube Amusement Device</answerC>

The attribute is part of the opening tag. The program that runs your quiz game could then read this attribute and assign the player 10 points for a correct guess.

Note

Yes, the magnificently titled Cathode Ray Tube Amusement Device was indeed the world's first completely electronic game with a video output source (an oscilloscope). It was designed and built by Thomas T. Goldsmith, Jr. and Estle Ray Mann in 1947. It was a missile game that allowed the player to fire at targets. A celluloid screen overlay displayed the targets, and the player used knobs to adjust the trajectory of the missiles. It was never commercialized, and no prototypes appear to have survived.

You can create as many different attributes with whatever names and values you need.

Elements, nodes, and attributes are XML's basic building blocks.

XML hierarchy

XML is hierarchical. That means that you can group elements inside other elements to keep related information together. Figure 10-5 shows how each question in the quiz and its three answers make up a single unit (which I've called "quiz items").

XML can keep related data together.

Figure 10.5. XML can keep related data together.

You can box together bits of related data like this by surrounding the data with additional tags. For example, we could create a <quizItem> tag that keeps together each question and its three answers. Here's what the XML structure might look like:

<quizItem>
  <question>What was the first video game?</question>
  <answerA>Pong</answerA>
  <answerB>Spacewar!</answerB>
  <answerC points="10">Cathode Ray Tube Amusement Device</answerC>
</quizItem>

The <quizItem> and </quizItem> tags are used to bookend all the related information.

You can see from Figure 10-5 that there are three question-and-answer groups in this quiz. Each of them can be grouped inside <quizItem> tags to keep them separated from each other.

<quizItem>
  <question>What was the first video game?</question>
  <answerA>Pong</answerA>
  <answerB>Spacewar!</answerB>
  <answerC points="10">Cathode Ray Tube Amusement Device</answerC>
</quizItem>
<quizItem>
  <question>What is the highest ever selling video game?</question>
  <answerA>Duck Hunt</answerA>
  <answerB points="10">Wii Sports</answerB>
  <answerC>Super Mario Bros.</answerC>
</quizItem>
<quizItem>
  <question>Which of these is not a programming language?</question>
  <answerA points="10">FlipFlop</answerA>
  <answerB>Groovy</answerB>
  <answerC>Scratch</answerC>
</quizItem>

Now you can easily see where one question group ends and the next begins.

Figure 10-5 also shows that the entire quiz is grouped together in a single box that contains all the quiz items. We can create another tag called <quiz> to enclose the quiz.

<quiz name="A little quiz">
  <quizItem>
    <question>What was the first video game?</question>
    <answerA>Pong</answerA>
    <answerB>Spacewar!</answerB>
    <answerC points="10">Cathode Ray Tube Amusement Device</answerC>
  </quizItem>
  <quizItem>
    <question>What is the highest ever selling video game?</question>
    <answerA>Duck Hunt</answerA>
    <answerB points="10">Wii Sports</answerB>
    <answerC>Super Mario Bros.</answerC>
  </quizItem>
  <quizItem>
    <question>Which of these is not a programming language?</question>
    <answerA points="10">FlipFlop</answerA>
    <answerB>Groovy</answerB>
    <answerC>Scratch</answerC>
  </quizItem>
</quiz>

This type of hierarchical structure is called a tree. <quiz> is the root element, or parent, of all the other elements. Each <quizItem> is a child of the <quiz> element. This is a very basic hierarchical structure that you should be quite familiar with by now. XML structure makes this very clear. Figure 10-6 shows how the XML structure of the quiz matches the quiz diagram.

A simple quiz structured as XML

Figure 10.6. A simple quiz structured as XML

Now that the quiz is completely described by XML, let's see how we can use that information with AS3.0.

XML and ActionScript

AS3.0 has a special syntax for working with XML called ECMAScript for XML (E4X). E4X allows you access elements, nodes, and attributes in XML documents in much the same way that you access properties in objects.

Note

In this chapter, I'm going to call XML files XML documents, just for the sake of simplicity. The official XML specification refers to XML applications. They're the same thing. From a programmer's point of view, XML doesn't contain the same kind of logic that we associate with software applications, and are more like plain text documents.

You can use XML in your programs in three ways:

  • Create XML objects directly in your code.

  • Embed the XML file at compile time, with the Embed metatag, in the same way that we've embedded other files in this book's examples.

  • Load the XML file at runtime. This allows a compiled SWF file to read an XML file when it loads. It means that if you want to change any data in your game, you just need to update the XML file, rather than recompile the SWF. This is probably the most common way that XML is used with Flash.

We'll look at all three ways to use XML in this chapter, beginning with the simplest: creating XML objects in code.

Creating XML objects

You can create XML objects directly in your AS3 code like this:

var title:XML = <title>This is the title</title>;

You can then access the XML document that you just created with the title variable. If you traced the value of title, it would appear like this:

<title>This is the title</title>

It might look like plain text, but because it's an XML object, you can access all its separate components individually. You'll soon see how.

Now let's see how we can create our little quiz as an XML object in AS3.0. Although it's not required, you'll make your code a little more readable if you give your XML object the same name as the root element. The root element is <quiz>, so a good name for the XML object is quiz.

var quiz:XML
=
<quiz name="A little quiz">
  <quizItem>
    <question>What was the first video game?</question>
    <answerA>Pong</answerA>
    <answerB>Spacewar!</answerB>
    <answerC points="10">Cathode Ray Tube Amusement Device</answerC>
  </quizItem>
  <quizItem>
    <question>What is the highest ever selling video game?</question>
    <answerA>Duck Hunt</answerA>
    <answerB points="10">Wii Sports</answerB>
    <answerC>Super Mario Bros.</answerC>
  </quizItem>
  <quizItem>
    <question>Which of these is not a programming language?</question>
    <answerA points="10">FlipFlop</answerA>
    <answerB>Groovy</answerB>
    <answerC>Scratch</answerC>
  </quizItem>
</quiz>
;

Yes, it's really long, but it works just fine. (Just don't forget that final semicolon!) If you trace the value of xmlData, you'll see the entire XML document, neatly formatted as it appears in the preceding code.

Now that we have an XML object, we need to access this information.

Reading elements, text nodes, and attributes

The quiz has nine possible answers to choose from: three answer As, three answer Bs, and three answer Cs. We can find all the <answerA> elements like this:

quiz.quizItem.answerA

Here's the result:

<answerA>Pong</answerA>
<answerA>Duck Hunt</answerA>
<answerA points="10">FlipFlop</answerA>

It might be more useful to just have access to the elements' text nodes, without the surrounding tags. We can use the text method to find them.

quiz.quizItem.answerA.text()

This will return all three of <answerA> 's text nodes. If you trace them, you'll see them all on one line, without spaces between them, like this.

PongDuck HuntFlipFlop

If you want to access an individual text node, you can do so by using an array index number. To retrieve the second text node, use [1].

quiz.quizItem.answerA.text()[1]

This will return the second <answerA> text node.

Duck Hunt

The logic is exactly the same as working with arrays. (And, as with arrays, the first element will have an index number of 0.)

The length() method works like an array's length property. To find out how many questions are in this quiz, we can use the length() method to return the number of <quizItem> elements, like this:

quiz.quizItem.length()

This will return 3.

If you're trying to target an element but are unsure of how deeply nested it is in the XML hierarchy, you can use the double-dot operator. For example, you can access answerA like this:

quiz..answerA

There are two dots between quiz and answerA to show that "there might be some other nodes between, but I have no idea what they might be." This syntax will target answerA without any problems.

Reading attributes

In the example XML, the name of the quiz is in the <quiz> tag's attribute, like this.

<quiz name="A little quiz">

It's important to access this attribute so that it can be displayed. The attributes method finds this information.

quiz.attributes()

This will return the values of all the attributes in the <quiz> element:

A little quiz

But what if any of your elements contain more than one attribute?

<quiz name="A little quiz" totalPoints="30" importance="not much">

Using quiz.attributes() will give you all of them. Here's how it would trace (again, without spaces or line breaks):

A little quiz30not much

You can also access these attributes individually by using an array index number. Here's how to find the third attribute:

quiz.attributes()[2]

This will return:

not much

You might also need to read the names of attributes. For example, the names of the three attributes in the <quiz> tag are name, totalPoints, and importance :

<quiz name ="A little quiz" totalPoints ="30" importance ="not much">

You can access them with the name method. To find the name of the third attribute, use this syntax:

quiz.attributes()[2].name() ;

This will return:

importance

Unfortunately, this works with only one attribute at a time. You can't use quiz.attributes().name() to list all the names. To do that, use a for each loop, like this:

for each(var attribute:XML in quiz.attributes())
{
  trace(attribute.name());
}

This will trace as the following:

name
totalPoints
importance

It could be that you already know the name of an attribute and just need its value. You can use the @ operator, followed by the attribute name, to find the value.

quiz.@ totalPoints

This will return:

30

You can also use the length() method to find out how many attributes an element has.

quiz.attributes().length()

This will return 3, based on the current example.

Looping through elements and attributes

Earlier, I showed you how you can access elements and text nodes directly, like this:

quiz.quizItem.answerA.text()

This will return all the text nodes within the <answerA> tags. You'll get all the information in one long line of text, like this:

PongDuck HuntFlipFlop

To make this usable, you'll almost certainly need to push this information into arrays. That means you'll need to loop through it.

AS3.0 has a special object called an XMLList, which is an array that contains a group of XML objects. In the preceding example, Pong, Duck Hunt, and FlipFlop are single XML objects. However, you can work with them together in an array as an XMLList of objects. Let's see how.

First, create an XMLList object that defines the array of XML objects of interest. Here's how we could create an array of <answerA> elements:

var answerAList:XMLList = quiz.quizItem.answerA;

answerAList is now an array that contains all three of the <answerA> elements. We can now loop through that array with a for each loop to find each item.

for each (var answerAElement:XML in answerAList)
{
  trace(answerAElement);
}

This code will trace as the following:

Pong
Duck Hunt
FlipFlop

Alternatively, we can produce the same result using a for loop and the length() method.

var answerAList:XMLList = quiz.quizItem.answerA;

for (var i:int = 0; i < answerAList.length(); i++)
{
  var answerAElement:XML = answerAList[i];
  trace(answerAElement);
}

It's entirely up to you which style you prefer.

You can loop through attributes in the same way. Let's imagine that we want to find out which of the <answerA> elements have point attributes. (In our example, we know that only one does, but let's pretend we're reading an XML document that we've never seen before.) We can use XMLList to help us loop through each <answerA> element and tell us the values of each of their point attributes.

var answerAPoints:XMLList = quiz.quizItem.answerA.attributes();

for each (var points:XML in answerAPoints)
{
   trace(points);
}

It loops through all three <answerA> elements looking for attributes called points. If it finds one, it will return the value. As expected, the preceding code will return 10. That's because there's only one <answerA> element that has a points attribute, and its value is 10.

Finding all the child elements

Let's say we want to loop through all the <answerA>, <answerB>, and <answerC> elements to find the total number of points in the quiz. Those three elements are all child nodes of <quizItem>. Figure 10-7 illustrates this relationship.

The answer nodes are all children of the <quizItem> parent node.

Figure 10.7. The answer nodes are all children of the <quizItem> parent node.

The children method will return all of the children of any node that you specify. Here's how to use it to find all the children of the <quizItem> nodes:

quiz.quizItem.children();

This will return all the child elements, which include the questions and answers.

<question>What was the first video game?</question>
<answerA>Pong</answerA>
<answerB>Spacewar!</answerB>
<answerC points="10">Cathode Ray Tube Amusement Device</answerC>
<question>What is the highest ever selling video game?</question>
<answerA>Duck Hunt</answerA>
<answerB points="10">Wii Sports</answerB>
<answerC>Super Mario Bros.</answerC>
<question>Which of these is not a programming language?</question>
<answerA points="10">FlipFlop</answerA>
<answerB>Groovy</answerB>
<answerC>Scratch</answerC>

To find all the point attributes in this data, use a for each loop. Here's how we can loop through them and add up all the points.

var attributeList:XMLList = quiz.quizItem.children().attributes();
for each (var points:XML in attributeList)
{
   trace(points);
}

This will trace the value of all three point attributes.

10
10
10

We can sum them to find the total value, like this:

var attributeList:XMLList
  = quiz.quizItem.children().attributes();

var totalPoints:uint = 0;

for each (var points:XML in attributeList)
{
  totalPoints += uint(points);
}

trace(totalPoints);

This will trace as 30.

You can also use the children method to find the values of child element text nodes. The following bit of code will display all the text nodes that are children of <quizItem>.

var quizItemChildren:XMLList = quiz.quizItem.children();

for each (var quizItemChild:XML in quizItemChildren)
{
   trace(quizItemChild);
}

This will trace as the following:

What was the first video game?
Pong
Spacewar!
Cathode Ray Tube Amusement Device
What is the highest ever selling video game?
Duck Hunt
Wii Sports
Super Mario Bros.
Which of these is not a programming language?
FlipFlop
Groovy
Scratch

Perhaps we just want to find the names of those elements. We can do that by using the name method. The following code is identical to the code from the previous example, except that it traces quizItemChild.name().

var quizItemChildren:XMLList = quiz.quizItem.children();
for each (var quizItemChild:XML in quizItemChildren)
{
    trace(quizItemChild.name() );
}

It will trace only the names of the elements, not their text node values.

question
answerA
answerB
answerC
question
answerA
answerB
answerC
question
answerA
answerB
answerC

That's great, but how useful is it? It's very useful for filtering information so that you can find what you're looking for.

Let's say we just want to find the quiz's questions. We could use the name() method to display only the questions and ignore the answers. Here's the code that will do that:

var quizItemChildren:XMLList = quiz.quizItem.children();
for each (var quizItemChild:XML in quizItemChildren)
{
 if(quizItemChild.name() == "question")
 {
   trace(quizItemChild);
 }
}

This will trace as the following:

What was the first video game?
What is the highest ever selling video game?
Which of these is not a programming language?

The name method works for attributes as well, so you can use this same technique to filter any specific attributes that you want to find.

A better way to find what you're looking for

Looking for specific data in XML documents is such a common task that E4X has some specialized syntax to deal with it.

Let's imagine that we want to find out which quiz items contain the question "What was the first video game?" We can use this line of code to find out:

var questionList:XMLList
  = quiz.quizItem.(question == "What was the first video game?");

This will return the entire <quizItem> element that contains a question text node that matches the search term.

<quizItem>
  <question>What was the first video game?</question>
  <answerA>Pong</answerA>
  <answerB>Spacewar!</answerB>
  <answerC points="10">Cathode Ray Tube Amusement Device</answerC>
</quizItem>

If we happened to have more than one match, all matching <quizItem> elements would be returned. This is a very powerful database-style search feature that is built right into the syntax.

The previous example displayed all the XML information. To display the exact information we're looking for, we can append the element name to the end of the search directive, like this:

var questionList:XMLList
   = quiz.quizItem.(question == "What was the first video game?").question ;

This will return:

What was the first video game?

That's interesting, but might in itself be rather useless. What could be useful is to determine the first possible answer to this question. We can do that by appending answerA to the search directive, like this:

var questionList:XMLList
   = quiz.quizItem.(question == "What was the first video game?").answerA ;

This will return the first answer that matches that question: Pong. Again, if there's more than one match in the XML document, all the matches will be returned.

You can search for attributes in the same way by using the @ operator to indicate that you're looking for an attribute. The following code will tell us which quiz item is the one that has answer A as the correct answer.

var pointsList:XMLList = quiz.quizItem.(answerA.@points == "10");

This will return the <quizItem> element in which <answerA> 's point attribute equals 10.

<quizItem>
  <question>Which of these is not a programming language?</question>
  <answerA points="10">FlipFlop</answerA>
  <answerB>Groovy</answerB>
  <answerC>Scratch</answerC>
</quizItem>

How can we use this to find out which answer equals 10 points? Just append the name of the element we want to find, like this:

var pointsList:XMLList
  = quiz.quizItem.(answerA.@points == "10").answerA;

This will return: FlipFlop.

As you can see, there are many ways to mix and match these techniques to help you find what you're seeking. The following code combines a few of the techniques to find the correct answer to a particular question.

var questionList:XMLList
  = quiz.quizItem.(question == "What was the first video game?");

var quizItemChildren:XMLList = questionList.children();
for each (var quizItemChild:XML in quizItemChildren)
{
 if(quizItemChild.@points == "10")
 {
   trace(quizItemChild);
 }
}

This will correctly trace as:

Cathode Ray Amusement Device

E4X syntax gives you very detailed access to XML data that you can use in your games however you wish. You'll find a working SWF called XMLBasics in the chapter's source files that illustrates all these examples using live code.

Changing XML data

It's very useful to be able store information about a game's current state in an XML document. You could create an XML document based on information such as a player's score and the current level, so that the game can be saved and resumed later. When the player resumes the game, you can load the custom XML document and feed the relevant data into the game engine. To do this, you need to be able to modify XML data and possibly add and remove nodes. E4X syntax gives you a number of tools to help you with this.

Adding new elements and attributes

Let's first start with an empty XML document called gameData.

var gameData:XML = <gameData></gameData>;

We can use this as a blank slate to add elements and attributes. Let's say that we need to add an element called <player>, which stores the player's name. We simply add the name of the new element to the XML object using dot notation, and assign an empty tag that matches the element name.

gameData.player = <player></player>;

If you now trace gameData, you'll see this:

<gameData>
  <player/>
</gameData>

We created a new, empty tag by using a single tag followed by a forward slash. You can also create a new empty tag with this line of code:

gameData.player = <player />;

This is preferable syntax to use when creating an empty element.

Now that we have a new, empty element, we can easily add a text node to it, like this:

gameData.player = "Player Name";

It's as simple as assigning a variable value. Our new XML document will now trace as follows:

<gameData>
  <player>Player Name</player>
</gameData>

But you can also create the element and text node together in a single step, like this:

gameData.player = <player>Player Name</player>;

This produces the same XML as the preceding trace.

You can just as easily create elements using existing variables. Simply assign the variable name to the new element. For example, if we have a variable called playerName that stores the player's name, we can assign its value to the <player> element as follows:

gameData.player = playerName;

This will also work with arrays or any other type of data.

Four specialized methods for adding elements are especially useful if you want to insert elements into an existing XML document: appendChild, prependChild, insertChildBefore, and insertChildAfter.

appendChild allows you add a new child element to any other element. Here's how we could add a new <score> element to our XML document:

gameData.appendChild (<score>50</score>);

Our XML document will now look like this:

<gameData>
  <player>Player Name</player>
  <score>50</score>
</gameData>

The new <score> element is added after the existing <player> element.

If we want to add a new element before the <player> element, we can do so using the prependChild method.

gameData.prependChild (<gameName>Cosmic Fluff Bubbles</gameName>);

prependChild will add this new element before all the others:

<gameData>
  <gameName>Cosmic Fluff Bubbles</gameName>
  <player>Player Name</player>
  <score>50</score>
</gameData>

You can specify exactly where in the order of elements you want to insert an element by using insertChildBefore and insertChildAfter. Let's insert a new element called <location> after the <player> element.

gameData.insertChildAfter
  (gameData.player, <location>Bangalore</location>);

This will trace as follows:

<gameData>
  <gameName>Cosmic Fluff Bubbles</gameName>
  <player>Player Name</player>
  <location>Bangalore</location>
  <score>50</score>
</gameData>

To insert a new element before another, use insertChildBefore rather than insertChildAfter.

You can add new attributes in the same way that you add new elements. Just include the @ operator in front of the name of the attribute you want to add. Here's how to add an attribute called bubblesPopped to the <score> element:

gameData.score.@bubblesPopped = 13;

This will trace as follows:

<score bubblesPopped="13" >50</score>

The @ operator always indicates that you're referring to an attribute.

Building XML documents from existing variables

You can easily build XML documents from existing variables. Surround each variable with a pair of curly braces, { }, and insert it in each element's text node.

Let's suppose our game uses these four variables.

var gameName:String = "Cosmic Fluff Bubbles";
var player:String = "Player Name";
var location:String = "Bangalore";
var score:uint = 50;

We can use them to build our XML document by surrounding them in curly braces and inserting them into each element's text nodes:

var gameData:XML
  =
  <gameData>
    <gameName>{gameName}</gameName>
    <player>{player}</player>
    <location>{location}</location>
    <score>{score}</score>
  </gameData>
  ;

This traces just as you would expect:

<gameData>
  <gameName>Cosmic Fluff Bubbles</gameName>
  <player>Player Name</player>
  <location>Bangalore</location>
  <score>50</score>
</gameData>

You can use any variable names you like; they don't need to match the element names.

You can create attributes from variables in the same way. In the following example, the timesPlayed variable is used as an attribute in the <score>element.

var timesPlayed:uint = 3;

 var gameData:XML

  =
  <gameData>
    <gameName>{gameName}</gameName>
    <player>{player}</player>
    <location>{location}</location>
     <score timesPlayed = {timesPlayed} >{score}</score>
  </gameData>
  ;

This will trace with the timesPlayed attribute having the value 3 :

<gameData>
  <gameName>Cosmic Fluff Bubbles</gameName>
  <player>Player Name</player>
  <location>Bangalore</location>
   <score timesPlayed="3" >50</score>
</gameData>

Removing nodes from XML documents

You can remove any element, attribute or text node with the delete keyword. To remove the <location> element in our example XML document, use this line of code:

delete gameData.location;

Our document now looks like this:

<gameData>
  <gameName>Cosmic Fluff Bubbles</gameName>
  <player>Player Name</player>
  <score bubblesPopped="13">50</score>
</gameData>

You can use delete to remove attributes as well. Just remember to target an attribute name with the @ operator. Here's how to delete the bubblesPopped attribute:

delete gameData.score.@bubblesPopped;

And now the XML looks like this:

<gameData>
  <gameName>Cosmic Fluff Bubbles</gameName>
  <player>Player Name</player>
  <score>50</score>
</gameData>

delete can also be used to remove text nodes. Let's use it to remove Cosmic Fluff Bubbles from the <gameName> element.

delete gameData.gameName.text()[0];

You also need to supply the index number of the text node, which will always be 0. Our new XML document looks like this:

<gameData>
  <gameName/>
  <player>Player Name</player>
  <score>50</score>
</gameData>

The <gameName> element is now completely empty.

You'll find working examples of all this code in the ChangingXML folder.

This has been a long but important introduction to XML. These general techniques cover most of the ways that you can manipulate XML data.

Note

The XML standard is maintained by the World Wide Web Consortium (W3C). For much more detail on XML, visit the official W3C site at http://www.w3.org/TR/REC-xml/. AS3.0's XML object also contains many more properties and methods than those covered here, some of which you might find useful. Be sure to check the XML entry in Adobe's ActionScript 3.0 Language and Components Reference for all the details.

There are countless ways in which you can use XML data in your games. Next, we'll look at one of the most useful: to load game levels.

Loading game levels from XML data

The ideal use for XML in games is to use it to load game map levels and values that you need to initialize the game. You can load all the game's data from an external XML file, which you can keep separate from your AS3.0 source files and the SWF. The easiest way to do this is to embed an XML file into your AS3.0 code in the same way that you embed any other type of file—using the Embed metatag.

To embed an XML file, use this syntax:

[Embed(source = "anyXmlFile.xml",
mimeType = "application/octet-stream")]
private var XmlData:Class;

The XML file is now accessible in the XmlData class.

Next, create an object from the XmlData class:

var level:Object = new XmlData();

There are two things you need to keep in mind when creating this new object:

  • Its name should preferably match the first element of the XML document. In this case, the first element of the XML document would be <level>.

  • The object that you create from the XmlData class must be typed as Object. You can't type it as XmlData. This is because the XmlData class was created directly in the class, and the compiler won't yet have access to it when it tries to compile the code.

Finally, cast the new object as XML.

level = XML(level);

This tells the compiler that it should interpret the level object as an XML object. You can now work with the level object just as you would any ordinary XML object.

Now let's see how you can use an embedded XML file to create a game level map.

Creating a game level map

Run the SWF in the chapter's source files called XMLMap. The SWF displays a standard tile-based level map that's identical to the kind of maps you saw in Chapter 8, as shown in Figure 10-8.

The map data is loaded from an external XML file.

Figure 10.8. The map data is loaded from an external XML file.

It's nothing special to look at, but what is significant is that all the data for the level map is loaded from an external XML file. Additionally, the SWF displays the name of the map, "Level One Map," which is also stored in the XML file.

The XML file is in the assets/xml folder and is called maps.xml. Here's what it looks like:

<level name = "Level One Map">
  <row>10,10,10,10,10,10,10,10,10,10</row>
  <row>00,00,10,10,10,10,10,10,10,10</row>
  <row>10,10,10,10,10,10,00,00,00,10</row>
  <row>10,10,10,00,00,10,10,10,10,10</row>
<row>10,10,10,10,10,10,10,10,10,10</row>
  <row>10,10,00,00,00,00,00,10,10,10</row>
  <row>10,10,10,10,10,10,10,10,10,10</row>
  <row>00,00,00,00,00,00,00,00,00,00</row>
</level>

You see the familiar two-dimensional array data that we've used to build all the tile-based maps in the book so far. But don't be fooled by this—it's not an array! It's XML data that has been structured so that it mimics two-dimensional array structure.

This XML file is going to be read by our AS3.0 code and eventually copied into a real two-dimensional array that's used to actually build the level. Structuring the XML data in an array-like format makes it easy for it to work with our existing tile-based game engine. You can format XML map data however you like, as long as your game engine has some way of interpreting it.

Note

If you're going to share your XML document with other information systems like web servers outside your AS3.0 code, you might need to preface it with a header tag, called a Document Type Declaration (DTD). This is the standard DTD header recommended by W3C:

<?xml version="1.0" encoding="ISO-8859-1"?>

Its job is to describe how the XML has been encoded so that it can be properly interpreted by whatever system is reading it. It should be the first tag in the XML document. If you're sending and loading XML data to and from a server, find out if there are custom headers that you need to use so that the server can interpret the XML file correctly. DTDs like this are ignored by AS3.0, so you can safely use any header that you choose, or none at all.

Loading and interpreting the XML map data

The XMLMap application loads the XML file that contains the map data and copies it into a two-dimensional array. The level that you see on the stage is actually being read and built from that two-dimensional array, using exactly the same buildMap method, unchanged, that we used in Chapter 8. What's important for you to know is how the XML file is loaded and how its data is copied into the two-dimensional array that's used to build the level.

This is the code from the XMLMap application class that embeds the file and creates the XML object called level :

//Embed the XML file
[Embed(source = "../assets/xml/maps.xml",
mimeType = "application/octet-stream")]
private var XmlData:Class;
public function XMLMap():void
{
  // Create the XmlData object
  var level:Object = new XmlData();

  //Cast it as an XML object. The name of the object
  //should preferably match the first XML element
  level = XML(level);
  //...

This is the same basic code that we looked at back at the start of this section. The level object contains the XML file, and we can now use it to build the two-dimensional array.

A blank array called _platformMap will be used to build the two-dimensional array. The code does this by looping through each <row> element and pushing the row's content into an array. There are eight rows in the XML files, so this results in eight row arrays. Each of those eight row arrays are then pushed into the _platformMap array. The result is that _platformMap becomes a two-dimensional array containing eight row arrays. Here's the section of code from the application class that does this:

//Create an XMLList object to help you loop through
//all the row elements
var rowList:XMLList = level.row;

 //Loop through each row
for each(var row:XML in rowList)
{
  //Convert the row text node into a string
  var rowString:String = row.text();

  //Convert the string into an array.
  //Commas in the XML file are used to
  //separate each array element.
  //This array represents one <row> in the XML document
  var rowArray:Array = rowString.split(",");

  //Push the new rowArray into the _platformMap array.
   //This creates a 2D level map
  //which will contain all 8 rows by the time the loop quits
  _platformMap.push(rowArray);
}

The result of this is that the _platformMap array can now be used to build the game level in exactly the same way as in Chapter 8.

Let's take a closer look at how this loop works. We first need an XMLList object of all the <row> elements.

var rowList:XMLList = level.row;

We can now loop through each element in the rowList using a for each loop.

for each(var row:XML in rowList)
{...

This will loop eight times—once for each <row> element.

The first thing that the loop does is to extract the row's text node and store it in a String variable called rowString.

var rowString:String = row.text();

The row is now just a simple string of numbers, separated by commas. If you traced rowString, this is what you would see for the first row:

10,10,10,10,10,10,10,10,10,10

It's just a plain string of characters. And because it's a single string, those numbers cannot be read individually. As far as AS3.0 is concerned, the string is a solid block of meaningless characters. It might just as well be "abracadabra" or "Eyjafjallajokull"; AS3.0 can't read the individual numbers.

Our next job is to find a way to extract each of those numbers and load them as individual elements into an array. AS3.0's String object has a method called split, which is perfect for doing this. The split method separates each character or related group of characters in a string and returns them as an array of individual elements.

To use split, you need to tell it how it should define a single element. You can clearly see from the <row> elements that each number is separated by a comma. This means that we can tell the split method to use commas to separate each element. Here's how:

var rowArray:Array = rowString.split(",");

The comma character is supplied as split 's argument. This means it will extract every character separated by a comma and return it as an individual array element. The result is a new array called rowArray. Each element in rowArray is one of the ten numbers from the <row> element.

Lastly, the loop pushes this new row array into the _platformMap array.

_platformMap.push(rowArray);

At the end of the loop, _platformMap will contain eight arrays, one for each row.

We now have a perfectly ordinary two-dimensional array that contains all the map data. We can use it with the buildMap method to build the level using exactly the same code that we used to build levels in Chapter 8.

buildMap(_platformMap);

The other thing this code does is to extract the name of the level from the XML file and display it in the status box. The level name is stored in the XML file in the name attribute of the <level> element.

<level name = "Level One Map">

The application class loads this into a variable called _levelName.

_levelName = level.@name;

It's then used to display the level name in the status box.

_statusBox.text += "
" + "NAME: " + _levelName;

You can store as much metadata like this as you like in the same XML file that describes the map.

It's important for you to see all this code in its proper context, so here's most of XMLMap 's constructor method and the Embed code that does all this work:

//Embed the XML file
[Embed(source = "../assets/xml/maps.xml",
mimeType = "application/octet-stream")]
private var XmlData:Class;

public function XMLMap():void
{
  //Create the XmlData object
  var level:Object = new XmlData();

  //Cast it as an XML object. The name of the object
  //should preferably match the first XML element
  level = XML(level);

  //Create an XMLList object to help you loop through
  //all the row elements
  var rowList:XMLList = level.row;

   //Loop through each row
  for each(var row:XML in rowList)
  {
    //Convert the row text node into a string
    var rowString:String = row.text();

    //Convert the string into an array.
    //Commas in the XML file are used to
    //separate each array element.
    //This array represents one <row> in the XML document
    var rowArray:Array = rowString.split(",");
//Push the row array into the _platformMap array.
     //This creates a 2D level map
    //which will contain all 8 rows by the time the loop quits
    _platformMap.push(rowArray);
  }

  //Capture the level "name" attribute like this:
  //and store it in the _levelName variable
  _levelName = level.@name;

  //Draw the tile sheet
  _tileSheetBitmapData.draw(_tileSheetImage);

  //Add the stage bitmap
  addChild(_backgroundBitmap);

  //Run the buildMap method to convert the
  //map's array data into a visual display
  buildMap(_platformMap);

  //Add the status box
  addChild(_statusBox);

  //Update status box
  _statusBox.text = "XML MAP:";
  _statusBox.text += "
" + "NAME: " + _levelName;
  _statusBox.text += "
" + "TILE SIZE: " + MAX_TILE_SIZE;
   _statusBox.text += "
" + "MAP_ROWS: " + MAP_ROWS;
  _statusBox.text += "
" + "MAP_COLUMNS: " + MAP_COLUMNS;
}

Refer to Chapter 8 if you have any questions about how the rest of the tile-based game engine works, including the buildMap method.

Now you've seen how to load one level map, but most games have many levels. You can use the same techniques to load multiple maps into the game, and switch levels as needed

Creating multiple game levels with XML

In the MultipleXMLMaps folder, you'll find an example of how to use XML to load multiple level maps into a game. Run the SWF, and you'll see that the result is the same as the SwitchingLevels example we looked at in Chapter 8, as shown in Figure 10-9. The underlying code that switches levels is identical. The difference is that all three game levels are loaded from a single XML file.

Multiple game levels made from a single XML file

Figure 10.9. Multiple game levels made from a single XML file

You'll find the maps.xml file that stores these levels in the assests/xml folder. Recall from Chapter 8 that we needed two maps for each level:

  • A map that contains the platforms

  • A map that contains the game objects

You can see in the following code that each level contains these two types of maps.

<maps>
  <level>
    <name>Platform Pandemonium!</name>
    <platformMap>
      <row>10,10,10,10,10,10,10,10,10,10</row>
      <row>00,00,10,10,10,23,10,10,10,10</row>
      <row>10,10,10,10,01,01,01,10,10,10</row>
      <row>10,10,10,10,10,10,10,10,01,10</row>
      <row>10,10,10,10,10,10,01,01,01,10</row>
      <row>10,10,10,10,01,01,01,01,01,10</row>
      <row>10,10,00,10,10,10,10,10,10,10</row>
      <row>00,00,00,00,00,00,00,00,00,00</row>
    </platformMap>
<gameObjectMap>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,20,−1,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
    </gameObjectMap>
  </level>
  <level>
    <name>Extensible Mark-up Mayhem!</name>
    <platformMap>
      <row>10,10,10,10,10,10,10,10,10,10</row>
      <row>10,10,01,01,10,10,01,10,01,10</row>
      <row>10,10,10,10,10,10,10,10,10,10</row>
      <row>23,10,10,00,10,10,01,01,01,10</row>
      <row>00,00,10,00,00,10,10,10,10,10</row>
      <row>10,10,10,00,00,00,00,10,10,10</row>
      <row>10,10,10,10,10,10,10,10,10,10</row>
      <row>00,00,00,00,00,00,00,00,00,00</row>
    </platformMap>
    <gameObjectMap>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row><row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,20</row>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
    </gameObjectMap>
  </level>
  <level>
    <name>Kittykat Katastrophe!</name>
    <platformMap>
      <row>10,10,10,10,10,10,10,10,10,10</row>
      <row>10,10,00,00,10,10,10,10,10,10</row>
      <row>10,01,10,00,00,00,00,10,10,10</row>
      <row>10,10,10,00,23,10,10,00,10,10</row>
      <row>00,10,10,00,00,00,10,10,00,10</row>
      <row>10,01,10,10,00,00,01,10,10,10</row>
      <row>10,10,10,10,00,10,10,10,10,10</row>
      <row>00,00,00,00,00,00,00,00,00,00</row>
    </platformMap>
    <gameObjectMap>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
<row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,−1,20,−1,−1,−1,−1,−1,−1,-1</row>
      <row>−1,−1,−1,−1,−1,−1,−1,−1,−1,-1</row>
    </gameObjectMap>
  </level>
</maps>

Here's what our AS3.0 code needs to do to make sense out of all this data:

  • Two-dimensional arrays for each map: Two types of maps are needed to create one game level: platform maps and game object maps. There are three of each type, for a total of six. Each of those six maps needs to be its own two-dimensional array. The code needs to loop through the XML data and create a two-dimensional array for each map.

  • Container arrays for each map type : The code needs to separate the platform maps from the game object maps so that it can work with each type separately. This means it must create two more container arrays: one for each map type. The result is that there will be one array containing all the two-dimensional platform map arrays and another array containing all the two-dimensional game object map arrays.

  • The current maps for the game level to display : The code needs to know which maps are current for the game level it will display.

If all this sounds a little confusing, take a look at Figure 10-10 which illustrates how all this data is organized. It's a bit like a row of Russian babushka dolls, with arrays inside bigger arrays, inside bigger arrays.

The process of converting the XML file into usable game level arrays

Figure 10.10. The process of converting the XML file into usable game level arrays

Let's put all our hard-won XML skills to work and take a look at the code that does this. Keep the diagram in Figure 10-10 close at hand as we walk though the code. It will make everything much easier to understand.

It all happens in a method called createXMLMaps. The method is long, but that's mostly because it repeats a lot of the same code twice: once for the platform maps and once for the game object maps. I've kept this repetitive code just to make everything a bit more understandable, but feel free to compact it.

Here's the entire createXMLMaps method so that you can see everything in context. I'll explain it in detail in the pages ahead.

//...Embed the XML file as in the previous examples

public function createXMLMaps():void
{
  //Load the XML file
  var maps:Object = new XmlData();

  //Cast it as an XML object
  maps = XML(maps);

  //Find out how many maps there are
  //and how many rows there are in each map
  var numberOfMaps:int = maps.level.platformMap.length();
  var numberOfRows:int = maps.level.platformMap.row.length();
  var rowsPerMap:int = numberOfRows / numberOfMaps;

  //Load the platform maps

  //Create empty map arrays for each map and store them in the
  //the _platformMapContainer array
  for(var i:int = 0; i < numberOfMaps; i++)
  {
    var platformMap:Array = [];
    _platformMapContainer.push(platformMap);
  }

  //Variables needed to build the maps
  var rowCounter:uint = 0;
  var mapCounter:uint = 0;
  var row:XML;
  var rowString:String;
  var rowArray:Array;
//Loop through each platformMap.row element
  var platformRowList:XMLList = maps.level.platformMap.row;
  for each(row in platformRowList)
  {
    //Convert the row text node into a string
    rowString = row.toString();

    //Convert the string into an array.
    //Commas separate each array element
    rowArray = rowString.split(",");

     //Push the rowArray into one of the
    //platform map arrays
    _platformMapContainer[mapCounter].push(rowArray);

    //Add one to the row counter
    rowCounter++ ;

     //If the rowCounter is divisible
    //by 8 (the value of rowsPerMap)
    //then you know you've reached the first row of the next map.
    // Increase the mapCounter by one
    if(rowCounter % rowsPerMap == 0)
    {
       mapCounter++;
    }
  }

  //Load the game object maps.
  //This code is almost identical to the code above that
  //loaded the platform maps

  //Create empty map arrays for each map and store them in the
  //the _gameObjectMapContainer array
  for(var j:int = 0; j < numberOfMaps; j++)
  {
    var gameObjectMap:Array = [];
    _gameObjectMapContainer.push(gameObjectMap);
  }

  //Reset the row and map counters so they
  //can be used again
  rowCounter = 0;
  mapCounter = 0;
//Loop through each row element in the
  //game object maps
  var objectRowList:XMLList = maps.level.gameObjectMap.row;
  for each(row in objectRowList)
  {
    //Convert the row text node into a string
    rowString = row.toString();

    //Convert the string into an array.
    //Commas separate each array element
    rowArray = rowString.split(",");

     //Push the rowArray into one of the
    //game object map arrays
    _gameObjectMapContainer[mapCounter].push(rowArray);

    //Add one to the row counter
    rowCounter++

     //If the rowCounter is divisible
    //by 8 (the value of rowsPerMap)
    //then you know you've reached the first row of
    //the next map. Increase the mapCounter by one
    if(rowCounter % rowsPerMap == 0)
    {
       mapCounter++;
    }
  }

  //Load the names of each level
  //by looping through all the <name> elements
  //and pushing them into an array
  var nameList:XMLList = maps.level.name;
  for each (var name:XML in nameList)
  {
    _levelNames.push(name.toString());
  }

  //Set the current maps for the first level
  //that you want to be displayed
  _currentPlatformMap = _platformMapContainer[_currentLevel];
  _currentObjectMap = _gameObjectMapContainer[_currentLevel];
}

The code first creates a maps XML object in the same way that we created XML objects in previous examples.

var maps:Object = new XmlData();
maps = XML(maps);

Its name is maps because <maps> is the first element in the XML file.

We then need to figure out how many levels there are in the XML file. We can find this out by calling the length method of the <platformMap> element.

var numberOfMaps:int = maps.level.platformMap.length();

The length method will tell us how many <platformMap> elements there are in the XML file. There happen to be three. We can use this value for a few different things, such as allocating the number of arrays we'll need to make to store these maps. (The number of platform maps is the same as the number of game object maps, so we can use this value for both kinds of maps.)

Next, we need to determine how many rows the <platformMap> elements have. We can find this out by calling the <row> element's length method.

var numberOfRows:int = maps.level.platformMap.row.length();

This will tell us the number of rows that all the <platformMap> elements have combined. There are three <platformMap> elements, and each contains eight <row> elements. This results in 24, but that is a rather useless number. What we really need to know is how many rows each map has. We can easily find this out by dividing the numberOfRows by the numberOfMaps.

var rowsPerMap:int = numberOfRows / numberOfMaps;

Now we know that there are three platform maps and each has eight rows. (Again, these numbers apply to both of the game object maps, because their structure is the same.)

Next, the code starts building the platform map arrays. The first thing it does is to create three empty arrays inside the _platformMapContainer. Remember that the _platformMapContainer is a container array that holds the all the two-dimensional platform map arrays. Refer back to Figure 10-10, and you'll see it in step 2. The following code creates three empty arrays inside that container array.

for(var i:int = 0; i < numberOfMaps; i++)
{
  var platformMap:Array = [];
  _platformMapContainer.push(platformMap);
}

This gives us an empty structure to work with. We can now start building each of the three arrays inside this container.

The next bit of code is a for each loop. It loops through each <platformMap> element and builds the two-dimensional array for each map. It also needs to figure out where one map ends and the next one starts.

var rowCounter:uint = 0;
var mapCounter:uint = 0;
var row:XML;
var rowString:String;
var rowArray:Array;
//Loop through each row
var platformRowList:XMLList = maps.level.platformMap.row;
for each(row in platformRowList)
{
  //Convert the row text node into a string
  rowString = row.toString();

  //Convert the string into an array.
  //Commas separate each array element
  rowArray = rowString.split(",");

   //Push the rowArray into one of the
  //platform map arrays
  _platformMapContainer[mapCounter].push(rowArray);

  //Add one to the row counter
  rowCounter++

   //If the rowCounter is divisible
  //by 8 (the value of rowsPerMap)
  //then you know you've reached the first row of
  //the next map. Increase the mapCounter by one
  if(rowCounter % rowsPerMap == 0)
  {
     mapCounter++;
  }
}

The first part of this loop will be familiar to you from the earlier example. The text node in the <row> element is copied into a string, and then each number is copied into an array using the split method.

rowString = row.toString();
rowArray = rowString.split(",");

Next, the rowArray is copied into the platformMap array. But which platformMap array? Remember that there are three of them. They're all inside the _platformMapContainer. (See Figure 10-10 for a reminder.)

To keep track of which platformMap array to use, we have a variable called mapCounter. It's initialized to 0 before the loop starts.

var mapCounter:uint = 0;

mapCounter is used as an array index number for the _platformMapContainer. If _platformMapContainer has an array index of 0, then it refers to the first map, like this:

_platformMapContainer[0]

That means that if you want to push a row array into the first platformMap array, your code will look like this:

_platformMapContainer[0].push(rowArray)

This could be a bit confusing. Remember that you're not pushing the row into the _platformMapContainer —you're pushing it into the first array that _platformMapContainer stores.

The mapCounter variable is keeping track of what the current map is, so we can use its value to refer to the correct platformMap array.

_platformMapContainer[mapCounter].push(rowArray);

But hey, there's a problem! How do we know when to increase the value of mapCounter ? How do we know when we've finished filling all the rows of one map and need to move on to the next one?

Remember that each map has eight rows. All we need to do is count the rows. If they exceed eight, then we know we can move on to the next map by increasing mapCounter by one. The code could look something like this:

if(rowCounter > 7)
{
  mapCounter++;
  rowCounter = 0;
}

In other words, "If we've counted more than eight rows (starting at zero), then increase the mapCounter by one and reset the rowCounter."

This code will work perfectly well. But, in the interest of showing you another use of the oh-so-talented modulus operator (% ), this is the code I've actually used.

if(rowCounter % rowsPerMap == 0)
{
  mapCounter++;
}

Imagine that the loop has run eight times, so that rowCounter has a value of 8. It just so happens that rowsPerMap also has a value of 8. The code will look like this:

if(8 % 8 == 0)
{
  mapCounter++;
}

This is another way of saying, "If the remainder of 8 divided by 8 is 0, we're at the start of a new map. Increase mapCounter by one."

Here's the important part: only the first row of each new map will ever be evenly divisible by eight. That means that this will by true if the rowCounter is 16, 24, 32, or any multiple of 8.

At the first row of the second map, rowCounter will equal 16, and mapCounter will be increased by one again, and thus a different platform map array will be referenced. As long as all our maps have eight rows, this code will work.

And that's how our three platformMap arrays are built. At the end of the loop, all three are safely tucked away in the _platformMapContainer array.

The rest of the code builds the three gameObjectMap arrays in exactly the same way and copies them into the _gameObjectMapContainer array.

Next, the code needs to decide which maps from each container array will be used to build the first level. The maps that are currently displayed are stored in the _currentPlatformMap and _currentObjectMap arrays. Here are the two directives that do this:

_currentPlatformMap = _platformMapContainer[_currentLevel];
_currentObjectMap = _gameObjectMapContainer[_currentLevel];

_currentLevel is set to 0 when the game initializes. This means the first map arrays from both containing arrays will be displayed first. (Refer to Figure 10-10 to see how this fits into the bigger picture.) When the game engine switches levels, _currentLevel is increased by one, and the next maps in the container arrays are set as the current maps.

The last thing that this code does is to make an array to store all the level names. The XML file has an element called <name> that contains all the names of the levels.

<name>Platform Pandemonium!</name>
...
<name>Extensible Mark-up Mayhem!</name>
...
<name>Kittykat Katastrophe!</name>

Note

Why didn't I create the level names as attributes, as I did in the previous examples? Just to show you that there are many different ways you can set up an XML file to store data, and no one right way. It makes no difference whether you store data as element text nodes or as attributes. The tendency is for attributes to be used to store metadata (extra information about the main data), but it's really just a matter of style, and it's completely up to you.

The example SWF displays these names in the status box, and they match the currently displayed level. Here's the code that loops through the <name> elements and copies their text nodes into an array.

var nameList:XMLList = maps.level.name;
for each (var name:XML in nameList)
{
  _levelNames.push(name.text());
}

Because all the names are now in an array, we can use the same _currentLevel value that's used to switch levels to also display the correct level names. The level name index numbers will always match the correct map index numbers because they were pushed into their arrays in the same order. Here's how the status box uses this feature to display the correct level name:

_statusBox.text
   += "
" + "LEVEL NAME: " + _levelNames[_currentLevel] ;

And now you know how to use XML to create game levels!

Note

You might be wondering how XML fits into the MVC framework that we've used so often in this book. It's associated with the model. The model's job is to store game data. That means you should load an XML file into the model when the game initializes. The main application class could load the XML file, and pass the XML object to the model as an argument when it builds the game's MVC system.

As you've seen, XML is very useful for storing game data. It's really the perfect data-storage solution for most games. I've shown you a few practical uses, but there are countless others. Jump right in and start using XML in your games whenever you can.

Next, let's look at one more way to load XML files.

Loading XML files at runtime

So far, I've shown you how to create XML data directly with AS3.0 code and how to embed an external XML file. These are actually the least common ways that XML is used in Flash games. Far and away the most common is to load the XML file at runtime. This is called runtime loading.

The problem with embedding an XML file with the Embed metatag is that whenever you make any changes in the XML, you need to recompile the SWF. Wouldn't it be far nicer if you could tinker with the game by just making changes to the XML, and have the SWF read the new XML data each time it runs? It would. This way, you could have a game running on a server and completely change the levels or any of the game data by just uploading a new XML file. You don't need to touch the rest of your code or recompile and re-upload the SWF.

Using URLLoader to load files

AS3.0 allows you to load files at runtime with the URLLoader class. You can use URLLoader to load any file, such as images or SWF files, not just XML files.

To load files at runtime, import the URLLoader and URLRequest class from the flash.net package.

import flash.net.URLLoader;
import flash.net.URLRequest;

First, create a URLRequest object.

var loader:URLLoader = new URLLoader();

Then use the loader object's load method and the URLRequest class to load the file.

loader.load(new URLRequest("fileName.xml"));

Because the file you're loading isn't part of the compiled SWF, your code shouldn't be dependent on it until the file has loaded. You can use the URLLoader 's COMPLETE event to tell you when the file has loaded. To use it, add a COMPLETE event listener to an URLLoader object and a matching event handler.

loader.addEventListener(Event.COMPLETE, fileLoadedHandler);

private function fileLoadedHandler(event:Event):void
{
  //Create an XML object based on the event target's data property

  var xmlData:XML = new XML(event.target.data);
  trace("File loaded");

  //Run any code that depends on this data to initialize...
}

The loaded file will be in the event.target.data property.

You'll find a working example of a simple system to load XML files using URLLoader in the LoadingXML folder in the chapter's source files.

Runtime loading security issues

There's an inevitable issue that arises when you load files at runtime: Flash Player security. As I mentioned earlier, Flash Player locks down SWF files extremely tightly so that there's no chance that they could be used to distribute worms, viruses, or spyware. This tight security is one of Flash's strengths and why it is so widely used. However, it also means that Flash Player is extremely picky about which files it allows SWFs to load.

SWF files created by Flash Professional and Flash Builder will be able to load local files without any problems. That's because they assume developers can be trusted. (Oh, if only they knew!) However, if you create a SWF file using some other IDE or test the SWF in the environment that users will be accessing it, it's very likely that URLLoader won't be able to load external files. Instead, you'll get a "security sandbox violation" error.

Here, I'll point you to some resources to help you resolve sandbox violation errors if you get them.

Problems loading files locally

If you're not using Flash Builder or Flash Professional to create a SWF, you'll need to explicitly tell Flash Player that a particular SWF is "trusted" and has the right to load external files. You can do this in two different ways: with a CFG file or by setting Flash Player's global security settings.

The CFG file is a text file with a .cfg extension. All it contains is the path to the SWF that you want to trust to load local files. In Windows, the path from the hard drive root might look like this:

C:CodeFlashGameingame.swf

In Mac OS X, you could use this format:

/Code/Flash/Game/bin/game.swf

If you want all SWF files in any given directory to be trusted, use the path to a general directory without specifying any SWF files. The following path will make all the SWF files in the Flash folder trusted and able to load local files:

C:CodeFlash

After you create a CFG file, you need to save it in the FlashPlayerTrust directory, which will be in a different place depending on your operating system. In Windows, you'll find it here:

C:WINDOWSsystem32MacromediaFlashFlashPlayerTrust

In Mac OS X, it will be here:

/Library/Application Support/Macromedia/FlashPlayerTrust

When you open the FlashPlayerTrust folder, you'll find a few other CFG files that were created by different applications.

As an alternative to creating a CFG file, you can adjust the Flash Player's security settings. The Flash Player's Settings Manager allows you specify trusted directories. Here's how:

  1. Run Flash Player and select Settings

    Problems loading files locally
  2. Click the Global Security Settings panel.

  3. Click the link that says Global security settings for content creators. This opens a new page with a Flash application running inside it. This Flash app is the actual advanced control panel for your Flash Player. Yes, it's the Flash Player program installed on your system. It's not an interface to anything on Adobe's website, even though it looks that way.

  4. Click the Edit Locations drop-down menu and select Add Location.

  5. A file browser will opening that allows you add local files or folders that your Flash Player should deem to be trusted.

Your SWF files should now be able to load local files at runtime with URLLoader.

Problems loading files remotely

If you're running a SWF on a server in one domain, and it tries to load a file from another server in another domain, you need to create a cross-domain policy file. This is an XML file that tells Flash Player which domains it can trust. Here's an example of a cross-domain policy file:

<?xml version="1.0"?>
<cross-domain-policy>
  <allow-access-from domain="www.anyDomainName.com" />
  <allow-access-from domain="www.anyOtherDomainName.com" />
</cross-domain-policy>

Files from any of the listed domains will be trusted.

Name your new policy file crossdomain.xml, and then upload it to your server's root directory. You should be able to see it if you browse for it. For example, browse to www.anywebsite.com/crossdomain.xml.

Flash Player will automatically look for a policy file with that name and in that location when the SWF loads.

You can also specify trusted domains directly within your AS3.0 code by using the allowDomain method, like this:

flash.system.Security.allowDomain("http://www.anyDomainName.com");

If it's a secure website (a site name with a URL that begins with https:// ), you need to use the allowSecureDomain method.

flash.system.Security.allowSecureDomain
  ("https://www.anyOtherDomainName.com");

This will also work for local files—just supply the path name to the local folder

flash.system.Security.allowSecureDomain("C:CodeFlash ");

If you know in advance which domains should be trusted, using allowDomain and allowSecureDomain is a good solution. However, you often don't know the details of these until the time comes to deploy the SWF. That's why using an XML policy file is useful. It allows you to change trusted sites by uploading a new policy file at any time, without needing to recompile and re-upload the SWF.

Note

Although voluminous tomes can (and have) been written about Flash Player security, the best place to start is the "Flash Player security" chapter in Adobe's online manual Programming ActionScript 3.0 for Flash (http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/). Specifically, for more information about cross-domain policy files, see the "Website controls (policy files)" chapter. To get to this chapter, follow these links: Flash Player security

Problems loading files remotely

The most important thing to remember regarding Flash Player security is to test the SWF in the same environment as users are going to be using it. Security issues often won't raise their head until you do this.

Are we there yet?

The ten chapters of this book have covered all the important topics that every Flash game designer needs to know. Is this the end of the road? Not by any means! What we've covered in this book will form the basis for a rich and creative career in game design, but it's nowhere near the end of the journey.

I have a few suggestions for where to go from here.

3D games

At the time of writing, if you want to make 3D games in Flash, you need to use a third-party API such as Alternativa3D, Away3D, or Papervision3D. This involves installing the API and learning how to use it. This is no small undertaking, so you'll need to commit to learning libraries of new methods, properties, events, and all the concepts and terminology that go along with it. You could also create your own 3D API from scratch, which would be a great learning experience, but a huge project.

You also need to be aware that the latest Flash Player at the time of this writing, 10.1, did not have true graphics processing unit (GPU) support. This means that all 3D Flash is software-rendered, which makes it quite low-tech and slow by modern standards. But if you moderate your expectations and don't expect next-gen 3D graphics, Flash can do a reasonable job. It's about on par with a Nintendo DS.

The good news is that you'll be able to apply all the general game design concepts that we've covered in this book to 3D games. You have just one more dimension to worry about. And the other good news is that there are some excellent books to help you get started:

  • The Essential Guide to 3D in Flash by Rob Bateman and Richard Olsson (friends of ED, 2010) is a complete introduction to 3D in Flash using the Away3D engine.

  • The Essential Guide to Open Source Flash Development by Chris Allen et al. (friends of ED, 2010) includes a chapter on Papervision3D.

However, watch this space! This is a very competitive, fast-changing industry, so make sure to check Adobe's website for the latest word on 3D for Flash Player (http://www.adobe.com/products/flashplayer/).

2D physics

Much of this book has been about physics (and thermodynamics, like friction) for games. For most general-purpose Flash games, what you've learned here will be all you'll need to know. You'll be able to apply the same formulae and concepts to other programming platforms as you broaden your repertoire of skills. And it has also laid the foundation for a much deeper exploration of game physics, if that's an area in which you want to specialize. But if you don't want to specialize in it and yet still take your game physics a few steps further, there's another option. You can use a physics API, like Box2D, Glaze, APE, or Fisix.

The advantage to using an API is that it will do all the math for you. Games like Angry Birds and Crush the Castle depend on detailed physics simulations of the real world. The physics are so complex that using a good API is really the only practical solution for a lone game developer or small team.

On the other hand, always consider writing as much original physics code as you can. As you've seen, most game physics can be boiled down to a few lines of fairly routine code. How many lines of code was our billiards game prototype? About 15 or 20. For most games, a big physics API is overkill and will unnecessarily eat up resources. The other problem with using an API is that you'll need to know it inside out to be able to debug easily, which can be a problem if you snag a bug in a complex project with deadlines looming. If you can't figure it out, your only hope might be for a leprechaun to whisper the answer in your ear. If you write your own physics code, you'll know it thoroughly, and debugging will be a snap.

Online multiplayer games

Creating multiplayer games are an important but vast and complex topic. This type of game development spans numerous technologies on many different platforms and requires a lot of knowledge about networking.

I strategically avoided multiplayer games in this book to put the focus on essential, core game-design techniques. However, you will inevitably want to start making online multiplayer games at some point—the temptation is just too compelling. So, let me help you out by pointing the way to some useful resources.

First, you'll need to install a multiplayer socket server. Here are some commercial servers that work especially well with Flash:

  • ElectroServer (http://www.electro-server.com)

  • SmartFoxServer (http://www.smartfoxserver.com)

  • dimeRocker (http://www.dimerocker.com)

  • Adobe Flash Media Server (http://www.adobe.com/devnet/flashmediaserver)

These servers are specialized for Flash games, but each has different strengths and weaknesses, so research them carefully before jumping in. They're all very powerful, robust products that can handle tens of thousands of simultaneous connections.

If you don't want or need to spend money on a commercial socket server and want to try something new, you can write your own. You'll need to learn a bit of Java, but as it's very closely related to AS3.0, that won't be a major hurdle. A good place to start is this Broculus.net tutorial (http://www.broculos.net/tutorials/how_to_make_a_multi_client_flash_java_server/20080320/en). It will show you how to write a Flash chat application using a Java socket server.

Further reading

There are many excellent books available to help inspire you further. Here are a few that I recommend:

  • The Essential Guide to Flash Games: Building Interactive Entertainment with ActionScript by Jeff Fulton and Steve Fulton (friends of ED, 2010) provides a detailed and comprehensive look at the making of ten complete Flash games, from the ground up. It offers unparalleled insight into building professional Flash games, and is a perfect complement to this book.

  • Foundation ActionScript 3.0 Animation: Making Things Move (friends of ED, 2007) and AdvancED ActionScript 3.0 Animation (friends of ED, 2008) by Keith Peters are both rich resources of scripted animation, math, and AI algorithms that are usable for all types of games. AdvancED ActionScript 3.0 Animation has a chapter on building isometric (pseudo 3D) environments for tile-based games and another on AI steering behaviors.

  • Real Time Collision Detection by Christer Ericson (Morgan Kaufmann, 2005) is the final word on collision detection for 2D and 3D games. This textbook-style book is quite math-heavy, but oh, what wonderful math it is! The sample code is written in C++, but since it's a close cousin of ActionScript, you should have no difficultly comprehending it. If you just need to implement a BSP tree in your next game, you'll find out how to do it here.

Where to next?

Where you go from here is entirely up to you. You now have all the skills you need to start making some amazing Flash games. The rest is just practice, experimentation, and—most of all—imagination.

Flash game designers tend to be independent developers working alone or in a small team. How can a lone video game designer with a tiny budget compete with the likes of Electronic Arts, Activision, or Ubisoft, all of whom employ thousands and have Hollywood-sized budgets? The answer is that great game design is not about technology. It's about ideas. Just one original game idea can completely transform the industry.

Video games are such a relatively new medium that the best game ideas just haven't been thought of yet. What could they be, and who will come up with them? The Bachs, Picassos, and Shakespeares of the game world have yet to make their mark. But you can be sure that they will come from the ranks of independent game designers, and there's no reason why you can't be one of them.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.90.235