-
Notifications
You must be signed in to change notification settings - Fork 193
Tutorial: Multiple Rooms
Note: This Tutorial is currently designed to work with Flixel version 1.52 – it may or may not work in other releases.
So several people have asked about making multiple rooms in a game and letting the player move back and forth between them.
A lot of the time, it seems like using a separate State for each room might be the way to go, but it’s not really. The best way to pull this off is to have your PlayState swap out the rooms whenever the player touches a door. It’s really not as hard as it sounds, but it can be a little complicated. Here’s how I solved the problem.
I tried to streamline this as much as I could, and I’m sure it can be streamlined a lot further if anyone wants to take the time to do it. In my test project, I simply started with a new project and ran the Flx.py script to get started, and then followed these steps. You can either try to do your own mini test project, or incorporate my code into your own project.
We need to make 2 maps.
I use Flan, but you can use whatever program you want to generate some Comma separated files.
I set up each map to have 3 layers: Walls, Loot, and Doors. Each of these layers is going to get it’s own txt file.
Here’s what each of my files looks like:
Map-01-Walls.txt:
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
Map-01-Loot.txt
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,1,0,1,0,1,0,1,0,1,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,1,0,1,0,1,0,1,0,1,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Map-01-Doors.txt
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Map-02-Walls.txt:
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
Map-02-Loot.txt
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Map-02-Doors.txt
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Whew!
In your PlayState, you need to import all of these maps:
[Embed(source = 'map-01-walls.txt', mimeType = "application/octet-stream")] private var Map01Walls:Class;
[Embed(source = 'map-01-loot.txt', mimeType = "application/octet-stream")] private var Map01Loot:Class;
[Embed(source = 'map-01-doors.txt', mimeType = "application/octet-stream")] private var Map01Doors:Class;
[Embed(source = 'map-02-walls.txt', mimeType = "application/octet-stream")] private var Map02Walls:Class;
[Embed(source = 'map-02-loot.txt', mimeType = "application/octet-stream")] private var Map02Loot:Class;
[Embed(source = 'map-02-doors.txt', mimeType = "application/octet-stream")] private var Map02Doors:Class;
So, for our demo, we’re going to have a few variables stored in PlayState.
private var Rooms:Array; // an array to hold all of our rooms
private var lyrWalls:FlxLayer; // some layers to separate our stuff
private var lyrStuff:FlxLayer;
private var lyrPlayer:FlxLayer;
private var P:Player; // the player
private var _currentRoom:uint; // what room we're currently in
private var mapItems:Array; // all the items in this map
private var arrWalls:Array; // all the walls on this map
private var arrLoot:Array; // all the loot on this map
private var arrDoors:Array; // all the doors on this map
private var txtScore:FlxText; // the player's score
Now let’s go ahead and start working on our items. Make an image for your player, your loot item, and your doors. I made my player and my loot 1 frame each, and my doors 2 frames so I can show an ‘in’ and ‘out’ door.
We’re going to want to create our Loot, Door, and Player classes next.
LootItem.as:
package
{
import org.flixel.FlxSprite;
public class LootItem extends FlxSprite
{
[Embed(source = 'loot.png')] private var ImgLoot:Class;
public function LootItem(X:uint, Y:uint)
{
super(X, Y, ImgLoot);
}
}
}
DoorTile.as:
package
{
import org.flixel.FlxSprite;
public class DoorTile extends FlxSprite
{
[Embed(source = 'doors.png')] private var ImgDoors:Class;
public var DoorType:uint;
public function DoorTile(X:uint,Y:uint,dType:uint)
{
super(X, Y);
loadGraphic(ImgDoors, true, false, 8, 8);
DoorType = dType;
specificFrame(dType);
}
}
}
Player.as:
package
{
import org.flixel.*;
public class Player extends FlxSprite
{
[Embed(source = 'guy.png')] private var ImgGuy:Class;
private var speed:uint = 15;
public var onDoor:Boolean = false;
public function Player()
{
super(0,0, ImgGuy);
width = 4;
height = 8;
offset.x = 2;
offset.y = 0;
onDoor = false;
}
override public function update():void
{
super.update();
if (FlxG.keys.UP) velocity.y = -speed;
else if (FlxG.keys.DOWN) velocity.y = speed;
else velocity.y = 0;
if (FlxG.keys.LEFT) velocity.x = -speed;
else if (FlxG.keys.RIGHT) velocity.x = speed;
else velocity.x = 0;
}
}
}
The Player is a pretty standard 4-direction Player sprite. The only real difference is that we have this ‘onDoor’ flag which we’ll use to see if the player just went through a door or not.
Now we’re going to create our Room class. This class will be setup to hold all the information about a given room. PlayState will use these to change whats on the screen.
Room.as:
package
{
import flash.geom.Point;
import org.flixel.*;
public class Room
{
public var RoomNo:uint; // this will be used to see what room we're on
public var WallMap:String; // this will be the string used to create our FlxTilemap
public var LootDef:Array; // this is the array of all our Loot on this map
public var DoorDef:Array; // this is the array of all the doors on this map
public function Room(rNo:uint,wallMap:String,lootDef:String,doorDef:String):void
{
RoomNo = rNo;
WallMap = wallMap;
LootDef = new Array();
DoorDef = new Array();
LootDef = parseMap(lootDef);
DoorDef = parseMap(doorDef);
}
private function parseMap(mapDef:String):Array
{
// This function will take a string (one of our map.txt files) and build a
// multi-dimensional array out of it to be used later on
var tmpArr:Array = new Array();
var widthInTiles:int = 0;
var heightInTiles:int = 0;
var c:uint;
var r:uint;
var cols:Array;
var rows:Array = mapDef.split("\n");
heightInTiles = rows.length;
for (r = 0; r < heightInTiles; r++)
{
cols = rows[r].split(",");
if(cols.length <= 1)
{
heightInTiles--;
continue;
}
tmpArr.push(new Array());
if(widthInTiles == 0)
widthInTiles = cols.length;
for(c = 0; c < widthInTiles; c++)
{
tmpArr[r].push(uint(cols[c]));
}
}
return tmpArr;
}
}
}
Next, we’ll go back to PlayState to finish everything up.
We’re going to need a function “DefineRooms” which will actually create our rooms.
private function DefineRooms():void
{
// Define our Rooms
Rooms = new Array();
Rooms.push(new Room(0, new Map01Walls, new Map01Loot, new Map01Doors));
Rooms.push(new Room(1, new Map02Walls, new Map02Loot, new Map02Doors));
}
Then we’ll need a function to actually change to one of the rooms.
private function ChangeRoom(whichRoom:uint):void
{
_currentRoom = whichRoom;
//kill all our old map stuff (if any)
if (mapItems.length > 0)
{
for each(var obj:FlxCore in mapItems)
{
obj.kill();
obj.exists = false;
}
}
// Clear our Arrays
mapItems = new Array();
arrWalls = new Array();
arrLoot = new Array();
arrDoors = new Array();
//create our wall tilemap
var tmpMap:FlxTilemap = new FlxTilemap();
tmpMap.auto = FlxTilemap.AUTO;
tmpMap.loadMap(Rooms[_currentRoom].WallMap, FlxTilemap.ImgAuto, 8, 8);
tmpMap.collideIndex = 1;
arrWalls.push(tmpMap);
mapItems.push(lyrWalls.add(tmpMap) as FlxTilemap);
// parse our item arrays and place them on the map
var r:uint;
var c:uint;
var aLoot:Array = RoomscurrentRoom].LootDef.concat();
var aDoor:Array = Rooms[currentRoom].DoorDef.concat();
var tmpL:LootItem;
var tmpD:DoorTile;
// read through our Loot list, and place loot on the map
for (r = 0; r < aLoot.length; r++)
{
for (c = 0; c < aLoot®.length; c++)
{
FlxG.log(c.toString() + ", " + r.toString() + " – " + aLoot®©.toString());
if (uint(aLoot®©) > 0)
{
// place a loot item on the map.
// you can do a switch or something to place differnt loots based on the number in the map
tmpL = new LootItem(c * 8, r * 8);
arrLoot.push(lyrStuff.add(tmpL) as LootItem);
mapItems.push(tmpL);
}
}
}
// read through our Door list, and place Doors on the map
for (r = 0; r < aDoor.length; r++)
{
for (c = 0; c < aDoor®.length; c++)
{
if (uint(aDoor®©) > 0)
{
// place a door on the map
tmpD = new DoorTile(c * 8, r * 8, uint(aDoor®©));
arrDoors.push(lyrStuff.add(tmpD) as DoorTile);
mapItems.push(tmpD);
}
}
}
// move our player to the door to look like he just came in.
// this will need to be changed if you want to have multiple doors on a map to get ‘smart’
// and tell where each door leads to
P.x = arrDoors0.x;
P.y = arrDoors0.y;
P.onDoor = true;
}
So we’ll need to call “DefineRooms” when we initialize our PlayState, and then each time we want a room to be drawn on the screen, we’ll call “ChangeRoom” and pass it the room number. So our PlayState initilization will look something like this:
public function PlayState()
{
//initialize our PlayState
super();
bgColor = 0xffffffff;
lyrWalls = new FlxLayer();
lyrStuff = new FlxLayer();
lyrPlayer = new FlxLayer();
mapItems = new Array();
_currentRoom = 0;
DefineRooms();
FlxG.state.add(lyrWalls);
FlxG.state.add(lyrStuff);
FlxG.state.add(lyrPlayer);
P = new Player(); //create the player
FlxG.state.add(lyrPlayer.add(P) as Player);
ChangeRoom(_currentRoom); //go to our first room
P.x = 2 * 8; // put our player where we want him to be
P.y = 12 * 8;
FlxG.score = 0;
txtScore = new FlxText(0, 0, FlxG.width, FlxG.score.toString()); // setup our score display
txtScore.scrollFactor.x = txtScore.scrollFactor.y = 0;
txtScore.setFormat(null, 8, 0x00ff00, "center");
FlxG.state.add(txtScore);
}
Finally, let’s add the rest of our code to just take care of the player and collisions and stuff.
The only thing ‘odd’ here is that we’re checking for overlap with any of our doors – and then checking which door the player is on to determine which room to go to. When the player goes to that room, we set onDoor to TRUE until they get off the door, so that we don’t risk constantly going back and forth between rooms.
override public function update():void
{
FlxG.collideArray(arrWalls, P);
FlxG.overlapArray(arrLoot, P,GetLoot);
FlxG.overlapArray(arrDoors, P,EnterDoor);
super.update();
txtScore.text = FlxG.score.toString();
if (P.onDoor)
{
var oD:Boolean = false;
for each(var d:DoorTile in arrDoors)
{
if (d.overlaps(P)) oD = true;
}
P.onDoor = oD;
}
}
private function GetLoot(lItem:LootItem, Play:Player):void
{
if (lItem.dead) return;
FlxG.score++;
lItem.kill();
}
private function EnterDoor(dTile:DoorTile, Play:Player):void
{
if (Play.onDoor) return;
switch(dTile.DoorType)
{
case 1:
ChangeRoom(1);
break;
case 2:
ChangeRoom(0);
break;
}
}
If you want to see my example in action, or download the complete source code, you can get to it here: http://tileisle.nfshost.com/Flixel/FlxRooms/