Source code for a very simple HTML5 Toddlers Game created using Haxe and OpenFL

Categories games, HTML5

Jump to source code.

You can see my son playing with it here, and the full game here, it’s in Romanian, it says the animal on click (I mean touch, but I am not used to this click/touch thing yet), or asks for the animal location when you press the upper right button (asking mode). It’s very basic without any UI effects, because my wife told me so, she is a Psychologist, so she knows better about this stuff, also after watching a lot of Grand Designs episodes, I have come to the conclusion that if you don’t listen to your wife you make dumb mistakes.

Most of the hard work on this game were the drawings, once I had those done, coding everything was pretty straight forward. Prior to this game, I did the MrNussbaums’s Boardwalk Challenge, which was way more complex in scope. That game was done in Flambe., and my first project in Haxe. While I did like Flambe, once I got used with the architecture, I did find adapting the game to the various screen sizes to be quite hard (you only got “scale”, and not “width” proprieties of your game objects). So when I started this new game, some time have already passed and OpenFL seemed the better candidate. Also I didn’t need the swf output since, this was going to be used mostly on mobile devices.

Here is the game specs doc:

Game scope: Learning new words (animals, in this case) using images and sound association.

Target Device use: mobile phones, tablets, mostly Android, but must work on iOS too.

Screen Orientation: must work both in landscape and portrait mode.

Screen sizes: must adapt to various screen resolutions and aspect ratios.

Design considerations: Since the target audience is of such small age, only 3 animals are shown on a screen at a time. Animations, or sound effects are not useful in this case since they would create a big distraction on a very easy to distract user (have you seen how easy toddlers loose concentration, almost as easy as us grownups 🙂 )

The game interaction:

1. The player (a very small child) touches a card and the sound associated is played. She can change the screen animals using the left or right navigation buttons, which loops the cards on the screen.

2. There is a second mode to play the game, when the child or parent touches the upper right button, “the question” mode will start playing. In this mode, the game asks the child where a particular animal is located on the screen. If the child answer correctly, a congratulation sound is played, else the game says the card that was touched and then asks again for the animal, until the child answers correctly, reinforcing the right association and thus teaching the word.

The Source code, together with all the artwork are here.

Prerequisites. To compile the code you need Haxe, OpenFL and Actuate together with your favorite IDE, I use HaxeDevelop.

Reasons to use OpenFL:

  • You don’t like to code in JavaScript
  • You like a more structure  (and scalable) way of doing things.
  • Mostly you will use it because you are an ActionScript developer and want to code in a familiar way,
  • You hate JavaScript ( have no idea why anyone would choose to hate a certain technology, but some people do, so)
Here is the full source code:

This is the Main.hx file:

 

 

ActionScript

  1. package primelecuvinte;
  2.  
  3. import openfl.display.Sprite;
  4. import openfl.display.BitmapData;
  5. import openfl.display.Bitmap;
  6. import openfl.display.StageDisplayState;
  7. import openfl.utils.Assets;
  8. import openfl.Lib;
  9. import openfl.events.Event;
  10. import openfl.events.MouseEvent;
  11. import motion.actuators.SimpleActuator;
  12. import motion.easing.Linear;
  13. import motion.Actuate;
  14. /**
  15. * ...
  16. * @author Cosmin Dolha
  17. */
  18. class Main extends Sprite
  19. {
  20. var bg:Bitmap;
  21. var gameStarted:Bool;
  22. var bgGameSp:Sprite;
  23. var scale:Float;
  24. var bgsp:Sprite;
  25. var title:Sprite;
  26. var intsrt:Sprite;
  27.  
  28. public function new()
  29. {
  30. super();
  31.  
  32. buildGameUI();
  33.  
  34. onResize(null);
  35. stage.addEventListener(Event.RESIZE, onResize);
  36.  
  37. }
  38. private function picSp(pic:String, sp:Sprite):Void
  39. {
  40. //empy sprite first
  41. if (sp.numChildren > 0)
  42. {
  43. sp.removeChildren();
  44. }
  45. var bd:BitmapData = Assets.getBitmapData('img/$pic');
  46. var bmp = new Bitmap(bd);
  47.  
  48. sp.addChild(bmp);
  49.  
  50. }
  51. private function buildGameUI():Void
  52. {
  53. bgsp = new Sprite();
  54. title = new Sprite();
  55. intsrt = new Sprite();
  56.  
  57. bgGameSp = new Sprite();
  58.  
  59. bgGameSp.alpha = 0;
  60.  
  61. addChild(bgGameSp);
  62.  
  63. addChild(bgsp);
  64. addChild(title);
  65. addChild(intsrt);
  66.  
  67. picSp("bg.png", bgGameSp);
  68. picSp("start_screen.jpg", bgsp);
  69. picSp("title.png", title);
  70. picSp("instr.png", intsrt);
  71.  
  72. addEvents();
  73. }
  74.  
  75. function addEvents():Void
  76. {
  77. stage.addEventListener(MouseEvent.CLICK, onStartGame);
  78. }
  79.  
  80. private function onStartGame(e:MouseEvent):Void
  81. {
  82.  
  83. Assets.getSound("img/sillent.mp3").play();
  84.  
  85. stage.removeEventListener(MouseEvent.CLICK, onStartGame);
  86.  
  87. stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
  88.  
  89. Actuate.tween (bgGameSp, 1, { alpha: 1 }).ease (Linear.easeNone).onComplete(startMyGame);
  90.  
  91. }
  92.  
  93. private function startMyGame():Void
  94. {
  95. gameStarted = true;
  96. bgsp.visible = false;
  97. intsrt.visible = false;
  98. title.visible = false;
  99.  
  100. bgsp.removeChildren();
  101. intsrt.removeChildren();
  102. title.removeChildren();
  103.  
  104. removeChild(title);
  105. removeChild(intsrt);
  106. removeChild(bgsp);
  107.  
  108. var gamePlay:GamePlay = new GamePlay();
  109. Lib.current.stage.addChild(gamePlay);
  110. }
  111.  
  112. private function onResize(e):Void
  113. {
  114. var scaleX = stage.stageWidth / 1280;
  115. var scaleY = stage.stageHeight / 720;
  116.  
  117. scale = Math.min(scaleX, scaleY);
  118.  
  119. stage.scaleX = scale;
  120. stage.scaleY = scale;
  121.  
  122. stage.x = (stage.stageWidth - 1280 * scale) / 2;
  123. stage.y = (stage.stageHeight - 720 * scale) / 2;
  124.  
  125. var pmode:Bool = stage.stageWidth > stage.stageHeight;
  126.  
  127. if (!gameStarted)
  128. {
  129.  
  130. if (pmode)
  131. {
  132.  
  133. fitScreenP(bgsp);
  134.  
  135. centerScreenP(title);
  136. centerScreenP(intsrt);
  137.  
  138. intsrt.y = stage.stageHeight - intsrt.height*1.5;
  139. title.y = title.height*1.5;
  140.  
  141. }
  142. if (!pmode)
  143. {
  144. fitScreenBgL(bgsp);
  145.  
  146. fitScreenL(title);
  147. centerScreenL(intsrt);
  148.  
  149. intsrt.y = stage.stageHeight - intsrt.height*4;
  150. title.y = title.height;
  151.  
  152. }
  153.  
  154. }
  155.  
  156. fitScreenStretch(bgGameSp);
  157.  
  158. }
  159. private function fitScreenStretch(sp:Sprite):Void
  160. {
  161. sp.width = stage.stageWidth;
  162. sp.height = stage.stageHeight;
  163. }
  164. private function fitScreenP(sp:Sprite):Void
  165. {
  166. sp.width = stage.stageWidth;
  167.  
  168. sp.scaleY = sp.scaleX;
  169. sp.x = Math.floor(stage.stageWidth / 2) - sp.width / 2;
  170. sp.y = Math.floor(stage.stageHeight / 2) - sp.height / 2;
  171. }
  172.  
  173. private function centerScreenP(sp:Sprite):Void
  174. {
  175. sp.scaleX = (stage.stageHeight / 720)*1.5;
  176. sp.scaleY = (stage.stageHeight / 720)*1.5;
  177. sp.x = Math.floor(stage.stageWidth / 2) - sp.width / 2;
  178. sp.y = Math.floor(stage.stageHeight / 2) - sp.height / 2;
  179. }
  180.  
  181. private function fitScreenL(sp:Sprite):Void
  182. {
  183. sp.scaleX = (stage.stageHeight / 720 );
  184. sp.scaleY = (stage.stageHeight / 720);
  185. sp.x = Math.floor(stage.stageWidth / 2) - sp.width / 2;
  186. sp.y = Math.floor(stage.stageHeight / 2) - sp.height / 2;
  187. }
  188.  
  189. private function fitScreenBgL(sp:Sprite):Void
  190. {
  191. sp.scaleX = (stage.stageHeight / 720);
  192. sp.scaleY = (stage.stageHeight / 720);
  193. sp.x = Math.floor(stage.stageWidth / 2) - sp.width / 2;
  194. sp.y = Math.floor(stage.stageHeight / 2) - sp.height / 2;
  195. }
  196.  
  197. private function centerScreenL(sp:Sprite):Void
  198. {
  199. sp.scaleX = (stage.stageHeight / 720 );
  200. sp.scaleY = (stage.stageHeight / 720);
  201. sp.x = Math.floor(stage.stageWidth / 2) - sp.width / 2;
  202. sp.y = Math.floor(stage.stageHeight / 2) - sp.height / 2;
  203. }
  204.  
  205. }

This is the GamePlay.hx file

ActionScript

  1. package primelecuvinte;
  2.  
  3. /**
  4.  * ...
  5.  * @author Cosmin Dolha
  6.  */
  7. import openfl.display.Sprite;
  8. import openfl.display.BitmapData;
  9. import openfl.display.Bitmap;
  10. import openfl.display.Stage;
  11. import openfl.display.StageDisplayState;
  12. import openfl.media.SoundChannel;
  13. import openfl.utils.Assets;
  14. import openfl.Lib;
  15. import openfl.events.Event;
  16. import openfl.events.MouseEvent;
  17. import motion.actuators.SimpleActuator;
  18. import motion.easing.Linear;
  19. import motion.Actuate;
  20. import Random;
  21. import haxe.Timer;
  22. class GamePlay extends Sprite
  23. {
  24.     var cardsHolderSp:Sprite;
  25.     var cardLeft:Sprite;
  26.     var cardMiddle:Sprite;
  27.     var cardRight:Sprite;
  28.     var navRight:Sprite;
  29.     var navLeft:Sprite;
  30.     var autoPlayOff:Sprite;
  31.     var autoPlayOn:Sprite;
  32.     var scale:Float;
  33.     var myStage:Stage;
  34.     var soundChannel:SoundChannel;
  35.     var playingSound:Bool;
  36.     var picsArray:Array<string>;
  37.     var totalPages:Int = 3;//starts at 0
  38.     var currentPage:Int = 0;
  39.     var autoPlayPickedCard:Int;
  40.     var cardClicked:Int;
  41.     var autoPlayMode:Bool;
  42.     var id:Int;
  43.    
  44.    
  45.     public function new()
  46.     {
  47.        
  48.         super();
  49.        
  50.         //the name of the picture and the mp3 sound associated with each
  51.         picsArray = ["albina", "vaca", "bufnita", "gasca", "caine", "oaie", "lup", "lama", "lebada", "papagal", "peste", "magar"];
  52.        
  53.         soundChannel = new SoundChannel();
  54.        
  55.         cardsHolderSp = new Sprite();
  56.        
  57.         cardLeft = new Sprite();
  58.         cardMiddle = new Sprite();
  59.         cardRight = new Sprite();
  60.        
  61.         navRight = new Sprite();
  62.         navLeft = new Sprite();
  63.        
  64.        
  65.         autoPlayOn = new Sprite();
  66.         autoPlayOff = new Sprite();
  67.  
  68.        
  69.         addChild(cardsHolderSp);
  70.        
  71.         addChild(navRight);
  72.         addChild(navLeft);
  73.         addChild(autoPlayOn);
  74.         addChild(autoPlayOff);
  75.        
  76.        
  77.        
  78.         cardsHolderSp.addChild(cardLeft);
  79.         cardsHolderSp.addChild(cardMiddle);
  80.         cardsHolderSp.addChild(cardRight);
  81.        
  82.  
  83.        
  84.         gotoPage(0);
  85.        
  86.         picSp("navarrow.png", navRight);
  87.         picSp("navarrowl.png", navLeft);
  88.        
  89.        
  90.         picSp("autooff.png", autoPlayOff);
  91.         picSp("autoon.png", autoPlayOn);
  92.        
  93.        
  94.        
  95.         cardLeft.addEventListener(MouseEvent.CLICK, leftClick);
  96.         cardMiddle.addEventListener(MouseEvent.CLICK, middleClick);
  97.         cardRight.addEventListener(MouseEvent.CLICK, rightClick);
  98.        
  99.        
  100.         navRight.addEventListener(MouseEvent.CLICK, navRightClick);
  101.         navLeft.addEventListener(MouseEvent.CLICK, navLeftClick);
  102.        
  103.         autoPlayOn.addEventListener(MouseEvent.CLICK, autoPlayOnClick);
  104.         autoPlayOff.addEventListener(MouseEvent.CLICK, autoPlayOffClick);
  105.        
  106.         addEventListener(Event.ADDED_TO_STAGE, onAdded);
  107.        
  108.         autoPlayOn.visible = false;
  109.         Lib.current.stage.addEventListener(MouseEvent.CLICK, goFullScreen);
  110.        
  111.     }
  112.     private function goFullScreen(e:Event):Void
  113.     {
  114.        
  115.         Lib.current.stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
  116.     }
  117.     private function anotherQuestionDelayed(e:Event):Void  
  118.     {
  119.         Timer.delay(anotherQuestion, 1500);
  120.     }
  121.    
  122.     private function anotherQuestion():Void
  123.     {
  124.                
  125.         autoPlayPickedCard = Random.int(0, 2);
  126.        
  127.         askQuestion();
  128.     }
  129.    
  130.     private function startAutoPlayGame():Void
  131.     {
  132.         autoPlayMode = true;
  133.         anotherQuestion();
  134.     }
  135.    
  136.     function askQuestion():Void  
  137.     {
  138.         soundChannel.stop();
  139.        
  140.         soundChannel = Assets.getSound("img/unde_este.mp3").play();
  141.         soundChannel.addEventListener(Event.SOUND_COMPLETE, askingFinished );
  142.        
  143.        
  144.        
  145.     }  
  146.    
  147.     function telTheCardClicked(e:Event):Void  
  148.     {
  149.         soundChannel.stop();
  150.        
  151.         Assets.getSound("img/"+picsArray[id*3+cardClicked]+".mp3").play();
  152.         soundChannel.addEventListener(Event.SOUND_COMPLETE, repeatQuestionDelayed );
  153.     }  
  154.    
  155.    
  156.     function repeatQuestionDelayed(e:Event):Void  
  157.     {
  158.         Timer.delay(repeatQuestionD, 1500);
  159.     }
  160.    
  161.     function repeatQuestionD():Void
  162.     {
  163.         soundChannel.stop();
  164.        
  165.         soundChannel = Assets.getSound("img/unde_este.mp3").play();
  166.         soundChannel.addEventListener(Event.SOUND_COMPLETE, askingFinished );
  167.     }
  168.    
  169.     function repeatQuestion(e:Event):Void
  170.     {
  171.         repeatQuestionD();
  172.     }
  173.    
  174.     function checkAnswer():Void  
  175.     {
  176.         if (autoPlayPickedCard == cardClicked)
  177.         {
  178.             soundChannel.stop();
  179.        
  180.             soundChannel = Assets.getSound("img/bravo.mp3").play();
  181.             soundChannel.addEventListener(Event.SOUND_COMPLETE, anotherQuestionDelayed );
  182.            
  183.         }else{
  184.            
  185.             soundChannel.stop();
  186.        
  187.             soundChannel = Assets.getSound("img/acolo_este.mp3").play();
  188.             soundChannel.addEventListener(Event.SOUND_COMPLETE, telTheCardClicked );
  189.            
  190.         }
  191.        
  192.     }
  193.     function playPickedCardSound():Void  
  194.     {
  195.         soundChannel.stop();
  196.        
  197.         soundChannel = Assets.getSound("img/"+picsArray[id*3+autoPlayPickedCard]+".mp3").play();
  198.        
  199.    
  200.     }
  201.    
  202.     private function askingFinished(e:Event):Void
  203.     {
  204.         soundChannel.removeEventListener(Event.SOUND_COMPLETE, askingFinished );
  205.         playingSound = false;
  206.        
  207.         playPickedCardSound();
  208.     }
  209.    
  210.    
  211.     private function autoPlayOffClick(e:MouseEvent):Void
  212.     {
  213.         autoPlayOff.visible = false;
  214.         autoPlayOn.visible = true;
  215.         startAutoPlayGame();
  216.        
  217.     }
  218.    
  219.     private function autoPlayOnClick(e:MouseEvent):Void
  220.     {
  221.         autoPlayOff.visible = true;
  222.         autoPlayOn.visible = false;
  223.         autoPlayMode = false;
  224.        
  225.     }
  226.    
  227.     function gotoPage(ids:Int) :Void
  228.     {
  229.         id = ids;
  230.        
  231.         picSp(picsArray[id*3]+".jpeg", cardLeft);
  232.         picSp(picsArray[id*3+1]+".jpeg", cardMiddle);
  233.         picSp(picsArray[id * 3 + 2] + ".jpeg", cardRight);
  234.        
  235.         if (autoPlayMode)
  236.         {
  237.              anotherQuestion();
  238.         }
  239.        
  240.     }
  241.    
  242.     private function navRightClick(e:MouseEvent):Void
  243.     {
  244.         currentPage++;
  245.         if (currentPage > totalPages)
  246.         {
  247.             currentPage = 0;
  248.         }
  249.        
  250.         gotoPage(currentPage);
  251.     }  
  252.    
  253.     private function navLeftClick(e:MouseEvent):Void
  254.     {
  255.         currentPage--;
  256.         if (currentPage < 0)
  257.         {
  258.             currentPage = totalPages;
  259.         }
  260.         gotoPage(currentPage);
  261.     }
  262.  
  263.    
  264.     private function cardsClicked(cardID:Int):Void
  265.     {
  266.         cardClicked = cardID;
  267.        
  268.         if (!playingSound && !autoPlayMode)
  269.         {
  270.             playingSound = true;
  271.             soundChannel.stop();
  272.             soundChannel = Assets.getSound("img/"+picsArray&#91;id*3+cardID]+".mp3").play();
  273.             soundChannel.addEventListener(Event.SOUND_COMPLETE, onSoundFinished );
  274.            
  275.         }
  276.        
  277.        
  278.         if (autoPlayMode)
  279.         {
  280.             checkAnswer();
  281.            
  282.         }
  283.     }
  284.     private function onSoundFinished(e:Event):Void
  285.     {
  286.         soundChannel.removeEventListener(Event.SOUND_COMPLETE, onSoundFinished );
  287.         playingSound = false;
  288.     }
  289.    
  290.     private function leftClick(e:MouseEvent):Void
  291.     {
  292.         cardsClicked(0);   
  293.     }
  294.    
  295.     private function middleClick(e:MouseEvent):Void
  296.     {
  297.         cardsClicked(1);
  298.        
  299.     }  
  300.    
  301.     private function rightClick(e:MouseEvent):Void
  302.     {
  303.         cardsClicked(2);
  304.     }
  305.    
  306.     private function onAdded(e:Event):Void
  307.     {
  308.         removeEventListener(Event.ADDED_TO_STAGE, onAdded);
  309.        
  310.         onResize(null);
  311.         stage.addEventListener(Event.RESIZE, onResize);
  312.        
  313.        
  314.        
  315.     }
  316.  
  317.    
  318.     private function onResize(e):Void
  319.     {
  320.         var scaleX = stage.stageWidth / 1280;
  321.         var scaleY = stage.stageHeight / 720;
  322.        
  323.         scale = Math.min(scaleX, scaleY);
  324.         var buttonScale = scaleY;
  325.  
  326.         var pmode:Bool = stage.stageWidth > stage.stageHeight;
  327.    
  328.         if (pmode)
  329.         {
  330.    
  331.             cardLeft.x = 0;
  332.             cardMiddle.x = 420;
  333.             cardRight.x = 840;     
  334.            
  335.             cardLeft.y = 0;
  336.             cardMiddle.y = 0;
  337.             cardRight.y = 0;
  338.             fitScreenL(cardsHolderSp);
  339.            
  340.             var buttonScale = scaleY;
  341.            
  342.         }  
  343.        
  344.         if (!pmode)
  345.         {
  346.            
  347.             cardLeft.y = 0;
  348.             cardMiddle.y = 420;
  349.             cardRight.y = 840;
  350.            
  351.             cardLeft.x = 0;
  352.             cardMiddle.x = 0;
  353.             cardRight.x = 0;   
  354.            
  355.             fitScreenP(cardsHolderSp);
  356.            
  357.             buttonScale = scaleY*0.75;
  358.         }
  359.         navRight.scaleX = buttonScale;
  360.         navRight.scaleY = buttonScale; 
  361.            
  362.         navLeft.scaleX = buttonScale;
  363.         navLeft.scaleY = buttonScale;
  364.            
  365.            
  366.         autoPlayOff.scaleX = buttonScale;
  367.         autoPlayOff.scaleY = buttonScale;      
  368.        
  369.         autoPlayOn.scaleX = buttonScale;
  370.         autoPlayOn.scaleY = buttonScale;
  371.        
  372.        
  373.         autoPlayOn.x = stage.stageWidth - autoPlayOn.width;
  374.         autoPlayOff.x = stage.stageWidth - autoPlayOff.width;
  375.        
  376.         navRight.x = stage.stageWidth - navRight.width;
  377.        
  378.         if (pmode)
  379.         {  
  380.            
  381.             navRight.y = stage.stageHeight - navRight.height;
  382.             navLeft.y = stage.stageHeight - navRight.height;
  383.        
  384.         }
  385.         if (!pmode)
  386.         {
  387.            
  388.             navRight.y = stage.stageHeight/2 - navRight.height/2;
  389.             navLeft.y = stage.stageHeight / 2 - navRight.height / 2;
  390.         }
  391.     }
  392.    
  393.     private function fitScreenL(sp:Sprite):Void
  394.     {
  395.         sp.scaleX = stage.stageWidth / 1280;
  396.         sp.scaleY = stage.stageWidth / 1280;
  397.         sp.x = Math.floor(stage.stageWidth / 2) - sp.width / 2;
  398.         sp.y = Math.floor(stage.stageHeight / 2) - sp.height / 2;
  399.     }
  400.        
  401.     private function fitScreenP(sp:Sprite):Void
  402.     {
  403.         sp.scaleX = stage.stageHeight / 1280;
  404.         sp.scaleY = stage.stageHeight / 1280;
  405.        
  406.         sp.x = Math.floor(stage.stageWidth / 2) - sp.width / 2;
  407.         sp.y = Math.floor(stage.stageHeight / 2) - sp.height / 2;
  408.     }
  409.    
  410.     private function picSp(pic:String, sp:Sprite):Void
  411.     {
  412.         //empy sprite first
  413.         if (sp.numChildren > 0)
  414.         {
  415.             sp.removeChildren();
  416.         }
  417.         var bd:BitmapData = Assets.getBitmapData('img/$pic');
  418.         var bmp = new Bitmap(bd);
  419.         bmp.smoothing = true;
  420.         sp.addChild(bmp);
  421.        
  422.     }
  423. }

Thats it, pretty simple code, nothing fancy in here. I hope you can take chunks from it and use it in you HTML5 game/app.

 

Share this
Facebooktwitter

Creating Mr. Nussbaum’s Boardwalk Challenge

Categories games

From time to time, when the stars align just the right way, Greg and I make awesome educational games. He is a fantastic Game Designer, and his games are truly unique.

I usually take the opportunity and push it a bit beyond my own capabilities. This obviously stretches the development time and leaves me drained of all energy. But I enjoy every second, and I am always extremely proud with the end result.

In March 2016 my beautiful son arrives into this wonderful world. Never before I felt so happy in my life. I took a couple of months off, so I can help the new mommy and also adjust to the new parent role.

After 4 months, exhausted, thrilled, still adjusting to this new role and the huge responsibility of being a parent, I slowly go back to my own game development. About this time, Greg reaches out to me with a new game. I said OK, we will do it,  but have to start in October. That’s when the baby will be 6 months, and mommy goes back to work, and in my naivety I thought my own game will be finished. But I set a date, and said the work on the new game will start then no matter what.

So it id.

I have received the detailed description from Greg, so the development is officially on the way. Let me take this time to tell you something, Greg’s descriptions document is so good and well planned that it doesn’t change throughout the development. This is extremely rare, and one of the many reasons I enjoy working with him. The majority of software failures are due to a bad or incomplete plan.

With a good plan in hand, it’s time to decide, flash or HTML5. We decide it is a good idea to publish it in HTM5, so that it can run on iPad, or Android tablets and will more or less future proof. It took a couple of days to investigate what’s out there, and what tools we can use to reach our goals. I knew I wanted some sort of OOP programming that will be then converted (transpiled) to JavaScript and HTML5. I also wanted the game to work the same on iPad, Android and PC with all the various browsers out there.

I remembered something about HAXE from somewhere, so I started looking into it. After trying out different engines, I found that Flambe was working well across different browsers and OS, and also was able to publish the code to swf. So flambe it is.

Moving from FlashDevelop to HaxeDevelop was easy, making Flambe work, was not. It needed some patch in the source code, nothing too hard, and after some good googling around I find exactly what needs to be done, but I am afraid that for beginners, this might be off-putting.

We are up and running, so actual coding will start soon, but first I need to structure the work ahead, I break down all the major tasks into smaller ones. The game is made up by 5 games. You play the first, game to earn points that you use in order to gain access to other 4 fun games, and you play those games in order to gain points that you can exchange for items in the store. These items, get “printed” on certificates that kids can save to their devices.

The first game requires you to arrange the numbers on the screen in the right order, I named it “Earn Tokens”. Nothing too complicated to do in here, the prototype was up and running in no time.

The second game, is “Whack the Pirates”. By this time, I was already making the layouts, establishing the main themes, but didn’t settled for a look yet. First code and layout, then later when everything pretty much works the way it supposed to, I’ll move on to the artwork.

So far everything works pretty good, there is an annoying thing in Whack the pirates, where because of Flambe limitation, I can’t use masks properly, so that the characters hide when going back into their holes. But things move well and it seems that this new Haxe/Flambe thing is not bad at all, and I am very happy with the progress being made.

At this time it’s worth mentioning that development was being done under some tight schedule, in the time when the baby was sleeping, and, when mommy got back from work. Leaving me with almost zero personal downtime, before going to sleep.

Moving forward to Air Hockey, I realized it’s going to be a bit more challenging, but nothing that I can’t handle. For Air Hockey I just used some simple trigonometry, and simple point/distance collision detection, nothing too fancy, it worked.

Next one will be Ski-ball. After looking at all the SkeeBall games on YouTube that I can find, I realized, I won’t be able to get away with some simple tricks, I actually started to build it using the same tricks in Air Hokey, but it didn’t cut it this time. It needed a physics engine. After some research, I found Nape. It was an excellent choice, it did what I wanted and I will soon discover It did even more stuff that I didn’t even know I needed.

In order to edit the physics shapes you need a physics editor that can export the shapes back to Haxe, for this I bought PhysicsEditor. It does a good job at editing the shapes, but you need to trim the generated output code, so that you can use it effectively inside your game.

Soon after the Ski-ball game prototype was done, we hit a problem. The game won’t scale nicely. There is now way to scale things inside in a similar way to the flash based display list, so to make it work, you need to apply a scale calculation to everything, from math calculations inside the Air hockey game, to individual graphics. At this point I was afraid that I will need to apply the same scale calculations to each and every single node in the data provided by the physics editor, a task that would be extremely time consuming, not to mention every time that you would need to make an adjustment you would redo the whole process. Nightmare! Luckily, the shapes have a scale property, and the task to adjust for the scale is not that bad.

Finally I move to the final game Roll the Ball, and I soon discover the sensors inside Nape and I am in awe. They are awesome! Nape is Awesome!

Prototyping done.

Without getting too much into details, Flambe is really cool, but beware of the limitations, no sound on some iOS devices, no easy system for adjusting to different screen resolutions, no masking, no printing, no easy way to combine multiple image into a single one, for say download or other uses (like in character customization), and the entity/component system might be a bit awkward in the beginning. But if you don’t need printing, mask, downloading generated images, it’s a pretty solid engine, that works well on different devices, inside the different browsers. The sound issue is a problem not exactly with flambe, rather with Apple’s way of doing things, so much for “promoting” HTML5 on their devices.

After the coding part was mostly done I moved to the Art Work, based on the already made layouts and the place holders characters.

Some of the Art Work was inspired by the works of http://danielmerriam.com

After the Art Work was done, I added background music from http://incompetech.com/ and sound that I recorded with my phone, making noise using the toys my son plays with.

After some more testing, and bug fixing, the game is completed:

http://mrnussbaum.com/mr-nussbaums-boardwalk-challenge/

I hope kids will enjoy it for many years to come, and if you like it do share it!

Thank you for reading 🙂

Save

Save

Save

Share this
Facebooktwitter