Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

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('mapView', Y.View, [], {
        events:{
          "#mainCanvas": {
              click:function(e)
              {
                  console.log("Mouse over");
              }
          }
        },
        initializer:function(){
            var results = Y.io('/scripts/views/templates/map.Template',{"sync":true});
            template = results.responseText;
        },
        prepareCanvas:function(){
            this.resizeEvent();
            createjs.Ticker.setFPS(30);
            createjs.Ticker.addListener(Y.bind(this.gameloop,this));

            Y.on('windowresize',this.resizeEvent,this);
            this.publish('windowresize');
        },
        render:function(){
            this.get('container').setHTML(template);
            this.prepareCanvas();
            return this;
        },
        gameloop:function(){
            this.stage.update();
            this.stage.getChildAt(0).x++;
            if(this.stage.getChildAt(0).x > this.stage.canvas.width)
                this.stage.getChildAt(0).x = 0;
        },
        resizeEvent:function(){
            var container = this.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 + "px");
            canvas.setStyle("height",height + "px");

            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']});

person.View.js (async version)

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']});

mainMenu.template

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

map.Template

<div id="mapPanel">
    <canvas width=300 height=300 id="mainCanvas" >
        Your browser doesn't support the canvas tag.
    </canvas>
</div>

person.Template

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

** – person isn’t styled because this is a place holder view anyways and is going to be removed from the project once I have an actual demonstration of a data-bound template.

Again, the entire archive can be downloaded here.

Programming, Design ,

24. October 2012

 

Zipline just announced a new release of the Moai SDK, 1.3r2.  This release is mostly due to the release of iOS 6 and is a bit of a double edged sword.  What’s the catch you say?

 

Most importantly, we have updated the Moai Xcode project to support Apple's latest Xcode version 4.5 and the iOS 6 SDK. What does this mean for you? The biggest change is that Xcode 4.5 adds support for armv7s and the iPhone 5, but drops support for older armv6 iOS devices, and requires a minimum of iOS 4.3. Apple's armv6 devices include the iPhone 3G and earlier iPhones, and the first two generations of iPod touch devices. The Moai codebase on github can still be used to build to armv6 with previous Xcode versions, but your build flags will need to be reverted to support this.

 

So basically if you are supporting the most popular iPhone (3g), Apple just forked your world up.  You of course can still do it, but you will need to support two toolchains to do so, which sucks.

 

Other details of the release:

-iOS 6 and armv7s support added

-Dropped armv6 support on iOS

-Added setFailOnError method to MOAIHTTPTask to allow getting information from 400+ codes

-Modified MOAITextDesigner's layout behaviour to snap the layout cursor's Y-Coordinate to integer boundaries between lines of text

-Modifed MOAITextDesigner's layout method to snap lines of text to integer boundaries to prevent blurring

-Fixed a buffer overrun issue in Visual Studio 2010

-Fixed 'setLength' function on MOAIBOX2DDistanceJoint

-Fixed a bug with Curl HTTP task resetting on certain calls

-Fixed offscreen buffer ( MOAIFrameBuffer ) clipping bug

-Fixed issue with MOAICpBody not registering MOAITransformBase functions

-Fixed issue with MOAISerializer not handling escaped quotes

-Fixing bug causing '.fnt' file to fail to load if not in the current directory

-Added MOAIBox2DRopeJoint to global Lua class registration

-Added a fill method to MOAIGrid

-Fixed issue with MOAIParticleEmmiter so that it uses the proper location and apply transforms in the correct order

-Fixed a bug with 3d picking

-Added sort modes to propListForRay

 

As I said, mostly all about iOS6.

 

Don’t blame Moai for the lack of Arm6 support, that’s Apple’s boneheaded decision.  If you are willing to forgo iOS6 SDK goodies, there appears to be a way to support arm6 devices in XCode 4.5.  Your actual mileage may vary!

News ,

22. October 2012

This post is going to look at getting your Moai app to run under NaCL, which is Google’s mechanism for allowing you to execute C++ code within Chrome.  There are a number of restrictions, but fortunately Zipline have done most of the hard work for us.

 

Like any other platform, your code is run within a host.  If you are working from the binary ( non-GitHub ) distribution, the host is already built for you and you can skip ahead until you encounter the text “STOP SKIPPING AHEAD!”.  If you are working from Github sources, you need to build the host first.  That is what we are going to do next.

 

Building the Chrome Host

 

First is a matter of locating it.  The source for the NaCL host is located at moai-install-dir/scons/

There are a few things you are going to need to continue…

 

First off, if you haven’t already installed Cygwin, I highly recommend that you do.  The Android  build process basically requires it, so I am going to assume you already have it.  If you don’t, refer to the Android installation guide Cygwin section for details.

You also need to have Python 2.6 or 2.7 installed.  To check, fire up Cygwin terminal and type:

python –V

If you get an error that the command wasn’t found, Python isn’t installed so let’s install it.  The easiest way is to run the Cygwin setup application, then click Next next next until you get to the Select Packages screen.  In the search box enter Python, in the results expand Python and select python: Python language interpreter.

image

Make sure you don’t have any Cygwin Terminal windows open, then click Next and let Cygwin do it’s thing.

 

Now that you have Python installed, we need to download the native client SDK. (That’s the direct download link btw… )

Save it somewhere you can remember.  Open the archive and extract the folder nacl_sdk.  I went with c:\dev\nacl_sdk, but choose whatever you want, just be sure to update your paths accordingly.

Now open a Cygwin terminal window and change in to the nacl sdk directory, which in my case is:

cd /cygdrive/c/dev/nacl_sdk/

Now we want to run the installer/downloader.  In the terminal window type:

./naclsdk update pepper_17

Even though the current version is 22, you need to install 17, as it ships with developer tools Moai depends on.  For some reason, Scons has been removed from future versions.  That is what the above command does, gets and attempts to install pepper version 17.



 

DEALING WITH GOOGLE DEVELOPER TOOLS GOTCHA BELOW!

OK, here’s the thing, we are dealing with Google developer tools, and Google developer tools are always broken in some way, especially on Windows, naclsdk is of course no exception.  After running the above command you will be greeted with the following error:

-------------------------------------------------------------------------------------------------

Updating bundle pepper_17 to version 17, revision 112997
Traceback (most recent call last):
  File "/cygdrive/c/dev/nacl_sdk/sdk_tools/sdk_update_main.py", line 759, in <module>
    sys.exit(main(sys.argv[1:]))
  File "/cygdrive/c/dev/nacl_sdk/sdk_tools/sdk_update_main.py", line 752, in main
    InvokeCommand(args)
  File "/cygdrive/c/dev/nacl_sdk/sdk_tools/sdk_update_main.py", line 741, in InvokeCommand
    command(options, args[1:], config)
  File "/cygdrive/c/dev/nacl_sdk/sdk_tools/sdk_update_main.py", line 583, in Update
    UpdateBundle()
  File "/cygdrive/c/dev/nacl_sdk/sdk_tools/sdk_update_main.py", line 564, in UpdateBundle
    RenameDir(bundle_move_path, bundle_path)
  File "/cygdrive/c/dev/nacl_sdk/sdk_tools/sdk_update_common.py", line 56, in RenameDir
    shutil.move(srcdir, destdir)
  File "/usr/lib/python2.6/shutil.py", line 260, in move
    copy2(src, real_dst)
  File "/usr/lib/python2.6/shutil.py", line 95, in copy2
    copyfile(src, dst)
  File "/usr/lib/python2.6/shutil.py", line 50, in copyfile
    with open(src, 'rb') as fsrc:
IOError: [Errno 2] No such file or directory: u'/cygdrive/c/dev/nacl_sdk/pepper_17_update'

YAY!  Don’t worry, it’s pretty easy to work around.  The installer is trying to execute a program that doesn’t exist, but the installer was downloaded as part of the above process.  Go in to the folder sdk_cache and locate the file naclsdk_win.exe and run it.  When prompted for an install path, install it to your NACL_SDK folder/pepper_17.  In my case that means C:\dev\nacl_sdk\pepper_17

Now we need to set an environment variable with the path to the NACL SDK.

setx NACL_SDK_ROOT /cygdrive/c/dev/nacl_sdk

Keep in mind, the setx command requires administrator rights, so be sure to run your cygwin terminal as administrator if you aren’t already.

Now the bummer part, exit and restart Cygwin terminal, system level environment variables don’t take immediate effect.

 

Are we there yet? Nope… it’s FMOD install time

Close… one more dependency left… FMOD.  FMOD is a commercial audio system ( AKA, if you ship a product, you’ve got to pay to use it ).  With most of Moai, you can get by using the free Untz audio system, but with NaCL, FMOD is required.  So you either have to gut the FMOD library from the build dependencies or download and configure FMOD.  I’ve opted for the second ( audio is after all, kind of nice! ), but either option is open to you.

Head on over to the FMOD download page and download the archive for FMOD for Google Native Client. Unfortunately you need to download a version that supports the same Chrome version as Moai (17).  The following direct link will download the correct version. (Direct linkIT IS VERY IMPORTANT YOU DOWNLOAD THIS VERSION…. just so you know.

Save and extract that archive somewhere.  This file is a tar.gz, so if you are using a program such as 7zip, you need to extract it, then extract the file you just extracted.  I took the resulting folder, renamed it fmodchrome and copied it to c:\dev\.  The resulting directory should look like:

image

Now we need to set yet another environment variable, one named FMOD_CHROME_SDK_ROOT and pointing at this new directory.  Once again in Cygwin terminal type:

setx FMOD_CHROME_SDK_ROOT /cygdrive/c/dev/fmodchrome

Once again, you need to exit and restart Cygwin terminal for this variable to take effect.

 

It’s building time!

 

At this point in time, there seems to be a problem with the scons build script so that the paths ../3rdparty and ../src aren’t working, at least, not on Windows.  The following is a brutal hack, and I will post a better solution when I come up with it.  For now, we simply copy all the source into the scons folder.  Copy the contents of [moaifolder]/src, [moaifolder]/3rdParty and [moaifolder]/scons/src to the scons directory.

Now cd in to the maoi scons directory, on my pc /cygdrive/c/dev/moai-dev/scons and run

./build.sh

Hopefully all went well.  If you get errors… something didn’t go so well… if you want, just skip ahead and download the version I compiled.  You only really need the build process working if you intend to alter the host.

 

Now copy the following files to a new folder:

moai.nmf

moai_x86_32.nexe

maoi_x86_64.nexe

 

This is your Moai Host ready to go. 

 

If for some reason you couldn’t get your host to build, you can download mine.

 

STOP SKIPPING AHEAD!

 

Packaging your app to run in Chrome

Now you need to package your application up into Chrome friendly goodness.  The steps are fairly straight forward

In the folder you copied the .nmf and .nexe files, create a new file called manifest.json here is what I put in mine:

manifest.json

{
    "name":"moai",
    "version":"42",
    "app": {
        "launch": {
          "local_path": "moai.html"
        }
      }
}

Now you need an html file to actually host your application. As you probably guessed by the manifest file, I called mine moai.html:

moai.html

<!DOCTYPE html>
<html>

<head>
<body>
  <title>Hello Moai!</title>
  <div>
    <embed name="nacl_module"
           id="moai"
           width=480 height=320
           src="moai.nmf"
           type="application/x-nacl" />
  </div>

</body>
</html>

Finally you need your Moai application ( the lua bits ).  Just copy your project sources into the same directory, just be sure a file is called main.lua, this is your app entry point and will automatically be called the the host.  Here for example is my folder:

image

I simply grabbed the sources from this tutorial.

 

 

Configure Chrome to run your app

Now you need to let Chrome know you want to enable NaCL applications.  In Chromes location bar enter chrome://flags, the following window should appear.

image

Scroll down and enable Native Client as shown by the arrow. You need to restart Chrome for this to take effect!

So, um, restart Chrome.

 

Now you need to add your application.  To do this, in Chrome, drop down the Menu and select Tools->Extensions.

 

image

In the resulting window, enable Developer Mode, then click Load unpacked extension…

 

In the browse dialog, navigate to the folder you’ve saved everything in then click OK.

image

 

Your extension should now be installed.  Launch a new tab ( CTRL+N ) in Chrome, and at the bottom of the screen, select Apps

image

 

Your app icon should appear on the page:

image

 

Click it.

 

Voila, a Moai application running in Chrome:

image

 

Enjoy.

Programming , , , ,

20. October 2012

A month ago I started looking for a Mac based alternative to the excellent Windows Live Writer and thought I may have found one in the form of MarsEdit. It came with a 30 day trial and my final verdict was a bit mixed.  It was a very pleasant writing experience, if that makes any sense.  It was like when you write with a quality pen, the actual experience itself is just better.  On the other hand, some things were a bit unwieldy too.  The picture formatting tools are primitive to nonexistent, it's hard to format source code ( something of key importance to me ), it didn't do site preview all that well ( although frankly, neither did Live Writer, at least with my site ) and it didn't support tags.  None of those is a deal breaker, but all of them make the experience slightly worse than Live Writer, to the point when I needed to do code related blogs, I rebooted in to Windows to use Live Writer.  It's a great little product, but the limitations make me a bit hesitant to pull the trigger.  If it had good source code pasting options and better image formatting, I would have purchased already.

 

At the end of the day, I was resigning myself to having to work in Windows only, which is certainly unfortunate.  There are a couple open source cross-platform options, but they all seem pretty much terrible.  There are a couple other commercial options such as Blogo and Ecto, but let's just say their online presence just doesn't fill me with a ton of optimism. So I figured it was either MarsEdit ( which I may still buy ) or reboot to use Windows Live Writer ( not a great option ).  Then I discovered Adobe Contribute, which apparently started life as Macromedia Contribute.

First things first, Contribute is expensive compared to the other two products.  MarsEdit is 40$ ( plus they charge for updates ), Windows Live Writer is free, which is hard to compete with.  Adobe Contribute on the other hand is 100$.  That said, I have no qualms about paying for software, it's what I do for a living after all, and I spend so much time in my blog editor, that I will gladly pay that price if the experience is an improvement.  Now, I just need to decide if Contribute is an improvement!  Fortunately, like MarsEdit, it comes with a 30 day trial.

 

One immediately appealing feature of Contribute is that it is available cross-platform.  I need to confirm that if I buy a seat I can use it under either operating system ( I am not buying it twice! ), but if I can, have a familiar editing environment on either platform is a big plus to me.  The rest of this post is a bit of a playground for testing Contibute out.  First of all, Contribute certainly has more imaging options than MarsEdit.  Actually it may have more options than Windows Live Writer!  At the same time though, I've run in to a couple of limitations.  For some reason, and this is a shame as it is a feature I use all the time, but I can't copy and paste images for some reason.  I pulled the above image in to Preview and resized it, did a select all and... nothing.  The entire Edit->Paste menu in Contribute just stayed disabled.  Need to see if this is a bug or not. 

That said, I didn't actually have to pull the image in to Preview in the first place, you can actually in place resize and crop images in Contribute.  You can also rotate, sharpen, adjust contrast and brightness, or send it to an external program for editing.  Unlike MarsEdit, you can also apply image padding, borders and edge colours, although unlike Live Writer, you cannot set padding on a side by side basis.  In the end, I would say the image handling is better than MarsEdit, and on par with Live Writer, if I can get paste working that is.

I am running in to some other problems in addition to the inability to paste images, I can't choose categories.  According to my blog settings it is allowed, but the Categories menu is disabled, and refreshing categories results in the Window:

Which by the way, not being able to copy paste the above was really disruptive, requiring me to capture it, convert it to png, import it.  Blah. On an unrelated note, why the heck does grab save things to tiff format?  Tiff? Is this 1992?

One other thing I have noticed is, I cannot drop directly to the HTML level.  I may be missing how to do it, but I don't see any menu option for editing as HTML or sending to an external editor.  This feature is handy when you run in to limitations of the blogging software and is something that MarsEdit does quite well, while Live Writer does it, but can be a bit destructive at times.  Contribute doesn't seem to do it at all.  There is however the ability to enter an HTML snippet, which should accomplish the same thing, at least most of the time.  As mentioned above, Categories are supported ( as the are in MarsEdit and Live Writer ), but do not work with my blog.  Tags are also supported, unlike MarsEdit, but it doesn't have the ability to download a list of tags to choose from.  Considering my blog treats HTML and Html as different tags, this missing feature is well, missed.

Contribute does seem to support pretty good table formatting...

  A B
1 DataData
2 DataData
 
Merged cells work!
 
So does per cell formatting

I have to say, table support is exceptional.  I never could get merged cells to work in Live Writer.  Tables are still a damned handy feature, so it's nice to see how well they work.  I never really tested table functionality in MarsEdit... oops.

 

Now what about code... does it preserve formating... a straight paste from XCode:

bool demo::drawScene(Node* node)

{

// If the node visited contains a model, draw it

Model* model = node->getModel();

if (model)

{

model->draw();

}

return true;

}

 

UGH, not be default anyways. There certainly doesn't appear to be an option for pasting as RTF either. Let's try pasting from Sublime Text Exported HTML:

-- create text to display on screen in 72point font
local helloText = display.newText("Hello World!",0,0,native.systemFont,72)
-- center to screen
helloText.x = display.contentWidth/2
helloText.y = display.contentHeight/2

Ick...

 

Now the same HTML as a snippet:

Strike that... didn't work.

 

 

Ok, that's enough for me.  For 100$ price tag, Contribute doesn't work for me.  Hell, for a 40$ price tag, it doesn't work for me.  One last test to see if this publishing even works...

 

Anyone know any other options I should look in to for Mac based blogging?  As of right now, it's seeming like MarsEdit or bust.

19. October 2012

 

Sony, actually… and file this under the category of longest most unwieldy names ever, Sony Computer Entertainment Worldwide Studios Europe External Development Studio (yeah… really) in collaboration with Creative England and Birmingham Science Park have put together a contestimage called PlayStation Pioneers, with a grand prize of £25,000.  That’s about 40K in real money! Smile

 

So, how then do you go about winning this money?  Well, here are the details:

  • The opportunity is open to UK-based developers only
  • The goal is to take a concept/prototype and develop a playable ‘vertical slice’ suitable for consumer trial/user testing in summer 2013.
  • The deadline for submissions is 31st October 2012
  • Five finalists will then be selected to exhibit at this year’s LAUNCH conference taking place at Birmingham Science Park on 13th and 14th November 2012 and present their concepts to a panel.
  • £25,000 will be awarded to the winning entry
  • XDev will have the first right of refusal in regard to publishing the final game.
  • The winner will be announced by the panel at LAUNCH on 14th November 2012.

 

What isn’t explicitly listed in those requirements, your project will be for PlayStation Mobile.

 

That deadline is fast approaching (less than 2 weeks!).  Keep in mind though, you are submitting a *concept* by October 31st, not a complete game. 

 

The other line of note you should be aware of was:

XDev will have the first right of refusal in regard to publishing the final game

In other words, if they like you game, they get first crack at being the publisher.  Given that most indie developers would love to have a publisher, this shouldn’t be a huge deal, but is certainly something you should be aware of.

 

 

If you have never heard of it before, PlayStation Mobile is cross platform game development system based around Mono.  It is C# based, built over OpenGL ES and able to target the PlayStation Vita, as well as select Android mobile devices ( most Sony Android devices, plus select devices from HTC, Asus and a few other manufacturers ).  This site has a series of tutorials you can used to get started.

 

You can read the original announcement thread right here.

 

If you are interested in proceeding, be sure to read this guide (PDF link).  It gives more details of what is involved.  From that document, here is what is recommended in your proposal:

1. Concept summary (ideally one page).
2. A walkthrough/storyboard detailing the proposed ‘vertical slice’ playable.
3. Artwork/Visualisation that is representative of proposed final quality.
4. Design briefs (prototypes where possible) explaining key features.
5. A summary schedule and risk assessment re delivery of the ‘vertical slice’ playable to be delivered for full
consumer trial/user testing in summer 2013.
6. A commercial/financial business model, illustrating market potential for the concept on PS Mobile certified
platforms; (PS Vita, Xperia, Sony Tablet S etc.)
7. Details of relevant prior experience.

 

The PDF however has no more legal issues regarding publishing or IP ownership.

News ,

Month List

Popular Comments

Unreal Engine #ue4jam Begins Today
Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


10. November 2016

 

In just a few hours the theme for the Unreal sponsored November #ue4jam is going to be announced.  The game jam starts today and runs until November the 13th.

 

Details from the announcement thread:

THE RULES

  • We will announce the theme on the November 10th stream, when the jam officially kicks-off.
  • Make an amazing game by yourself or with a team (up to 5) around the given theme in Unreal Engine 4.
  • Submit a download link to the Submission Thread (link incoming) before midnight on the night of November 13th/ morning of November 14th including the following:
    • Team Name
    • List of Team Members
    • Name of submission (Please format it with your team's name attached ex. TeamName_GameName)
      After you post your submission, PM me with the email addresses of your team members (up to 5), the name of your team and the name of your project. That makes it easier to get in contact with everyone, so make sure to send that info over!
  • A small panel at Epic will judge entries on the following criteria:
    • Unique use of theme
    • Fun factor
    • Overall Visuals
  • Detailed rules to come!
  • All submissions will be featured in a highlight reel posted to our YouTube and shown on Twitch.


THE PRIZES
The top 3 submissions will receive an Epic Swag Box with all sorts of goodies, a "Game Jam Finalist" forum badge, a featured playthrough on the December 1st Twitch stream (Sorry for the wait, American holiday the week before!), as well as a highlight on Unreal Engine launcher!


HOUDINI LICENSES
In addition to the Epic Swag Box, each member of the winning teams will be receiving 1-year of Houdini Indie (including the Houdini Engine) provided by SideFX. This amazing tool can be used with UE4 through the official Houdini plugin.


AIPD KEYS
Also in addition to the Epic Swag Box, each member of the winning teams will be receiving a free copy of AIPD. You will have a choice of Xbox One, PS4 or Steam keys.
Happy developing, and good luck!
Theme Announce Countdown
Submission Deadline Countdown

GameDev News

blog comments powered by Disqus

Month List

Popular Comments