Tiny Text Worlds

TTW_Screen

To help get me get up to speed on Haxe, and in particular Haxe JS stuff I have been working on and off on a prototype of text adventure engine. The three main things I wanted to achieve were:

  • I wanted to make a game my mum could, and would want to play. So I simplified the input so that the player should be able to see clearly which objects can be interacted with.
  • The game should have a constant scene description of the that changes as you alter or learn about the objects in it. In the game any object who’s text has changed text is highlighted red.
  • That it should work well on any device with a browser. I was particularly trying to make a decent experience on mobile. It seems to work OK on the browsers I’ve tested, touch areas on a Kobo eReader seemed a bit small.

I’d be really interested in some feedback on how people think the prototype works.

You can play it here:
http://nickholder.co.uk/ttw/worlds/snow.html

The Xml the world is loaded from is here:
http://nickholder.co.uk/ttw/worlds/snow.xml

And the source is here ( MIT )
https://bitbucket.org/Nick_Holder/tiny_text_worlds

Should you feel the urge to use it please feel free do so, with the exception of the demo world.

Tracing to the “haxe:trace” div in Haxe 2.10 & 3.01 JS

Up until Haxe 2.10 using trace in the JS target wrote the contents of your trace to the <div> in the html file with the id “haxe:trace” something like this:

Main.hx:34: The contents of the trace

That’s changed with 2.10 with the content of the traces often being written to the browsers console (ctrl+shift+K in Windows or option+apple+K on a Mac).

Some kind folks on the mailing list gave me some pointers on how to switch back to writing to the <div id=”haxe:trace”> by setting the trace function to a method you’ve written yourself.

To change the trace method used by haxe you need to use ( and change divTrace to the name of your new trace method):

haxe.Log.trace = divTrace;

Here’s my method which traces out all the info in the PosInfos object which you can read a bit about here:

Haxe 2.10

public function divTrace(v:Dynamic, ?i :PosInfos)
{
	Lib.document.getElementById("haxe:trace").innerHTML +="<br>[" + i.fileName + " : " + i.lineNumber +"] " + i.className + "." + i.methodName + "() - " + v;
}

Haxe 3.01

public function divTrace(v:Dynamic, ?i :PosInfos)
{
	Browser.document.getElementById("haxe:trace").innerHTML +="<br>[" + i.fileName + " : " + i.lineNumber +"] " + i.className + "." + i.methodName + "() - " + v;
}

It writes something like this to the <div id=”haxe:trace”> in the html:
[Main.hx : 34] Main.new() - The contents of the trace

 

AIR 3.3: Set an iOS app’s MinimumOSVersion

As of Air 3.3 you can set the minimum iOS version of AIR packaged applications. Add the following (with your desired OS version) into <InfoAdditions> in your app descriptor XML file:

<key>MinimumOSVersion</key>
<string>5.0</string>

(Thanks to vabhatia on Stack Overflow)

Waxe: FileDialog


Note:

I’ve updated this post to work with Haxe 3 by changing:
import neko.FileSystem; and import cpp.FileSystem; to import sys.FileSystem;

and a change to support Waxe 3.0:
waxePanel.setSizer(sizer); to waxePanel.set_sizer(sizer);


To explore Waxe’s FileDialog class a little let’s add some functionality to the useless button from my NMEStage post. We’ll use the button to open a file dialog box and load in a .jpg or .png file and display it in on the NMEStage.

First let’s change the button’s text:

var button:Button = Button.create( waxePanel, null, 'Open a jpg or png');

And give it a function to perform when clicked:

button.onClick = showFileDialog;

Next up the showFileDialog method for our button it needs to take a single parameter of type Dynamic sent by the button when clicked:

private function showFileDialog( event:Dynamic )
{
}

Next we’ll make the file dialog inside the showFileDialog function:

var fileDialog:FileDialog = new FileDialog( frame , "Choose an image" , "" , "" , "PNG and JPG files|*.png;*.jpg|PNG files|*.png|JPG files|*.jpg");

In the FileDialog’s constructor we set it’s parent, the title text displayed in the FileDialog itself, default directory, default file, then a file filter string. Let’s take a closer look at the file filter string:

"PNG and JPG files|*.png;*.jpg|PNG files|*.png|JPG files|*.jpg"

The above string has 3 different filters, one that shows PNG and JPGs in the FileDialog, one that shows just PNGs and lastly one for just JPGs. Each filter takes the form:

|

multiple file filters are separated by a single | . Be careful of extra white spaces when listing file formats as this will stop filters working as expected.

With our FileDialog instanced we now need to show it. We do this with .showModal().

if( fileDialog.showModal() )
{
	trace( "Selected" );
}
else
{
	trace( "Cancelled" );
}

.showModal() returns a Bool when done. The Bool is true when a file has been selected, and false when the FileDialog has been cancelled without selecting a file. It also returns true if the user enters a file name with the keyboard and presses the open button, as it does not check whether the file exists when the user enters a file name we will have to check ourselves later.

Here’s what the showFileDialog method should look like so far:

private function showFileDialog( event:Dynamic )
{
	var fileDialog:FileDialog = new FileDialog( frame , "Choose an image" , "" , "" , "PNG and JPG files|*.png;*.jpg|PNG files|*.png|JPG files|*.jpg");

	//
	if( fileDialog.showModal() )
	{
		trace( "Selected" );
	}
	else
	{
		trace( "Cancelled" );
	}
}

To load the file we selected let’s replace “trace( “Selected” );” with some code to load our file:

// Build path
var path:String =  fileDialog.directory + "/" + fileDialog.file ;

// File Exists?
if( !FileSystem.exists(  path ) )
{
	trace( "File does not exist." );
	return;
}

loader = new nme.display.Loader( );
loader.contentLoaderInfo.addEventListener( nme.events.Event.COMPLETE , imageLoadComplete );
loader.load( new nme.net.URLRequest( path ) );

FileDialog has a number of useful properties for once the user has selected a file:

var path:String =  fileDialog.directory + "/" + fileDialog.file ;

Here we build the path of our file from FileDialogs directory and file properties.

Next I check to see if the file exists using Neko or Cpp’s FileSystem.exists method:

if( !FileSystem.exists(  path ) )
{
	trace( "File does not exist." );
	return;
}

Next the actual loading of the file using NME’s Loader class.

loader = new nme.display.Loader( );
loader.contentLoaderInfo.addEventListener( nme.events.Event.COMPLETE , imageLoadComplete );
loader.load( new nme.net.URLRequest( path ) );

Finally we need to add a handler for when loader completes it’s load operation:

private function imageLoadComplete( event:nme.events.Event ):Void
{
	nmeStage.stage.addChild( loader );
}

We’ll need to change the nmeStage from a local variable to an instance variable so we can access it the handler, then we can add our image to it’s stage.

Here’s the full source:

package;

import wx.App;
import wx.Button;
import wx.Frame;
import wx.NMEStage;
import wx.Panel;
import wx.Sizer;
import wx.BoxSizer;
import wx.FileDialog;

import nme.display.StageAlign;
import nme.display.StageScaleMode;
import nme.events.Event;
import nme.net.URLRequest;
import nme.display.Loader;

#if neko
import sys.FileSystem;
#end

#if cpp
import sys.FileSystem;
#end

class Main
{
	private var frame:Frame;
	private var loader:Loader;
	private var nmeStage:NMEStage;

	function new()
	{
		// Make an app window
		frame = Frame.create(null, "WaxeApp" , null , { width:400 , height:400 } );

		// Make a panel
		var waxePanel:Panel = Panel.create( frame );
		var button:Button = Button.create( waxePanel, null, 'Open a jpg or png');

		button.onClick = showFileDialog;

		// Make an NME stage
		nmeStage = NMEStage.create( waxePanel );
		nmeStage.stage.align = StageAlign.TOP_LEFT;
		nmeStage.stage.scaleMode = StageScaleMode.NO_SCALE;
		// Draw something on the NMEStage
		nmeStage.stage.graphics.beginFill( 0x000000 );
		nmeStage.stage.graphics.drawCircle( 50 , 50 , 50 );
		nmeStage.stage.graphics.endFill();

		// Auto position the panel and NMEStage with a sizer
		var sizer:Sizer = BoxSizer.create(true);
		waxePanel.set_sizer(sizer);
		sizer.add( button , 1 , Sizer.EXPAND | Sizer.BORDER_ALL , 5 );
		sizer.add( nmeStage , 3 , Sizer.EXPAND | Sizer.BORDER_ALL , 5);

		// Show your window
		App.setTopWindow( frame );
		frame.shown = true;
	}

	// Make and show the FileDialog
	private function showFileDialog( event:Dynamic )
	{
		var fileDialog:FileDialog = new FileDialog( frame , "Choose an image" , "" , "" , "PNG and JPG files|*.png;*.jpg|PNG files|*.png|JPG files|*.jpg");

		//
		if( fileDialog.showModal() )
		{
			// Build path
			var path:String =  fileDialog.directory + "/" + fileDialog.file ;

			// File Exists?
			if( !FileSystem.exists(  path ) )
			{
				trace( "File does not exist." );
				return;
			}

			loader = new nme.display.Loader( );
			loader.contentLoaderInfo.addEventListener( nme.events.Event.COMPLETE , imageLoadComplete );
			loader.load( new nme.net.URLRequest( path ) );
		}
		else
		{
			trace( "Cancelled" );
		}
	}

	// Loaded an Image
	private function imageLoadComplete( event:nme.events.Event ):Void
	{
		nmeStage.stage.addChild( loader );
	}

	// Entry
	public static function main()
	{
		App.boot( function(){ new Main(); } );
	}
}

Waxe: NMEStage


Note:

I’ve updated this post to work with Waxe 3.0 by changing:
waxePanel.setSizer(sizer); to waxePanel.set_sizer(sizer);

Once you have a waxe window created, it’s easy to add both NME stages and Waxe content to the application.

We’ll add a panel to hold our content.

var waxePanel:Panel = Panel.create( frame );

then add a button to it:

var button:Button = Button.create( waxePanel, null, 'A Button');

Then we’ll add an NMEStage:
Note: It’s posible to add multiple NMEStages.

var nmeStage:NMEStage = NMEStage.create( waxePanel );

The NMEStage’s stage ( NMEStage.stage ) behaves, as far as I’ve seen, as the stage does in a standard NME project. So we can set it up to deal with scaling using the same techniques. In this case I’ll align it to the top left, and tell it not to scale.

nmeStage.stage.align = StageAlign.TOP_LEFT;
nmeStage.stage.scaleMode = StageScaleMode.NO_SCALE;

Let’s draw something on the stage so we can see it’s all working:

nmeStage.stage.graphics.beginFill( 0x000000 );
nmeStage.stage.graphics.drawCircle( 50 , 50 , 50 );
nmeStage.stage.graphics.endFill();

We can then position the button and NMEStage with a Sizer, the NMEStage will scale following the same rules as other waxe objects( More on Sizers at CambiataBlog ).

var sizer:Sizer = BoxSizer.create(true);
waxePanel.set_sizer(sizer);
sizer.add( button , 1 , Sizer.EXPAND | Sizer.BORDER_ALL , 5 );
sizer.add( nmeStage , 3 , Sizer.EXPAND | Sizer.BORDER_ALL , 5);

Here’s the full source:

package;

import wx.App;
import wx.Button;
import wx.Frame;
import wx.NMEStage;
import wx.Panel;
import wx.Sizer;
import wx.BoxSizer;

import nme.display.StageAlign;
import nme.display.StageScaleMode;

class Main
{
	private var frame:Frame;
	
	function new()
	{
		// Make an app window
		frame = Frame.create(null, "WaxeApp" , null , { width:400 , height:400 } );
		
		// Make a panel
		var waxePanel:Panel = Panel.create( frame );
		var button:Button = Button.create( waxePanel, null, 'A Button');
		
		// Make an NME stage
		var nmeStage:NMEStage = NMEStage.create( waxePanel );
		nmeStage.stage.align = StageAlign.TOP_LEFT;
		nmeStage.stage.scaleMode = StageScaleMode.NO_SCALE;
		// Draw something on the NMEStage
		nmeStage.stage.graphics.beginFill( 0x000000 );
		nmeStage.stage.graphics.drawCircle( 50 , 50 , 50 );
		nmeStage.stage.graphics.endFill();
		
		// Auto position the panel and NMEStage with a sizer
		var sizer:Sizer = BoxSizer.create(true);
		waxePanel.set_sizer(sizer);
		sizer.add( button , 1 , Sizer.EXPAND | Sizer.BORDER_ALL , 5 );
		sizer.add( nmeStage , 3 , Sizer.EXPAND | Sizer.BORDER_ALL , 5);
		
		
		// Show your window
		App.setTopWindow( frame );
		frame.shown = true;
	}

	
	
	// Entry
	public static function main()
	{
		App.boot( function(){ new Main(); } );
	}
}

This should compile and give you a window with a large, currently useless Waxe button, and the circle on a plain which rectangle of our NMEStage like this:

Waxe: Setting up FlashDevelop

NOTE: This post was written for FlashDevelop 4.0.3 RTM and Haxe 2.09.

Turns out setting up FlashDevelop to use Waxe is a simple task. Unfortunately I didn’t spot how simple until now, so thought I’d write a quick note in case it helps someone else.

  1. First off make a new Haxe Neko project.
  2. Once made go to Project -> Properties then click on the Compiler Options tab.
  3. Click the Additional Compiler options row and then the “…” button which appears end of that row.
  4. Enter the following into the field
    -lib waxe
    -lib nme
  5. Enjoy the code completion🙂

Waxe: Compiling with .Hxml

NOTE: I’m a beginner at Haxe and even more so at Waxe so please point out anything stupid I’m doing🙂

I’ve been following CambiataBlog’s great tutorials on getting started with Waxe (A project which allows access to wxWidget’s GUI tools in haxe ), and have been trying to branch out a bit on my own. I’d recommend reading through the these tutorials before looking at this as some fixes are required in the waxe source before the code below will run in haxe 2.09.

I’m really keen to use waxe for some tools to help me and my designer mate make our games. To make these tools NME’s display list and ability to load multiple image file formats will be really useful. Currently, as Andreas Mokros pointed out in the Haxe google group, if you compile using NME a NMEStage is instantiated above your waxe content, hiding it all. I want to use both waxe and nme display elements in my applications so looked a bit more at the alternative way of compiling in Waxe’s samples. If you compile with haxe and including nme as a -lib in your .hxml, there is no NMEstage made by default.

Here are a few notes on how to set this up.
Here’s my compile.hxml:
I’ve been testing in neko as it’s a bit quicker to compile on my machine. But this works as well in the cpp target.

-cp src
-neko bin/neko/WaxeApp.n
-main Main
-lib waxe
-lib nme

And then compile with:

haxe compile.hxml

Next up my bare bones Main.hx file which will make a new empty 400×400 window called “WaxeApp”.

package;

import wx.App;
import wx.Frame;
import wx.Panel;

class Main
{
	private var frame:Frame;

	function new()
	{
		// Make the app frame
		frame = Frame.create(null, "WaxeApp" , null , { width:400 , height:400 } );
		
		// Show your frame
		App.setTopWindow(frame);
		frame.shown = true;
	}

	
	
	// Entry
	public static function main()
	{
		App.boot( function(){ new Main(); } );
	}
}

Unlike the nme workflow covered in CabiataBlog’s tutorials which gets the window from the ApplicationMain, here we make a frame by calling Frame.create then supplying the parent of the window (in this case null), the window’s name, position(here null), and size.

frame = Frame.create(null, "WaxeApp" , null , { width:400 , height:400 } );

We then show this window with:

App.setTopWindow(frame);
frame.shown = true;

You can make multiple windows using Frame.create and .show = true again for each new one. If you set a new window’s parent as another window when creating it, it will be closed along with it’s parent.

This code in place you should be able to compile an application which opens a blank window. Which we will be able to add waxe and nme elements to.

Follow

Get every new post delivered to your Inbox.