Previously:
- Making a game with Flash Develop + Flixel: Part 1, Installing and Configuring
- Making a game with Flash Develop + Flixel: Part 2, Coding a simple example
- Making a game with Flash Develop + Flixel: Part 3, Input and Objects
- Making a game with Flash Develop + Flixel: Part 4, Tilemaps and Collisions
- Making a game with Flash Develop + Flixel: Part 5, Objects and Animation
- Making a game with Flash Develop + Flixel: Part 6, Cameras and Text
(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); | |
| } | |
| } | |
| } |
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() | |
| { | |
| } | |
| } | |
| } |
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(); | |
| } | |
| } | |
| } |
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); | |
| } | |
| } | |
| } |
Finally, we can move to add a NPC.

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.
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.
