Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon
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="https://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


8. October 2012

 

As per this post I am currently going through the process of creating some simple game creation tools using HTML5, more specifically using the YUI 3 library as well as the EaselJS canvas library.

 

This post illustrates the very skeleton upon which we are going to create our app.  YUI3 provides a full MVC framework which you can use to create your application so I decided to make use of it.  The end result of this code is remarkably minimal, it just creates a single page web application with different views representing different portions of the UI.  Specifically, we will create a top zone where the menu will go, a left hand area where the level editing will occur, then a right hand panel which will change contextually.  I also created a very simple data class, to illustrate how data works within the YUI MVC environment.

 

First off, if you have never heard of MVC, it is the acronym of Model View Controller.  MVC is a popular design practice for separating your application in to logically consistent pieces.  This allows you to separate your UI from your logic and your logic from your data ( the last two get a little gray in the end ).  It adds a bit of upfront complexity, but makes it easier to develop, maintain and test non-trivial applications… or at least, that’s the sales pitch.

 

The simplest two minute description of MVC is as follows.  The Model is your application’s data.  The View is the part of your application that is responsible for displaying to the end user.  The Controller part is easily the most confusing part, and this is the bit that handles communications between the model and view, and is where you actual “logic” presides.  We aren’t going to be completely pure in this level in this example ( MVC apps seldom are actually ), as the Controller part of our application is actually going to be a couple pieces, you will see later.  For now just realize, if it aint a view and it aint a model, it’s probably a controller.

 

It is also worth clarifying that MVC isn’t the only option.  There is also MVVM ( Model-View-ViewModel ) and MVP ( Model-View-Presenter ), and semantics aside, they are all remarkably similar and accomplish pretty much the same thing.  MVC is simply the most common/popular of the three.

 

Put simply, it will look initially more complex ( and it is more complex ), but this upfront work makes life easier down the road, making it generally a fair trade off.

 

Alright, enough chatter, now some code!  The code is going to be split over a number of files.  A lot of the following code is simply the style I chose to use, and is completely optional.  It is generally considered good practice though.

 

image 

At the top level of our hierarchy we have a pair of files, index.html and server.js.  server.js is fairly optional for now, I am using it because I will (might?) be hosting this application using NodeJS.  If you are running your own web server, you don’t need this guy, and won’t unless we add some server-side complexity down the road.

 

index.html is pretty much the heart of our application, but most of the actual logic has been parted out to other parts of the code, so it isn’t particularly complex.  We will be looking at it last, as all of our other pieces need to be in place first.

 

Now within our scripts folder, you will notice two sub-folders models and views.  These predictable enough are where our models and views reside.  In addition, inside the views directory is a folder named templates. This is where our moustache templates are.  Think of templates like simple HTML snippets that support very simple additional mark-up, allowing for things like dynamically populating a form with data, etc.  If you’ve ever used PHP, ASP or JSP, this concept should be immediately familiar to you.  If you haven’t, don’t worry, our templates are remarkably simple, and for now can just be thought of as HTML snippets.  The .Template naming convention is simply something I chose, inside they are basically just HTML.

 

If you are basing your own product on any of this code, please be sure to check out here, where I refactored a great deal of this code, removing gross hacks and cleaning things up substantially!

 

Let’s start off with our only model person.js, which is the datatype for a person entry.  Let’s look at the code now:

 

person.js

YUI.add('personModel',function(Y){
    Y.Person = Y.Base.create('person', Y.Model, [],{
            getName:function(){
                return this.get('name');
            }
        },{
            ATTRS:{
                name: {
                    value: 'Mike'
                },
                height: {
                    value: 6
                },
                age: {
                    value:35
                }
            }
        }
    );
}, '0.0.1', { requires: ['model']});

The starting syntax may be a bit jarring and you will see it a lot going forward.  The YUI.add() call is registering ‘personModel’ as a re-usable module, allowing us to use it in other code files.  You will see this in action shortly, and this solves one of the biggest shortcomings of JavaScript, organizing code.

 

The line Y.Person = Y.base.create() is creating a new object type in the Y namespace, named ‘person’ and inheriting all of the properties of Y.Model.  This is YUI’s way of providing OOP to a relatively un-OOP language.  We then define a member function getName and 3 member variables name, height and age, giving each of the three default values… just cause.  Of course, they aren’t really member variables, they are entries in the object ATTRS, but you can effectively think of them as member variables if you are from a traditional OOP background.  Next we pass in a version stamp ( 0.0.1 ), chosen pretty much at random by me.  Next is a very important array named requires, which is a list of all the modules ( YUI, or user defined ) that this module depends on.  We only need the model module.  YUI is very modular and only includes the code bits you explicitly request, meaning you only get the JavaScript code of the classes you use.

 

So that is the basic form your code objects are going to take.  Don’t worry, it’s nowhere near as scary as it looks.  Now let’s take a look at a view that consumes a person model.  That of course would be person.View.js.  Again, the .View. part of that file name was just something I chose to do and is completely optional.

person.View.js

YUI.add('personView',function(Y){
        Y.PersonView = Y.Base.create('personView', Y.View, [], {
        initializer:function(){
            var that=this,
                request = Y.io('/scripts/views/templates/person.Template',{
                    on:{
                        complete:function(id,response){
                            var template = Y.Handlebars.compile(response.responseText);
                            that.get('container').setHTML(template(that.get('model').getAttrs(['name','age','height'])));
                        }
                    }
                });
        },
        render:function(){
            return this;
        }
    });
}, '0.0.1', { requires: ['view','io-base','personModel','handlebars']});

Just like with our person model, we are going to make a custom module using YUI.add(), this one named ‘personView’.  Within that module we have a single class, Y.PersonView, which is to say a class PersonView in the Y namespace.  PersonView inherits from Y.View and we are defining a pair of methods, initializer() which is called when the object is created and render() which is called when the View needs to be displayed.

 

In initializer, we perform an AJAX callback to retrieve the template person.Template from the server.  When the download is complete, the complete event will fire, with the contents of our file in the response.responseText field ( or an error, which we wrongly do not handle ).  Once we have our template text downloaded, we “compile” it, which turns it into a JavaScript object. The next line looks obscenely complicated:

that.get('container').setHTML(template(that.get('model').getAttrs(['name','age','height'])));

A couple things are happening here.  First we are using that because this is contextual in JavaScript.  Within the callback function, it has a completely different value, so we cached the value going in.  Next we get the property container  that every Y.View object will have, and set it’s HTML using setHTML().  This is essentially how you render a view to the screen.  The parameter to setHTML is also a bit tricky to digest at first.  Essentially the method template() is what compiles a moustache template into actual HTML.  A template, as we will see in the moment, may be expecting some data to be bound, in this case name, age and height which all come from our Person model.  Don’t worry, this will make sense in a minute.

 

Our render method doesn’t particularly do anything, just returns itself.  Again we specify our modules dependency in the requires array, this time we depend on the modules view, io-base, personModel and handlebars.  As you can see, we are consuming our custom defined personModel module as if it was no different than any of the built-in YUI modules.  It is a pretty powerful way of handling code dependencies.

 

Now let’s take a look at our first template.

person.Template

<div style="width:20%;float:right">
    <div align=right>
        <img src=https://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>

As you can see, a template is pretty much just HTML, with a few small exceptions.  Remember a second ago when we passed data in to the template() call, this is where it is consumed.  The values surrounded by {{ }}  ( thus the name moustache! ) are going to be substituted when the HTML is generated.  Basically it looks for a value by the name within the {{ }} marks and substitutes it into the HTML.  For example, {{name}}, looks for a value named name, which it finds and substitutes it’s value mike in the results.  Using templates allows you to completely decouple your HTML from the rest of your application.  This allows you to source out the graphic work to a designer, perhaps using a tool like DreamWeaver, then simply add moustache markup for the bits that are data-driven.

 

What you may be asking yourself is, how the hell did the PersonView get it’s model populated in the first place?  That’s a very good question.

 

In our application, our view is actually going to be composed of a number of sub-views.  There is a view for the area the map is going to be edited in, a view for the context sensitive editing will occur ( currently our person view ), then finally a view where our menu will be rendered.  However, we also have a parent view that holds all of these child views, sometimes referred to as a composite view. This is ours:

editor.View.js

YUI.add('editorView',function(Y){
    Y.EditorView = Y.Base.create('editorView', Y.View, [], {
        initializer:function(){

            var person = new Y.Person();
            this.pv = new Y.PersonView({model:person});
            this.menu = new Y.MainMenuView();
            this.map = new Y.MapView();
        },
        render:function(){
            var content = Y.one(Y.config.doc.createDocumentFragment());
            content.append(this.menu.render().get('container'));

            var newDiv = Y.Node.create("<div style='width:100%'/>");
            newDiv.append(this.map.render().get('container'));
            newDiv.append(this.pv.render().get('container'));

            content.append(newDiv);
            this.get('container').setHTML(content);
            return this;
        }
    });
}, '0.0.1', { requires: ['view','io-base','personView','mainMenuView','mapView','handlebars']});

The start should all be pretty familiar by now.  We again are declaring a custom module editorView. This one also inherits from Y.View, the major difference is in our initializer() method, we create a Y.Person model, as well as our 3 custom sub-views, a PersonView, a MainMenuView and a MapView ( the last two we haven’t seen yet, and are basically empty at this point ).  As you can see in the constructor for PersonView, we pass in the Y.Person person we just created.  This is how a view gets it’s model, or at least, one way.

 

Our render() method is a bit more complicated, because it is responsible for creating each of it’s child views.  First we create a documentFragment, which is a chunk of HTML that isn’t yet part of the DOM, so it wont fire events or cause a redraw or anything else.  Basically think of it as a raw piece of HTML for us to write to, which is exactly what we do.  First we render our MainMenuView, which will ultimately draw the menu across the screen.  Then we create a new full width DIV to hold our other two views.  We then render the MapView to this newly created div, then render the PersonView to the div.  Finally we append our new div to our documentFragment.  Finally we set our view’s HTML to our newly created fragment, causing all the views to be rendered to the screen.

 

Once again, we set a version stamp, and declare our dependencies.  You may notice that we never had to include personModel, this is because personView will resolve this dependency for us.

 

Lets quickly look at each of those other classes  ( mainMenuView and mapView ) and their templates, although all of them are mostly placeholders for now.

 

mainMenu.View.js

YUI.add('mainMenuView',function(Y){
    Y.MainMenuView = Y.Base.create('mainMenuView', Y.View, [], {
        initializer:function(){
            var that=this,
                request = Y.io('/scripts/views/templates/mainMenu.Template',{
                    on:{
                        complete:function(id,response){
                            var template = Y.Handlebars.compile(response.responseText);
                            //that.get('container').setHTML(template(that.get('model').getAttrs(['name','age','height'])));
                            that.get('container').setHTML(template());
                        }
                    }
                });
        },
        render:function(){
            return this;
        }
    });
}, '0.0.1', { requires: ['view','io-base','handlebars']});

mainMenu.Template

<div style="width:100%">This is the area where the menu goes.  It should be across the entire screen</div>

 

map.View.js

YUI.add('mapView',function(Y){
    Y.MapView = Y.Base.create('mapView', Y.View, [], {
        initializer:function(){
            var that=this,
                request = Y.io('/scripts/views/templates/map.Template',{
                    on:{
                        complete:function(id,response){
                            var template = Y.Handlebars.compile(response.responseText);
                            that.get('container').setHTML(template());
                            //that.get('container').setHTML(template(that.get('model').getAttrs(['name','age','height'])));
                        }
                    }
                });
        },
        render:function(){
            return this;
        }
    });
}, '0.0.1', { requires: ['view','io-base','handlebars']});

map.Template

<div style="width:80%;float:left">
    This is where the canvas will go
</div>

Now, we let’s take a quickly look at server.js.  As mentioned earlier, this script simply provides a basic NODEJS based HTTP server capable of serving our app.

server.js

var express = require('express'),
    server = express();

server.use('/scripts', express.static(__dirname + '/scripts'));

server.get('/', function (req, res) {
    res.sendfile('index.html');
});

server.listen(process.env.PORT || 3000);

 

I wont really bother explaining what’s going on here.  If you are going to use Node, there is a ton of content on this site already about setting up a Node server.  Just click on the Node tag for more articles.

 

Finally, we have index.html which is the heart of our application and what ties everything together and this is the file that is first served to the users web browser, kicking everything off.

index.html

<!DOCTYPE html>

<html>
<head>
    <title>GameFromScratch example YUI Framework/NodeJS application</title>
</head>
<body>

<script src="http://yui.yahooapis.com/3.5.1/build/yui/yui-min.js"></script>
<script src="/scripts/models/person.js"></script>
<script src="/scripts/views/person.View.js"></script>
<script src="/scripts/views/map.View.js"></script>
<script src="/scripts/views/mainMenu.View.js"></script>
<script src="/scripts/views/editor.View.js"></script>

<script>
    YUI().use('app','editorView', function (Y) {

        var app = new Y.App({
            views: {
                editorView: {type: 'EditorView'}
            }
        });

        app.route('/', function () {
            this.showView('editorView');//,{model:person});
        });

        app.render().dispatch();
    });
</script>


</body>
</html>

 

This sequence of <script> tags is very important, as it is what causes each of our custom modules to be evaluated in the first place.  There are cleaner ways of handling this, but this way is certainly easiest.  Basically for each module you add, include it here to cause that code to be evaluated.

 

Next we create our actual Y function/namespace.  You know how we kept adding our classes to Y., well this is where Y is defined.  YUI uses an app loader to create the script file that is served to your clients browser, which is exactly what YUI.use() is doing.  Just like the requires array we passed at the bottom of each module definition, you pass use() all of the modules you require, in this case we need the app module from YUI, as well as our custom defined editorView module.

 

Next we create a Y.App object.  This is the C part of MVC.  The App object is what creates individual views in response to different URL requests.  So far we only handle one request “/”, which causes the editorView to be created and shown.  Finally we call app.render().dispatch() to get the ball rolling, so our editorView will have it’s render() method called, which will in turn call the render method of each of it’s child views, which in turn will render their templates…

 

Don’t worry if that seemed scary as hell, that’s about it for infrastructure stuff and is a solid foundation to build a much more sophisticate application on top of.

 

Of course, there is nothing to say I haven’t made some brutal mistakes and need to rethink everything! Smile

 

Now, if you open it up in a browser ( localhost:3000/ if you used Node ), you will see:

image

 

Nothing too exciting as of yet, but as you can see, the menu template is rendered across the top of the screen, the map view is rendered to the left and the Person view is rendered on the right.  As you can see from the text, the data from our Person model is compiled and rendered in the resulting HTML.

 

You can download the complete project archive right here.

Design Programming


25. July 2012

In prior parts we setup a Node.js/Express server to serve a cocos2D HTML project, showed how to host that project in the cloud using Heroku, then we created the backbone of the application itself, providing the ability to populate the app with data, as well as a means to retrieve that data.

 

Now, it’s time to actually create the game itself.

 

As I mentioned at the very beginning, the game is remarkably simple and isn’t really a game at all.  It is a simple series of two configurable pictures, it shows both side by side, then if you click it one zooms in, then if you click again, the next image zooms in, then finally, both images are shown side by side again.  The end result is I can show my daughter a sequence of events so she can better understand cause and effect.

 

Here is a screenshot of our ultimate end result:

image

 

Not earth shattering by any means, but it a) accomplishes what I need to accomplish and can be hosted on any device and accessed anywhere I need it  b) demonstrates all of the core technologies needed to make a much more complex web hosted game or web application.

 

 

Lets jump right in and look at the code.  The graphics are powered by cocos2D HTML, a 2D JavaScript based game library.  You can read some tutorials about it here, as I am not going to get into how cocos2D works in any detail.

 

First we need our AppDelegate.js class, which fires up our cocos2D game.

var cc = cc || {};

cc.AppDelegate = cc.Application.extend({
    ctor:function () {
        this._super();
    },
    initInstance:function () {
        return true;
    },
    applicationDidFinishLaunching:function () {
        var pDirector = cc.Director.sharedDirector();

        var size = pDirector.getWinSize();
        pDirector.setAnimationInterval(1.0 / 60);
        var pScene = FirstThis.scene();
        pDirector.runWithScene(pScene);
        return true;
    },
    applicationDidEnterBackground:function () {
        cc.Director.sharedDirector().pause();
    },
    applicationWillEnterForeground:function () {
        cc.Director.sharedDirector().resume();
    }
});

 

For more information on what is happening here, check the tutorial link I posted earlier.  The most important part to us is the creation of FirstThis, which is the heart of our application.

 

Let’s take a look at FirstThis.js.  Again, I apologize for the wonky formatting, it was done to fit the blog.

 

var FirstThis = cc.LayerColor.extend({
    leftSprite:null,
    rightSprite:null,
    mode:0,
    imageChanged:function(imgName,whichSprite){
        if(this.mode != 0) // We were in transition when select box was changed!
        {
            this.resetVisibility();
            this.mode=0;
        }
        this.removeAllChildrenWithCleanup(true);

        if(this.leftSprite != null && whichSprite=="right")
        {
            this.addChild(this.leftSprite);
        }
        if(this.rightSprite != null && whichSprite=="left"){
            this.addChild(this.rightSprite);
        }

        var imageSize;
        YUI().use('node','io-base',function(Y){
            var results = Y.io("/imageSize/" + imgName, {"sync":true});
            imageSize = JSON.parse(results.responseText);
        });

        var newSpriteWidth = cc.Director.sharedDirector().getWinSize().width/2;
        var newSpriteHeight = cc.Director.sharedDirector().getWinSize().height/2;

        if(whichSprite == "left"){
            this.leftSprite = cc.Sprite.create("/image/" + imgName,
                new cc.Rect(0,0,imageSize.width,imageSize.height));
            this.addChild(this.leftSprite);
            this.leftSprite.setScale(
                (newSpriteWidth * this.leftSprite.getScaleX())/imageSize.width);
            this.leftSprite.setAnchorPoint(new cc.Point(0,1));
            this.leftSprite.setPosition(
                new cc.Point(0,cc.Director.sharedDirector().getWinSize().height));
        }
        else
        {
            this.rightSprite = cc.Sprite.create("/image/" + imgName,
                new cc.Rect(0,0,imageSize.width,imageSize.height));
            this.addChild(this.rightSprite);
            this.rightSprite.setScale(
                (newSpriteWidth * this.rightSprite.getScaleX())/imageSize.width);
            this.rightSprite.setAnchorPoint(new cc.Point(0,1));
            this.rightSprite.setPosition(
            new cc.Point(newSpriteWidth,cc.Director.sharedDirector().getWinSize().height));
        }
    },
    resetVisibility:function()
    {
        this.leftSprite.setIsVisible(true);
        this.rightSprite.setIsVisible(true);
        this.leftSprite.setPosition(
            new cc.Point(0,cc.Director.sharedDirector().getWinSize().height));
        this.rightSprite.setPosition(
                new cc.Point(cc.Director.sharedDirector().getWinSize().width/2,
                cc.Director.sharedDirector().getWinSize().height));
    },
    ctor:function()
    {
        this._super();
    },
    init:function()
    {
        this.setIsTouchEnabled(true);
        this.initWithColor(cc.ccc4(0,0,0,255));

        var that = this;

        YUI().use('node',function(Y){
            Y.one("#firstSel").on("change",function(event){
                if(event.currentTarget.get("selectedIndex") == 0) return;
                    that.imageChanged(event.currentTarget.get("value"),"left");
            });
            Y.one("#thenSel").on("change",function(event){
                if(event.currentTarget.get("selectedIndex") == 0) return;
                    that.imageChanged(event.currentTarget.get("value"),"right");
            });
        });
        this.setAnchorPoint(0,0);
        return this;
    },
    ccTouchesEnded:function (pTouch,pEvent){
        if(this.leftSprite != null && this.rightSprite != null ){
            this.mode++;
            if(this.mode == 1)
            {
                this.leftSprite.setIsVisible(true);
                this.rightSprite.setIsVisible(false);
                this.leftSprite.setPosition(
                        new cc.Point(cc.Director.sharedDirector().getWinSize().width/4,
                        cc.Director.sharedDirector().getWinSize().height));
            }
            else if(this.mode == 2)
            {
                this.leftSprite.setIsVisible(false);
                this.rightSprite.setIsVisible(true);
                this.rightSprite.setPosition(
                    new cc.Point(cc.Director.sharedDirector().getWinSize().width/4,
                        cc.Director.sharedDirector().getWinSize().height));
            }
            else{
                this.resetVisibility();
                this.mode = 0;
            }
        }

    }
});


FirstThis.scene = function() {
    var scene = cc.Scene.create();
    var layer = FirstThis.layer();

    scene.addChild(layer);
    return scene;
}

FirstThis.layer = function() {
    var pRet = new FirstThis();

    if(pRet && pRet.init()){
        return pRet;
    }
    return null;
}

 

Again, most of what is going on here is covered in the earlier tutorial, but I will give a quick overview, and point out some of the application specific oddities as I go.

 

Starting from the top, we declare a pair of variables for holding our two active sprites, as well as a value for the current “mode”, which essentially represents our click state, which will make sense shortly.

 

We then define a method imageChanged which will be called when the user selects a different value in one of the two select boxes at the top of the screen.  On change we first chack to see if the mode is not 0, which means that we are in the process of showing images to the user ( meaning a single image might be visible right now ), in which case we reset visibility and positioning so our two images are side by side again, and reset the mode back to zero.  Then we remove all of the sprites from the layer, effectively erasing the screen.

 

Next we want to check if the user is updating the left or the right image.  If the user is updating the right image for example, we check to see if the left image has been assigned a value yet, and if it has assign it back to the scene.  This test is to prevent trying to push a null sprite onto the scene if the user hasn’t selected images with both drop-downs yet.  We do this for the left and right image.

 

Next we run into a bit of a snag.  This application wants to size images so they each take up 50% of the screen.  There is a problem with this however, as when you declare a sprite with cocos2D, unless you specify the image size, it doesn’t have any dimension data!  This is ok with a game when you will probably know the image dimensions in advance, but in this situation where the images are completely dynamic, it presents a major problem!  This is a problem we solved rather nicely on the server side using node.  We will look at the changes to server.js shortly, but for now realize we add a web service call that returns the specified image dimensions.  We then make a synchronous call using YUI’s io() module to retrieve that information… note the complete lack of error handling here!  My bad.

 

We ideally want to scale our image so their width is half the screen.  We now create our sprite, depending if it is on the left or right side, but the logic is basically identical.  First we create the sprite using the filename passed as a value from our select box, and the dimensions we fetched earlier.  We then add that file to the scene, scale it so the width is 50% of the screen ( scaling it up or down as required ), then position it relative to the top left corner of the sprite, with the X value changing if the sprite is on the left or right.

 

Next up we create the function resetVisibility() which we used earlier on.  Basically it just makes both sprites visible again and puts them back in their default positions.  Next we implement a simple constructor that calls layers constructor.  This is to verify some methods needed to handle touch input are properly called. The cocos2D tutorial on handling input covers this in a bit more detail.

 

Next up is our initialization function, named appropriately enough init().  We tell cocos2D that we are going to handle touch (click) events, that we want to create a layer with a black opaque background.  Next we wire up event handlers to our two select boxes that call our imageChanged() method when, um, and image is changed.  Lastly we tell our layer to anchor using the default bottom left corner.  This call is rather superfluous, as this is the default. I like to include it for peace of mind though, as if there is something I fight with ( and hate! ) about cocos, it’s the coordinate system.

 

Next up we have our touch handler ccTouchesEnded, which will be called when the user taps the screen or when they click a mouse.  This is where the mode variable comes into play.  At a value of 0, it means the screen hasn’t been touched yet, so on first touch, we set the left image to the center of the screen and the right image as invisible.  On the next touch, we do the opposite, then on any further touches we set both images back to their default positions and reset mode back to zero.  The remaining code is simple boiler plate setup code used to create our layer, which again is covered in more detail in these tutorials.

 

In a nutshell, that is our game’s code.  You may remember earlier we made a change to server.js, let’s take a look at the file now.

 

Here is server.js in it’s entirety.  Remember, you do not need to host on Heroku to run this app.  Simple run node server.js from the command line or terminal, then hit localhost:3000 in your web browser.

 

server.js

var express = require('express'),
    server = express.createServer(),
    im = require('imagemagick'),
    files = {};

server.use('/cocos2d', express.static(__dirname + '/cocos2d') );
server.use('/cocosDenshion', express.static(__dirname + '/cocosDenshion') );
server.use('/classes', express.static(__dirname + '/classes') );
server.use('/resources', express.static(__dirname + '/resources') );

server.use(express.bodyParser());

server.get('/', function(req,res){
    res.sendfile('index.html');
    console.log('Sent index.html');
});

server.get('/settings',function(req,res){
   res.sendfile('settings.html');
   console.log('Send settings.html');
});

// API calls
server.get('/image/:name', function(req,res){
    if(files[req.params.name])
    {
        res.contentType(files[req.params.name].contentType);
        res.sendfile(files[req.params.name].path);

        console.log("Returning file" + req.params.name);
    }
});

server.get('/imageSize/:name',function(req,res){
   im.identify(files[req.params.name].path,function(err,features){
       console.log("image/" + req.params.name);
       if(err) throw err;
       else
        res.json({ "width":features.width, "height":features.height });
   });
});

server.get('/getPhotos', function(req,res){
    res.json(files);

});

server.get('/clearAll', function(req,res){
    files = {};
    res.statusCode = 200;
    res.send("");
})

server.post('/upload',function(req,res){
    files[req.files.Filedata.name] = {
        "name":req.files.Filedata.name,
        "path":req.files.Filedata.path,
        "size":req.files.Filedata.size,
        "contentType":req.files.Filedata.type,
        "description":req.body.description };
        console.log(req.files.Filedata);

    console.log(Object.keys(files).length);
    res.statusCode = 200;
    res.send("");
});
server.listen(process.env.PORT || 3000);

Most of this code we have seen before, but there are a couple of new changes.  First thing to notice is:

    im = require('imagemagick'),

We added a new library to the mix to handle image process, the venerable ImageMagick.  You need to install this library before running this code.  First we need to add it to node, that is as simple as, from a command line, cd to your project directory then type:

npm install imagemagick

If you are deploying to Heroku, you also need to update the dependencies in the package.json file, so Heroku is aware of the dependencies.  That file should now look like:

{
    "name": "firstthis",
    "version": "0.0.1",
    "dependencies": {
        "express": "2.5.x",
        "imagemagick":"0.1.x"
    },
    "engines": {
        "node": "0.8.x",
        "npm":  "1.1.x"
    }
}

Finally, you need to install ImageMagick itself.  On Windows the easiest way is to download the binary installer, while for Linux it’s probably easiest to use a package manager of your choice.  Once you install imagemagick, be sure to start a new command line/terminal so it picks up the path variables image magick sets.

 

Ok, now that we have the dependency out of the way, the code itself is trivial:

server.get('/imageSize/:name',function(req,res){
   im.identify(files[req.params.name].path,function(err,features){
       console.log("image/" + req.params.name);
       if(err) throw err;
       else
        res.json({ "width":features.width, "height":features.height });
   });
});

We get file details about the image passed in to the url ( for example with the URL /imageSize/imagename.jpg, name will = imagename.jpg ), then if no errors occur, we return the width and height as a JSON response.

 

 

Finally, we get to the actual HTML, index.html which is served by Node if you request the “/” of the website.

<html>
<head>
 <script src="http://yui.yahooapis.com/3.5.1/build/yui/yui-min.js"></script>
 <script>
  YUI().use('node','io-base',function(Y){

    Y.on("load", function(){
     var canvas = Y.DOM.byId('gameCanvas');
     canvas.setAttribute("width",window.innerWidth-30);
     canvas.setAttribute("height", window.innerHeight-70);
     Y.Get.script(['/classes/cocos2d.js']);

    });

    Y.io('/getPhotos',{
     on: {
      complete:function(id,response){
       var files = JSON.parse(response.responseText);
       var firstSel = Y.DOM.byId("firstSel");
       var thenSel = Y.DOM.byId("thenSel");

       for(var key in files)
       {
           firstSel.options.add(
             new Option(files[key].description,files[key].name));
           thenSel.options.add(
             new Option(files[key].description,files[key].name));
       }
      }
     }
    });

   });
 </script>
</head>
<body style="padding:0; margin: 0; background: black">
 <form>
  <span style="align:left;vertical-align:top;padding-top:0px">
   <label style="color:white;height:40px;font-size:26;vertical-align:middle">
       First:
   </label>
   <select style="height:40px;font-size:22;width:250" id="firstSel">
       <option selected>Drop down to choose</option>
   </select>
   <label style="color:white;height:40px;font-size:26;
   padding-left:10px;vertical-align: middle;">Then:</label>
  <select style="height:40px;font-size:22;width:250" id="thenSel">
    <option>Drop down to choose</option>
   </select>
  </span>
  <span style="float:right;vertical-align: top;margin-top:0px;top:0px;">
   <input align=right type=button value=settings id=settings
       style="height:40px;font-size:26"
      onclick="document.location.href='/settings';"  />
  </span>
 </form>
<div width=100% style="text-align:center;clear:both;">
    <canvas id="gameCanvas">
        Your browser does not support the canvas tag
    </canvas>
</div>
</body>
</html>

Everything here we have seen before, except perhaps this small chunk of extremely important code:

    Y.on("load", function(){
     var canvas = Y.DOM.byId('gameCanvas');
     canvas.setAttribute("width",window.innerWidth-30);
     canvas.setAttribute("height", window.innerHeight-70);
     Y.Get.script(['/classes/cocos2d.js']);

    });

 

This code will be executed once the page finishes loading.  As you may notice, the canvas tag gameCanvas did not have a size specified, as we want it to take the full width of the device it is run on.  Unfortunately you cannot just say width=100% and be done with it, so we set the width and height programmatically when the page loads.  Finally, to make sure that cocos2D objects aren’t loaded and set until after the Canvas tag is resized, I deferred the loading until now, so after we resize the canvas, we dynamically load the cocos2d script, making sure it initializes with the proper dimensions.

 

So, after all our hard work, here is our running completed application in action.  ( Or direct link here, if you don’t want to see it in an iframe ).  Again a warning the data for this application is completely viewer driven, I give no guarantees the content is appropriate! Please be civil ).

 

 

That basically completes the application tutorial.  There are a few issues right now:

1- It doesn’t persist user data.  If the server restarts or a certain period of time elapses, all the data stored on Heroku is lost.  This is easily fixed, but beyond the scope of this tutorial

2- There is a complete lack of hardening or error handling, so the application is probably incredibly fragile

3- There are a few HTML bugs ( oh… aren’t there always? ).  The change event doesn’t always fire when you select the first drop down the first time ( this is a browser bug, and can be worked around, but the code isn’t tutorial friendly. ).  Also, on Safari mobile, cocos2D sprite scaling doesn’t appear to work.

 

 

I hope you found this series useful.  As I was working with git anyways for the Heroku deployment, I decided to make this entire project available on github.  So if you want to fork it and play around, have fun!  I am a massive github newbie though, so don’t be shocked if I screwed something up.  If you, like me, have a child that isn’t doing transitions very well, I hope you find it useful.  I am actually making a more production worth version of this application, so if you need an online first-then board, drop me a line!

 

On a final note, I am going to look into making a PhoneGap version of this application.  If that exercise bares fruit, I will have another section for this tutorial!

Programming


7. July 2012

I have run into another snag with using YUI in an MVC app, one that is causing me to look into JQuery + Backbone as an alternative.

 

It’s a pretty small thing over all, I can’t get actual YUI controls working from Moustache templates.  You need to pass the ID to a control, but at the point of Render() it doesn’t appear to be valid. This isn’t the actual problem though, the problem(s).

  • The community is quite small, and I found ZERO examples except Mojito based.  Even that is pretty sparse.
  • The everything and the kitchen sink approach YUI takes makes dropping in a replacement much trickier.
  • The only community is only really active on the official YUI forums, and they are moderated.

 

 

That last one is a gigantic deal breaker for me.  See, the community at the forums is wonderful and responsive.  It was quite impressive to deal with them and they have my kudos.

 

However…  my first post took 3 days to get approved! My second comment took another 8 hours. 

 

3 days in like a decade in internet-years!  It may seem a small point, but Yahoo!, if you want people to adopt YUI, you really need to remove this restriction.

General


25. June 2012

 

In my prior post introducing the bones of our YUI based level editor, I showed the following way of loading in a template from the server:

initializer:function(){ var that=this, request = Y.io('/scripts/views/templates/person.Template',{ on:{ complete:function(id,response){ var template = Y.Handlebars.compile(response.responseText); that.get('container').setHTML(template(that.get('model').getAttrs(['name','age','height']))); } } }); }

 

This works, but it is less than ideal.  There are alternatives, but each has a downside.  You can compile the template into a JavaScript string, but then, what is the point of creating a template in the first place?  The nice thing about a template is, it is basically just HTML with a small bit of specialized markup in it.  You can send that template to a designer, who can then make modifications to your UI without knowing anything but basic HTML.  Of course, nothing is preventing you from going through the compilation step with the finished result, but this will be a massive time sink on your productivity. 

 

Probably the best answer, is to compile the templates on the server.  I didn’t go this route because frankly, it made for a really lousy tutorial.  It would basically take the same amount of code for JUST loading dynamic templates, as it would take for all the rest of the project combined!  This IMHO added a needless level of complexity to the tutorial and would only confuse people.  However, loading from the server is *IS* a better way to do it, and I was considering making an additional post for people interested in doing it that way.

 

Nicely, I don’t have to, someone else has done it for me! Smile

 

In this thread (about this thread… a real Conception moment here)  on the YUI forums, user Satyam has taken the ball and run with it!  So if you are interested in how you would implement templating on the server side, be sure to check it out.  The nice part about implementing it server side is, you still get your markup nicely separated from the rest of your code, however, you eliminate the asynchronous call to fetch the template from the server.  Thanks Satyam.

 

This, is the modifications he made to server.js to support loading of templates server side, while this is the modification he made to the view.  It is certainly more complex, but it is also a much cleaner implementation.

Programming


AppGameKit Studio

See More Tutorials on DevGa.me!

Month List