With any project, you’ve got to start at the very beginning and that is exactly what I am going to do with Haxe. First we are going to create the seminal Hello World and explore the cross platform nature of Haxe.
I originally worked in FlashDevelop to author these examples. I ran into a couple gotchas as I went. First, I discovered that package names need to start with a lower case character, but FlashDevelop will allow you to make this mistake. On top, packages are more like Java, than C# or C++ namespaces. Behind the scenes, they are mirrored on the file system. So a package mike.is.great becomes /mike/is/great on the file system. So, when renaming my package, I had to change at the file system level too… lesson learned. Second, the debugger is incredibly fragile in FlashDevelop, if it goes on the fritz, restart FlashDevelop. Finally, for some bizarre reason, you can’t set a watch on a non local variable. So if you want to watch a global value, make a local reference. All told though, working in FlashDevelop is pretty pleasant.
Alright, back to HelloWorld. I’ll start with a straight Haxe version that runs in the Neko VM:
package gfs; import neko.Lib; class Main { static function main() { Lib.println("Hello world"); } }
Not the most exciting code, but Hello World rarely is. Now we run our application in the Neko VM:
Nothing too exciting, but that’s Hello World. Now let’s look at the Flash version of Hello World:
package gfs; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.Lib; import flash.text.TextField; class Main { static function main() { var stage = Lib.current.stage; // entry point var text = new TextField(); text.text = "Hello world!"; stage.addChild(text); } }
And here it is running in Flash:
Once again, not the most exciting app, but it’s Hello World.
That said, it’s pretty annoying creating a version for each platform. For this we will turn to NME. NME provides a series of libraries that abstract out the different platform capabilities using a consistent set of libraries. This HelloWorld is a little bit more advanced:
package gfs; import nme.display.Sprite; import nme.events.Event; import nme.Lib; import nme.text.TextField; import nme.text.TextFieldAutoSize; import nme.text.TextFormat; class Main extends Sprite { public function new() { super(); var stage = Lib.current.stage; var textFormat = new TextFormat(); textFormat.font = "Courier"; textFormat.color = 0xff0000; textFormat.size = 42; var text = new TextField(); text.text = "Hello World!"; text.x = Lib.current.stage.stageWidth / 2 - 145; text.y = Lib.current.stage.stageHeight / 2 - 21; text.width = 290; text.setTextFormat(textFormat); Lib.current.stage.addChild(text); } public static function main() { // static entry point Lib.current.addChild(new Main()); } }
This code is pretty similar, once again, there is a main method. You may find it a bit strange that Main inherits from Sprite… this is an ActionScript/Flash thing, the library NME models after. Now the cool part is running it on various platforms, here are the results (in order) of running:
nme test ios -simulator
nme test Flash
nme test html5
iOS
Flash
HTML5
One source, many platforms… have we reached nirvana?
Sadly no. Here is the result of nme test mac:
Ugh, something obviously went wrong. Coincidentally the same code runs on Windows, so this is a Mac specific problem. The problem is, this code is implemented in C++, so then you have to jump in to the generated code. So, what does this generated code look like? Well, here’s some now:
#include <hxcpp.h> #ifndef INCLUDED_gfs_Main #include <gfs/Main.h> #endif #ifndef INCLUDED_native_display_DisplayObject #include <native/display/DisplayObject.h> #endif #ifndef INCLUDED_native_display_DisplayObjectContainer #include <native/display/DisplayObjectContainer.h> #endif #ifndef INCLUDED_native_display_IBitmapDrawable #include <native/display/IBitmapDrawable.h> #endif #ifndef INCLUDED_native_display_InteractiveObject #include <native/display/InteractiveObject.h> #endif #ifndef INCLUDED_native_display_MovieClip #include <native/display/MovieClip.h> #endif #ifndef INCLUDED_native_display_Sprite #include <native/display/Sprite.h> #endif #ifndef INCLUDED_native_display_Stage #include <native/display/Stage.h> #endif #ifndef INCLUDED_native_events_EventDispatcher #include <native/events/EventDispatcher.h> #endif #ifndef INCLUDED_native_events_IEventDispatcher #include <native/events/IEventDispatcher.h> #endif #ifndef INCLUDED_native_text_TextField #include <native/text/TextField.h> #endif #ifndef INCLUDED_native_text_TextFormat #include <native/text/TextFormat.h> #endif #ifndef INCLUDED_nme_Lib #include <nme/Lib.h> #endif namespace gfs{ Void Main_obj::__construct() { HX_STACK_PUSH("Main::new","gfs/Main.hx",13); { HX_STACK_LINE(14) super::__construct(); HX_STACK_LINE(15) ::native::display::Stage stage = ::nme::Lib_obj::get_current()->get_stage();
HX_STACK_VAR(stage,"stage"); HX_STACK_LINE(17) ::native::text::TextFormat textFormat = ::native::text::TextFormat_obj::__new(null(),null(),
null(),null(),null(),null(),null(),null(),null(),null(),null(),null(),null());
HX_STACK_VAR(textFormat,"textFormat"); HX_STACK_LINE(18) textFormat->font = HX_CSTRING("Courier"); HX_STACK_LINE(19) textFormat->color = (int)16711680; HX_STACK_LINE(20) textFormat->size = (int)42; HX_STACK_LINE(22) ::native::text::TextField text = ::native::text::TextField_obj::__new();
HX_STACK_VAR(text,"text"); HX_STACK_LINE(23) text->set_text(HX_CSTRING("Hello World!")); HX_STACK_LINE(24) text->set_x(((Float(::nme::Lib_obj::get_current()->get_stage()
->get_stageWidth()) / Float((int)2)) - (int)150)); HX_STACK_LINE(25) text->set_y(((Float(::nme::Lib_obj::get_current()->get_stage()
->get_stageHeight()) / Float((int)2)) - (int)21)); HX_STACK_LINE(26) text->set_width((int)300); HX_STACK_LINE(28) text->setTextFormat(textFormat,null(),null()); HX_STACK_LINE(29) ::nme::Lib_obj::get_current()->get_stage()->addChild(text); } ; return null(); } Main_obj::~Main_obj() { } Dynamic Main_obj::__CreateEmpty() { return new Main_obj; } hx::ObjectPtr< Main_obj > Main_obj::__new() { hx::ObjectPtr< Main_obj > result = new Main_obj(); result->__construct(); return result;} Dynamic Main_obj::__Create(hx::DynamicArray inArgs) { hx::ObjectPtr< Main_obj > result = new Main_obj(); result->__construct(); return result;} Void Main_obj::main( ){ { HX_STACK_PUSH("Main::main","gfs/Main.hx",33); HX_STACK_LINE(33) ::nme::Lib_obj::get_current()->addChild(::gfs::Main_obj::__new()); } return null(); } STATIC_HX_DEFINE_DYNAMIC_FUNC0(Main_obj,main,(void)) Main_obj::Main_obj() { } void Main_obj::__Mark(HX_MARK_PARAMS) { HX_MARK_BEGIN_CLASS(Main); super::__Mark(HX_MARK_ARG); HX_MARK_END_CLASS(); } void Main_obj::__Visit(HX_VISIT_PARAMS) { super::__Visit(HX_VISIT_ARG); } Dynamic Main_obj::__Field(const ::String &inName,bool inCallProp) { switch(inName.length) { case 4: if (HX_FIELD_EQ(inName,"main") ) { return main_dyn(); } } return super::__Field(inName,inCallProp); } Dynamic Main_obj::__SetField(const ::String &inName,const Dynamic &inValue,bool inCallProp) { return super::__SetField(inName,inValue,inCallProp); } void Main_obj::__GetFields(Array< ::String> &outFields) { super::__GetFields(outFields); }; static ::String sStaticFields[] = { HX_CSTRING("main"), String(null()) }; static ::String sMemberFields[] = { String(null()) }; static void sMarkStatics(HX_MARK_PARAMS) { HX_MARK_MEMBER_NAME(Main_obj::__mClass,"__mClass"); }; static void sVisitStatics(HX_VISIT_PARAMS) { HX_VISIT_MEMBER_NAME(Main_obj::__mClass,"__mClass"); }; Class Main_obj::__mClass; void Main_obj::__register() { Static(__mClass) = hx::RegisterClass(HX_CSTRING("gfs.Main"), hx::TCanCast< Main_obj> ,
sStaticFields,sMemberFields, &__CreateEmpty, &__Create, &super::__SGetClass(), 0, sMarkStatics, sVisitStatics); } void Main_obj::__boot() { } } // end namespace gfs
You can read it, but barely. So when the cross platform part of NME breaks down, you are in for a world of hurt. That such a simple example breaks doesn’t bode well for the future.
In the next post we look at graphics in Haxe and NME.