Making a game with Flash Develop + Flixel: Part 7, Adding NPCs

Previously:

(Want to bypass the long explanations and see the final product? Here, download this post’s code as a ZIP file.)

Now that we have the ability to show messages to the player, we also have a problem. If we want to show lots of text, we will need to both write it and have it displayed from its associated person or place. Yet, how do we do that?

Of course, we could write it all in the PlayState. Each response and every possible message could be written and then added to the showDialog array when needed. However, we would still need to associate the text with a person.

One solution to this problem, then, involves actually adding new people, non-player characters, as separate objects. Each one could have their own responses to the player, which could be triggered when needed. This would allow us to give each NPC, if we wanted, different information, appearances, and even properties.

Building from our PlayerSprite, we can create a NPCSprite with the following properties in mind:

  • Has an image
  • Has collision
  • Has speech
package
{
/**
* ...
* @author Dan Cox
*/
import org.flixel.*;
public class NPCSprite extends FlxSprite
{
public var speech:Array;
public function NPCSprite(X:Number = 0, Y:Number = 0, Image:Class = null)
{
super(X, Y, Image);
immovable = true;
speech = new Array();
}
public function addSpeech(line:String):void
{
speech.push(line);
}
}
}
view raw NPCSprite.as hosted with ❤ by GitHub

And, because we want the ability to draw and update them, we can build a new FlxGroup: NPCSpriteGroup. Then, because FlxGroups can hold other groups too, we can copy and paste the dialog code from PlayState into the code.

package
{
/**
* ...
* @author Dan Cox
*/
import org.flixel.*;
public class NPCSpriteGroup extends FlxGroup
{
private var dialog:FlxDialog;
public function NPCSpriteGroup()
{
dialog = new FlxDialog(0, 160, FlxG.width, 80);
dialog.setFormat(null, 8, 0xFFFFFFFF);
dialog.finishCallback = removeDialogBox;
}
override public function update():void
{
super.update();
}
private function removeDialogBox():void
{
remove(dialog);
player.isStopped = false;
}
}
}

However, even with this code, there is a new and complicated problem. It’s on line 28. We want player to be stopped, but “player” doesn’t exist in this scope. It’s in PlayState, not NPCSpriteGroup.

One solution — there are many — is to move player to a larger scope, something that both PlayState and NPCSpriteGroup can see. For that, we can use a Registry with variables declared to be “static public” (singleton).

(Note: While not always ideal for every project, a Registry pattern for this project is fine. Without much complexity, we can manage the few items in the registry without risking putting too much code in there.)

package
{
/**
* ...
* @author Dan Cox
*/
public class Registry
{
public static var player:PlayerSprite;
public function Registry()
{
}
}
}
view raw Registry.as hosted with ❤ by GitHub

Because of that, we need to change PlayState.

package
{
/**
* ...
* @author Dan Cox
*/
import org.flixel.*;
public class PlayState extends FlxState
{
private var map:MapGroup;
private var NPCGroup:NPCSpriteGroup;
public function PlayState()
{
}
override public function create():void
{
map = new MapGroup();
add(map);
Registry.player = new PlayerSprite(32, 32);
add(Registry.player);
NPCGroup = new NPCSpriteGroup();
add(NPCGroup);
FlxG.worldBounds = new FlxRect(0, 0, 640, 640);
FlxG.camera.setBounds(0, 0, 640, 640, true);
FlxG.camera.follow(Registry.player);
Registry.player.cameras = new Array(FlxG.camera);
}
override public function update():void
{
super.update();
}
}
}
view raw PlayState.as hosted with ❤ by GitHub

And, because player is now in the same scope as everything else, we can move the simple collision code for our map into its object too.

package
{
/**
* ...
* @author Dan Cox
*/
import org.flixel.*;
public class MapTilemap extends FlxTilemap
{
[Embed(source = "data/tilemap.png")] private var tilemapPng:Class;
[Embed(source = "data/map1.txt", mimeType = "application/octet-stream")] private var mapTxt:Class;
public function MapTilemap()
{
loadMap(new mapTxt, tilemapPng, 16, 16, 0, 0, 0);
}
override public function update():void
{
FlxG.collide(Registry.player, this);
}
}
}
view raw MapTilemap.as hosted with ❤ by GitHub

Finally, we can move to add a NPC.

step1
Click image for actual file (NPC1.png)

Within NPCSpriteGroup, we can embed the file and then add the collision code. However, this time we can use the third argument of Flxg.collide to list a callback function. Whenever Registry.player collides with the NPC, we can check (within this callback function) to see if “ENTER” was pressed too. If so, we display the speech of the NPC.

package
{
/**
* ...
* @author Dan Cox
*/
import org.flixel.*;
public class NPCSpriteGroup extends FlxGroup
{
private var dialog:FlxDialog;
private var npc:NPCSprite;
[Embed(source="data/NPC1.png")]
private var NPCPng:Class;
public function NPCSpriteGroup()
{
dialog = new FlxDialog(0, 160, FlxG.width, 80);
dialog.setFormat(null, 8, 0xFFFFFFFF);
dialog.finishCallback = removeDialogBox;
npc = new NPCSprite(48, 320, NPCPng);
npc.addSpeech("Hi, player! I'm a NPC.");
add(npc);
}
override public function update():void
{
super.update();
FlxG.collide(Registry.player, npc, showDialog);
}
private function showDialog(a:FlxObject, b:FlxObject):void
{
if(FlxG.keys.justPressed("ENTER"))
{
Registry.player.isStopped = true;
add(dialog);
dialog.showDialog(npc.speech);
}
}
private function removeDialogBox():void
{
remove(dialog);
Registry.player.isStopped = false;
}
}
}

Now, if we press “ENTER” while colliding with the NPC, we get its speech.

step2

That’s it for Part 7.

In Part 8, we finally get to making different states and then using them as both menus and rooms.

2 thoughts on “Making a game with Flash Develop + Flixel: Part 7, Adding NPCs

  1. I must say that this the best Flixel Game tutorial I’ve ever read. Very detailed and easy to follow steps. I got one question, though, do you know how to use tiled map editor to make it to work with Flixel? Thanks, buddy.

    1. Dan Cox

      Short answer: yes.

      Long answer: The answer on what to use within Flash is predicated on what file format you prefer to use. There are a couple of different AS3 libraries out there that will read the default TMX format — both with and without compressed tilemap data.

      There is also a good GameDevTuts+ article on reading the XML format out there too. It covers how to parse each group and tags, and some of the potential problems therein.

      I’ve also heard of some people using the JSON file format as exported from Tiled too. Of course, with that, you would need to target newer runtimes of Flash, basically 11 and up, to get JSON decoding functionality.

      So, I don’t know exactly what to write as an easy answer. There doesn’t seem to be one at the moment. It’s all a matter of picking a file format and either writing some parsing code or finding an additional library you like.

      (Not that I’ll be able to get to it any time soon, but I’ve been thinking about this problem for awhile and might write some posts on it at some point. I’ve some experience, having written my own TMX parse in JavaScript, and think it might be worth covering for other people too.)

Comments are closed.