Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

27. December 2012

 

Over the holidays a new game development book Game Tool Gems was released.  It is somewhat unique being focused on creating game tools, a subject I actually really enjoy that isn’t often covered.  The publishers description:

 

Game developers love what they do because of whizzy graphics and clever technical ideas, but creating games boils down to one hard reality we all know and hate to admit: the entire process depends on the robustness of the game tools pipeline. This book is all about that pipeline and the tools that support it. And that simply means one thing: if you’re a game developer, you must have it. Written by developers and researchers, the book offers tips and tricks relating to asset and data management, geometry and models, Web tools, and programming.

 

The table of contents:

Asset and Data Management

  • Plug-in based Asset Compiler Architecture
  • Where is it? GFX Asset Data Management

 

Geometry and Models

  • 3D Format Conversion (FBX, COLLADA)
  • Building Procedural Geometry using MAXSCRIPT
  • A Uniform Geometry Workflow for Animated Feature Films
  • Rock Solid Content Pipeline with the COLLADA Conformance Test Suite
  • Rendering COLLADA Assets on Mac OS X with Scene Kit
  • COLLADA Exporter for Unity Developers in the Unity Asset Store

 

Web Tools

  • Creating Web-based Tools with Django
  • Introduction to Utilizing HTML, CSS and JavaScript to Create Rich Debugging Information
  • Moving Tools to the Cloud: Control, Configure, Monitor and View Your Game with WebSocket

 

Programming

  • Decoupling Game Tool GUIs from Core Editing Operations
  • Do-it-yourself Game Prototyping Tool for Mobile Devices
  • Engineering Domain-Specific Languages for Games

 

A wide variety of subjects covered.  Over-all the book is fairly short at approximately 250 pages.  There is a fairly large amount of preview available on Amazon if the above topics sound interesting to you.  No reviews are available as of yet.  If you pick up this book, let us know what you think.

News

21. December 2012

I am not sure if a recent release has made it worse or it's always been this annoying, but Chrome does a lousy job of refreshing changes to JavaScript files.  It used to be you simply hit CTRL+F5, or CMD+F5 on MacOS to for the browser to reload with changes, but this simply doesn't work with Chrome, which is really really irritating when developing in HTML.

 

One option is to use a cache breaker, but this is an irritating process.  Basically you just add a random number to the end of your script inclusion script.

 

So instead of:

<script src="MyScript.js">

You do

<script src="MyScript.js?Num=21421423asdfsdf2">

 

Then you basically change the magic number every time you update the script, which WILL result in the new script being reloaded regardless to the browser settings.  In development, this is still a gigantic pain in the arse.

 

Fortunately there is a solution.  On a PC hit CTRL+SHIFT+i or on a Mac hit CMD+Option+i, to bring up the developer tools.  This will bring up the developer tools, now locate the gear icon in the bottom right corner:

Google Chrome Developer Settings Icon

 

Then under the General Tab, select Disable cache:

DisableCacheChrome

 

 

Voila, much more sane JavaScript development under Chrome.

 

 

General

21. December 2012

So my HTML5 game tool creation tutorial series took a bit of a hiatus, but now it's back.  As a quick recap, if you are interested in creating a) game tools using HTML5/JavaScript and/or b) are looking for examples of non-trivial web applications, this is the series for you!  Keep in mind though, I am not spending a lot of forethought on the whole process, so the design may not be the most sound!

 

 

In this section, we are doing very little new, it's mostly all stuff we covered in the past, just with an eye towards adding more features to our application.

 

First we make a small convenience change to our code.  Previously the workflow in our add sprite sheet dialog addSpriteSheetDialog.js was a bit… ugly.  The order you filled in the dialog were important and arbitrary.  It made no sense to have to upload the file before you could fill in the fields, so we sorted that with a fairly minor series of changes.

 

addSpriteSheetDialog.js

YUI.add('addSpriteSheetDialog', function(Y){

 

    Y.AddSpriteSheetDialog = new Y.Base();

    var spriteSheets = null;

    var name,width,height,img = null;

    Y.AddSpriteSheetDialog.show = function(ss,onComplete){

        spriteSheets = ss;

        var panel = new Y.Panel({

            width:500,

            height:300,

            centered:true,

            visible:true,

            modal:true,

            headerContent:'Select the image file containing your sprite sheet',

            bodyContent:Y.Node.create(

                "<DIV>\

                <input type=file id=spritesheet /> \

                <br /> <div id=imgName class='spritesheetDialog'> \

                Click above to select a file to download</div>\

                <br />Sheet name:<input type=Text id=name size=30 value=''> \

                <br />Sprite Width:<input type=Text id=width size=4 value=32> \

                Sprite Height:<input type=Text id=height size=4 value=32> \

                <br /><input type=button id=done value=done />\

                </DIV>\

                "

            ),

            render:true

        });

 

        var fileUpload = Y.one("#spritesheet");

        fileUpload.on("change", Y.AddSpriteSheetDialog._fileUploaded);

 

        var buttonDone = Y.one("#done");

        buttonDone.on("click", function(){

            name = Y.one('#name').get('value');

            width = Y.one('#width').get('value');

            height = Y.one('#height').get('value');

            spriteSheets.add(name,width,height,img);

            panel.hide();

            onComplete();

        })

        panel.show();

 

    };

 

    Y.AddSpriteSheetDialog._fileUploaded = function(e){

        if(!e.target._node.files[0].type.match(/image.*/)){

            alert("NOT AN IMAGE!");

            return;

        }

        var selectedFile = e.target._node.files[0];

        var fileReader = new FileReader();

 

        fileReader.onload = (function(file){

            return function(e){

                if(e.target.readyState == 2)

                {

                    var imgData = e.target.result;

                    img = new Image();

                    img.onload = function(){

                        Y.one('#imgName').set('innerHTML',selectedFile.name + " selected");

                    }

                    img.src = imgData;

                }

            };

 

        })(selectedFile);

        fileReader.readAsDataURL(selectedFile);

 

    };

},'0.0.1', {requires:['node','spriteSheet','panel']});


Changes are bolded and italicized. Basically we simply moved name, width and height to become member variables ( which they actually don't really need to be as of right now… they currently aren't used outside the scope of the Done button click handler ), and moved the logic from the img.onload handler to the much more sensible buttonDone click handler.  Not really sure why I didn't do this all along.  Not though, there should really be some error handling/field validation in the click handler as currently there is none, so you could specify a width of "Ralph" if you really wanted to! 

 

Now that the workflow of uploading a sprite sheet is much more sane, we add a few menu items to enable the user to toggle the grid between sizes of 32x32, 64x64 or off completely.  This is done in multiple locations.  First off, the handler in mainMenu.View.js receives all the incoming menu events.  For now we are simply rebroadcasting them so any other view can respond to the fact a menu was selected.

 

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 menuViewGridNone = container.one('#menuViewGridNone');

            menuViewGridNone.on("click",function(e){

                Y.Global.fire('menu:viewGridNone', {

                    msg:"Hello"

                });

            });

 

            var menuViewGrid32 = container.one('#menuViewGrid32');

            menuViewGrid32.on("click",function(e){

                Y.Global.fire('menu:viewGrid32', {

                    msg:"Hello"

                });

            });

 

            var menuViewGrid64 = container.one('#menuViewGrid64');

            menuViewGrid64.on("click",function(e){

                Y.Global.fire('menu:viewGrid64', {

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

 
Cut and paste coding 101!  This code is getting awfully 'samey', so we may want to revisit it with something more elegant in the future as we add more and more menu items.  Then again, if you have done any C++ GUI work, this kind of code is probably looking awfully familiar to you.
 
Oh yeah, we also needed to alter the HTML in or menu template MainMenu.template to add our new menu items.
 

<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>

 

                <li>

                    <a class="yui3-menu-label" href="#file">View</a>

                    <div id="file" class="yui3-menu">

                        <div class="yui3-menu-content">

                            <ul>

                                <li class="yui3-menuitem" id="menuFileAddSpriteSheet">

                                    <a class="yui3-menu-label" href="#">Grid</a>

                                    <div id="gridMenu" class="yui3-menu"><!-- Bounding box -->

                                        <div class="yui3-menu-content" ><!-- Content box -->

                                            <ul>

                                                <li>

                                                    <a class="yui3-menuitem-content" id="menuViewGridNone" href="#">None</a>

                                                </li>

                                                <li>

                                                    <a class="yui3-menuitem-content" id="menuViewGrid32" href="#">32 x 32</a>

                                                </li>

 

                                                <li>

                                                    <a class="yui3-menuitem-content" id="menuViewGrid64" href="#">64 x 64</a>

                                                </li>

                                            </ul>

                                        </div>

                                    </div>

                                </li>

 

                            </ul>

                        </div>

                    </div>

                </li>

            </ul>

        </div>

    </div>

</div>

 

The syntax can get a bit ugly, but its nothing too outrageous.  Basically we have an LI for our View menu, then within it we are basically creating another menu for the child (pop-out) menu by creating a DIV with the yui3-menu-content style.  We do the same thing again to provide a pop-out menu for the Grid menu item.  Its not as convoluted as it looks, it's mostly a matter of applying the proper CSS class to each item, YUI3 takes care of the rest.

 

OK, now our template is updated and our menu view dispatches an event when the menu items are selecting.  Now lets handle those events, which we will do in editor.View.js which you can think of as our main or parent view.

editor.View.js

YUI.add('editorView',function(Y){

    Y.EditorView = Y.Base.create('editorView', Y.View, [], {

        initializer:function(){

 

            var person = new Y.Person();

            this.pv = new Y.PersonView({model:person});

            this.menu = new Y.MainMenuView();

            this.map = new Y.MapView();

 

            this.model = this.get('model');

            this.model.on('change',function(e){

                alert("Hi");

            });

 

            Y.Global.on('menu:fileExit', function(e){

               alert(e.msg);

            });

 

            Y.Global.on('menu:viewGridNone', function(e){

                this.map.setGridStyle("None");

 

            },this);

 

            Y.Global.on('menu:viewGrid32', function(e){

                this.map.setGridStyle("32");

 

            },this);

 

            Y.Global.on('menu:viewGrid64', function(e){

                this.map.setGridStyle("64");

 

            },this);

 

            Y.Global.on('menu:fileAddSpriteSheet',function(e){

                var ss = this.model.getSpritesheets();

                var dialog = Y.AddSpriteSheetDialog.show(ss, Y.bind(function(){

                    var sheet = this.model.getSpritesheets().get('spritesheets')[0];

                    console.log(sheet);

                    console.log(this.model.get('name'));

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


We are simply wiring up a event handler that listens to each of our menu events.  In each case they all do the same thing, call the setGridStyle() method of our MapView.  So… let's take a look at map.View.js.


map.View.js


YUI.add('mapView',function(Y){

     Y.MapView = Y.Base.create('mapView', Y.View, [], {

        grid:null,

        gridSize:"32",

        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;

            this.GridSize = "None";

        },

        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();

 

        },

        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("winWidth");

            var screenHeight = body.get("winHeight");

 

            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;

 

            if(this.gridSize !== "None")

                this.createGrid();

        },

        setGridStyle: function(gridSize){

            this.gridSize = gridSize;

            this.createGrid();

        },

        createGrid:function(){

            if(this.grid !== null)

                this.stage.removeChild(this.grid);

 

            var rows,columns,gridWidth;

 

            if(this.gridSize == "None")

                return;

 

            if(this.gridSize == "32"){

                gridWidth = 32;

                columns = Math.floor(this.stage.canvas.width/gridWidth);

                rows = Math.floor(this.stage.canvas.height/gridWidth);

            }

            else if(this.gridSize == "64"){

                gridWidth = 64;

                columns = Math.floor(this.stage.canvas.width/gridWidth);

                rows = Math.floor(this.stage.canvas.height/gridWidth);

            }

 

            console.log(rows + " " + columns);

            var g = new createjs.Graphics();

            g.setStrokeStyle(1);

            g.beginStroke(createjs.Graphics.getRGB(0,0,255));

 

            //Vertical lines first

            for(var i = 0; i <= columns; i++){

                g.moveTo(i*gridWidth,0);

                g.lineTo(i*gridWidth,this.stage.canvas.height);

            }

 

            //Now the horizontals

            for(var j = 0; j <= rows; j++){

                g.moveTo(0,j * gridWidth);

                g.lineTo(this.stage.canvas.width,j * gridWidth);

            }

 

            this.grid = new createjs.Shape(g);

 

            this.stage.addChild(this.grid);

        }

    });

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



As you can see, map.View.js is where most of the changes have occurred.  Previously it just drew a green ball travelling across the screen.  Now we are getting a bit closer to becoming an application.  It will create a grid on screen that is either off, 32x32 pixels or 64x64 pixels in size.  The vast majority of logic is in the createGrid method.  It starts off by removing the existing grid if it has already been added to our Easel stage.  We then check to see what value has been set for gridSize.  It ifs none, we simply return, which will have resulted in any existing grid being removed and nothing more.  Otherwise we set the gridWidth value to either 32 or 64, and calculate the number of columns and rows that we need to draw based off the value selected ( rounded down ).  Then we simple draw a series of lines, first travelling left to right across the screen, then repeat the process going from top to bottom.  Finally we add our new created grid to the stage.

 

 

At this point, our application is starting to look a bit more like a level editor!

 

 

 

 

You can download the complete updated source code here.

Programming

20. December 2012

So I fired up Steam today and lo and behold, what's under the new release section?

 

Silo 2.

 

 

Silo 3D graphics

 

If you've ever read the GameFromScratch list of 3D Applications  you will see I am a fan of Silo.  It's mostly a 3D modeller, its very good and previously used to be 200$.  Now a gigantic word of warning, Silo has been pretty much abandoned, with no new updates for years.  This is unfortunate, because it was a great product at a great price.  Even abandoned, for 50$ it's still awfully tempting, if you want a modeller on the cheap… it's a lot like Modo with a fraction of the price tag.

 

Maybe the Steam sale will encourage them to put some more effort into developing Silo again.

 

If you are interested, there is a 30 day trial available.

Art, News

20. December 2012

GarageGames have announced the 2.0 release of the recently open-sourced Torque 3D game engine.

 

Torque3D game engine screen shot

So, what is new?

 

  • Project Manager, a Qt based project creation tool, for creating projects from Torque Templates
  • A dedicated Linux game server, guess this is easily the biggest new feature of this release
  • Various smaller bug fixes and improvements
 

 

Plus a series of small improvements and bug fixes:

 

 

  • Fix some PHP 5.3.0 errors and warnings (Issue 42)
  • Fixes the dllmain.h template file to include MIT header (Issue 16)
  • Added support for FMOD 4.42.03 Stable (Issue 3)
  • Torque 3D Toolbox fix for project launching (Issue 12)
  • Prevented #define for in VS2012 (Issue 39)
  • Made Item network members properly in the editor. (Issue 37)
  • Required changes to the Torque3D to make it Linux-compatible. (Issue 19)
  • Fix for Issue #54 for PhysX client crash on exit (Issue 54)
  • Fix for Issue #56 for ShapeEditor save crash (Issue 56)
  • AI related bug fixes and improvements. (Issue 11)
  • Fix for Issue #62 for Large decal disappears (Issue 62)
  • Fix for Issue #64 for GuiTabBookCtrl rendering (Issue 64)
  • Rename AiPlayer::produce() to AiPlayer::spawnAtLocation() (Issue 67)
  • Fix for Issue #66 for Skinned mesh crash (Issue 66)
  • SpawnSphere leaves its spawned object at the origin (Issue 44)
  • Fix for Issue #88 for River Editor Snapping (Issue 88)
  • Fix for Issue #90 for Camera orbit object bug (Issue 90)
  • Fix for Issue #94 for Cubemap render bug (Issue 94)
  • Fix for Issue #96 for DAE updating crash (Issue 96)
  • Issue #106 Convex Shapes Consume Too Much Bandwidth (Issue 106)
  • Issue #108 Turret assumes "heading" node is parented to node with zeroed rotation (z-up) (Issue 108)
  • Issue #111 Fix various bitstream issues (Issue 111)
  • Issue #110 T3D 1.2 - Particle Emitter ejectionPeriodMS has 1000 limit (Issue 110)
  • Issue #114 FootStepSounds issue has occurred again (Issue 114)
  • Issue #116 Crash with text using high-numbered unicode characters (Issue 116)
  • Issue #118 Collada Importer doesn't render meshes without materials from blender (Issue 118)
  • Issue #120 ArrayObject Sort - Desc Parameter is inverted (Issue 120)
  • Issue #122 Issue in Material Editor Setting for Sounds (Issue 122)
  • Issue #124 CustomMaterial Back Buffer Refraction (Issue 124)
  • Issue #126 TCPObject gives "Got bad connected receive event" (Issue 126)
  • Issue #128 Soldier shadow displayed in 1st-person view looks wrong with no feet (Issue 128)
  • Issue #130 Decals not obeying smoothing groups on meshes (Issue 130)
  • Issue #132 RenderOcclusionMgr uses fixed function rendering (Issue 132)
  • Issue #134 VS2010 and .rc files (Issue 132)
  • Issue #136 Zoning Bug in Underwater Cave (Issue 136)
  • Issue #138 guiArrayCtrl Clip Rectangle Fix (Issue 138)
  • Issue #69 RayInfo distance with ContainerRayCast (Issue 69)
  • Issue #71 BitVector don't have copy-constructor/operator= (Issue 71)
  • Issue #141 Remove Toolbox and all game binaries (Issue 141)
  • Issue #61 GuiHealthTextHud - a C++ replacement for the scripted numerical health hud. (Issue 61)
  • Issue #151 New gitignore for Project Manager (ssue 151)
  • Issue #155 Billboard Creation on Dedicated Server (Issue 155)
  • Issue #158 Partial revert of changes from Pull #125 (Issue 158)
  • Issue #152 Unmatched Bitstream changes in Pull Request #112 (Issue 152)
  • Issue #166 Client does not completely load with a dedicated server (Issue 166)
  • Issue #170 Some assets do not load under Linux dedicated server (Issue 170)
  • Issue #172 Dedicated server - Crash when a turret fires at an enemy (Issue 172)
  • Issue #174 Player turret weapon targets the wrong direction (Issue 174)

 

 

You can read the official announcement thread here.

You can download the Torque3D source code on Github.

You can browse the documentation here.

While the new project manager is available here.

 

Good job on the new release Torque team, keep 'em coming.

News

Month List

Popular Comments