Scripting and managing state of events in a Flixel game.

Well, that’s what a next post will be about…once I figure out a way!

 

Events are like:

 

Enter a room, but freeze the controls, lock the doors, change the music and spawn enemies.

 

 


sprite placement in new game.

I wrote earlier about wanting to have certain sprites (enemies, locked doors, etc), have state that is persistent in a number of levels – screen-local (respawning when leaving/entering a room), map-local (enemies don’t respawn tiill you leave a dungeon and re-enter), and global (bosses stay dead, unlocked doors stay open!)

I kind of figured out a way to deal with this.  I never sucked it up to figure out some stuff with the wonderful DAME exporter (an awesome map editor for flixel/flashpunk/etc games) and its lua exporter business…but then I did, sort of, at least enough to make it kind of do what I wanted.

Every map I make is split into grids, because that’s how I want the camera to work. I also only want to load sprites on a certain grid. Currently when I export the sprites from DAME, they end up all under one XML <level> node. This is fine, but on a big map when I want to load certain sprites for a grid, I’d have to calculate every single object’s grid x and y coordinates!! Although that would probably work fine, that’s not as nice. So, I did the following:

After embedding the XML from DAME into my game, I iterate through its <level> nodes (remember, level nodes just correspond to contiguous “areas” in my game – perhaps a floor of a dungeon, someone’s house, etc.). I create a new XML (one that will be serialized/saved and later checked to determine state of a room) object, and create a new <level> node. For every object (enemy, door, etc) in the <level> node of the DAME XML, I convert its x and y coordinates into grid-coordinates, so [0,screen_width) -> 0, [screen_width,2*screenwidth) -> 1, etc. I check if I’ve already added a <grid> node to my new <level> node, if so, I append the object to the grid node, otherwise, make a new grid node, append it to the level node, and append the object to the grid node.

Conceptually I guess this wasn’t too unwieldy, but it took a little time to figure out the syntax and what not.

This DAME-generated XML:

<root>
	<level name="Dungeon_Test">
		<Slime x="96" y="32" p="1" alive="true" />
		<Slime x="128" y="32" p="1" alive="true" />
		<Slime x="80" y="32" p="1" alive="true" />
		<Slime x="176" y="224" p="1" alive="true" />
		<Slime x="224" y="368" p="1" alive="true" />
		<Slime x="80" y="256" p="1" alive="true" />
		<Slime x="16" y="336" p="1" alive="true" />
		<Slime x="32" y="352" p="1" alive="true" />
	</level>
	<level name="Area_X">
		<Slime x="80" y="80" p="1" alive="true" />
		<Slime x="0." y="32" p="1" alive="true" />
		<Slime x="16" y="112" p="1" alive="true" />
		<Slime x="48" y="32" p="1" alive="true" />
		<Slime x="48" y="80" p="1" alive="true" />
	</level>
</root>

is transformed to this XML (which will end up being serialized and used in the game):

<root>
  <level name="Dungeon_Test">
    <grid grid_x="0" grid_y="0">
      <Slime x="96" y="32" p="1" alive="true"/>
      <Slime x="128" y="32" p="1" alive="true"/>
      <Slime x="80" y="32" p="1" alive="true"/>
    </grid>
    <grid grid_x="1" grid_y="1">
      <Slime x="176" y="224" p="1" alive="true"/>
    </grid>
    <grid grid_x="1" grid_y="2">
      <Slime x="224" y="368" p="1" alive="true"/>
    </grid>
    <grid grid_x="0" grid_y="1">
      <Slime x="80" y="256" p="1" alive="true"/>
    </grid>
    <grid grid_x="0" grid_y="2">
      <Slime x="16" y="336" p="1" alive="true"/>
      <Slime x="32" y="352" p="1" alive="true"/>
    </grid>
  </level>
  <level name="Area_X">
    <grid grid_x="0" grid_y="0">
      <Slime x="80" y="80" p="1" alive="true"/>
      <Slime x="0." y="32" p="1" alive="true"/>
      <Slime x="16" y="112" p="1" alive="true"/>
      <Slime x="48" y="32" p="1" alive="true"/>
      <Slime x="48" y="80" p="1" alive="true"/>
    </grid>
  </level>
</root>

By this chunk of AS3 code (FYI, there are some static thingies from me playing around calling this function from elsewhere, also saveXML needs to be referenced globally)


[Embed (source =  '../xml/Seen.xml', mimeType = "application/octet-stream")] public static const EmbedXML:Class;
public static var embedXML:XML = new XML(new EmbedXML);
public static var saveXML:XML = <root> </root>;

public static function embed2saveXML():void {

    var savelevel:XML; //Will be a level node for our saveXML tree
    var o:XML; //Generic object XML
    var level:XML; //For each "level" (house/dungeon/whatever)
    var grid:XML; //Represents a "grid"
    var grid_exists:Boolean; // Set to true when an existing grid is found
                             // while iterating through some level's grids
    var name:String;
    var x:int; var grid_x:int;
    var y:int; var grid_y:int;

    //Every level corresponds to a floor of a dungeon,
    //a house, the world map, etc.
    for each (level in embedXML.level) {
        savelevel = <level/>;
        savelevel.@name = level.@name;
        // These are all the DAME objects.
        for each (o in level.*) {
            //convert XY into Grid X,Y.
            x = parseInt(o.@x);
            y = parseInt(o.@y) + HEADER_HEIGHT;
            grid_x = x / Registry.SCREEN_WIDTH_IN_PIXELS;
            grid_y = y / Registry.SCREEN_HEIGHT_IN_PIXELS; 

            // Append the object "o" to the grid node if it already exists.
            grid_exists = false;
            for each (grid in savelevel.grid) {
                if (grid.@grid_x == grid_x.toString() && grid.@grid_y == grid_y.toString()) {
                    grid.appendChild(o);
                    grid_exists = true;
                    break;
                }
            }
            // Otherwise, create a new grid node and append "o" to it.
            if (!grid_exists) {
                grid = <grid/>
                grid.@grid_x = grid_x.toString();
                grid.@grid_y = grid_y.toString();
                grid.appendChild(o);
                savelevel.appendChild(grid);
            }
        }
        //Finally, append the transformed level node to the serialized XML.
        saveXML.appendChild(savelevel);
    }
}

Is there a better way to do this? Probably! But this works…it seems. Of course, more attributes will have to be added and what not to deal with state, but this is a good base for that.


inspiration dave: platformer movement

A little background, first – I finished my first “real” Flixel game last week, “Inspiration Dave” – it’s a maybe hour-few hour long platformer driven by mechanics of par times and collectible items, as well as silly unlockable secrets…mostly a game to get my feet wet in starting and finishing a game…cutting my teeth, so to speak. Had to fit dev time in between all the schoolwork and obligations I’ve put upon myself, but I managed to put a good chunk of time in over the past 7/8 weeks. Possibly more than I spend on the other stuff. I’m hoping some sponsor will find it in their heart to host it before I give up and just throw it on a bunch of sites. But what matters is that I finished it.

Anyways, player movement. It was a fun thing to think about and implement.

Inspiration Dave started as my first ludum dare entry and attempt at making a game. It was terrible. The difficulty was ridiculously hard and the controls were static and merciless….but I thought it had concepts that could have been expanded on. Anyways, I mostly want to talk about the controls.

My first attempt at controls was this terrible kludge of if statements…it worked, sort of, but putting any sort of flexibility was a massive pain, so I just rehauled the whole thing as a state machine diagram. This was considerably easier. The controls are by no means perfect, but they’re decent for the game I think (You can be the judge if you play it some day!).

Dave has a few states – still, walking, running, jump from walk, jump from run, air drag after walk, air drag after run. By putting Dave in various states, I could transition to other states when certain conditinos are met, thus making debugging movement orders of magnitude more simple. For example, in S_STILL (still state), there are no buttons pressed. Dave has his normal standing frame. If left is pressed, we go to S_WALK, where input is handled – setting velocity to a predetermined number, and so forth. This is more or less how the rest of movement is handled. When Z is held, Dave enters S_RUN – jumping, S_RUN_JUMP, and letting go of directional keys puts you in the air drag state, where you’re slowed down horizontally in air.

These variables were tweaked until things felt okay. One of my biggest issues was figuring out how exactly to change velocity when Dave moves left and right in midair – in some platformers, the fairness of precision jumping (single width block to the next) is a matter of how well this velocity change is taken into account. If you just switch the sign on the velocity, suddenly you’re traveling the other direction and going to miss the platform and must mash left and right to land, if you don’t give enough velocity when switching, it’s easy to go too early or late and be screwed.  I think I ended up using some sort of halved velocity with deceleration in my movement scheme. It seems to feel fine.

And that was movement.

A lot had to be tweaked to fit in with the level design – precision jumping between spikes is needed, and we need to give an intuitive way to control jump height, which was handled simply with variable jump height. This way, you can make a short-distance hop, or a short-distance jump over something high, etc.


Screen transitions, legend of zelda gbc games

So, a favorite set of games of mine has always been the legend of zelda GBC games (link’s awakening, oracle of seasons, oracle of ages – yes, LA was on the Game boy originally but they’re similar in gameplay mechanics/graphics)

Image

The game uses 16×16 tiles, and every room is 10 by 8 tiles. Pretty amazing if you ask me. Dungeons and the map are laid out in a grid of these 10×8 tile rooms. It’s quite elegant how they did this and managed to well, make it work on something so tiny. I’m pretty impressed!

I was interested in using a similar type of map system in my next game, and got thinking about what it would take to do in Flixel. Quite a fun problem to think about. Let’s see. These are my initial thoughts and attacks at the problem, maybe you’ll learn something too.

Requirements:

-Touching the edges of a map must push you a tile forward, but move the camera view an entire screen in the direction of travel.

-Enemies and events need to be able to be triggered. Unlocked doors must stay open, bosses defeated stay dead, etc. That is, each screen must be able to maintain state for some period of time

-Enemies/bullets can’t travel from one screen to the next.

Tackling this problem one step at a time:

1. Making the map appear. Using tilesets, just draw this in a map editor. For simplicity let’s say I’m going to use one tileset for my map tiles. Then I export this gigantic map as a CSV (comma separated values – like “1,2,3,4,4,4,4”) , which with a quick calculation probably wouldn’t be more than a couple hundred KB (assuming I have over 100 tiles, my screens are 8×10 tiles, and I have like a 1000 screens, which is probably overcounting by a bit). Not a memory problem, in any case. We can pick which of the tiles we want to assign certain properties. E.g., some tiles act as holes, some as spikes that do damage – we can either replace these tiles with sprites at draw-time, change their properties to be one way, etc.

A few caveats with this. Drawing the whole map is obviously a horrible idea, we don’t want to have to deal with that being redrawn constantly, and in any case it’s just inefficient. So, we’ll want to only show one part at a time, and maybe have neighboring grids in memory for easy drawing, which leads to

2. Traversing the map.

Ahead of time, we know that our screens can be represented as a grid of points – you might start at screen (4,2). Now, luckily, our screens are equal sized *and* grids! So, it’s just a matter of some array index trickery and we can pull out grid-sized chunks from our whole-map CSV. We might have small 80-member CSVs (10×8) of mappings to tiles, one for the current screen, and one for up, down, left, and right (if they exist). So, we’d create tilemaps out of these CSVs and have them drawn.  When we want to move screens, we just push our character a tile forward, and move the camera an entire screen in whatever direction. Watch a video of zelda to see how this works. It visually works quite well and you can’t really tell that your character has moved completely across the screen!

Once you’re in the new map, of course you will trash those other tilemaps from memory, and find your new neighbor tilemaps, and load those into memory.

It’s kind of like your traversing a building, but you only really can see parts at a time and process that visually. If we had to process every thing in the building at once, even though we’re not there, that would be tiring!

3. Dealing with sprite generation.

In a screen , we’ll have enemies, one-way doors, locked doors, NPCs, etc. I’m not sure 100% how I want to do this. I could create a tilemap that maps tiles to certain enemies. That is how I did pills/notes/doors/entrances in my previous game. That might be annoying on my end because I would have to hand-code this mapping. But it wouldn’t be TOO annoying. However, I would end up with an enemy/sprite whatever CSV map the same size as my map-map. The other idea is exporting data to XML and parsing that instead, but maybe that would take up more space in the XML file. Not something to worry too much over, though. In some way, there will be a way to map sprites to tiles, etc.

4.  But what if we unlock a door? NOW WHAT? OR KILL A BOSS?

There’s a few ways to go about it. My favorite at the moment would be to store a dictionary into the save file for our “state” – it would have two keys, an “area’ and “subarea” key. E.g., “Dungeon 1, screen (2,3)”. With in it is just an array of objects. When you enter a screen, this array is generated from anything that holds state – a boss that may be alive at first, but dead forever later, a teleporter that is activated only past a certain event in the game. The state of tehse objects is saved as necessary, so if the boss is dead then I’d leave some data in my array saying that the boss should be dead.
How will this be made to work? Well, there are different kinds of sprites. There are sprites that don’t really have state – say a turnstile object, or a one way door. These things are *Always* where they are on the map, no matter what! Thus we don’t label them as stateful. A boss, on the other hand, will be “stateful” – is it dead? or alive?

So, we just take our array of numbers that maps to certain sprites. I’ll have to hard code whether a sprite is stateful, but if it’s not, we stick it on the screen. Otherwise, we look into our saved data for the specific screen’s  “state”. If it doesn’t exist, this means we have visited the screen for the first time, and so everything should have  default behavior ( a door should be locked, the boss should be there, etc.) If the entry in our large dictionary IS there, we use the state that we stored (maybe one door is open, a wall is broken,…)

This very well might be what I go with, it seems like it would work quite nicely. I shouldn’t need to store more than a few things per screen, anyways, so storage shouldn’t be an issue.
5. Enemies shouldn’t be able to move between screens! Or maybe they should for scary effects!

Not too hard. Just set bounds on where it can move based on what screen it spawns on. Maybe set “max/min x/y” value when you create the enemy object ,and don’t let it move outside. Likewise with bullets!

—–

So that lets us create dungeons, bosses, and some basic state. There is, of course, the question of how to handle global state (what dungeons are open? What events have I triggered? Etc), but that comes more into play with the actual game, I’d say.

An elegant way of handling events would be what to think about next, I think…

 

annnnd it kind of works, screen moving. I have a LOT to think about still, though,.