4. June 2012

 

This tutorial we are going to look at SpriteList and show how it can result in a massive increase in performance.  Along the way, we are going to create two custom Scene classes, one that performs poorly, and one the does not.  Along the way we are going to demonstrate dynamically generating a UI and how UI scenes and GameEngine2D scenes can co-exist.

 

A lot of the following code we have covered in previous tutorials, so I am not going to go into a great deal of detail.

 

 

First lets take a look at our AppMain.cs

 

using System; using System.Collections.Generic; using Sce.Pss.Core; using Sce.Pss.Core.Environment; using Sce.Pss.Core.Graphics; using Sce.Pss.Core.Input; using Sce.Pss.HighLevel.GameEngine2D; using Sce.Pss.HighLevel.GameEngine2D.Base; using Sce.Pss.HighLevel.UI; namespace SpriteList { public class AppMain { static private bool quit = false; public static void Main(string[] args) { Director.Initialize(); Director.Instance.RunWithScene(new Sce.Pss.HighLevel.GameEngine2D.Scene(),true); UISystem.Initialize(Director.Instance.GL.Context); Sce.Pss.HighLevel.UI.Panel dialog = new Panel(); dialog.Width = Director.Instance.GL.Context.GetViewport().Width; dialog.Height = Director.Instance.GL.Context.GetViewport().Height; Button buttonUI1 = new Button(); buttonUI1.Name = "buttonGoSlow"; buttonUI1.Text = "Slow version"; buttonUI1.Width = 300; buttonUI1.Height = 50; buttonUI1.Alpha = 0.8f; buttonUI1.TouchEventReceived += (sender, e) => { Director.Instance.ReplaceScene(new SlowVersion()); }; Button buttonUI2 = new Button(); buttonUI2.Name = "buttonGoFase"; buttonUI2.Text = "Fast version"; buttonUI2.Width = 300; buttonUI2.Height = 50; buttonUI2.SetPosition(0,55); buttonUI2.Alpha = 0.8f; buttonUI2.TouchEventReceived += (sender, e) => { Director.Instance.ReplaceScene(new FastVersion()); }; Button buttonUI3 = new Button(); buttonUI3.Name = "buttonExit"; buttonUI3.Text = "Exit App"; buttonUI3.Width = 300; buttonUI3.Height = 50; buttonUI3.SetPosition(0,110); buttonUI3.Alpha = 0.8f; buttonUI3.TouchEventReceived += (sender, e) => { quit = true; }; dialog.AddChildLast(buttonUI1); dialog.AddChildLast(buttonUI2); dialog.AddChildLast(buttonUI3); Sce.Pss.HighLevel.UI.Scene scene = new Sce.Pss.HighLevel.UI.Scene(); scene.RootWidget.AddChildLast(dialog); UISystem.SetScene(scene); while(!quit) { Director.Instance.Update(); Director.Instance.GL.Context.Clear(); Director.Instance.Render(); UISystem.Update(Touch.GetData(0)); UISystem.Render(); Director.Instance.GL.Context.SwapBuffers(); Director.Instance.PostSwap(); } } } }

 

We set up our Director object and tell it to run with an empty scene.  We then set up our UISystem in a similar manner.  Then we create a Panel object named dialog by hand.  This process is basically the same as what UI Composer generates for us, just all created in code.  We then add 3 buttons to our panel.  For each button we add a TouchEventReceived handler in the form of a lamda function.

 

If they click the Slow Version button, we create a new SlowVersion scene object and set it as the active scene in the Director.  If we click the Fast Version button, we create a FastVersion scene object and make it the active scene.  We will cover creating these two classes in just a moment.  Finally for the Exit App button, we toggle the quit bool to true, causing the event loop to exit.

 

Now that we have created our 3 buttons, we add them to our dialog Panel.  We then create an empty Scene ( UI scene, not GameEngine2D! ), add our dialog to it and set it as the active scene.  This will render our buttons visible.

 

Now we handle our event loop.  Everything here is pretty much identical to in previous tutorials, the only key difference is that we call our UISystem.Update() and Render() within the game loop.  It is important that UISystem.Render is called after Director.Instance.Render, or the Director will render over top of it!  This loop will now continue processing until the quit bool is set by pressing the Exit App button.

 

Now lets take a look at SlowVersion.cs… the bad way of doing things!

 

 

using System; using Sce.Pss.Core.Graphics; using Sce.Pss.HighLevel.GameEngine2D; using Sce.Pss.HighLevel.GameEngine2D.Base; namespace SpriteList { public class SlowVersion : Scene { private TextureInfo _textureInfo; private Texture2D _texture2D; private System.Collections.Generic.List<SpriteUV> _sprites; public SlowVersion () { this.Camera.SetViewFromViewport(); _texture2D = new Texture2D("/Application/Jet.png",false); _textureInfo = new TextureInfo(_texture2D); var w = Director.Instance.GL.Context.GetViewport().Width; var h = Director.Instance.GL.Context.GetViewport().Height; System.Random rand = new System.Random(); _sprites = new System.Collections.Generic.List<SpriteUV>(); for(int i = 0; i < 1000; i++) { SpriteUV sprite = new SpriteUV(_textureInfo); sprite.Position = new Sce.Pss.Core.Vector2(rand.Next(0,w) - w/2 ,rand.Next(0,h) -h/2); sprite.Quad.S = new Sce.Pss.Core.Vector2(_texture2D.Width,_texture2D.Height); sprite.Rotate(rand.Next (0,360)); sprite.Schedule( (dt) => { sprite.Position = new Sce.Pss.Core.Vector2(rand.Next(0,w)-w/2 + _texture2D.Width/2,rand.Next(0,h)-h/2); }); _sprites.Add(sprite); } foreach(var sprite in _sprites) { this.AddChild(sprite); } FPS fps = new FPS(); fps.Position = new Sce.Pss.Core.Vector2(0,0); this.AddChild(fps); } public override void Draw () { base.Draw (); } } }

 

Most everything here has also been covered in prior tutorials, but perhaps not in this form.  SlowVersion is inherited from Scene.  For member variables it contains a single Texture2D and it’s corresponding TextureInfo object.  It also contains a List of SpriteUV’s to be rendered every frame.  All of the sprites point to the same Texture, our Jet.png graphic:

 

Jet

 

Bonus points if you can identify the type of jet! Winking smile

 

In our constructor we go about the usual things, first we setup our camera, load our texture from disk and create our TextureInfo from the loaded texture.  Next we allocate our List and then create 1000 instances of our jet sprite, randomly rotated and positioned on screen.  We also register a lamda Schedule method that will be called every frame. During each update we simply randomly relocate the airplane sprite on screen.  Finally, we add the newly created sprite to our list.  Next we loop through all of our sprites and add them to our scene.

 

In the next bit we create an FPS object, a simple helper object for displaying the current frame rate on screen.  We will cover the code in a second.

 

 

Now let’s take a look at FastVersion.cs, which you will soon notice is basically just a copy and paste job!

 

using System; using Sce.Pss.Core.Graphics; using Sce.Pss.HighLevel.GameEngine2D; using Sce.Pss.HighLevel.GameEngine2D.Base; namespace SpriteList { public class FastVersion : Scene { private TextureInfo _textureInfo; private Texture2D _texture2D; private System.Collections.Generic.List<SpriteUV> _sprites; private Sce.Pss.HighLevel.GameEngine2D.SpriteList _spriteList; public FastVersion () { this.Camera.SetViewFromViewport(); _texture2D = new Texture2D("/Application/Jet.png",false); _textureInfo = new TextureInfo(_texture2D); var w = Director.Instance.GL.Context.GetViewport().Width; var h = Director.Instance.GL.Context.GetViewport().Height; System.Random rand = new System.Random(); _sprites = new System.Collections.Generic.List<SpriteUV>(); for(int i = 0; i < 1000; i++) { SpriteUV sprite = new SpriteUV(_textureInfo); sprite.Position = new Sce.Pss.Core.Vector2(rand.Next(0,w) - w/2 ,rand.Next(0,h) -h/2); sprite.Quad.S = new Sce.Pss.Core.Vector2(_texture2D.Width,_texture2D.Height); sprite.Rotate(rand.Next (0,360)); sprite.Schedule( (dt) => { sprite.Position = new Sce.Pss.Core.Vector2(rand.Next(0,w)-w/2,rand.Next(0,h)-h/2); }); _sprites.Add(sprite); } _spriteList = new Sce.Pss.HighLevel.GameEngine2D.SpriteList(_textureInfo); foreach(var sprite in _sprites) { _spriteList.AddChild(sprite); } FPS fps = new FPS(); fps.Position = new Sce.Pss.Core.Vector2(0,0); this.AddChild (_spriteList); this.AddChild(fps); } public override void Draw () { base.Draw (); } } }

 

You may notice two things… first, neither SlowVersion nor FastVersion do *ANY* cleanup and leak like sieves!  In non-demonstration code, be sure to clean up after yourself!

Second, they are virtually identical, but if you run them, FastVersion runs easily 4-5x faster… why is this?

 

That is the power of SpriteList, which performs a function very similar to SpriteBatch if you are familiar with XNA programming.  We only made two changes.

 

1:

private Sce.Pss.HighLevel.GameEngine2D.SpriteList _spriteList;

We declared out spriteList member.

 

2:

_spriteList = new Sce.Pss.HighLevel.GameEngine2D.SpriteList(_textureInfo); foreach(var sprite in _sprites) { _spriteList.AddChild(sprite); }

 

Then, instead of adding the sprites to the scene, we add them to our spriteList object.  That’s it!

 

There are limitations, all sprites must have a common TextureInfo, BlendMode and Color property to be added to a spriteList, so basically create one spriteList per TextureInfo if you have multiple sprites on screen and you will see a large performance boost.

 

 

Finally lets take a quick look at the FPS.cs widget.  This is just a crude hack to display FPS on screen by creating a small texture.

 

using System; using Sce.Pss.Core; using Sce.Pss.Core.Graphics; using Sce.Pss.Core.Imaging; using Sce.Pss.HighLevel.GameEngine2D; using Sce.Pss.HighLevel.GameEngine2D.Base; namespace SpriteList { public class FPS : SpriteUV { TextureInfo _ti; public FPS () { Texture2D texture = new Texture2D(150,1000,false, PixelFormat.Rgba); _ti = new TextureInfo(texture); this.TextureInfo = _ti; this.Quad.S = new Sce.Pss.Core.Vector2(150,100); Scheduler.Instance.ScheduleUpdateForTarget(this,1,false); } public override void Update (float dt) { _ti.Dispose(); Image img = new Image(ImageMode.Rgba, new ImageSize(150,100), new ImageColor(255,255,255,0)); img.DrawText("FPS:" + (1/dt).ToString(), new ImageColor(255,255,255,255), new Font(FontAlias.System,32,FontStyle.Bold), new ImagePosition(0,0)); Texture2D texture = new Texture2D(150,100,false, PixelFormat.Rgba); texture.SetPixels(0,img.ToBuffer(),PixelFormat.Rgba); img.Dispose(); _ti = new TextureInfo(texture); this.TextureInfo = _ti; base.Update (dt); } } }

 

All we are doing here is creating our own SpriteUV derived object, FPS, but instead of loading the texture from file, we generate an image dynamically, just like we did way back in Hello World.  We then schedule ourselves to update every frame.  In the update we create an image and print the the current frames per second ( the fraction of a second our current time delta takes) and update our texture to the newly created image.

 

 

Here is our code in action.  Unfortunately the nature of the output resulted in a horrifically large animated gif, so I had to put this one up on YouTube:

 

Running our SpriteList demo

 

You can download all of the project code here.

Programming , , ,

blog comments powered by Disqus

Month List

DisqusCommentsSummary

All posts tagged 'YUI'
10. July 2013

I'll be the first to admit the expression "cloud computing" is overly abused these days. The reality is, while a bit abused by marketing types, cloud computing is a huge deal for developers.  Gamers simply expect certain functionality in your game that eventually is going to require you to have a server side component.  You may think to yourself…  well, rolling my own server isn't all that difficult.  If you have the skill set to do so, creating say… a server side score board is not all that difficult.  Frankly, it isn't either, in fact I covered exactly that topic in a prior tutorial series.  Easy, right?  Ok… now where are you going to host it and what is that going to cost you?  How secure is it?  Is it encrypted?  How well does it scale?  What about back-ups and fault tolerance?  Who is going to administrate your servers?  What about reporting?  Suddenly this simple little task quickly becomes a much more costly and complicated process and takes time and money away from doing what you set out to do… actually developing a game.

 

At the end of the day, for many developers letting someone else take care of all of these things generally makes a lot of sense.  This is the exact problem that Scoreoid sets out to solve.  You can essentially think of them as a your game's server in the cloud.  Using a fairly simple REST based api you can easily accomplish pretty much anything you could want from a dedicated server and they take care of the infrastructure and day to day operation for you.  In this post we are going to take a closer look at how Scoreoid works and what it does for you.

 

From a developers perspective, Scoreoid can be thought of as a product in two parts.  First you have the developer portal/dashboard where you can monitor, access and maintain your data.  Then you have the REST based API that the programmer uses to work with Scoreoid.

 

WARNING TIME!

To illustrate the various abilities I put together a web application that demonstrates much of Scoreoid's functionality.  This application is hitting live data and you the viewers have the ability to update and create new data.  This means I have NO IDEA what you might encounter when using the application and take no responsibility for anything written.   Please guys, be mature about it.  If you see something hateful or excessively stupid, let me know and I will remove it.

 

The Scoreoid Dashboard

First let's take a quick look at the Scoreoid web interface.  If you haven't already, you need to sign up for a Scoreoid account.  Don't worry, it's completely free and they don't spam you with emails.  Once you are signed up, log in and you will arrive at the main Dashboard page:

 

Dashboard

 

Here you get a top level overview of activity for your game.  In this case you can see that Henry is a very prolific and talented player of Test Game!

Across the top are a series of tabs.  These provide you a way to drill down into data for each category, for example, below is the result of clicking the Player tab.

DashPlayers

 

Here you can modify or delete any value in the Players database.  Scores, Games, Stats and Notifications work similarly, allowing you to access and modify data using a graphic interface.  The one I didn't mention was Console, and this is a handy little tool.  Console allows you to make Scoreoid API calls using a form.

 

ScoreoidConsole

 

Using the Console you pick the API call to make, and the form below is populated with the available parameter fields.  Once you execute it, you see the results below in the results panel, in either XML or JSON format.  You can make use of the entire Scoreoid API using the console.

 

Finally, let's take a look at adding a game.  Click on the Game tab and click the Add New Game button.  You need at least one game to use Scoreoid, but you can of course make many if you wish.  You pass the Game ID as a parameter to a number of the API calls.  Creating a game is simple enough:

CreatingAGame

 

Finally the Help link brings you to the documentation.  You can access the documentation without the need to log in if you want to take a look at the API.  The documentation is complete and has examples in multiple languages for each API call.  Now, let's get down to some coding.

 

 

Programming with Scoreoid

 

Scoreoid is accessed using a REST based interface.  The nice part about a REST based api is, if your application can programmatically access the web, it can use the API.  The actual programming language you use doesn't matter.  In this example, I am going to mostly use JavaScript.  In my case I like working with the YUI library, but you can easily use whatever library you want, the methodology stays very similar although the syntax changes slightly.

 

There are a couple things to note up front.  In the name of keeping the code easy to understand, I've performed a couple programming no-no's in this example.  First I gave you all access to my Scoreoid token…  in your application you obviously would not want to do this.  If you create a JavaScript based Scoreoid application, be sure to read about using an auto generated proxy for securing your application.  Next, I used the HTTP calls instead of the HTTPS ones.  In a secured application, you would want to obviously use the encrypted option.  Finally I made only synchronous networking calls… this means my networking calls cause programming execution to stop until results are returned.  In a real application you would generally want to make asynchronous calls.  As a result of these decisions, the code should however be fairly easy to understand.

 

Let's jump right in to the sample app.  We will be looking at portions of the code as we go.  However a great deal of the code has nothing to do with Scoreoid in particular ( YUI3 or markup related code ), while a lot of it is very repetitious in nature.  Therefore instead of listing it all here, I created a github for it. You can also download a zip of the project here

 

The application requires a server to be run due to the AJAX calls.  Therefore I have included a simple Node based server.  If you have Node installed, you can run the application by typing:

node server.js

Then you can access it in your browser at http://localhost:3000. The vast majority of interesting code for this application is in the views folder, while the templates folder contains the markup. Again, keep in mind this application is working with live data, so play around, but please be mature about it:

 

( Below is the application, not a screen shot :) )

 

 

There is a tab for each area of functionality we want to look at, Game, Player, Score and Cloud.  Under each is a form for interacting with Scoreoid.  At the bottom of each form is a field showing the raw XML or JSON results returned by the server.

 

Before we continue too far, let's take a quick look inside Index.html at the following lines.

        //Global Y variables
        Y["SCOREOID_KEY"] = "bde61648959d0c364b04b93e257035abd5ee3b26";
        Y["GAME_ID"] = "3e99cf43ab"
 

These are two values we will be needing over and over again.  First is my Scoreoid token… you should KEEP YOURS PRIVATE!  The second is the ID of a game I created earlier using the dashboard.  Once again, Scoreoid can support multiple games at once, but in this case I am using only one.  So in the code when you see Y.SCOREOID_KEY and Y.GAME_ID, this is where those values are set.

 

Now let's take a look at the Game tab in the application:

Game

First we will look at the code that executes when the user clicks the Get Game Information button in game.View.js.

        getGameData:function(){
            // This is a blocking web request... in real world, BAD!
            var requestCfg = {
                sync:true,
                data : {
                    api_key: Y.SCOREOID_KEY,
                    game_id: Y.GAME_ID,
                    response:"JSON"
                }
            };
            var request = Y.io("http://api.scoreoid.com/v1/getGame", requestCfg);
 
            Y.Global.fire('displayResults', {msg:request.responseText});
        }
 

Y.io() is what you use to make a HTTP request in YUI3.  The data object within the requestCfg object is where you pass the parameters to Scoreoid.  We pass in our api_key, game_id ( both defined earlier in index.html) as well as response.  The response variable tells Scoreoid what format you want the results returned in, either JSON or XML.  For the vast majority of examples, we are going to go with JSON as it's a bit easier to read and lighter weight.  Finally you call the function in the form of the URL you pass to Y.io, in this case http://api.scoreoid.com/v1/getGame which calls the getGame() method.  There is a wiki page for every API call ( here is getGame for example ), that lists the possible parameter fields as well as the values that will be returned.

 

Click the button and in the results area you should see:

[{"Game":{"user_id":"51b8b1dcfd8978203e000b9d","name":"Test Game","short_description":"This game is so amazingly awesome, it doesn't exist!","description":"Really, it doesn't exist.  The game is a lie.","players_count":6,"scores_count":7,"status":1,"created":"2013-06-12 19:39:07","updated":"2013-06-12 19:39:07"}}]

 

This is the return value of getGame() in JSON format.  JSON is a handy format, as it is basically just a JavaScript object encoded in string from.  You can turn a JavaScript object into JSON by calling JSON.stringify(myObject) and you can turn a JSON string back into a JavaScript object by calling JSON.parse(myString).  Pretty much every programming language under the sun has a JSON parsing library.  For those that don't you can always get Scoreoid to return XML ( we look at an XML example later ).

 

If YUI calling convention is alien to you, and you are more comfortable using jQuery, you can make the above call using the following jQuery code:

$.post("http://api.scoreoid.com/v1/getGame", { api_key:Y.SCOREOID_KEY,game_id:Y.GAME_ID,response:"JSON"},

   function(response) {

     //response will now contain the JSON data returned by Scoreoid

     //note, this is an async call

   });

 

As you can see, it's pretty simple to make REST based calls.  The other two examples in the Game tab are Get Players and Get Highest Gold Amount.  Below is the code for GetPlayers:

        getGamePlayers:function(){
            // This is a blocking web request... in real world, BAD!
            var requestCfg = {
                sync:true,
                data : {
                    api_key: Y.SCOREOID_KEY,
                    game_id: Y.GAME_ID,
                    response:"JSON"
                }
            };
            var request = Y.io("http://api.scoreoid.com/v1/getPlayers", requestCfg);
 
            Y.Global.fire('displayResults', {msg:request.responseText});
        }
 

As you can see the code is basically identical, the only difference is you call a different URL.  getPlayers() returns a list of all of the players of your game, unless of course you tell it to limit the results returned.  Get Highest Gold Amount is also virtually identical:

        getGameTop:function(gameField){
            var requestCfg = {
                sync:true,
                data : {
                    api_key: Y.SCOREOID_KEY,
                    game_id: Y.GAME_ID,
                    response:"JSON",
                    field:gameField
                }
            };
            var request = Y.io("http://api.scoreoid.com/v1/getGameTop", requestCfg);
 
            Y.Global.fire('displayResults', {msg:request.responseText});
 
        }
 

Once again, we call a different URL. In this case we are also passing in an additional parameter field.  Field tells the method which value we want to get the highest value of when calling getGameTop.  As you can see when we called getGameTop(), the value that was passed in was gold.

            container.one("#buttonGetWealthiest").on("click",function(){
                this.getGameTop("gold");
            },this);
 
There are a number of valid options for field ( bonus, best_score, gold, money, kills, lives, time_played, unlocked_levels ).  Once again, you can get a list of valid options in the documentation for getGameTop.  If you press the Get Highest Gold Amount button, you should see:
 
GetHighestGold
 
Of course, since you guys have full access to all the data in the application, the actual value returned might change.
 
Now let's take a look at working with Player data:
 
 
Player
 
The code behind this form is player.View.js.
 
Creating a player is once again a very straight forward process.  First the code that is called when the user clicks the Create Player button:
 
            this.get('container').one("#buttonCreatePlayer").on("click",function(){
                this.createPlayer(Y.one('#playerName').get('value'));
            },this);
 
 

This simply gets the value of the playerName text field ( see player.Template in the templates folder for HTML markup ) and passes it to the function createPlayer().  Now let's look at createPlayer:

 

        createPlayer:function(playerName){
            var requestCfg = {
                sync:true,
                data : {
                    api_key: Y.SCOREOID_KEY,
                    game_id: Y.GAME_ID,
                    username: playerName,
                    response:"JSON"
                }
            };
            var request = Y.io("http://api.scoreoid.com/v1/createPlayer", requestCfg);
            Y.Global.fire('displayResults', {msg:request.responseText});
        }
 

Once again, the actual code remains almost identical.  The URL we are calling is now http://api.scoreoid.com/v1/createPlayer and we pass the new player's name in the field username.

 

Next we will look at working with XML data instead.  If you click the Get All Players button, the following grid will appear:

GetPlayers

 

The YUI grid binds to an XML results set, so this time we are going to call /getPlayers, but instead we want it to return XML results instead of JSON.  This code is going to look a bit more daunting then it really is.  This is what executes when the user presses Get Players:

this.get('container').one("#playerGetAll").on("click",function(){
 
                var requestCfg = {
                    method:"POST",
                    data : {
                        api_key: Y.SCOREOID_KEY,
                        game_id: Y.GAME_ID,
                        response:"XML"
                    }
                };
 
                var ds = new Y.DataSource.IO({source:"http://api.scoreoid.com/v1/getPlayers"});
                ds.plug(Y.Plugin.DataSourceXMLSchema, {
                        schema: {
                            resultListLocator: "player",
                            resultFields: [
                                { key:"Username", locator:"@username" },
                                { key:"Email", locator:"@email" },
                                { key:"TimePlayed", locator:"@time_played" },
                                { key:"Gold", locator:"@gold" },
                                { key:"BestScore", locator:"@best_score" }
                            ]
                        }
                    });
                ds.sendRequest({
                    callback:{
                        success: function(e)
                            {
                                var dt = new Y.DataTable(
                                    {
                                    columns:[{key:"Username"},{key:"Email"},{key:"TimePlayed"},{key:"Gold"},{key:"BestScore"}],
                                    data: e.response.results,
                                    summary:"List of all players in the game",
                                    caption:"Players"
                                    });
                                // Clear the table, in case one already exists
                                Y.one("#dataTable").setContent("");
                                // Now populate
                                dt.render(Y.one("#dataTable"));
 
                                Y.Global.fire('displayResults', {msg:e.data.response});
                            },
                        failure: function(e){
                            Y.log(e);
                        }
                    },
                    cfg:requestCfg
                });
 
            },this);
 

Once again we are making a call to the getPlayers url. Notice however that we set Response to XML.  This time instead of getting data using Y.io(), we instead want to populate an XML dataset using Y.DataSource.IO().  The actual call is made when we call sendRequest and we pass in the requestCfg here.  The remaining code is about selecting which fields from the XML we want to have displayed in the grid.

 

Speaking of XML, here is the results of getPlayers() in XML form:

<?xml version="1.0" encoding="UTF-8"?>
<players>
<player username="Henry" unique_id="" first_name="" last_name="" email="mike@isawesome.com" bonus="0" achievements="" gold="9000" money="0" kills="0" lives="0" time_played="300" unlocked_levels="" unlocked_items="" inventory="" last_level="" current_level="" current_time="0" current_bonus="0" current_kills="0" current_achievements="" current_gold="0" current_unlocked_levels="0" current_unlocked_items="" current_lives="0" xp="" energy="" boost="" latitude="" longtitude="" game_state="" platform="" rank="0" best_score="90003" created="2013-06-21 16:39:10" updated="2013-06-24 16:33:05">
</player>
<player username="Mike" unique_id="" first_name="" last_name="" email="" bonus="0" achievements="" gold="4512" money="0" kills="0" lives="0" time_played="42" unlocked_levels="" unlocked_items="" inventory="" last_level="" current_level="" current_time="0" current_bonus="0" current_kills="0" current_achievements="" current_gold="0" current_unlocked_levels="0" current_unlocked_items="" current_lives="0" xp="" energy="" boost="" latitude="" longtitude="" game_state="" platform="" rank="0" best_score="0" created="2013-06-21 16:27:33" updated="2013-06-25 15:56:07">
</player>
------------------------------ RESULTS SNIPPED ------------------------------------
</players>

 

As you can see, there is a great deal more information available than what we are choosing to display.  Switching between JSON and XML results is a trivial task.

 

The final part of the Player page illustrates how to retrieve and update a single field of an individual player.  The code is pretty much identical to what we have seen thus far.  See the getPlayerField() and setPlayerField() functions for more details of how this code works.  The fields in the um… Field drop down is set in the players.Template code.  It is simply a combo box filled with the available values.  Once again, valid values can be obtained  from the documentation. 

 

One of the most common tasks that game programmers require a server for is online leader boards.  Making an online scoreboard with Scoreoid is a pretty simple affair, as we will see looking at the Scores tab:

 

Score

 

The code controlling this form is available at score.View.js.  Using this form you can get high scores, get scores sorted high to low and low to high, get the average over all score and create a new high score.  At this point in time, the code should be intimately familiar, as the logic is identical, just the URLs and parameters change.  The first three buttons call the method /getScores.  The only difference is for the sorted results we pass the parameter order_by with the value either "asc" for ascending or "desc" for descending results.  Get Average Score calls the method /getAverageScore ( predictably enough… ).  The /getScores method has a number of parameters we didn't use here for fine tuning your results.  You can fine tune the results by specifying difficulty level, date range, platform as well as limit your results to N number of values returned.  In the end, implementing a complete scoreboard is pretty simple.

 

Finally, let's take a look at the Cloud tab.

 

Cloud

 

Sometimes you just need to store "stuff" ( technical term there! ) in the cloud.  This is a key/value database stored at the game level ( more globally ) instead of the player or score level.  Most of the code here makes use of the /getGameData() and /setGameData() methods.  You can access the code behind this form in cloud.View.js.

 

If for example you click the Set Cloud Data, the value you pass is going to be written to the hard coded key "myData".  Perhaps the most interesting aspect of Scoreoid cloud storage is it supports JavaScript "dot" notation.  So for example, you can set data for monster, such as monster:orc, but also like monster.hp = 42.  Here for example are the current results in JSON format if you hit Get Monster from the cloud:

 

{"monster":{"alignment":"Lawful Evil","maxHitPoints":"42"}}

 

This makes storing structured hierarchical data easy.

 

So, that's the basics of using Scoreoid.  The API is remarkably consistent, so once you've made a single call you pretty much know how to use the entire API.  The rest is a matter of looking at the documentation for what parameters and results you can expect.  The rest is just a matter of parsing the results that are returned.

 

As I mentioned earlier, a REST based API is usable by any programming language capable of making programmatic web requests.  So far, I've entirely focused on JavaScript, but now I will give an example using a different programming language.  In this case, C# for the PlayStation Mobile platform.  The following example shows how you would access a scoreboard in a PS Vita application.  Since the PSM SDK doesn't included a JSON parser, but does include an XML one, I've gone with XML for the return value.

 

 

 

using System;
using System.Collections.Generic;
using Sce.PlayStation.Core;
using Sce.PlayStation.Core.Graphics;
using Sce.PlayStation.Core.Input;
using Sce.PlayStation.HighLevel.UI;
using System.Net;
using System.Linq;
using System.Xml.Linq;
using System.IO;

namespace Scoreoid
{
	public class AppMain
	{
		private static GraphicsContext graphics;
		private const string SCOREOID_KEY = "bde61648959d0c364b04b93e257035abd5ee3b26";
		private const string GAME_ID = "3e99cf43ab";
		
		public class ScoreEntry
		{
			public string Name;
			public string Score;
		}
		public static void Main (string[] args)
		{
			graphics = new GraphicsContext();
			UISystem.Initialize(graphics);
			
			var scene = new Sce.PlayStation.HighLevel.UI.Scene();
			var panel = new Panel() {
				Width = graphics.Screen.Width,
				Height = graphics.Screen.Height };
			
			
			var textOut = new Label();
			textOut.Width = panel.Width;
			textOut.Height = panel.Height;
			textOut.HorizontalAlignment = HorizontalAlignment.Center;
			textOut.Text = "High Scores Go Here";
			panel.AddChildFirst(textOut);
			scene.RootWidget.AddChildFirst(panel);
			
			var request = HttpWebRequest.Create (@"http://api.scoreoid.com/v1/getScores?response=XML&api_key=" +
			                                     SCOREOID_KEY +
			                                     "&game_id=" +
			                                     GAME_ID);
			request.ContentType = "application/xml";
			request.Method = "GET";
			
			using (HttpWebResponse response = request.GetResponse() as HttpWebResponse) {
				if (response.StatusCode != HttpStatusCode.OK)
					Console.Out.WriteLine("Error fetching data. Server returned status code: {0}", response.StatusCode);
				else {
					using (StreamReader reader = new StreamReader(response.GetResponseStream())) {
						var content = reader.ReadToEnd();
						if (string.IsNullOrWhiteSpace(content)) {
						System.Diagnostics.Debug.WriteLine("Scoreoid returned no results");
						}
						else {
							XDocument doc = XDocument.Parse(content);
							var scores = (from e in doc.Root.Elements("player").Elements("score")
							select new ScoreEntry {
								Name= (string)e.Parent.Attribute("username"),
								Score= (string)e.Attribute("score")
							}).ToList();
							
							string text = "";
							foreach (var score in scores) {
								text += score.Name + " " + score.Score + "\n";
							}
							
							textOut.Text = text;
						}
					}
				}
			}
			
			UISystem.SetScene(scene);
			
			while(true){
				graphics.Clear ();
				UISystem.Update(Touch.GetData(0));
				UISystem.Render ();
				graphics.SwapBuffers();
			}
		}
	}
}

 

If you run this code, you will see:

ScreenShot

 

If you look closely at the code, the actual Scoreoid calls take only a few lines of code.  You create a HttpWebRequest passing the URL as well as the parameters in the URL ( this time we are doing a GET request instead of a POST request ).  Then its simply a matter of getting the results using GetResponse().  The remaining code is about parsing the XML results and PSM specific display code.

 

 Other Info

 

There are a few downsides to outsourcing your game server that you need to be aware of up front.  What happens if something happens to your provider or you want to switch providers?  Here is Scoreoid's policy on data from their website:

With Scoreoid your data belongs to you, we respect that you retain ownership of your data. Scoreoid isn’t another game network taking away your most valuable asset your players and game data. Our bulk storage engine is MongoDB, our preferred method of returning your data to you is via zipped ‘mongodump’.

MongoDB is a freely available open source NoSQL database with binaries available on Windows, Mac, Linux and Solaris. 

The next most obvious question… what does it cost?

This one is a bit trickier to answer, as as of right now, the pricing hasn't been set.  The good new is, right now it's completely free!  There are a number of live games currently using Scoreoid.  That then leaves the question, what will it cost?  For that, we go to the FAQ:

How much will the pro accounts cost?

We're currently focusing on adding new features and we're still working on the pro account options of course there will always be a freemium option.

Scoreoid's mission statement is to provide a free version to every developer which includes updates and new features. We believe every game developer should have an option to use Scoreoid helping them save time and cost while allowing them to improve there game titles and brand.

As for pricing we will offer a number of plans that will fit every developers budget. Currently looking at having 3 main plans between $15 to $50 per month and then a 4th enterprise plan for big publishers and big game studios.

We're also looking at the possibility of have a $5/$8 month option for developers who use very little data or bandwidth.

Our goal is be very cost effective and help game developers, most users will be fine with the free or lower end plan. Transparence is very important to us especially with our pricing model we will notify all users in advanced before we active the pro options.

We also plan on doing active surveys before we activate the pro plans as we would like to get as much feedback as possible from our users giving you a chance for direct input and influence.

 

I have been using Scoreoid since it launched what will happen when pro accounts are activated?

All current users who are using Scoreoid for active games (who have at least one live game that is using Scoreoid) and decide to upgrade to a pro account will receive major discounts and additional benefits. We will also include you in our "loyalty program".

We will keep your current plan and limits for set time limit grandfathering you into a new plan sets. Don't forgot Scoreoid will always offer a freemium option.

 

So basically, the prices aren't set yet, they will be tiered from free on up and if you publish now you will get a discount once pricing goes live. Perhaps most important of all for smaller publishers, there will always be a free tier.

 

So if you are looking at adding server side functionality to your game and don't want to roll your own solution, consider checking out Scoreoid.

Programming , , ,

19. February 2013

I have been working on a long running, but slow in development, series of posts on authoring a level creation tool using HTML5.  It covers how to actually create an application using the popular MVC design pattern, implemented using the YUI3 libraries, as well as the EaselJS graphic library.

 

If you are interested in HTML5 application development, or in a ( simple for now ) level editor, hopefully this series is of interest to you.  

 

 

Table of contents link.

 

 

Current Contents:

 

Expect this to be updated over time as I continue.

Programming , ,

26. October 2012

 

Due to a bunch of great feedback I received from the YUI community and learning a bit more about how YUI works, I’ve made some minor, but extensive ( yes, that actually makes sense ) changes to the guts of my upcoming HTML based level editor.

 

As a bit of a recap, so far we have covered:

Creating the basic MVC framework

Integrating the EaselJS canvas library

Adding an application menu (that does nothing)

Adding a file upload dialog

 

In this section, we are going to simply clean things up a bit.  Add a layer of polish, remove some of the hackish behaviour and simply make it a better foundation.  Instead of simply editing the previous posts, I figured there was some value in seeing the evolution of an application. In some ways, nothing illustrates a concept better than a before and after.

 

this = that = gross;

 

A quirk of JavaScript is that it absolutely clobbers the this pointer in callbacks.  Of course, it’s all a matter of perspective if this is a feature or not, but from someone who is from a C++/Java/C# background it certainly seems alien, you certainly wouldn’t expect the value of this to change within the same code file, but of course it does.  A very common work around is to copy this into another variable, often that or self at a higher scope, but there are certainly limitations ( plus it feels like a hack ).  Consider this common simplified example:

var that=this;
buttonDone.on("click", function(){
    that.doSomething();
})

In most (all?) YUI handlers you are actually able to solve this with incredible ease. You can pass the context in as a parameter:

buttonDone.on("click", function(){
    this.doSomething();
},this)

This is a change I made through-out the project.  However, what happens when you are dealing with a non-YUI method?  A very good example is in map.View.js, we provide a callback function that the EaselJS library calls each frame.  How exactly do we deal with that?  Consider:

createjs.Ticker.addListener(this.gameloop);

How do you handle the this value getting clobbered in this situation?  I used a global variable named Instance, which obviously was a gross hack.  I sadly couldn’t extended the callback to accept a context without making massive changes to the easelJS library, which obviously I don’t want to do.  So, how then do you cleanly solve this issue?  With incredible ease apparently:

createjs.Ticker.addListener(Y.bind(this.gameloop,this));

That’s it…  just wrap your function parameter in a Y.bind() call, and pass in the context you wish to be bound and VOILA, this is preserved.  How does it work?  ….  Black magic probably, with a great deal of chickens being sacrificed.

 

These two changes, passing the context when possible or using Y.bind() when not, reduced a great many horrible hacks from the code and made me feel a great deal better about life, the universe, everything…

 

If you support templates to make life easier for designers, why the hell aren’t you using style sheets?

 

That’s a very good question to which I simply do not have a good answer.  When I did most of my development work in HTML, it was a world without CSS and it is a technology I never really took to.  In a world where CSS selectors are increasingly important, and in an application I am making designer friendly, that is not a valid excuse. 

 

Therefore, I pulled most of the styling out to a style sheet.  This also means I removed various JavaScript based styling calls.  I also added the YUI style skin yui-skin-sam the to app <BODY> tag in index.html.  This was missed mostly out of … well, I kinda forgot I had a body tag.  Part of my brain thought that editor.View.js was the root level HTML construct, I completely forgot about the contents of index.html.

 

In order to add stylesheet support, I added a root level directory called stylesheets and created the file style.css within.  It also required adding an additional route for express in server.js, in case you are hosting from node.

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

This line basically just adds another directory to serve static files from.  If you didn’t add this, you will get 404 errors when you request a stylesheet.

 

Speaking of templates…

 

Copy and paste coding rather bit me in the butt here.  You see, I started from the person.View.js and person.js as a starting point, code that was never intended to be in the final product and code that contained a great deal more problems then I realized.  Code however, that also demonstrated the complete lifecycle of populating a view with a model, and compiling and displaying a template.

Problem is, thus far in this application, we have NO DATABINDING.  None.  It will of course come later, but most templates are actually just straight HTML with no need to process.  Thing is, I was compiling them anyways, like so:

var results = Y.io('/scripts/views/templates/map.Template',{"sync":true});
template = Y.Handlebars.compile(results.responseText);

Which was a waste of processing power. So instead we simply do:

var results = Y.io('/scripts/views/templates/map.Template',{"sync":true});
template = results.responseText;

There is the possibility that templates are overkill and handlebars is too heavy weight, and this is quite likely true.  At the end of the day though, this isn’t an application that needs to scale out massively, so I don’t really need to squeeze every cycle, so I will stick with handlebars templates for now.  The nice thing about templates is, they can be swapped out relatively easily later on.  Lightweight or not, handlebars is one of the most popular templating engines.

 

To async or not to async

 

One other areas of feedback I got, that I am not sure I entirely agree with, is that I should be loading the templates asynchronously. On the surface, this certainly makes sense, as JavaScript is a highly asynchronous language ( taken to laughable extremes at times… you will know what I mean if you’ve worked in Node.js and found yourself nested 5 or 6 callbacks deep ) and the DOM certainly encourages an async model.  Your UI will “hang” while waiting on code to complete unless it is handled asynchronously.

My catch is, this is exactly what *should happen*.  Loading a template is a synchronous task, period.  All of the rest of your code is going to be spent first checking to see if the template has loaded before proceeding.  Nothing can happen until the template has loaded, period.  Therefore it makes little sense to perform a serial action in parallel.

That said, this is just *my* opinion on the matter.  I was however offered an elegant solution to the complexity of dealing with async callbacks, and I figured I would share it here.  So here is the person.View.js rewritten to work async:

YUI.add('personView',function(Y){
        Y.PersonView = Y.Base.create('personView', Y.View, [], {
        initializer:function(){
            this.pending = new Y.Parallel();
            Y.io('/scripts/views/templates/person.Template',{
                on:{
                    complete:this.pending.add(function(id,response){
                        template = Y.Handlebars.compile(response.responseText);
                    })
                }
            },this);
        },
        render:function(){
            this.pending.done(Y.bind(function(){
                this.get('container').setHTML(template(this.get('model').getAttrs()));
            },this));

            return this;
        }
    });
}, '0.0.1', { requires: ['view','io-base','person','handlebars','parallel']});

 

The secret sauce here is the Y.Parallel module.  It allows you to batch up a number of parallel functions, which provides a callback for when they are all complete.  If you are following along and prefer to go pure async, use the above code as a template, or better yet, refactor to a common base class shared between your views.

 

A little longer, a lot less ugly

 

One other thing I hated about the previous code was the <SCRIPT> mess of includes that was developing at the top of index.html.  As of the last update, it looked like:

<script src="http://yui.yahooapis.com/3.5.1/build/yui/yui-min.js"></script>
<script src="http://code.createjs.com/easeljs-0.5.0.min.js"></script>
<script src="/scripts/models/person.js"></script>
<script src="/scripts/models/spriteSheet.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/classes/AddSpriteSheetDialog.js"></script>
<script src="/scripts/views/editor.View.js"></script>

 

This is ugly and only going to get uglier and I knew there had to be a better way, I just didn’t know what it was.  I thought the Y.Loader was a likely candidate, but I was wrong ( but very close ).  Instead there is a global variable called YUI_config you can use to declare all of your custom modules and their dependencies.  Therefore I created a new file named /scripts/config.js with the following contents:

YUI_config = {
    groups: {
        classes: {
            base: 'scripts/classes',
            modules:{
                addSpriteSheetDialog: {
                    path:'/addSpriteSheetDialog.js',
                    requires: ['node','spriteSheet','panel']
                }
            }
        },
        models: {
            base: 'scripts/models',
            modules: {
                person: {
                    path: '/person.js',
                    requires: ['model']
                },
                spriteSheet: {
                    path: '/spriteSheet.js',
                    requires: ['model']
                },
                tile: {
                    path: '/tile.js',
                    requires: ['model']
                }
            }
        },
        views: {
            base: 'scripts/views',
            modules: {
                editorView: {
                    path: '/editor.View.js',
                    requires: ['view','io-base','addSpriteSheetDialog','personView',
                        'mainMenuView','mapView','event-custom','handlebars']
                },
                mainMenuView: {
                    path: '/mainMenu.View.js',
                    requires: ['view','io-base','node-menunav','event','handlebars']
                },
                mapView: {
                    path: '/map.View.js',
                    requires: ['view','event','io-base','handlebars']
                },
                personView: {
                    path: '/person.View.js',
                    requires: ['view','io-base','person','handlebars']
                }
            }
        }
    }
}

 

This allows the YUI loader to load your scripts and their dependencies.  Ideally too, this allows the loader to load them asynchronously, which in this case is a very good thing.  Ideally then, this will cause your app to load quicker.

 

Y.App, I hardly knew you!

 

On other thing that has been mentioned ( a couple times from a couple sources ) is I am not really making use of Y.app routing, and this is 100% true, I am not.  As you can see in index.html:

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

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

        app.route('*', function () {
            this.showView('editorView');
        });

        app.render().dispatch();
    });

So, yeah, a router with exactly one route is rather pointless.  So, why do I have it at all?

Well, that’s mostly a matter of reality not matching expectations and is a bi-product of “winging it”.  As things developed, once I chose to go with a composite view, the parent view editor.View.js essentially usurped the roll of controller from Y.app, which is perfectly OK.

So, why keep Y.App?  Well it’s perfectly possible that I will have tasks outside of the single composite view, in which case the app will be used.  If not, it is easily used later.  If you were looking at the code and thinking “hmmmm… that code seems superfluous”, you were exactly right.

 

Summary

 

Almost every “code smell” I had is now gone, which always makes me feel better about things. The experience also enlightened me to some of the nuances of YUI.  A great deal of thanks to Satyam on the YUI forums for taking the time to educate me.  My thanks again to all others who have commented or messaged me.  Now back to adding new features!

 

The Code

 

You can download the new sources right here.

 

As pretty much every single file changed, I am just going to dump full sources below.

 

At this point in time, our project looks like:

image

 

index.html

<!DOCTYPE html>

<html>
<head>
    <title>GameFromScratch example YUI Framework/NodeJS application</title>
</head>
<body class="yui3-skin-sam">


<script src="http://yui.yahooapis.com/3.5.1/build/yui/yui-min.js"></script>
<script src="http://code.createjs.com/easeljs-0.5.0.min.js"></script>
<script src="scripts/config.js"></script>
<link rel="Stylesheet" href="/stylesheets/style.css" />

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

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

        app.route('*', function () {
            this.showView('editorView');
        });

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


</body>
</html>

server.js

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

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

server.get('/', function (req, res) {
    res.set('Access-Control-Allow-Origin','*').sendfile('index.html');
});

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

 

config.js

YUI_config = {
    groups: {
        classes: {
            base: 'scripts/classes',
            modules:{
                addSpriteSheetDialog: {
                    path:'/addSpriteSheetDialog.js',
                    requires: ['node','spriteSheet','panel']
                }
            }
        },
        models: {
            base: 'scripts/models',
            modules: {
                person: {
                    path: '/person.js',
                    requires: ['model']
                },
                spriteSheet: {
                    path: '/spriteSheet.js',
                    requires: ['model']
                },
                tile: {
                    path: '/tile.js',
                    requires: ['model']
                }
            }
        },
        views: {
            base: 'scripts/views',
            modules: {
                editorView: {
                    path: '/editor.View.js',
                    requires: ['view','io-base','addSpriteSheetDialog','personView',
                        'mainMenuView','mapView','event-custom','handlebars']
                },
                mainMenuView: {
                    path: '/mainMenu.View.js',
                    requires: ['view','io-base','node-menunav','event','handlebars']
                },
                mapView: {
                    path: '/map.View.js',
                    requires: ['view','event','io-base','handlebars']
                },
                personView: {
                    path: '/person.View.js',
                    requires: ['view','io-base','person','handlebars']
                }
            }
        }
    }
}

 

style.css

body { margin:0px;overflow:hidden; }

#mapPanel { margin:0px;float:left;display:block; }

#mapPanel #mainCanvas { background-color:black; }

.spritesheetDialog { spadding-top:25px;padding-bottom:25px; }

person.js

YUI.add('person',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']});

 

spriteSheet.js

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

 

tile.js (ok, this one is new… )

YUI.add('tileModel',function(Y){
    Y.Person = Y.Base.create('tile', Y.Model, [],{
            getName:function(){
                return this.get('name');
            }
        },{
            ATTRS:{
                src: {
                    value: ''
                },
                offsetX: {
                    value: 0
                },
                offsetY: {
                    value:0
                },
                width: {
                    value:0
                },
                height:{
                    value:0
                }

            }
        }
    );
}, '0.0.1', { requires: ['model']});

 

editor.View.js

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

            var person = new Y.Person();
            this.pv = new Y.PersonView({model:person});
            this.menu = new Y.MainMenuView();
            this.map = new Y.MapView();

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

            Y.Global.on('menu:fileAddSpriteSheet',function(e){
                var dialog = Y.AddSpriteSheetDialog.show(this.spriteSheets, Y.bind(function(){
                    var sheet = this.spriteSheets.get("spritesheets")[0];
                    console.log(sheet);
                },this));
            },this);
        },
        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%;margin:0px;padding:0px'/>");
            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','addSpriteSheetDialog','personView',
    'mainMenuView','mapView','event-custom','handlebars']});

mainMenu.View.js

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

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

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

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

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

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

map.View.js

YUI.add('mapView',function(Y){
    Y.MapView = Y.Base.create(