00:00
00:00
Newgrounds Background Image Theme

YubaOfficial just joined the crew!

We need you on the team, too.

Support Newgrounds and get tons of perks for just $2.99!

Create a Free Account and then..

Become a Supporter!

As3: External Text Files

1,095 Views | 1 Reply
New Topic Respond to this Topic

As3: External Text Files 2013-06-28 01:45:56


Fun with External Text
AS3: Main

Using: AS3, any text editor, Cutscene.xml
You should be familiar with the file format you choose to use.

External text files are used by me to centralize a lot of data that might need editing. Specifically, it's easier for me to edit things when there isn't a bunch of code to sift through. This tutorial will apply to any external text format (XML, JSON, txt, etc), but I have the most experience with XML.

Why do you need them?
For my most recent game and another that I'm working on now, I've been using XML files to script cutscenes. I'm also using it to store the types and locations of enemies and allies. The point is, if you have lots of hard-coded numbers, strings, positions, etc, you should consider an external text file.

Embedding
No matter what format you use, all simple text files (things notepad can make) are streams of bytes (octets). So the embed tag will always be the same:

[Embed(source="filename.fileformat", mimeType="application/octet-stream")]
private const MY_TEXT_FILE:Class;

When you instantiate MY_TEXT_FILE, the result is a ByteArray(Asset). From here, it differs on how to get the data out. AS3 has native XML handling (E4X): you create a new XML object and pass the ByteArray as a parameter to be parsed to XML.

var x:XML = new XML(new MY_TEXT_FILE());

For JSON and regular txt files, you have to manually get the string out from the ByteArray using readUTFBytes.

var s:String = myBytes.readUTFBytes(myBytes.length); // where myBytes:ByteArray = new MY_TEXT_FILE()

If you're using a txt file, you're done, you have the string you wanted. For JSON, you have to parse the string to an Object.

var o:Object = JSON.parse(s);

Parsing data
This part will vary per format and per project. Maybe all you need one game is a list of comma separated values (csv/txt), maybe you need nodes and subnodes and attributes (XML, JSON). Whatever you choose, you will need to parse that data. For reference, everything in a text file is a String; every XML attribute is a String, the content is an XML, and the nodes are in an XMLList; JSON supports Number, Boolean, Object, Array, String, and null. For txt and XML, you will need to cast the data to get the type you actually want.

Since JSON gives you a native Object whose properties match up with the JSON file, it should be self-explanatory to find the data you want. For a txt file, you would use the split method of String to separate the comma (or any delimiter) separated values. Split returns an array of everything before, between, and after the commas. After that, you would use a combination of search, match, charAt, etc. to get to the data you want.

var example:Number = jsonObject.numberIWant;
var positions:Array = csvString.split(",");

To navigate XML, you have to realize that there is also the XMLList class, which is an array of XML. Each time you use the .childNode to go deeper into the XML, the value returned is an XMLList. You can use array access[] to get to the specific XML node you want. To access attributes, you put a "@" before the name of the attribute. If you try to access the attribute of an XMLList, it will concatenate the attributes of each node. You can also get all nodes with a specific attribute using E4X magic. The length FUNCTION of XMLList allows you to iterate over it like a normal array.

//<names>
//    <n name="Nick" />
//    <n name="MSGHero" />
//    <n name="Nick" />
//</names>
var x:XMLList = names.n;
trace(x.@name, x[0].@name, x[1].@name, x.length, x.length()); // "NickMSGHeroNick","Nick","MSGHero",undefined,3 <-- "length()" is a function, "length" is just another node name
trace(x.(@name=="Nick"); // traces an XMLList containing nodes 0 and 2 since their attributes match the condition

When nodes and attributes don't actually exist, undefined is returned, not null or an error. To check if something is NOT undefined, you have to use "!=" rather than "!==" which I learned the hard way.

trace(x[0].@age, x[0].@name != undefined); // undefined,true

My level data XML is organized into rows of numbers, exactly like an array; I then loop through each row and look at each character to form the actual level array.

//<level id="0">
//    <row>000</row>
//    <row>011</row>
//    <row>012</row>
//</level>
var rows:XMLList = level.row;
for each xml:XML in rows
   var s:String = xml.toString()
   for each character in the string
      levelArray[i][j] = int(character); // level data, properties of an object, whatever you need

Response to As3: External Text Files 2013-06-28 01:47:20


Continued...

What can I do with this?
Using these tips, you can access the data you put in an external file. But what kind of stuff could you actually put in those files, and how useful could it really be? The XML file I provided up top is a sample of one that I'm actually using right now. I'll go through it, explaining why I chose some things and how the code works with it. Take this as an example of one usage of external text files, not necessarily a how-to.

This file is one of my cutscenes. Rather than hard-coding anything in AS3, these XML nodes function as commands that my CutsceneState then performs actions from. It's organized into levels, which correspond to another XML file which contains elevation data, and cutscenes, which are just numbered as they appear in that level.

After the cutscene id, there are a lot of nodes, each telling the engine to do something. The "pos" node gets parsed first; it contains the initial positions of each character. After that, most of the rest of the nodes have the same name: this is so I can make a single XMLList of commands.

var commands:XMLList = cutscene.c;

The first command is "cam" for camera movement. I want the camera to either "pan" to a location, "set" to a location, or "follow" someone who is moving. I don't want to update the camera movement each frame with XML, I want the XML to tell another class how it should be updated. For each of these conditions, the rest of the attributes might differ: I might want to specify a duration of the pan, and I'd definitely have to specify a unit for the camera to follow.

The next command is "fade." I specify either "in" or "out" and a duration (there is also a default duration in case @dur is undefined). Previously, I had one node per unit, but I realized I could combine similar commands on different units by using a csv and parsing and looping through that (like parsing a txt file!) in order to have the action occur on each unit.

Next is "move." Here I have 4 possible attributes, x,y,byX,byY. x and y are for when I want the unit(s) to move to the x,y location. byX and byY are for when I want to add to his current position. I use another undefined test to check which ones are actually specified (obviously there can't be x and byX, but there can be x and byY). Protip: I don't specify the exact coordinate to move to; my game is tile-based with a constant tile size, so I divide everything by that number. Instead of x="200", with a tile size of 50 I say x="4". Either way, this gets sent to a MoveManager which then handles pathfinding and stuff. If you want a specific path that the pathfinder won't grind out, you would need multiple move commands using waypoints of some sort. I have a queue that concatenates additional m nodes if that unit is already moving.

I repeat fade and move because I can.

Next is "dia" for dialogue. All I need is a speaker and text. I put those into a Dialogue class, then put all those into a Vector and have a DialogueManager handle the rest.

Then finally, I have a "next" node. When I run out of "c" nodes (explained in a sec), it looks at "next" to see what to do next. If there's another cutscene after this one, it specifies the "level" and cutscene "id" to load next. If the cutscene is really just a continuation of the current one (same level, same units, same positions), then I have a "same" attribute that I check for and I leave out specifying positions in the next cutscene. Otherwise, I have a "goto" attribute which tells it what state to move to next, like the battle state or whatever.

The nodes and attributes are really up to your creativity. I could also have a "play" command which plays an audio "file" at a certain "volume", or an "anim" command to change someome's animation.

The Engine
So the XML is great and all, but how does MY engine work? MY is emphasized cuz it's really up to what you want and need. This part I do sorta recursively with a nextCommand() function; using a for loop wouldn't work since you probably want your commands to take time (I use a for loop when I want to skip all commands). For each command type I have, I have an int keeping track of the current one I'm on. For instance, if 2 "move" commands have passed, currMove would be equal to 2. When I add up all the indices of current commands, I get the exact node within the commands XMLList that should be run next. With the example file, if currCam=1, currFade=1, currMove=1, currDia=0:

var currCommand:XML = commands[currCam + currFade + ...];

Then I check currCommand.@cmd, which will be equal to "dia", a dialogue command. After the DialogueManager sends a DIALOGUE_COMPLETE event or sets inDialogue to false or whatever, currDia++ and then nextCommand() is called, bringing it back to the top and moving on to the next command. Similarly, after ALL_MOVEMENT_COMPLETE is dispatched, currMove++ and then nextCommand() again. However, and once again this is purely for me, I don't want to wait for the camera to finish panning or units to finish fading before the next command should get called. So for these commands, which are both tween-based, I currFade/currCam++ right after setting the tween and then nextCommand() rather than waiting for the command to tell me it's finished. Since XMLList is basically an array but not quite, after the final "c" command gets run and the function gets called again, I get a null (rather than an error). So when currCommand==null, I call endCutscene(), which then checks the "next" node I mentioned earlier.

To skip an entire cutscene, I use a for loop over commands.length() but starting from the command I'm at now (skip the rest of the current command, then skip the following commands). I can ignore dialogue. The rest I either have a "set" method or I specify a 0 duration tween. The final result is that everyone and the camera are in their final positions with their final alpha value right after I press skip.

Conclusions
The only hard-coded bits in the cutscene engine are the names of each node and command. Other than that, everything that I need is specified in the XML. Where units should go, what to do and in what order, and then what state to go to once the cutscene is finished. But I also have an XML file for level data. And I have another one that specifies where units should be positioned in battle and what level the enemies will be. And I have yet another one that holds the data for each unit, like base stats, descriptions, and whatnot. I've hard-coded values in the AS before, and I definitely think this way is much easier to edit and add to. It also makes your code so much more organized (cf. my Juggernaut I Post Mortem). That's really what it's all about: organization, helping future you out a bit.

And getting mad honies, bitches love external text files and organization.