The trick to this is really just finding a way to balance the amount of data parsing and hard-coded behavior. I'm sure that a teleporter is not the only type of behavior you'd like to add to your tile. Ideally you'll be able to add new behaviors quickly, and have an obvious way for describing them in a data format.
You didn't provide a lot of details on your tile class, so I'm gonna make some crap up. Let's assume you have some sort of base GameObject
class, which has some basic variables like position and group. I'm assuming GameObject
s are things that are generally dynamic in your game. Things like static scenery wouldn't fall under this category. Group
is just something I tacked on to illustrate the effect of checking for just the player, all NPCs, etc.
public class GameObject {
public static const PLAYER_ONLY:int = 1;
public static const ALL_CHARACTERS:int = 2;
public static const ALL_ENEMIES:int = 3;
public static const ALL_FRIENDLY_NPCS:int = 4;
public static const INTERACTABLES:int = 42;
public static const BLOCKS:int = 99;
public static const TREASURE_CHESTS:int = 20384293;
public var Group:int;
// assuming you have some Point class encapsulating X/Y coordinates
public var position:Point;
}
Now for a tile class. I'm using a library called as3signals (alternative library: TurboSignals). It basically allows you to create C#-style delegates to get around the hassle of having to use Flash's standard event system with strings and EventDispatcher. You can accomplish the same thing using the EventDispatcher method, but it's much less sexy.
The way it works is you have fields in your class that are of type Signal. Each signal has an add() function, which behaves like eventDispatcher.addEventListener, except that the listener you are adding is for that specific signal, and nothing else. It makes your events type-safe, so it's good for events where you know you will absolutely need them, such as OnObjectEnter and OnObjectLeave. Sure, you don't get the flexibility of arbitrarily defining event string literals to shoot out into the ether, but these things are pretty easy to use. And nothing stops you from using EventDispatcher in conjunction with signals.
One thing that's nice about as3signals is you can define the type of argument that you send with the dispatch. In the case of the Tile, it will send a GameObject parameter, so that whoever is interested in the event will know which game object entered the tile.
I just created two signal slots: OnObjectEnter
and OnObjectLeave
. These serve to notify other objects when a GameObject
has entered/left the tile. This is what we will use to implement the teleporter.
public class Tile {
// Useful to know when GameObjects enter and leave this tile
public var OnObjectLeave:Signal = new Signal(GameObject);
public var OnObjectEnter:Signal = new Signal(GameObject);
public var position:Point;
// Use Vector.<T>s, they're faster than plain Arrays
private var properties:Vector.<TileProperty> = new Vector.<TileProperty>();
public function addProperty(property:TileProperty):void {
properties.push(property);
property.attachToTile(this); // Because we have no double dispatch :(
}
public function Update(deltaTime:Number):void {
// This update function is kinda dumb, it just serves the purpose of
// having a starting point for reacting to an object entering the tile
if (someIndicationThatThisTileWasEntered) {
OnObjectEnter.dispatch(getObjectThatJustEntered());
}
if (someIndicationThatThisTileWasLeft) {
OnObjectLeave.dispatch(getObjectThatJustLeft());
}
for (var property in Properties) {
if (property.NeedsUpdating) {
property.Update(deltaTime);
}
}
}
}
You'll also see the properties field in there, a vector of TileProperty
s. If you want to code special behavior and be able to duplicate it in multiple areas (such as a teleportation behavior), then you subclass this. It provides an overridable Update(deltaTime)
function, information about the parent tile and an overridable function that notifies you when the parent changes (good for object pools), and a bool for toggling whether it gets Update()
called on it every frame or not.
I put an update function in the Tile, but it's really just to have some point in this example where you can follow the chain of collision detection -> trigger teleportation. In a real game I would leave the responsibility of firing the signals to some larger collision system class.
public class TileProperty {
public var NeedsUpdating:Boolean;
private var parentTile:Tile;
protected function get ParentTile():Tile { return parentTile; }
public final function attachToTile(tile:Tile):void {
var oldTile:Tile = parentTile;
parentTile = tile;
onNewParent(oldTile);
}
protected function onNewParent():void {
// Override this
}
public function Update(deltaTime:Number):void {
// Override this
}
}
So now we've got a TeleportTriggerProperty class, and this is where the teleportation actually happens. When OnNewParent() is called, it adds a listener (teleportObject
) to the OnObjectEnter signal. That way, whenever an object enters the tile that this TileProperty is attached to, it can teleport that object.
public class TeleportTriggerProperty extends TileProperty {
private finalDestination:Point;
public function TeleportTriggerProperty(destination:Point, group:int) {
finalDestination = destination;
}
override protected function OnNewParent(oldTile:Tile):void {
// I'm not sure if trying to remove a null listener, or
// a non-existent listener throws exceptions
// If it does, then wrap it in try-catch.
// Or better yet, modify the library so it DOESN'T :P
oldTile.OnObjectEnter.remove(teleportObject);
parentTile.OnObjectEnter.add(teleportObject);
}
private function teleportObject(thing:GameObject):void {
// Maybe in some scenario this OnObjectEnter could get called if a
// chest gets placed on the tile, so it's good to confirm the Group.
// And maybe you want to be able to configure it to only teleport
// the player, or only enemies.
if (thing.Group == GameObject.ALL_CHARACTERS) {
// BAM! Teleported. Insert special effects as necessary.
thing.position = finalDestination;
}
}
}
So now you've got all this infrastructure. How does this apply to the level editor?
Well, notice that really the only thing that a TeleportTriggerProperty needs to work is a Point and a Group ID. These are the two fields that you can expose in your level editor. Then you can presumably export your level structure in XML or some other data format to look like this (I used XML, since its hierarchical nature makes sense in this scenario, and as3 parses it natively):
<Tile>
<Position>24,42</Position>
<Properties>
<TeleportTriggerProperty destination="42,24" group="2" />
<TriggerMusicProperty track="NeverGonnaGiveYouUp.mp3" />
<BrutallyMurderPlayerInFutureProperty time="3m30s" />
</Properties>
</Tile>
It probably isn't ideal to store a big XML hierarchy for each and every tile in your game, but this was the most straightforward example I could think of.
Then in your parser routine, you could iterate through and construct your TileProperties as needed. You could conceivably automate this process with serialization. I haven't done serialization in as3 though, so that's homework for you if you're interested. If you don't use serialization, I'd recommend having a few separate classes responsible for parsing specific types of elements (a GameObject parser, a Tile parser, and maybe even a separate TileProperty parser).
I called this thing a TileProperty
in the hopes that it could possibly accomodate both direct actions like your teleporter problem, or tile effects, such as diminishing the health of whatever character stands on it every few seconds. But, it may be better to implement a couple different systems to get done what you want to. It depends on your game.