Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

12. May 2013

Now that I have decided to go with Haxe and NME, there is the question of which game engine to use.  You may be thinking to yourself "isn't NME a game engine"?  No, not really, although it performs some game engine-y functions.  NME is more like a cross platform Haxe implementation of a Flash like development environment.  A game engine is built on top of this layer as ideally makes me life easier.  So, what are the options then?

 

Build my own

Well first of all there is the option to use nothing.  NME is fairly high level as it is, so the "cost" of building a game engine on top of it is much lower than with many other language/library combinations.  This has the advantage of removing a layer of code I am not intimately familiar with.  On the other hand, I'm lazy and in the business of creating a game, not an engine.  If someone else wants to do the work for me, and freely at that, who am I to say no?

 

HaxeFlixel

http://www.haxeflixel.com/

Flixel is one of the most common Flash 2D game frameworks, and HaxeFlixel is a Haxe port.  The reference documentation is pretty solid.  I have read that HaxeFlixel is a bit further along than our next entry.  The engine itself is state driven, with a state being the fundamental organization model of your game, while your game loop basically flips between states.  Examples of states would be say… Playing, MainMenu, HighScore, etc…  Flixel targets the most of the major targets including iOS, Android, Mac and Windows as well as Flash.  I don't believe HTML5 is supported.

 Status: Under active development

 

Sample HaxeFlixel code from here. ( a Menu state ) :

package;
 
import org.flixel.plugin.photonstorm.FlxDisplay;
import nme.Assets;
import nme.geom.Rectangle;
import nme.net.SharedObject;
import org.flixel.FlxButton;
import org.flixel.FlxG;
import org.flixel.FlxPath;
import org.flixel.FlxSave;
import org.flixel.FlxSprite;
import org.flixel.FlxState;
import org.flixel.FlxText;
import org.flixel.FlxU;
 
class MenuState extends FlxState
{
    override public function create():Void
    {
        #if !neko
        FlxG.bgColor = 0xff131c1b;
        #else
        FlxG.bgColor = {rgb: 0x131c1b, a: 0xff};
        #end 
        FlxG.mouse.show();
         
        //create a button with the label Start and set an on click function
        var startButton = new FlxButton(0, 0, "Start", onStartClick);
        //add the button to the state draw list
        add(startButton);
        //center align the button on the stage
        FlxDisplay.screenCenter(startButton,true,true);
    }
 
    //The on click handler for the start button
    private function onStartClick( ):Void
    {
        //Tell Flixel to change the active game state to the actual game
        FlxG.switchState( new PlayState( ) );
    }
     
    override public function destroy():Void
    {
        super.destroy();
    }
 
    override public function update():Void
    {
        super.update();
    }
}

 

 

HaxePunk

http://haxepunk.com/

HaxePunk is another popular Flash game framework that was ported to Haxe.  Instead of being organized around States like Flixel, HaxePunk is built around entities and scenes, a rather more traditional design.  Haxepunk seems to support the same targets as HaxeFlixel, which means most of the CPP targets ( iOS, Android, Windows, Mac, etc… ) but no HTML5.  Reference docs are pretty complete.

Status: Under active development.

 

Sample code taken from here.

 

 

package scenes;
 
import com.haxepunk.Scene;
import com.haxepunk.HXP;
 
class GameScene extends Scene
{
    public function new()
    {
        super();
    }
 
    public override function begin()
    {
        add(new entities.Ship(16, HXP.halfHeight));
        spawn(); // create our first enemy
    }
 
    public override function update()
    {
        spawnTimer -= HXP.elapsed;
        if (spawnTimer < 0)
        {
            spawn();
        }
        super.update();
    }
 
    private function spawn()
    {
        var y = Math.random() * HXP.height;
        add(new entities.Enemy(HXP.width, y));
        spawnTimer = 1; // every second
    }
 
    private var spawnTimer:Float;
}

 

 

Firmament Game Engine

http://martamius.github.io/Firmament.hx/

Another Haxe 2D game engine, this one supports almost every platform, and unlike Flixel and FlashPunk, that includes HTML5.  Like the others, it is completely open source.  Reference docs are OK, but a little light on description.

Status: Last github commit was 2 months ago as of writing.

No sample code located.

 

Stencyl

http://www.stencyl.com/

Stencyl is an interesting option.  It's a bit higher level than the other frameworks we mentioned earlier, as you can see directly below, Stencyl has an IDE.

Stencyl
In some ways, its much more similar to higher level tools like Construct 2 or Gamemaker in that you visually author your game.  Stencyl however is powered by Haxe and you can drop down to the Haxe code level if you want.  That said, it is a commercial product, so if you want to publish to iOS or ( I believe… the site doesn't make it obvious either way ) Android, you need to pay.  Documentation is pretty solid.
 
Status: Commercial product under active development.
 
No sample in this case due to the unique nature of the product.
 
Stencyl is certainly worth checking out, but probably not a good fit for the project I am working on, as I want the blog posts to be code focused and Stencyl abstracts most of that away. 

 

Citrux Engine

https://github.com/alamboley/CitruxEngine

CitruxEngine is a port of the Flash based game engine.  Sadly it seems to have been abandoned 

Status: Last update was 8 months ago.  May simply be complete but on first glance appears to be a dead end.

Sample code from here.

 

 

package fr.aymericlamboley.test;

import aze.display.SparrowTilesheet;
import aze.display.TileLayer;

import box2D.dynamics.contacts.B2Contact;

import com.citruxengine.core.CitruxEngine;
import com.citruxengine.core.State;
import com.citruxengine.math.MathVector;
import com.citruxengine.objects.CitruxSprite;
import com.citruxengine.objects.Box2DPhysicsObject;
import com.citruxengine.objects.platformer.box2d.Baddy;
import com.citruxengine.objects.platformer.box2d.Coin;
import com.citruxengine.objects.platformer.box2d.Crate;
import com.citruxengine.objects.platformer.box2d.Hero;
import com.citruxengine.objects.platformer.box2d.MovingPlatform;
import com.citruxengine.objects.platformer.box2d.Platform;
import com.citruxengine.objects.platformer.box2d.Sensor;
import com.citruxengine.physics.Box2D;
import com.citruxengine.utils.ObjectMaker;
import com.citruxengine.view.spriteview.SparrowAnimationSequence;
import com.citruxengine.view.spriteview.SpriteLoqAnimationSequence;
import com.citruxengine.view.spriteview.SpriteView;

import com.eclecticdesignstudio.spritesheet.SpriteSheet;
import com.eclecticdesignstudio.spritesheet.importers.SpriteLoq;

import format.SWF;

import nme.Assets;
import nme.geom.Rectangle;

class GameState extends State<GameData> {

    public function new() {

        super();
    }

    override public function initialize():Void {

        super.initialize();

        _ce.gameData.dataChanged.add(_gameDataChanged);

        var box2d:Box2D = new Box2D("Box2D");
        //box2d.visible = true;
        add(box2d);

        //ObjectMaker.FromMovieClip(new SWF(Assets.getBytes("Assets/LevelA1.swf")).createMovieClip());

        var background:CitruxSprite = new CitruxSprite("background", {x:0, y:0, view:"Assets/background.jpg"});
        add(background);

        var physicsObject:Crate = new Crate("physicsObject", 
{x:250, y:200, width:70, height:75, view:"Assets/crate.png"});
        //var physicsObject:PhysicsObject = new PhysicsObject("physicsObject", {x:100, y:20});
        //var physicsObject:PhysicsObject = new PhysicsObject("physicsObject", {x:100, y:20, radius:20});
        add(physicsObject);

        add(new Platform("platform1", {x:498, y:403, width:948, height:20}));
        add(new Platform("platform2", {x:0, y:202, width:20, height:404}));
        add(new Platform("platform3", {x:1278, y:363, width:624, height:20}));
        add(new Platform("platform4", {x:1566, y:165, width:20, height:404}));

        var spriteSheet:SpriteSheet = SpriteLoq.parse(ApplicationMain.getAsset("Assets/heroSpriteLoq.xml"), "Assets");
       
        var tileSheet:SparrowTilesheet = new SparrowTilesheet(Assets.getBitmapData("Assets/heroSparrow.png"), 
Assets.getText("Assets/heroSparrow.xml"));
        var heroTileLayer:TileLayer = cast(view, SpriteView).createTileLayer(tileSheet, "hero");
        var hero:Hero = new Hero("hero", {x:100, y:20, width:60, height:135, 
view:new SparrowAnimationSequence(heroTileLayer, "idle")});

        add(hero);

        spriteSheet = SpriteLoq.parse(ApplicationMain.getAsset("Assets/baddySpriteLoq.xml"), "Assets");
        tileSheet = new SparrowTilesheet(Assets.getBitmapData("Assets/baddySparrow.png"), 
Assets.getText("Assets/baddySparrow.xml"));
        var baddyTileLayer:TileLayer = new TileLayer(tileSheet);
        var baddy1:Baddy = new Baddy("baddy1", {x:440, y:200, width:46, height:68, 
view:new SparrowAnimationSequence(baddyTileLayer, "walk")});
        add(baddy1);

        var coin:Coin = new Coin("Coin", {x:Std.random(400), y:Std.random(300) + 100, radius:30, 
view:"Assets/jewel.png"});
        add(coin);
        coin.onBeginContact.add(_recoltCoin);

        view.setupCamera(hero, new MathVector(320, 240), new Rectangle(0, 0, 1550, 0), new MathVector(.25, .05));
    }

    override public function update(timeDelta:Float):Void {

        super.update(timeDelta);
    }

    private function _gameDataChanged(object:String, value:Dynamic):Void {

        trace(object + " - " + value);
    }

    private function _recoltCoin(ctc:B2Contact):Void {

        var hero:Hero = Std.is(ctc.m_fixtureA.getBody().getUserData(), Hero) ? 
ctc.m_fixtureA.getBody().getUserData() : Std.is(ctc.m_fixtureB.getBody().getUserData(), 
Hero) ? ctc.m_fixtureB.getBody().getUserData() : null;

        if (hero != null) {

            remove(Std.is(ctc.m_fixtureA.getBody().getUserData(), Coin) ? 
ctc.m_fixtureA.getBody().getUserData() : ctc.m_fixtureB.getBody().getUserData());
            _ce.sound.playSound("collect");
        }
    }
}

 

 

Cocos2D for Haxe

https://github.com/ralcr/cocos2d-haxe

It's Cocos2D… for Haxe ported from Cocos2D for iPhone.  I really don't want to try to explain the Cocos2D family tree… it's… confusing.

Status: Last updated on Github 4 months ago.  Not encouraging.

Sample code from here.

 

 

import cocos.support.UIImage;

class Sample_UIImage {

    public function new(){
        var uiimage = new UIImage().initWithContentsOfFile("grossini.png");
        uiimage.onComplete = callback (onComplete, uiimage);
        //flash.Lib.current.addChild ( new flash.display.Bitmap ( new Girl(0,0)));
    }
    function onComplete(uiimage:UIImage) {
        flash.Lib.current.addChild ( uiimage.bitmap );
    }

    public static function main(){
        haxe.Firebug.redirectTraces();
        flash.Lib.current.stage.scaleMode = flash.display.StageScaleMode.NO_SCALE;
        flash.Lib.current.stage.align = flash.display.StageAlign.TOP_LEFT;
        new Sample_UIImage();
    }
}

 

 

Spur

https://github.com/PixelPounce/Spur

A component based game engine.  It appears to be young and hasn't been updated in a long time, a bad combination so I am ignoring it.

 

Hydras

A port of the PushButton engine.  A component based game engine built over top of NME.  Supports many targets, but hasn't been updated in a while.  Documentation?  None, zilch, nodda.  

Status: Last updated about a year ago

Sample code from here:

 

package ;

import com.pblabs.components.scene2D.CircleShape;
import com.pblabs.components.scene2D.SceneAlignment;
import com.pblabs.components.spatial.SpatialComponent;
import com.pblabs.components.tasks.FunctionTask;
import com.pblabs.components.tasks.LocationTask;
import com.pblabs.components.tasks.RepeatingTask;
import com.pblabs.components.tasks.SerialTask;
import com.pblabs.engine.core.PBContext;
import com.pblabs.engine.core.PBGame;
import com.pblabs.engine.core.SignalBondManager;
using Lambda;

using com.pblabs.components.scene2D.SceneUtil;
using com.pblabs.components.tasks.TaskUtil;
using com.pblabs.engine.core.PBGameUtil;
using com.pblabs.engine.util.PBUtil;

class Demo
{
    public function new()
    {
        //Setup logging.
        com.pblabs.engine.debug.Log.setup();

        var game = new PBGame();
        game.addBaseManagers();

        //The main "context". This is equivalent to a level, or a menu screen.
        var context :PBContext = game.pushContext(PBContext);
        //This method is via 'using' SceneUtil
        var scene2D = context.createBaseScene();
        scene2D.sceneAlignment = SceneAlignment.TOP_LEFT;
        var layer = scene2D.addLayer("defaultLayer");

        //Create our blob that we will move around.
        var so = context.createBaseSceneEntity();
        // var blob = context.allocate(com.pblabs.components.scene2D.RectangleShape);
        // blob.borderRadius = 10;
        var blob  = context.allocate(com.pblabs.components.scene2D.CircleShape);
        // blob.radius = 30;

        blob.fillColor = 0xff0000;

        blob.width = 100;
        // blob.height = 300;
        blob.parentProperty = layer.entityProp();
        so.addComponent(blob);
        so.initialize("SomeSceneObj");

        var topLeft = scene2D.getAlignedPoint(SceneAlignment.TOP_LEFT);
        var topRight = scene2D.getAlignedPoint(SceneAlignment.TOP_RIGHT);
        var bottomRight = scene2D.getAlignedPoint(SceneAlignment.BOTTOM_RIGHT);
        var bottomLeft = scene2D.getAlignedPoint(SceneAlignment.BOTTOM_LEFT);

        //This method is via 'using' SceneUtil
        so.setLocation(50, 100);
        //This method is via 'using' TaskUtil
        so.addTask(new RepeatingTask(
            new SerialTask(
                LocationTask.CreateEaseOut(topLeft.x + blob.width / 2, topLeft.y + blob.height / 2, 2),
                LocationTask.CreateEaseOut(topRight.x - blob.width / 2, topRight.y + blob.height / 2, 2),
                LocationTask.CreateEaseOut(bottomRight.x - blob.width / 2, bottomRight.y - blob.height / 2, 2),
                LocationTask.CreateEaseOut(bottomLeft.x + blob.width / 2, bottomLeft.y - blob.height / 2, 2)
            )
            ));

        //Prevents the first frame have the location at (0,0)
        scene2D.update();
    }

    public static function main()
    {
        new Demo();
    }
}

 

Flambe

https://github.com/aduros/flambe

Unfortunately it's web and Flash only so a no-go for me.

 

AWE6

https://code.google.com/p/awe6/

Awe6 is an inversion-of-control / component based game engine.  If you've never heard of IoC or Dependency injection, let me show you this wonderful example from Stack Overflow that shows IoC at it's simplest.

Traditional way:

Class car {

Engine _engine; Public Car() { _engine = new V6(); }

}

Inverted Way

Class car {

Engine _engine;

Public Car(Engine engine) { _engine = engine; }

}

var car = new Car(new V4());

Essentially you are "injecting" functionality into your class, as here, you "Inject" the engine type into the car via the constructor.

Alright, enough about IoC and Dependency Injection, back to Awe6.

Awe6Overview

The above graphic is the overview from the Awe6 site.  The framework has fairly good documentation.

Status: Most recent change was a couple days ago:

Sample code from here:

 

 

package demo.scenes;
import awe6.core.Scene;
import awe6.extras.gui.Text;
import awe6.interfaces.EAudioChannel;
import awe6.interfaces.EMessage;
import awe6.interfaces.EScene;
import awe6.interfaces.ETextStyle;
import awe6.interfaces.IEntity;
import awe6.interfaces.IKernel;
import demo.AssetManager;
import demo.entities.Bouncer;
import demo.entities.Sphere;
import demo.Session;

class Game extends AScene
{
    public static inline var TIME_LIMIT = 30;
    private var _timer:Text;
    private var _score:Int;

    override private function _init():Void
    {
        super._init();
        isPauseable = true;
        isSessionSavedOnNext = true;
        _session.isWin = false;
        var l_textStyle = _kernel.factory.createTextStyle( ETextStyle.SUBHEAD );
        #if js
        // for js performance boost (realtime filters very constly)
        l_textStyle.filters = [];
        l_textStyle.color = 0x020382;
        #end
        _timer = new Text( _kernel, _kernel.factory.width, 50, Std.string( 
_tools.convertAgeToFormattedTime( 0 ) ), l_textStyle );
        _timer.y = 70;
        addEntity( _timer, true, 1000 );

        _kernel.audio.stop( "MusicMenu", EAudioChannel.MUSIC );
        _kernel.audio.start( "MusicGame", EAudioChannel.MUSIC, -1, 0, .5, 0, true );
        for ( i in 0...10 )
        {
            addEntity( new Sphere( _kernel ), true, i + 10 );
        }
        _kernel.messenger.addSubscriber( _entity, EMessage.INIT, handleSphere, Sphere );
        _kernel.messenger.addSubscriber( _entity, EMessage.DISPOSE, handleSphere, Sphere );
    }
    
    public function handleSphere( p_message:EMessage, p_sender:IEntity ):Bool
    {
// trace( p_message + " " + p_sender );
        return true;
    }
    

    override private function _updater( ?p_deltaTime:Int = 0 ):Void
    {
        super._updater( p_deltaTime );

        _score = Std.int( _tools.limit( ( 1000 * TIME_LIMIT ) - _age, 0, _tools.BIG_NUMBER ) );
        if ( _score == 0 )
        {
            _gameOver();
        }
        _timer.text = _tools.convertAgeToFormattedTime( _age );
        var l_spheres:Array<Sphere> = getEntitiesByClass( Sphere );
        if ( ( l_spheres == null ) || ( l_spheres.length == 0 ) )
        {
            _gameOver();
        }
    }

    override private function _disposer():Void
    {
        _kernel.audio.stop( "MusicGame", EAudioChannel.MUSIC );
        super._disposer();
    }

    private function _gameOver():Void
    {
        if ( _score > _session.highScore )
        {
            _session.isWin = true;
            _session.highScore = _score;
        }
        _kernel.scenes.next();
    }

}

 

Ash Entity Framework

https://github.com/nadako/Ash-HaXe

This is a Haxe port of the Ash Framework a popular entity framework.  Unlike earlier examples, this is not a game engine, but may be an option as NME provides a great deal of the functionality you would normally require from a game engine.  That said, it is marked as PRE-ALPHA… that's pretty early on.  It's pretty active development wise but Haxe specific documentation is basically non-existent.

Status: Last commit 5 days ago.

Sample code from here:

 

package net.richardlord.asteroids;

import flash.display.DisplayObjectContainer;

import ash.tick.ITickProvider;
import ash.tick.FrameTickProvider;
import ash.core.Engine;

import net.richardlord.asteroids.systems.BulletAgeSystem;
import net.richardlord.asteroids.systems.CollisionSystem;
import net.richardlord.asteroids.systems.GameManager;
import net.richardlord.asteroids.systems.GunControlSystem;
import net.richardlord.asteroids.systems.MotionControlSystem;
import net.richardlord.asteroids.systems.MovementSystem;
import net.richardlord.asteroids.systems.RenderSystem;
import net.richardlord.asteroids.systems.SystemPriorities;
import net.richardlord.asteroids.systems.AnimationSystem;
import net.richardlord.asteroids.systems.DeathThroesSystem;
import net.richardlord.input.KeyPoll;

class Asteroids
{
    private var container:DisplayObjectContainer;
    private var engine:Engine;
    private var tickProvider:ITickProvider;
    private var creator:EntityCreator;
    private var keyPoll:KeyPoll;
    private var config:GameConfig;

    public function new(container:DisplayObjectContainer, width:Float, height:Float)
    {
        this.container = container;
        prepare(width, height);
    }

    private function prepare(width:Float, height:Float):Void
    {
        engine = new Engine();
        creator = new EntityCreator( engine );
        keyPoll = new KeyPoll( container.stage );
        config = new GameConfig();
        config.width = width;
        config.height = height;

        engine.addSystem(new GameManager( creator, config ), SystemPriorities.preUpdate);
        engine.addSystem(new MotionControlSystem( keyPoll ), SystemPriorities.update);
        engine.addSystem(new GunControlSystem( keyPoll, creator ), SystemPriorities.update);
        engine.addSystem(new BulletAgeSystem( creator ), SystemPriorities.update);
        engine.addSystem(new DeathThroesSystem( creator ), SystemPriorities.update);
        engine.addSystem(new MovementSystem( config ), SystemPriorities.move);
        engine.addSystem(new CollisionSystem( creator ), SystemPriorities.resolveCollisions);
        engine.addSystem(new AnimationSystem(), SystemPriorities.animate);
        engine.addSystem(new RenderSystem( container ), SystemPriorities.render);

        creator.createGame();
    }

    public function start():Void
    {
        tickProvider = new FrameTickProvider( container );
        tickProvider.add(engine.update);
        tickProvider.start();
    }
}

 

Please let me know if I have missed any!



Personally I am leading towards Flixel ( community size and maturity level ), but am going to take a closer look at the Awe6 engine first. If neither works for me, I will simply roll me own!

,

4. April 2013

Now that my book is published it's time to move on to my next project.  Given the original purpose of this site, with a two year distraction, I think it's high time that I get around to actually creating and publishing a game!  I intend to share as much of the development process, code, assets, etc… as I go, including having the WIP game playable here!

 

What I have in my head is a science fiction business simulation game.  Trust me, the actual game is a hell of a lot more exciting than that title indicates, I will flesh out the details in further blog posts.  If you played old school BBS games, or many of the recent Kairosoft or Tycoon style games, you should have an inkling of the type of game I am considering.  I actually tend to find these styles of turn based games are the best fit for mobile games, especially in the world of play 5 minutes here, 5 minutes there.

 

One of the biggest requirements in creating a game is being able to post about it here.  I want to share as much of the development experience as possible, and hopefully be as interesting to as large an audience as possible.  Ultimately I do intend to sell the game on at least iOS and Android, but I would also like to give it away here.  I would also like to make all of the source, and eventually ( once I've hopefully sold a few copies ) the assets available as I go.

 

So here are my current requirements:

  • Turn based game
  • May require modest 3D support, otherwise sprite based
  • Excellent UI support ( UI heavy game )
  • Available on Android, iOS and on GameFromScratch.com at a minimum
  • In a language of interest or known to a large number of developers
  • Tools at low or no cost, so maximum number of readers can follow along
  • Reasonably quick development time
  • Good library support, little desire to re-invent the wheel

 

For the last week I've been mulling these requirements around in my head and I think I've settled on my technology of choice.  First I will go through my list of considered but discounted technologies and the reasons why.

LoomScript I recently finished evaluating Loom and I was rather impressed.  There is CSS style markup and tweening support for making a solid UI relatively easily, it targets all the desired platforms, except the web ( MacOS and Windows builds might offset this lacking to a degree ).  That said though, at the end of the day, it's just not that popular yet, so I would be writing about a technology a very small minority of you are interested in.

Unity is an obvious choice.  It would handle the 3d portions better than any other engine i've considered and it is certainly popular, so would probably be relevant for a lot of you.  At the end of the day though, it's a 3D engine, not a 2D one.  Of course, you can accomplish a great deal using 3rd party libraries, for an additional cost.  At the end of the day, that was the biggest mark against Unity, the cost factor.  Although I own a 3.x license from the give away, and could ultimately buy a 4.x license when ready to ship, I don't want to go that route if I don't really need the majority of the features it buys me.  The UI support too is also remarkably sparse.

Scaleform was another one I considered.  On the UI side of the fence, it may be the strongest option.  It's basically a game optimized version of Flash and is used in hundreds of games to provide the UI functionality.  That said, it's pretty niche in the indie world at this point and the amount of supporting materials is pretty light right now.

C++, very little gain.  Would love to take a closer look at the GamePlay3D engine, but it's overkill for this game project.

Flash/AIR is another option.  In all my years, I somehow completely avoided doing any Flash development.  Keep in mind, I am not talking about browser based Flash, I am talking Flash as a development environment.  I haven't completely disregarded Flash as an option, but puzzling out the eco-system was rather confusing.  Not really sure what level of commitment would be need to be made to work with Flash.  I do really wish FlashDevelop was available on MacOS…  Not ruled out Flash yet, but probably not.

The LUAs. I really like all of the Lua based game engines but none are a particularly good fit.  I don't believe any of them support web deployment right now, Corona and Gideros both have a price tag attached and Moai is a poor fit… I am not writing a high performance game, so there aren't enough wins to make it worth while, as I'd have to roll my own UI system, etc.

LibGDX/PlayN Both support a good number of platforms, both are libraries of interest to me.  In the case of PlayN, I just found the entire build process ( MAVEN!!! ) so tedious and prone to breakage, plus I would be pretty much rolling my own UI.  LibGDX is on my todo to look into further and hasn't been discounted completely, but UI support is there.

HaXe + NME Still looking into this one, it's a maybe.

 

At the end of the day, that leaves an obvious choice and two maybes.

 

HTML5 It's popular, library support is staggering, UI support is a no brainer ( I can use a few thousand existing HTML libraries ), hosting it on this website is also a no brainer, I can easily port to Windows 8 and Chrome app stores and frankly, I want to see what the HTML5 developer experience is like on a full game.  There is one possible achilles heel to this decision though… native support.  I want to deploy as a native app at the end of the day, and there are options for this ( XDK, CocoonJS, PhoneGap ), but there are also problems.  WebGL support is I believe only available on Cocoon and it's "coming soon".  The game in my mind isn't performance intensive, but I don't know if native WebView is fast enough, probably rendering PhoneGap a non-starter… I need to (very very very soon) try the various native deployment options and see how it goes.

 

That said, if HTML5 doesn't work out, I've got two runners up picked out.  HaXe and libGDX.

 

Is there any mainstream language you would prefer I do this project in?  Anyways… wish me luck, first steps and all of that...

News, Programming ,

17. October 2012

 

A level is made up of sprites and sprites come from somewhere.  In our editor, we are going to allow the user to “upload” multiple image files containing sprite sheets.  However, are server is not required and that is going to require a bit of work.  Also, we are going to need some form of UI where users can upload the spritesheet, without cluttering our main UI too much, so we will implement it as a modal dialog box.

 

Well, let’s get to it.  First lets create a data type for holding our sprite sheet collection.  For now, a spritesheet is simply an image, the dimensions of each sprite and a name.  In your models folder create a new file named spriteSheet.js

spriteSheet.js

 

YUI.add('spriteSheet',function(Y){
    Y.SpriteSheet = Y.Base.create('spriteSheet', Y.Model, [],{
            count:function(){
                return this.get('spritesheets').length;
            },
            add:function(name,width,height,img){
                this.get('spritesheets').push({name:name,width:width,height:height,img:img});
            }
        },{
            ATTRS:{
                spritesheets: {
                    value: []
                }
            }
        }
    );
}, '0.0.1', { requires: ['model']});

Nothing really special.  Our spritesheets attribute is just an empty array for now.  We also included a pair of methods, add, for adding a new spritesheet and count for getting the current count of spritesheets already declared.  Everything else here should already be familiar at this point.

 

Now we want to create a dialog that will be displayed when the user wants to add a spritesheet.  As a bit of a spoiler, here is what we are going to create:

image

This isn’t a View and it isn’t a model, so we create a new folder called classess and create the long-winded file named AddSpriteSheetDialog.js

AddSpriteSheetDialog.js

YUI.add('addSpriteSheetDialog', function(Y){

    Y.AddSpriteSheetDialog = new Y.Base();
    var spriteSheets = null;
    Y.AddSpriteSheetDialog.show = function(ss,onComplete){
        spriteSheets = ss;
        var panel = new Y.Panel({
            width:500,
            height:300,
            centered:true,
            visible:true,
            modal:true,
            headerContent:'Select the image file containing your sprite sheet',
            bodyContent:Y.Node.create(
                "<DIV>\
                <input type=file id=spritesheet /> \
                <br /> <div id=imgName style='padding-top:25px;padding-bottom:25px'> \
                Click above to select a file to download</div>\
                <br />Sheet name:<input type=Text id=name size=30 value=''> \
                <br />Sprite Width:<input type=Text id=width size=4 value=32> \
                Sprite Height:<input type=Text id=height size=4 value=32> \
                <br /><input type=button id=done value=done />\
                </DIV>\
                "
            ),
            render:true
        });

        var fileUpload = Y.one("#spritesheet");
        fileUpload.on("change", Y.AddSpriteSheetDialog._fileUploaded);

        var buttonDone = Y.one("#done");
        buttonDone.on("click", function(){
            panel.hide();
            onComplete();
        })
        panel.show();

    };

    Y.AddSpriteSheetDialog._fileUploaded = function(e){
        if(!e.target._node.files[0].type.match(/image.*/)){
            alert("NOT AN IMAGE!");
            return;
        }
        var selectedFile = e.target._node.files[0];
        var fileReader = new FileReader();

        var that=this;
        fileReader.onload = (function(file){
            return function(e){
                if(e.target.readyState == 2)
                {
                    var imgData = e.target.result;
                    var img = new Image();
                    img.onload = function(){
                        Y.one('#imgName').set('innerHTML',selectedFile.name + " selected");
                        var name = Y.one('#name').get('value');
                        var width = Y.one('#width').get('value');
                        var height = Y.one('#height').get('value');
                        spriteSheets.add(name,width,height,img);
                    }
                    img.src = imgData;
                }
            };

        })(selectedFile);
        fileReader.readAsDataURL(selectedFile);

    };


},'0.0.1', {requires:['node','spriteSheet','panel']});

The editorView owns the spritesheet collection, and passes it in to the show() method of AddSpriteSheetDialog.  We also pass in a callback function that will be called when we are done.

We start off creating the panel which is a Y.Panel.  Most of the properties should be pretty straight forward, headerContent is the title and bodyContent is either the ID of the object to render the panel in, or in our case, we actually create a new node with our dialog HTML.  We then wire up a change handler on our file upload button, this will fire when a file is uploaded and call the _fileUploaded function.  We then wire up the Done button’s on click handler to hide the panel then call the callback function that was passed in.  Finally we display the panel.

 

When the user clicks the Choose File button, _fileUploaded is called.  First thing we check to make sure it is an image that is uploaded and error out if it isn’t.  We then want to read the selected file, which we do with the FileReader api.  Word of warning, this isn’t completely supported in every browser… frankly though, I don’t care about supporting IE in a project like this, cross browser support takes all of the fun out of web app development! Smile

 

Next is well… JavaScript at it’s most confusing. We are registering an onload event that will be fired once the file has been loaded, which in turn fires off an anonymous method.  It checks the readystate of the file to make sure it is ready and if so, our “uploaded” file will be in e.target.result.  We then create an Image object, then register yet another onload handler, this one for when the image has completed loading.  Once the user has uploaded the file, its finished loading and populated in our newly create Image, we then get the width, height name and our newly populated image and at it to the screenSheets object we passed in during show().  Yes, this is a bit screwy of an interface, in that you need to populate the text fields before uploading the interview.  I will ultimately clean that up ( and add edit ability ), but it would needlessly complicate the code for now.  Finally, no that our fileReader.onload() event is done, we actually read the file now with readAsDataUrl() the file that was chosen, which fires off the whole onload event handler in the first place.   Welcome to asynchronous JavaScript programming!  Don’t worry, if this is new to you, thinking async will come naturally soon enough…

 

So, that is how you can create a modal dialog to edit app data.  Now we wire it up and deal with a bit of a gotcha.

 

The gotcha first…  the Panel dialog requires a parent HTML element in the DOM to have a YUI skin CSS class declared.  At the bottom on the render function in editor.View.js add the following code:

Y.one('body').setStyle("margin",0);
Y.one('body').setStyle("overflow","hidden");
// The below needs to be added as some controls, such as our add sprite dialog, require a parent container
// to have the YUI skin defined already
Y.one('body').setAttribute("class","yui3-skin-sam");
return this;

This adds the yui3-skin-sam class to the page’s body, which brings in all the styling for the Panel ( and other YUI widgets ).

 

While we are in editor.View.js, we wire up a menu handler for when the user clicks the add spritesheet button ( we will add in a second ).  That handler is basically the same as the menu:fileExit handler we created earlier.  Right below that handler in the initializer function, add the following:

 

var that = this;
Y.Global.on('menu:fileAddSpriteSheet',function(e){
    var dialog = Y.AddSpriteSheetDialog.show(that.spriteSheets,function(){
        var sheet = that.spriteSheets.get("spritesheets")[0];
        console.log(sheet);
    });
});

There is the that=this hack again, there are alternatives ( you can pass the context in to the Y.Global.on event handler ), but this is a fair bit easier at the end of the day, as we would lose this again when the callback is called.  Otherwise, when the menu:fileAddSpriteSheet event is received, we simply call AddSpriteSheetDialog.show(), passing in our spritesheet and the function that is called when the panel is complete.  For now we simply log the spritesheet out to the console to prove something changed.

We also need to add the SpriteSheet to our editor.View.js, like so:

 

 Y.EditorView = Y.Base.create('editorView', Y.View, [], {
        spriteSheets:new Y.SpriteSheet(),
        initializer:function(){

 

Now we need to add the menu item.  First add it to the template mainMenu.Template,like so:

<ul>
    <li class="yui3-menuitem" id="menuFileAddSpriteSheet">
        <a class="yui3-menuitem-content" href="#">Add SpriteSheet</a>
    </li>
    <li class="yui3-menuitem" id="menuFileExit">
        <a class="yui3-menuitem-content" href="#">Exit</a>
    </li>
</ul

And we wire it up in the mainMenu.View.js, add the bottom of render() add the following code:

var menuFileAddSpriteSheet = container.one('#menuFileAddSpriteSheet');
            menuFileAddSpriteSheet.on("click", function(e){
                Y.Global.fire('menu:fileAddSpriteSheet', {msg:null});
            });

Oh, and our newly added script AddSpriteSheetDialog.js is added to index.html to guarantee it gets loaded and evaluated.

 

And done.  We now added a dialog for adding sprite sheet images, and can store the image results locally without requiring any server interaction at all.

 

Here is the end result, select File->Add Spritesheet to bring up the newly created dialog:

 


You can download the entire updated source code here.

One step closer to a full web based game editor, one very tiny step. Smile

Programming, General , , ,

11. October 2012

Our application hasn’t looked very… applicationy up until this point.  The menu area was basically a space full with “coming soon”.  In this post we will address adding a menu to our HTML app and show how we can pass fire and handle menu click events.

 

First change I suppose, we need a menu.  We will be using the YUI menu plugin MenuNav.  If I am honest, it is unweildy compared to some HTML UI widgets I have used in the past, but since we are using YUI, might as well use it for everything.

 

We make the following changes to mainMenu.Template

 

<div style="width:100%" class="yui3-skin-sam">
    <div id="appmenu" class="yui3-menu yui3-menu-horizontal"><!-- Bounding box -->
        <div class="yui3-menu-content" ><!-- Content box -->
            <ul>
                <li>
                <a class="yui3-menu-label" href="#file">File</a>
                <div id="file" class="yui3-menu">
                    <div class="yui3-menu-content">
                        <ul>
                            <li class="yui3-menuitem" id="menuFileExit">
                                <a class="yui3-menuitem-content" href="#">Exit</a>
                            </li>
                        </ul>
                    </div>
                </div>
                </li>
            </ul>
        </div>
    </div>
</div>

Read the link above for more details about exactly what is going on here.  The key things to notice are the id’s for the menu (appmenu) and menu item (menuFileExit), both of those will be used shortly.  It is also of key importance to give the containing div the class yui3-skin-sam, as this is what brings in all of the YUI3 css and formatting.  You could also add this to the <BODY> tag in editor.View.js, which we may do as we add more YUI controls.  Just be aware that a parent node within the DOM needs to have this class declared.

 

So, that’s is our markup, lets look at the code side of things.  Open up and change mainMenu.View.js

YUI.add('mainMenuView',function(Y){
    Y.MainMenuView = Y.Base.create('mainMenuView', Y.View, [], {
        initializer:function(){
            var results = Y.io('/scripts/views/templates/mainMenu.Template',{"sync":true});
            // No need to compile, nothing in template but HTML 
            // this.template = Y.Handlebars.compile(results.responseText);
            this.template = results.responseText;
        },
        render:function(){
            this.get('container').setHTML(this.template);
            var container = this.get('container');

            var menu = container.one("#appmenu");
            menu.plug(Y.Plugin.NodeMenuNav);

            //Register menu handlers
            var menuFileExit = container.one('#menuFileExit');

            menuFileExit.on("click",function(e){
                alert("Publishing");
                Y.Global.fire('menu:fileExit', {
                    msg:"Hello"
                });
            });

            var menuFileAddSpriteSheet = container.one('#menuFileAddSpriteSheet');
            menuFileAddSpriteSheet.on("click", function(e){
                Y.Global.fire('menu:fileAddSpriteSheet', {msg:null});
            });

            return this;
        }
    });
}, '0.0.1', { requires: ['view','io-base','node-menunav','event','handlebars']});

Here we changed our initializer to load synchronously as well, otherwise the basics are pretty much the same.  Not that we added the ‘node-menunav’ and ‘event’ dependencies to our requires array.  Otherwise the key changes are:

var menu = container.one("#appmenu");
menu.plug(Y.Plugin.NodeMenuNav);

This locates our appmenu div and plugs the NodeMenuNav into it, turning our DIV into a YUI3 style menu.  Basically this is where the magic happens.  Then:

var menuFileExit = container.one('#menuFileExit');
menuFileExit.on("click",function(e){
    alert("Publishing");
    Y.Global.fire('menu:fileExit', {
        msg:"Hello"
    });
});

Next we find our menuFileExit menu item and register an onClick handler for it.  When a click occurs we fire a global event named “menu:fileExit”, with a msg of Hello.  The name menu:fileExit was chosen by me and can be anything.  So, when the user clicks the Exit item in the menu, this event will be fired.  Let’s look at how you handle “catching” this event.  Open up editor.View.js and at the bottom of the initializer() function, add the following code:

Y.Global.on('menu:fileExit', function(e){
   alert(e.msg);
});

Basically, this monitors for a menu:fileExit event being fired, and simple alerts the contents.  This illustrates a simple way to provide a global menu which can be handled across multiple views.

 

Here is our project in action now:

Basically, it is exactly the same as before, but now it has a menu.

 

You can download the complete source code here.

Design, Programming , , ,

9. October 2012

As we saw in the last part, our application is made up of a single view composed of 3 child views.  In this post I am going to focus on the left hand view, which is where the actual map will be drawn.  This is easily the most important part.

 

All I hoped to accomplish today was to get an EaselJS stage integrated in to a YUI View, which with some horrific hacking, I have accomplished.  There are a few very important requirements.

 

First, we need to have a canvas element that EaselJS can work with.

Second, we want the canvas element to take up as much room on the UI as possible.  The right hand view is going to be fixed at 280 pixels in width, so we want the map editing area to consume the rest of the screen.

Finally, I want the whole thing to resize if the window is resized, so our application can support any resolution.

 

To accomplish this, I have altered the person.Template ( the right hand side placeholder for now ), to look like this:

<div style="width:280px;min-width:280px;max-width: 280px;float:right">
    <div align=right>
        <img src="http://www.gamefromscratch.com/image.axd?picture=HTML-5-RPG_thumb_1.png" 
             alt="GameFromScratch HTML5 RPG logo" />
    </div>
    <p><hr /></p>
    <div>
        <h2>About {{name}}:</h2>
        <ul>
            <li>{{name}} is {{height}} feet tall and {{age}} years of age.</li>
        </ul>
    </div>
</div>

Only real change here is the alteration to the parent div.

 

In editor.View.js I made the following simple change that the bottom of the render() function:

Y.one('body').setStyle("margin",0);
return this;

This is simply overriding the YUI default BODY styling, as I do not want any margins, padding or spaces between elements.

 

Then I altered map.Template as follows:

<div style="margin:0px;float:left;display:block" id="panel">
    <canvas width=300 height=300 id="mainCanvas" style="background-color: black;">
        Your browser doesn't support the canvas tag.
    </canvas>
</div>

I needed a named div to access programmatically, so I created one called “panel”.  I also changed the styling on the canvas so the background color would be black, making debugging a bit easier.  The dimensions passed to the canvas are going to be completely ignored.  Why the heck Canvas didn’t support % layout, I will never understand.

 

Finally, the majority of changes are in map.View.js, which I basically re-wrote:

YUI.add('mapView',function(Y){
    var Instance = null;
    Y.MapView = Y.Base.create('mapView', Y.View, [], {
        events:{
          "#mainCanvas": {
              click:function(e)
              {
                  alert("Blah");
              }
          }
        },
        initializer:function(){
            Instance = this;
            var results = Y.io('/scripts/views/templates/map.Template',{"sync":true});
            template = Y.Handlebars.compile(results.responseText);
        },
        prepareCanvas:function(){
            this.resizeEvent();
            createjs.Ticker.setFPS(30);
            createjs.Ticker.addListener(this.gameloop);

            Y.on('windowresize',this.resizeEvent);
        },
        render:function(){
            if(this.template === null)
                this.initializer();
            this.get('container').setHTML(template());
            this.prepareCanvas();
            return this;
        },
        gameloop:function(){
            Instance.stage.update();
            Instance.stage.getChildAt(0).x++;
            if(Instance.stage.getChildAt(0).x > Instance.stage.canvas.width)
                Instance.stage.getChildAt(0).x = 0;
        },
        resizeEvent:function(){
            var container = Instance.get('container');
            var canvas = container.one("#mainCanvas");
            var panel = container.one('#panel');

            var body = Y.one("body");
            var screenWidth = body.get("clientWidth");
            var screenHeight = body.get("scrollHeight");

            var width = Math.floor(screenWidth -280);
            var height = Math.floor(screenHeight );

            canvas.setStyle("width",width);
            canvas.setStyle("height",height);

            this.stage = new createjs.Stage(canvas.getDOMNode());
            // for some reason, easel doesn't pick up our updated canvas size so set it manually
            this.stage.canvas.width = width;
            this.stage.canvas.height = height;

            var shape1 = new createjs.Shape();
            shape1.graphics.beginFill(createjs.Graphics.getRGB(0,255,0));
            shape1.graphics.drawCircle(200,200,200);

            this.stage.addChild(shape1);
        }
    });
}, '0.0.1', { requires: ['view','event','io-base','handlebars']});

 

The variable Instance is a horrible hack, that I intend to replace at some point in the future.  That’s the joy of exercises like this, I can refactor out my hacks later on.  So, why does it exist… well you see, I make a couple of functions that are called back by external code, which completely clobber my this pointer.  I suppose it’s a poor mans singleton of sorts.

 

The end result of this code:

 

 

First thing I did within MapView is declare an event if someone clicks on our canvas ( which needs the id mainCanvas… this is another hackish solution that should possibly be factored away, although frankly, I am OK with requiring the canvas tag to have a certain ID, so I probably wont ) this function is called.  It was simply written to figure out how YUI views handled events.  All it does is pops up an alert with the text Blah.  As you can see, handling element level events in relatively simple, although sadly I couldn’t figure out how to capture document level events here.  Another thing on the todo list.

 

In the initializer function I take a copy of the this pointer in the Instance variable ( *hack* *hack* ), and have changed the template fetching code to no longer be asynchronous, to completely remove some unnecessary race conditions that can result from a network delay retrieving the template. Frankly in this case, async bought us nothing but headaches.

 

prepareCanvas is the method responsible for setting up the easelJS integration.  It starts off by calling resizeEvents, which is where the bulk of the actual work is done.  resizeEvents was factored out to a separate function, because this logic is the same on initial creation as it is when the window is resized.  When resizeEvent() is called, we first find the BODY tag, and get its width and height using clientWidth and scrollHeight.  You would think the obvious value would be clientHeight, but you would be wrong, this is just one of those ways that HTML sucks.  Once we have the width and height, we then calculate our view dimensions, by subtracting the space needed for the other views ( or… will soon for height that is ).  We then set the canvas to those dimensions using setStyle(), which resizes the CANVAS in the browser.  We then create our Stage object from our canvas.  One thing to keep in mind, YUI get() and one() functions return YUI Node objects, not actual DOM objects, so when dealing with 3rd party libraries, you need to access the actual DOM item the node contains, that can be done with .getDOMNode().  Next we manually update the stage.canvas width and height, because of what I can only assume is a bug, EaselJS doesn’t pick up the modifications we made to the Canvas dimenions… who knows, there might be something else going on behind the scenes.  Next we create a circle… just so we have something visible on screen, and add it to our stage.

 

Now that resizeEvents is done, back in prepareCanvas we then set up a Ticker, which is an EaselJS callback mechanism, somewhat like setTimeout.  This is the heartbeat of your application, and due to the setFPS(30) call, it *should* be called 30 times per second.  This is your traditional game loop within the application and will probably be used quite a bit in the future.  Finally we handle windowresize events using the Y.on() handling mechanism, to catch the case the user resizes the screen, and if they do we call resizeEvents ( which being an eventhandler, will clobber all over our this pointer ).

 

Finally, we have the aforementioned gameloop, which is a function that is going to be called every time createjs.Ticker, um… Ticks.  For now we simply update our stage, then find the one and only item on our stage with getChildAt(0), which is our circle, and increment it’s X value until it scrolls off the screen.

 

It seems a bit more complicated than it is, but the basics of most of what we are going to want to deal with is now in place.  We can handle UI events via YUI, can render to the Canvas using the EaselJS library and best of all, take full advantage of the screen resolution, no matter how big or small, and gracefully handle changes in size, something Canvas doesn’t do easily.

 

Figuring out how everything interacted was more of a headache than I expected, but I am reasonably happy with this setup for now.  Of coure, I am going to need to add real functionality to the map view, and have it feed by a Map model instead of just drawing a circle on screen, but all things in time.

 

You can download the complete project as of this point right here.

You can see the project in action by clicking here.  As you resize, so will the canvas element.

Programming, Design , , ,

Month List

Popular Comments