Ok, I have to apologize : since this summer, I didn’t write a single line for Small-Codes. I could say that I had no longer enough time for it. I could say that other projects or my job, or my life, stole the necessary time for writing posts. But with this year’s ending, time as come for new years resolutions. So here is my main one: this blog will revive.
First : Update the old posts ! in the next few days, all posts about CreateJS will be updated to run the 0.5 version of EaselJS. This version has a new namespace. So any class instanciated have to be namespaced with “createjs” namespace.
Second : It’s time to write again ! New posts will be written, at least 1 each month, but I’ll try to write more. My goal is to write 2 or 3 articles every month in 2013. Sometimes it’ll be short ones. But it’s better than nothing !
Third : I’ll cover new topics ! CreateJS is still cool, and I’ve used it it for personal projects as well as in my job. No other library offers the coolness of CreateJS for building HTML5 canvas games or apps. I’ll go on writing posts about it. But there are other topics I’d like to write about :
eLearning, The ADL Xperience API and Rusticy Tin Can project
Did I already wrote about my job ? I don’t think so. I’m an eLearning developer and designer for a small (but great) company called Callimedia. So everyday, I mainly write code and design eLearning courses. These courses may be simple PowerPoint, Flash, or HTML5 based. We also build native IOs or Android apps and produce quality anatomical charts. Ours customers are mainly pharmaceutical laboratories and healthcare industry. This is the context in which I discovered the xAPI from ADL and Tin Can Project from Rusticy Softwares.
To make it short, it’s an API that will be used by eLearning providers to track and record learners activity. I’ll develop in a next post, but I think this API could be used in a LOT of other cases. I’m very interested with this project, and some of the next year’s posts will talk about the xAPI and TIN CAN.
Working with Laravel
In 2012, I felt the urge to learn a PHP framework. Symphony2 or Zend were to big for my little projects (for now! ) and they had a too high learning curve. So I decided to look for a good but more simple framework, and came across Laravel.
Now, except if my job asks for it, I don’t think I’ll ever learn another framework. Laravel has everything. So it’ll be one of the Small-code’s hot topics this year.
A personal project called Kenpath
In 2012, I had an idea. Ok. I got more other Ideas in 2012, don’t be silly. Let’s say this one is a great idea. So, I thought about it. I wrote the concept. Finally, I made a presentation about the main concept. Next I wrote a first prototype. And now, I’m actually writing a second, more robust prototype for an alpha test before June 2013. You’ll learn more about Kenpath in the next months in this blog. For now, all you have to know is that it involves eLearning and the xAPI.
Other random topics
I’ll occasionally write about other random subjects : JavaScript, HTML5, php, AS3, other blogs or websites I like, events… 2013 is a great year to come for Small-codes, stay tuned. I wish you a very Happy Holiday Season !
Here are some recipes I use to add a fullscreen feature to the HTML5 canvas tag in browsers.
Firstly, I think we have to differentiate two concepts : browser fullscreen and in-browser fullscreen or full window.
Browser fullscreen is activated when the user press the F11 key. The browser windows extends itself across the whole screen, and hide it’s toolbars. you can find on the web some snippets examples allowing to replicate this behavior, but they are neither compatible with all browsers, nor a good practice: the user must be able to choose it’s navigation mode. Otherwise he could encounter problems with regard to accessibility.
In-browser fullscreen is not really a full screen behavior: it’s often called full-window. It forces a DOM element’s size to conform to the browser window’s bounds. This practice is better considered. The user may complete his full screen experience by using the F11 key, if he wants to.
This is the behavior we are going to add to the canvas.
Build the HTML page and its CSS sheet
We are going to build a simple page containing a HTML5 Canvas. This canvas will show an animation created with EaselJS.
I added a wrapper around the H1 title and the canvas: in a web page, the canvas will rarely be put directly under the body of your page.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" >
<title>Tutorial: Full-window canvas mode with CreateJS</title>
<script src="http://code.createjs.com/easeljs-0.4.2.min.js" > </script>
<script src="http://code.createjs.com/tweenjs-0.2.0.min.js" > </script>
<link rel="stylesheet" href="css/style_start.css" >
<script>
var canvas,
wrapper,
stage,
shape;
function init(){
var g;
canvas = document.getElementById("canvas");
wrapper = document.getElementById("wrapper");
stage = new Stage(canvas);
g = new Graphics();
g.setStrokeStyle(1);
g.beginStroke(Graphics.getRGB(0,0,0));
g.beginFill(Graphics.getRGB(255,0,0));
g.drawCircle(0,0,100);
shape = new Shape(g);
shape.x = 400;
shape.y = 300;
stage.addChild(shape);
Ticker.setFPS(32);
Ticker.addListener(stage);
animate();
}
function animate(){
var tween;
tween = Tween.get(shape)
.to({x:Math.random()*canvas.width,y:Math.random()*canvas.height},1000,Ease.cubicOut)
.call(animate);
}
window.onload = init;
</script>
</head>
<body>
<div id="wrapper" class="wrapperNormal">
<H1>Tutorial: Full-window canvas mode with CreateJS</H1>
<canvas id="canvas" class="canvasNormal" width="800" height="600">
<p>Your browser is sooooo old! Update or download a modern one now!</p>
</canvas>
</div>
</body>
</html>
First, we are going to write a full-window function and add a “onclick” manager on the canvas itself. So we’ll just have to click anywhere on the canvas to switch from a ste to another.
To store the actual state of the canvas, we create a boolean variable : fullWindowState.
var canvas,
wrapper,
stage,
shape,
fullWindowState;
Then we create the fullWindow function, that change the canvas size, and it’s CSS rules. Note that I change the CSS class of the wrapper too, so I avoid some over-size effects, which can make the browser scrollbars to show. We don’t want that.
<canvas id="canvas" class="canvasNormal" width="800" height="600" onclick="fullWindow(this)" >
<p>Your browser is sooooo old! Download a modern one now! </p>
</canvas>
Finally, all we have to do is to add these rules in the CSS sheet:
Please note that if we would have added a rule like “width: 100%; height= 100%;” on our canvas, we increase it’s dimensions, but also it’s render size. So we will obtain a pixelate effect. This effect could be Nous obtiendrons donc un effet de pixelisation. This effet could be desired: pixel art is still fashion these days.
You can see here the result
With a click on the canvas, it extends itself properly in the whole window. But if we resize this one, or if we switch to a real fullscreen by pressing the F11 key, we lose the effect. Scrollbars or blank zones appears
To maintain the full-window effect when resizing, we have to listen this event and manage it.
Adding a window.onresize event management
All we have to do is to add a onResizeHandler() function to resize properly the canvas if the browser’s window’s size change…
function onResizeHandler(e){
if (fullWindowState){
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
}
…then we add the listener for the onresize event on the window object, targeting our callback function.
window.onresize = onResizeHandler;
Here’s the result. The effect works as expected on all newests browsers, including in the case of window resizing, or if the user press the F11 key.
A word about the Fullscreen API
Specifications about the Full-screen API are being discussed by the W3C. Firefox, Chrome and Safari browsers already included them as an experiment. But as the specs are not finished, it’s too soon to use it. Here’s a good tutorial (in french…).
Conclusion
We added a flexible in-browser fullscreen feature, with ease, that will work on any recent browser.
We can resize the browser window, toggle the browser itself in fullscreen mode with the F11 key and our element will gracefully adapt it’s size.
It works for a canvas, blso with any HTML element with a block type.
In my previous posts about CreateJS, we could see that this Javascript library has many positive attributes, but all the things we did with it were confined in the HTML5 canvas. But there’s an object in the EaselJS library that allow to break through the canvas, while remaining in the display list. This is the DomElement object.
What’s the DOMElement object ?
The DOMelement object is, as I said, part of the EaselJS library. Its purpose is to allow adding to the display list (which start with the Stage), some HTML elements layering outside the Canvas. It can be a div, a span, an image, or even a form.
These elements may be moved, made transparent, and they will inherit some properties from to their parent container in the display list. We’ll se that DOMElements objects don’t have as much methods and properties as a display object in EaselJS, even if they inherits from it.
Moreover, it’s important to keep in mind that the DOMElement object is still experimental and bugs will happen, mainly on complex manipulations. Don’t hesitate to report these bugs on the createJS website, following this link.
Building HTML page and CSS rules
We are going to create a basic HTML page, that will contain two main elements : a form, and a canvas. On page load, we will start the EaselJS engine by creating a stage from the canvas and assign a Ticker object to it. If you didn’t follow my firsts posts about EaselJS, and if you don’t know what I am talking about, you may read this post.
Here’s the HTML code :
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>DOMElement Tutorial</title>
<script src="http://code.createjs.com/easeljs-0.4.2.min.js"></script>
<script src="http://code.createjs.com/tweenjs-0.2.0.min.js"></script>
<link rel="stylesheet" href="css/style.css">
<script>
var canvas, stage, exportRoot;
window.onload = init;
function init() {
canvas = document.getElementById("canvas");
stage = new Stage(canvas);
Ticker.setFPS(32);
Ticker.addListener(stage);
}
</script>
</head>
<body>
<div id="main">
<H1>DOMElements example</H1>
<form id="myform" >
<label for="firstName">Enter your firstName:</label>
<input type="text" id="fistName/><br>
<label for="lastName">Enter your lastName:</label>
<input type="text" id="lastName"/><br>
<br>
<input type="checkbox" id="case" />
<label for="case">Alpha 50%</label><br>
<p id="submit" >
<input type="submit" value="Submit"/>
</p>
</form>
<canvas id="canvas" width="800" height="600" style="background-color:#000000">
<p>Your browser is sooooo old ! Download a modern one now !</p>
</canvas>
</div>
</body>
</html>
Then we will add a basic CSS sheet, Nous allons rajouter une feuille CSS basique, in order to have a nice looking “block” form:
So we have our working base, that should looks like this :
Animating the form
We start by creating our DOMElement() object, with the HTML element as a parameter :
function init() {
canvas = document.getElementById("canvas");
//DOMElement creation
form = document.getElementById("myform");
formDOMElement = new DOMElement(form);
As the rotation center is at the coordiantes (0,0) by default, we have to move it at the center of the form. To do this, we use the regX and regY properties of the DOMElement() object.
Then, we move the form above the screen, by giving it a negative Y coordinate. We also center it on the X coordinate.
//move it's rotation center at the center of the form
formDOMElement.regX = form.offsetWidth*0.5;
formDOMElement.regY = form.offsetHeight*0.5;
//move the form above the screen
formDOMElement.x = canvas.width * 0.5;
formDOMElement.y = - 200;
Next, we add the DOMElement() object to the display list, with the addChild() method of the Stage() object.
//add the formDOMElement to the display list
stage.addChild(formDOMElement);
All we have to do now is to create a Tween(), thank’s to the TweenJS library.
Ticker.setFPS(32);
Ticker.addListener(stage);
//Apply a tween to the form
Tween.get(formDOMElement).to({alpha:1, y:canvas.height * 0.5, rotation:720},2000,Ease.cubicOut);
We can now see our form falling from the top of the screen. For me, best results comes with Chrome, it may vary from one browser to another. On Internet Explorer there is an issue. the form lose the focus again and again and is not usable.
To fix this, we add a callback to our tween. In this callback function, we remove the form from the display list :
The form is no longer in the EaselJS’s stage display list, but it stays in place. If we want to apply a new Tween to it, we’ll have to add the form to the display list again.
Apply a transparency effect to the form
To conclude, we will add a “turn light on/off” effect to the form. Let’s listen to the “onchange” event of the checkbox, like this :
In the event callback, we get back the checkbox’s state. In relation to this state, we assign a target value to the alpha property of our form. We have to destroy any tween that would be still running on the formDomElement. Then we add it to the display list and execute the Tween.
We assign the “oncomplete” event’s callback of our tween to our tweenComplete function, which will remove the form from the display list again, fixing the Internet Explorer issue.
//Checkbox change
function onCheckClick(e){
//get the checkbox element
var myCheck = e;
//create a property to stock the alpha target value for the tween
var alphaTarget;
//add the form to the display list (IE)
stage.addChild(formDOMElement);
//If the checkbox's state is checked, turn off the lights, else turn them on.
myCheck.checked == false?alphaTarget=1:alphaTarget=.2;
//remove all tweens on the DOMElement object (in case of unfinished tweens when user click on the checkbox)
Tween.removeTweens(formDOMElement);
//use the callback tweenComplete to remove the form
//from the display list (IE) when the tween is complete.
Tween.get(formDOMElement).to({alpha:alphaTarget},1000,Ease.bounceIn).call(tweenComplete);
}
the DOMElement object can be added to the display list, but it inherit only partly from the DisplayObject() class, cause it is never physically added to the canvas in a bitmap form. All transformations are made in the CSS properties of the HTML element.
So, many DisplayObjects methods can’t apply to a DOMElement(): globalToLocal(), hitTest(), filters, and all things that CSS can’t do. On the other side, we can play with skew, for example, and bend a little our DOMElement.
To conclude…
The main flaw of Flash animations is the difficulty to make text contents readable by search engines robots. This flaw still remain in the HTML5 canvas. The DOMElement() object will fix this, by including directly our texts, titles, images, and why not our videos and forms in canvas animations, while keeping them reachable for SEO.
More, for some games, and on some browers, keeping some elements outside of the canvas, in a HTML tag, can make us gain some precious FPS, as these elements are not drawn on the canvas.
Here’s the second video about the Toolkit for CreateJS, where we have a look inside the code exported in the first episode. The video is in french but you’ll have english subtiles.
You’ll watch how is the javascript version of the code for the Flash Professionnal scene and it’s library.
A third and a fourth video are on theyr way. One about adding interaction with an exported button, and another about code manipulation, in particular the creation of new classes inheriting from the ones that you can find in the exported javascript code.
Flash CS6 will be released in may 7th in France. Amongst a few new tools, it’ll be packed with the new Toolkit for CreateJS, which allow partial exports of the Flash CS6 scene to HTML5 canvas, using createJS.
I’m recording a few videos to help using it at best. Theese will be in french but there’ll be caption in english. We’ll start with this videos, showing a simple export.
For now, quality is not at best, but this post will be updated later with a better quality video. Meanwhile, don’t hesitate to watch the video directly on u-tube. It’ll be better. And don’t forget to share !
In the next one, we’ll explore the exported code, html5 and javascript side, it’s structure, et the manner to use it to go farther than a simple export.
This new set of posts will focus on hexagonal tiles map generation. The subject is broader than you might think. It may involve MVC architecture, random generation, pathfinding algorithms, implementation of biomes, management of user interfaces, and server-side connections by sockets. And this is certainly not an exhaustive list.
Here is what I did recently: a basic architecture for random maps generation including pathfinding:
This generator is a part of a larger project and will evolve over time. In the next posts I will introduce some of the techniques used. And I’ll start right now with the MVC architecture for use with createJS and a note on the Promises (or futures, or deferreds).
Coding MVC with createJS
Over time I spend programming the javascript, I feel an increasingly need to organize my code, find and keep a clear logic. But with an untyped prototyped language, code can quickly become a horror.
I had the chance to participate recently in the Web-5 conference in Béziers. Among the speakers was Kamil Trebunia, which introduced some MVC code architecture for HTML5. My map generator was already done at this time, and worked fine. But after seeing this presentation, I deeply changed it into a Model View Controller architecture.
What’s the point ?
The main interest for me is that with a decoupled view and data control, we gain a great clarity in code. Which greatly facilitates the maintenance, modification, and the addition of new features. On a constantly evolving project, I think it’s essential to keep things in order. This architecture also makes possible to reserve some processing to the server side, biome management for example, and other to client-side processing, rendering for example.
Entry point
The entry point of the application is the main.js file. Its role is to load data files, create a namespace and store global variables, then start up the system.
//some parts of the code were written by Kamil Trebunia.
(function () {
'use strict';
window.ylende = {};
var screenWidth,screenHeight,wrapper,agent,preloader;
ylende.SPRITE_MAP_URL = "data/sprites.json";
ylende.SOUND_MAP_URL = "sounds.json"; //not yet used
ylende.CONFIG_URL = "data/config.json";
window.addEventListener("load", function () {
if(!(!!document.createElement('canvas').getContext)){
wrapper = document.getElementById("canvasWrapper");
wrapper.innerHTML = "Your browser does not appear to support " + "the HTML5 Canvas element";
return;
}
ylende.agent = window.navigator.userAgent.toLowerCase();
var configD = ylende.net.getJSON(ylende.CONFIG_URL);
var spriteMapD = ylende.net.getJSON(ylende.SPRITE_MAP_URL);
var imagesD = new ylende.Deferred();
spriteMapD.addCallback(function (spriteMap) {
var objectsURIs = Object.keys(spriteMap);
var loadedObjectsNumber = 0;
objectsURIs.forEach(function (uri) {
spriteMap[uri].image = new Image();
spriteMap[uri].image.addEventListener("load", function () {
loadedObjectsNumber += 1;
if (loadedObjectsNumber === objectsURIs.length) {
imagesD.callback();
}
}, false);
spriteMap[uri].image.addEventListener("error", function () {
imagesD.errback();
}, false);
spriteMap[uri].image.setAttribute("src", spriteMap[uri].url);
});
});
ylende.Deferred.gatherResults([spriteMapD, configD, imagesD]).addCallback(function () {
ylende.images = spriteMapD.result[0];
ylende.mapDataStore = new ylende.Store();
var core = new ylende.Core({
entityTypes: spriteMapD.result[0],
config: configD.result[0]
});
core.init();
ylende.view = new ylende.View(core);
ylende.view.play();
});
}, false);
})();
Loading data – for now, these are JSON configuration files – is provided by the use of Deferreds. This technique is useful for not blocking data loading with asynchronous callbacks. It also helps bring all loading results into a single condition, as in this code: it only runs when all Deferreds are in State.SUCCESS state.
The main controller: the Core class
Then we start the separation of the code by creating an instance of Core. Core class is the main controller of the application. I pass my data objects as parameters, which allows him to create his own data: the map. The Store object is just a dummy for now. I’ll talk about it later.
ylende.Deferred.gatherResults([spriteMapD, configD, imagesD]).addCallback(function () {
ylende.images = spriteMapD.result[0];
ylende.mapDataStore = new ylende.Store();
var core = new ylende.Core({
entityTypes: spriteMapD.result[0],
config: configD.result[0]
});
core.init();
[...]
The class makes sure that map data are existing in the data object. If this is not the case, it creates a new mapcontroller (HexMap instance). HexMap class contains the scripts for generating map datas (I’ll talk about it in the next post).
The main view: the View class
We’re back in the startup script (main.js) where I instantiate the View class:
[...]
ylende.view = new ylende.View(core);
ylende.view.play();
});
… passing it the main controller as parameter. The View is the general view of the application. It will be used to collect general window events (onresize, onscroll …), but also of canvas, and therefore of course createJS. Thus, the View contains the course, while all other type objects view (such as hexagons) are elements in the course EaselJS (containers, … movieclips).
Here’s the code og the View class.
(function () {
'use strict';
function View(controller) {
this.initialize(controller);
}
var canvas = document.getElementById("stageCanvas");
var map;
View.prototype.initialize = function(controller){
this.gameBounds = {top: 0, bottom: canvas.height,right: canvas.width, left: 0};
this.controller = controller;
this.stage = new Stage(canvas);
this.stage.enableMouseOver();
Touch.enable(this.stage);
Ticker.setFPS(32);
Ticker.addListener(this.stage);
map = new ylende.HexMapView(this);
this.stage.addChild(map);
this.inviteText = new Text("Click on it, drag it - MouseWheel to zoom in and out - Double click to center", "bold 11pt courier","#FFF");
this.inviteText.textAlign = "center";
this.inviteText.x = this.gameBounds.right * .5;
this.inviteText.y = this.gameBounds.bottom - 15;
this.stage.addChild(this.inviteText);
window.onorientationchange = function (e) {
//console.log(e);
};
window.onresize = function (e) {
//console.log(e);
};
window.onscroll = function (e) {
//console.log(e);
return false;
};
window.onmousewheel = function(e){
//console.log(e);
map.resize(e.wheelDelta);
}
//firefox users...
window.addEventListener('DOMMouseScroll', scroll, false);
function scroll(e){
map.resize(e.detail*-0.01);
}
}
View.prototype.play = function(){
Ticker.setFPS(32);
Ticker.addListener(this);
}
View.prototype.tick=function(){
}
window.ylende.View = View;
})();
The View class creates an object of type HexMapView, which will receive map data from the HexMap class, and will take care of displaying it (and only this). This pattern is repeated for each object on the map (hexagons. ..), with a controller that manages object properties and a view that displays them.
Note that in a classic MVC, views classes all implement a draw () function. This is not the case with CreateJS, as the Ticker object updates the views automatically.
What about data ? The Store class
On the model side, for now, i just created a minimal Store class that I’ll develop later. My goal is to make datas evolving with time.
Conclusion: CreateJS and MVC
Finally, CreateJS is well suited for MVC. It may seem tedious to organize the code in that way, but reserving to EaselJS the only task of the display, the code is much cleaner and logical, and finally you work better and faster.
In the next post, I will show how I generate the map, and talk about Dijkstra’s algorithm and its use in a hexagonal context.
I did not had a lot of time to spend on Space Kamikaze last past weeks, and even less on this blog.
Je n’ai pas eu beaucoup de temps à passer sur Space Kamikaze ces dernières semaines, et encore moins sur ce blog. I’m totaling a 30 hours of work on the game, I think … But I lose a lot of time to do graphics for a… questionable result … .
Here is an almost playable demo, with limited graphics, and a rather thin content.
There are still many bugs to solve and points for improvement!
But I have other places to visit in the world of HTML5, and it’s time I go …
I will improve the game throughout my free time, and developments of CreateJS (the next version of SoundJS should improve sound management, and PreloadJS should allow me to unify the loading of sounds and images files.)
Here are some small pieces of code on some features.
A Command Pattern for ennemies behavior
Pour le comportement des ennemis, je voulais quelque chose de souple, que je pourrais changer à la volée, et avoir la possibilité d’en ajouter de nouveaux facilement, et sans retoucher au code.
Pour cela, j’ai créé une classe “Behavior.js” qui devient une propriété supplémentaire de ma classe Foe. Lors de la lecture du XML pour la construction de l’ennemi, je lis quel comportement appliquer à mon ennemi. Ce nom est passé en paramètre de la classe Behavior, qui remplit automatiquement une liste d’ordres.
Dès que l’ennemi est activé, il commence à exécuter la liste d’ordres, une tache après l’autre. En fait à chaque évènement tick du moteur d’EaselJS, je vérifie que le vaisseau n’est pas en état “iddle”, si oui, je lance l’exécution de l’ordre suivant.
For ennemies behavior , I wanted a flexible solution, allowing me to change things on the fly, and making me able to easily add new ones, with a few code to edit.
So, I created a class “Behavior.js” which becomes an additional property in my class Foe. When reading the XML for the construction of the enemy, I read which behavior must be applied to my foe. This variable is passed as a parameter of the Behavior class, which automatically populates a list of orders.
Once the enemy is activated, it starts to execute the orders list, one after another.
In fact, at each tick event of the EaselJS engine, I check that the ship is not in a “iddle” state, if so, I start running the next order in list.
This part was pretty simple and works well. A tip for IOS devices: the key name in LocalStorage must be emptied before filling it, otherwise you could run into a “Not enought space …” error.
At the end of each level, I update the information stored. This information is nothing more than the “Player.js” instance, serialized in JSON in order to have a plain text: LocalStorage saves only String typed data.
When loading, I deserializes the data and fill the Player object’s properties.
The sound
I did some tests with SoundJS, but it is clear that this library still has some progress to make: the events are somewhat confusing, and some errors occurs unexpectedly. But with short, light sounds to load, it works pretty well.
Files to load were included in the XML, without extension. Indeed, differents extensions are needed depending on the browser. So you need to look at the user agent, and select the right extension for the sound files that will be loaded before starting.
Tablets and smartphones
Space Kamikaze tourne sur les tablettes et les téléphones. Encore une fois, je détecte l’user agent pour savoir quel est le matériel utilisé. Quand un matériel tactile est identifié, je fais passer le mode de tir en automatique, ce qui rend le jeu complètement jouable…
… Si le matériel est au minimum un Ipad1.
Pour être complètement propre, il me faudrait créer une version de dimensions plus petite du jeu, et optimiser beaucoup mieux. Quelques pistes d’optimisation : limiter les boucles switch, essayer de limiter la hiérarchie sur le stage en utilisant moins de containers, alléger les sprites… Si j’ai du temps, un de ces jours, je le ferai…
Kamikaze Space runs on tablets and phones. Again, I detect the user agent to determine which device is used. If a tactile device is identified, I enable the automatic fire mode, which makes the game completely playable …
… If the device is at least one Ipad1.
To be completely clean, I should create a smaller size version of the game, and optimize much better. Some keys in the search for optimization: limiting the use switch loops, try to limit the use of inheritance by using less containers, reducing the sprites sizes … If I have time, someday, I will do these improvements …
Conclusion
Working with EaselJS is really nice.
Ok, not as enjoyable as doing the same in AS3, but we can do very clean stuff. The rendering is very unequal on browsers, the best results being obtained with IE9 for me.
The CreateJS Suite is going to be a must soon. It has great potential for improvement (an event-integrated system would be great), and is bound to attract many flashers.
And for Space Kamikaze, there is still so much to do, but I’m in not in a hurry …
Meanwhile, this work could be usefull in part for other games, making development much shorter.
For now, I intend to deal with ExtJS, Sencha Touch, perhaps with a bit of NodeJS …
This week some good news were heard from Amsterdam and the Creative Layer Blog from Adobe, all about EaselJS and it’s future.
Reminding the story
Since january 2011, Grant Skinner is working on an open source project called EaselJS. This is a library powering the HTML5 canvas tag with actionscript-like codings methods and objects. With the help of Mike Chambers from adobe and others contributors, the project growed fast.
Soon, two other libraries are created with the same purpose: SoundJS and TweenJS, and so is ZOE, a convert tool extracting SpriteSheets from SWF.
Some cool demo are showed, like this one from Mike Chambers himSelf and the well known “Pirates loves daisies”, a tower defense game using partly EaselJS.
Ok, I wasn’t there… But I saw the direct stream from the Woodoo Lounge, with Grant Skinner, the one with the wiskey thing (It was great !), and when I heard some guys asking about “createJS”, I was very curious to find out what it could be.
In fact, Grant introduced the EaselJS suite, which is now called the CreateJS suite. A new site opened at the same time : createJS.com
I was glad to see that a new addition was made to the suite : PreloadJS (coming soon…). I’m still working on my little EaselJS based game and had to use A third part library to preload my content (PxLoader – worth the test). It’s a good idea to integrate a loading solution in CreateJS.
The Adobe Flash Professional Toolkit for CreateJS
Finaly, a new blog post in the Adobe’s Creative Layer Blog revealed “The Adobe Flash Professional Toolkit for CreateJS”, a Flash IDE extension showing as a panel, which export an entire Flash animation in the HTML5 canvas, using Eas… sorry… CreateJS.
The video shows the resulting code, a html5 page importing Easel.js, and amonst others, a “MovieClip.js” class.
The tool could be a part of Flash CS6, but nothing is clear about it’s availability when the next version of Flash will be released.
CreateJS is going to grow very fast from this point. This is good news.
I wanted to move to Sencha and ExtJS V2 (which looks promising) in February, and leave EaselJS aside, but I finally changed my mind.
I will try, over this year, not only to study different libraries HTML5, but also, for each of them, create an app or a game. After all, one can’t realize the possibilities of a tool only by using it on “real life” problems.
With this in mind, I got into creating my first HTML5 game : Space Kamikaze 2. Sencha can wait until March!
Space Kamikaze 2
This game will be a not-so-classic Shoot-them-up (I hope so!), of which here is roughly the specifications :
Content
The game will be amde of 50 levels with growing difficulty
for each level, player will have to survive each wave of enemy ships
Between levels, the player can buy upgrades for his ship with money gathered on exploded enemies
It’ll be possible to save the game between levels
Audio will be basic
Compatibility
The game will be compatible with all latest versions of major browsers on desktop: FireFox, IE9, Chrome,Safari and Opéra
The game will be playable on Android and iPad tablets, with some adjustments
Delay
The game must completed by the end February. ( I think there are 6 or 7 full days of work, 1 ° but I also have a real job full-time during the day – 2 ° I am not alone in life, which leaves me with approximately one hour per day to work on it.. )
Technics
The game will be coded with HTML5+Javascript, using EaselJS and JQuery frameworks
The game will be coded with OOP
In the beginning was the schematization
Before starting, I scribbled two or three schematic, one for the navigation by the user point of view, one for different interface elements, and last roughly the skeleton of my classes. I knew these were not final scheme and that I should them adapt gradually. But it’s a huge time saver to take a moment to think about how to do things before the start.
Mechanics and navigation menu
I then laid the basis of the navigation engine in the game. I defined some states (“main-menu”, “level”, “level-ending”, “shop” …) that I associate with a gameState variable. EaselJs it’s Ticker object, allow you to read this property’s status during a “tick”. The game then displays the interface part associated with that state, after the scene clean-up.
This system allows me to deal with a pretty cruel lack in EaselJS at this level: almost no event system. But I am hopeful that the many contributors to the framework will fill sooner or later this shortcoming. If I have some time to spend, I’ll try to implement a system of my own with the help of jQuery. But for now, this states system works pretty well.
External datas
For now, external data is loaded by XML with Ajax. But it is clear that this system has serious limitations in terms of security. I think I can do better with a database.
The XML contains the levels contents with the successive waves of enemies. Each enemy is described by an entry in the XML: type (bomber, chaser …), speed, hit points and behavior.
So I also created a class “Foe.js”, which is built based on data from the XML. This class contains another class “Behavior.js”, which governs the behavior of the enemy ship (moves, shoots), still according to the XML.
With this system, I can easily create new enemies and new levels. I even thought of making a custom levels generation system. Anyway, later, if I have time.
Gameplay
The gameplay – that is, for me, everything is managed in the “gameLoop” – is still quite poor.
For the collisions, I simplified to the max with a management system “by circles”, which is fine for a Shoot-em-up. All the rest is operated by classes.
The score is counted, the money gains too.
I still work on the handling of the ship, that i’d like to be less flexible.
Graphismes et animations
Again, it is still very limited.
I created the ships on Photoshop, and some animations with Flash. I then used Zoe to transform swf to SpriteSheets.
But at this state, I have a problem to solve:I have to slow down my animations, but I need to keep my framerate for general movements. I saw on this tutorial from David Rousset a method that seems the right one to me, but I try to find an easy way to apply it in my own system.
It’s still a bit early for a demo, but I hope to post one next week.
In the meantime, I hope I progress through menus, and the shop.
Feel free to send me advices, or ask me details or snippets, if you’re interested.
Today, I’ll just apply what I have shown in my previous posts to create advanced dynamics menus, with externalized in XML content.
So I’ll create a Menu() class, which will take a Jquery loaded XML as parameter . This class will create the menu’s icons and manage various mouse events to react according to the position of the pointer.
Save it in a “/data” folder at the site ‘s root and name the file “menuData.xml”.
In order for the XML to be loaded on all covered browsers (Chrome, Safari and Firefox …), using jQuery is the best way.
The loading code will be written in the “main.js” file, like this:
(function(){
var stage;
var myCanvas = $("#stageCanvas").get(0);
this.init = function() {
stage = new createjs.Stage(myCanvas);
createjs.Ticker.setFPS(32);
createjs.Ticker.addListener(this);
$.ajax({
type: "GET",
crossDomain: false, //edit du 25/01 : cette propriété doit être passée à false.
url: "data/menuData.xml",
dataType: "xml",
success: initMenu
});
}
this.initMenu = function(data){
console.log(data);
}
this.tick = function(){
}
window.onload = init();
})();
Your browser’s console should show the xml content.
Prior to start to serious things, I will change a little the Icon class to make it fully suitable for use in our menu class:
(function (window) {
function Icon(iconWidth,imgSrc,color,titleText,link) {
//Passe les paramètres au constructeur
this.initialize(iconWidth,imgSrc,color,titleText,link);
}
//Héritage de container
Icon.prototype = new createjs.Container();
Icon.prototype.Container_initialize = Icon.prototype.initialize;
Icon.prototype.Container_tick = Icon.prototype._tick;
//Constructeur
Icon.prototype.initialize = function (iconWidth,imgSrc,color,titleText,link) {
this.Container_initialize();//Execute le contructeur de la class Container
this._iconWidth = iconWidth;//Taille de l'icone
this._link = link;//Lien URL
this._imgSrc = imgSrc;//Source de l'image
this._color = color;//Couleur du fond
this._titleText = titleText;//Texte
//Placement du point d'origine au centre de l'icone
this.regX = this._iconWidth*0.5;
this.regY = this._iconWidth*0.5;
//Création du fond
this.graphic = new createjs.Graphics();
this.graphic.beginFill("#777");
this.graphic.drawRoundRect(0,0,this._iconWidth,this._iconWidth,this._iconWidth*.1);
this.roundRectangle= new createjs.Shape(this.graphic);
this.addChild(this.roundRectangle);
//Création du texte
this.text = new createjs.Text(this._titleText, "bold 16px Courier",this._color);
this.text.textAlign ="center";
this.text.y = this._iconWidth+5;
this.text.x = (this._iconWidth*0.5);
this.text.alpha = 0; //Le texte est masqué
this.addChild(this.text);
//Ajout de l'image
this.bitmap = new createjs.Bitmap(this._imgSrc);
this.bitmap.regX = this._iconWidth*0.5;
this.bitmap.regY = this._iconWidth*0.5;
this.bitmap.x = this._iconWidth*0.5;
this.bitmap.y = this._iconWidth*0.5;
this.addChild(this.bitmap);
//En cas de click : ouverture d'une nouvelle fenetre
this.roundRectangle.onClick = function(){
//console.log("click "+this.graphics);
window.open(this._link);
}
//En cas de survol
this.roundRectangle.onMouseOver = function(){
//console.log("mouseOver ");
//On rafraichit le graphics du fond
this.parent.graphic.clear();
this.parent.graphic.beginFill(this.parent._color);
this.parent.graphic.beginStroke(createjs.Graphics.getRGB(255,255,255));
this.parent.graphic.drawRoundRect(0,0,this.parent._iconWidth,this.parent._iconWidth,this.parent._iconWidth*.1);
this.parent.roundRectangle.graphics = this.parent.graphic;
//On aggrandis l'icone
this.parent.scaleX = 1.2;
this.parent.scaleY = 1.2;
//On montre le texte
this.parent.text.alpha = 1;
}
//Si le pointeur quitte l'icone
this.roundRectangle.onMouseOut = function(){
console.log("mouseOut ");
//On rafraichit le graphics du fond
this.parent.graphic.clear();
this.parent.graphic.beginFill("#777");
this.parent.graphic.drawRoundRect(0,0,this.parent._iconWidth,this.parent._iconWidth,this.parent._iconWidth*.1);
this.parent.roundRectangle.graphics = this.parent.graphic;
//L'icone reprends sa taille d'origine
this.parent.scaleX = 1;
this.parent.scaleY = 1;
//Le texte est masqué
this.parent.text.alpha = 0;
}
}
Icon.prototype._tick = function () {
this.Container_tick();
}
window.Icon= Icon;
} (window));
A link parameter is passed to the Icon, which can now be clicked, opening a new browser window.
The Icon width grows when the pointer is over it, and shrink back to normal when the mouse get out of it.
The DockMenu class
The DockMenu() class will take the loaded XML datas as parameter. It will read the datas and create icons in a “for each” loop.
The icons are stored in an array “icons”. This array is not used here, but it can be useful, as we will see below.
(function (window) {
function DockMenu(xml, menuIconMin, menuIconMax) {
//passe les paramètres au constructeur
this.initialize(xml, menuIconMin, menuIconMax);
}
//Héritage de Container();
DockMenu.prototype = new createjs.Container();
DockMenu.prototype.Container_initialize = DockMenu.prototype.initialize;
DockMenu.prototype.Container_tick = DockMenu.prototype._tick;
DockMenu.prototype.initialize = function (xml, menuIconMin, menuIconMax) {
this.Container_initialize(); //obligatoire
this.icons = new Array(); //tableau qui contiendra les icones du menu
this._data = xml;
this._menuIconMin = menuIconMin; //Taille minimale des icones
this._menuIconMax = menuIconMax; //Taille maximale des icones
this._space = this._menuIconMin*0.5; //Espace entre les icones
this._numIcons = $(this._data).find('item').length;//extraction du nombre d'icones
var root = this; //Création d'une référence à la racine de la classe
var num = 0;
//Boucle de création des icones
$(this._data).find('item').each(function(){
//Extraction des paramètres de chaque icone dans le xml
var title = $(this).find('title').text();
var color = $(this).find('color').text();
var source = $(this).find('source').text();
var link = $(this).find('link').text();
//Creation d'une icone avec ces paramètres
var icon = new Icon(root._menuIconMin,source,color,title,link);
//Placement et ajout à la liste d'affichage
icon.x = num*( root._space + root._menuIconMin);
icon.y = root._menuIconMax;
root.addChild(icon);
//ajout de l'icone dans le tableau icons
root.icons.push(icon);
//incrémentation
num++;
});
}
DockMenu.prototype._tick = function () {
this.Container_tick();
//console.log("Icon Ticked");
}
window.DockMenu= DockMenu;
} (window));
All we have to do now is to instanciate the dock menu in the “main.js” file :
(function(){
var stage;
var myIcon;
var myMenu;
var myImage;
var myCanvas = $("#stageCanvas").get(0);
this.init = function() {
stage = new createjs.Stage(myCanvas);
stage.enableMouseOver();
createjs.Ticker.setFPS(24);
createjs.Ticker.addListener(this);
$.ajax({
type: "GET",
crossDomain: false,
url: "data/menuData.xml",
dataType: "xml",
success: initMenu
});
}
this.initMenu = function(data)
{
myMenu = new DockMenu(data, 60, 80);
myMenu.x = 180;
myMenu.y = 200;
stage.addChild(myMenu);
stage.update();
}
this.tick = function(){
stage.tick();
}
window.onload = init();
})();
At this state, AS3 coders reading this are probably thinking: “Yeah… But with Flash, At least, we had Tween transistions. It was better…”.
Great news : Grant Skinner created two other libraries working hand in hand with EaselJS: tween.js et sound.js.
Here we will use Tween.js in our Icon class to make it more smoothy.
First, download the Tween.js file, and add it in your “js/libs” project’s folder. Then import it in the html page :
<script src="js/libs/tween.js"></script>
Next, we have to modify a little our Icon() class to add some resize transitions:
(function (window) {
function Icon(iconWidth,imgSrc,color,titleText,link) {
//Pass parameters to the class constructor
this.initialize(iconWidth,imgSrc,color,titleText,link);
}
//container() inheritance
Icon.prototype = new createjs.Container();
Icon.prototype.Container_initialize = Icon.prototype.initialize;
Icon.prototype.Container_tick = Icon.prototype._tick;
//Constructor
Icon.prototype.initialize = function (iconWidth,imgSrc,color,titleText,link) {
this.Container_initialize();//required
this._iconWidth = iconWidth;
this._link = link;
this._imgSrc = imgSrc;
this._color = color;
this._titleText = titleText;
//Place the registration point in the center of the icon
this.regX = this._iconWidth*0.5;
this.regY = this._iconWidth*0.5;
//Background creation
this.graphic = new createjs.Graphics();
this.graphic.beginFill("#777");
this.graphic.drawRoundRect(0,0,this._iconWidth,this._iconWidth,this._iconWidth*.1);
this.roundRectangle= new createjs.Shape(this.graphic);
this.addChild(this.roundRectangle);
//Text
this.text = new createjs.Text(this._titleText, "bold 16px Courier",this._color);
this.text.textAlign ="center";
this.text.y = this._iconWidth+15;
this.text.x = (this._iconWidth*0.5);
this.text.alpha = 0; //Le texte est masqué
this.addChild(this.text);
//Image
this.bitmap = new createjs.Bitmap(this._imgSrc);
this.bitmap.regX = this._iconWidth*0.5;
this.bitmap.regY = this._iconWidth*0.5;
this.bitmap.x = this._iconWidth*0.5;
this.bitmap.y = this._iconWidth*0.5;
this.addChild(this.bitmap);
this.tweenSize
//If clicked, open a new window
this.roundRectangle.onClick = function(){
console.log("click "+this.graphics);
window.open(this._link);
}
//Mouse over
this.roundRectangle.onMouseOver = function(){
console.log("mouseOver ");
//Refresh the bg graphics
this.parent.graphic.clear();
this.parent.graphic.beginFill(this.parent._color);
this.parent.graphic.beginStroke(createjs.Graphics.getRGB(255,255,255));
this.parent.graphic.drawRoundRect(0,0,this.parent._iconWidth,this.parent._iconWidth,this.parent._iconWidth*.1);
this.parent.roundRectangle.graphics = this.parent.graphic;
//Resize icon with a Tween transistion
//First we have to clean any Tween running
createjs.Tween.removeTweens(this.parent);
//Then create a tween object
this.parent.tweenSize = createjs.Tween.get(this.parent).to({scaleX:1.3,scaleY:1.3},500);
this.parent.text.alpha= 1;
}
//Mouse out
this.roundRectangle.onMouseOut = function(){
console.log("mouseOut ");
//Refresh the bg graphics
this.parent.graphic.clear();
this.parent.graphic.beginFill("#777");
this.parent.graphic.drawRoundRect(0,0,this.parent._iconWidth,this.parent._iconWidth,this.parent._iconWidth*.1);
this.parent.roundRectangle.graphics = this.parent.graphic;
//Resize icon to base width with a Tween transistion
createjs.Tween.removeTweens(this.parent);
this.parent.tweenSize = createjs.Tween.get(this.parent).to({scaleX:1,scaleY:1},500);
this.parent.text.alpha= 0;
}
}
Icon.prototype._tick = function () {
this.Container_tick();
}
window.Icon= Icon;
} (window));
Here is another example of menu, which Flash technology made inevitable: the Carrousel menu.
I just changed the DockMenu class, to make a CarrouselMenu class. That said, I could have worked cleaner by creating a generic class “Menu”, and by inheriting from it to create my DockMenu and CarrouselMenu classes.
Here’s the CarousselMenu class.
(function (window) {
function CarousselMenu(xml, menuIconMin, menuRadius) {
this.initialize(xml, menuIconMin, menuRadius);
}
//Container() inheritance
CarousselMenu.prototype = new createjs.Container();
CarousselMenu.prototype.Container_initialize = CarousselMenu.prototype.initialize;
CarousselMenu.prototype.Container_tick = CarousselMenu.prototype._tick;
CarousselMenu.prototype.initialize = function (xml, menuIconMin, menuRadius) {
this.Container_initialize();//Required
this._icons = new Array();//Array containing menu icons
this._data = xml;
this._radius = menuRadius;//Rayon du menu
this._speed = 1; //Rotating speed
this._angles = new Array();//Array containing each icon angle.
this._menuIconMin = menuIconMin;
this._numIcons = $(this._data).find('item').length; //Icon count extraction
var root = this;//Create a reference for the class root
var num = 0;
$(this._data).find('item').each(function(){
//Extract parameters for each icons in xml
var title = $(this).find('title').text();
var color = $(this).find('color').text();
var source = $(this).find('source').text();
var link = $(this).find('link').text();
//Create a new icon with these parameters
var icon = new Icon(root._menuIconMin,source,color,title,link);
var angle = num*(360/root._numIcons);
//Add the icon angle and the icon to the arrays
root._angles.push(angle);
root._icons.push(icon);
//placing it and adding it to the display list
root.addChild(icon);
num++;
});
}
CarousselMenu.prototype._tick = function () {
this.Container_tick();
//At each "tick" we browse all icons
for (var i= 0; i< this._numIcons; i++)
{
var cible= this._icons[i];
//We take the radian angle for the icon
var radian = deg2rad(this._angles[i]);
//And the icon is placed with cos and sin for this angle
//The *.5 for Y allows a flattened circle, giving the impression of 3D
cible.x = this.x+this._radius*Math.cos(radian);
cible.y = this.y+this._radius*Math.sin(radian)*.5;
//Math.Sin(radian) is always between 0 and 1
//so we can use it's value as scale and alpha parameters
Math.sin(radian)+1>0.8?cible.scaleX = cible.scaleY = 0.8:cible.scaleX = cible.scaleY=Math.sin(radian)+1;
Math.sin(radian)+1<-0.5?cible.scaleX = cible.scaleY = 0.2:cible.scaleX = cible.scaleY=Math.sin(radian)+1;
Math.sin(radian)>-0.5?cible.alpha = Math.sin(radian):cible.alpha = 0;
//We add the speed value to the icon's angle
this._angles[i]+=this._speed;
//The modulo restrain the angle to a number between 0 and 360
this._angles[i] %=360;
}
}
//Transforms degrees to radian
this.deg2rad = function (deg){
return deg * (Math.PI/180)
}
window.CarousselMenu= CarousselMenu;
} (window));
Here’s my carrousel.
(Tested on Chrome, Firefox, IE9, Safari and Opera)
I used the same icon, but given that the management of alphas and scales is here devolved to menu itself, I removed the transitions of the Icon() class.
And yes, new design for the example page!
Note that Touch is not supported in this example, but it could be possible to set up the mobile support with the Touch object of easelJS.
Canvas and SEO
This is a cool menu, but it represent a problem in terms of SEO. Spiders just won’t see our links and texts in the canvas.
To correct this, we just have to add the structure of the menu under le canvas tag, like this: