Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

10. April 2012



Got an Android device and dreamed about coding while out and about?  Until now your choices have been slim to none, but that’s changed with the release of AIDE.aide  What is AIDE?  It’s a complete Java toolchain, including IDE that runs on your Android device.  Frankly, it’s really really really cool.  Did I mention yet, it’s very cool?  Cause it is!



Everything you need to create an Android app is included, from a text editor with full auto-completion, full compiler, app signing.  Perhaps more impressively, it also includes a full Git implementation and the ability to load Eclipse projects from your DropBox account.



Here is AIDE in action on my Samsung Galaxy Note:




On my Transformer, with full keyboard, it is actually a remarkably desktop-like development experience, but even more shocking, developing on my phone was actually do-able.  Not something I would want to do every day, but editing code was very natural.  The zooming feature is extremely smooth.




Now ready to be really impressed?



First off… it’s free!



Second, it’s just over 6MB in size!  An entire Java toolchain, including IDE in under 6 MB?  Extremely impressive.



AIDE is still currently in beta, but well worth checking out!  You do require Android 2.2 or higher.

Cool Thing of the Week

5. April 2012





Uggh.  Create a new Ice Cream Sandwich ( Android 4 ) emulator in AVD Manager, hit run and KABOOM! “emulator-arm.exe has stopped working”.  Lovely.


Still haven’t figured out exactly what the problem is, but I do know disabling the camera “fixes” the problem.  The most confusing part, creating an emulator that runs ICS, but uses a different screen resolution than WXGA and everything works just fine.


So, if you are trying to run the Android emulator using WXGA resolution and it keeps crashing, try disabling the camera.  I’ll update if I ever figure out exactly what is causing the problem.  I found this thread, which suggested disabling ATI Tray tools, however, my machine is nVidia based so that isn’t going to do it.

General ,

3. April 2012






Well, sorta, just not for free.  Since my earlier post about trying to get Blackberry working with Appcelerator Titanium, I’ve gotten a number of messages, emails and search requests asking exactly this question. “Does Titanium Studio support Blackberry?”.  It is understandable why, Appcelerator sure doesn’t make it easy to figure out!



So, to actually answer the question, the free version of Titanium does not, and cannot currently be made to support Blackberry development.  It looks like it does, and you can download all the Blackberry SDKs ( except one ), but other than wasting a lot of time, this will accomplish absolutely nothing.



However, as you can see following this link, you can target Blackberry if you download the Blackberry Beta SDK from Appcelerator.  The catch, in order to have access to that download, you need to be a paying subscriber.



So,  yes, it supports Blackberry development.



However… you have to pay.


30. March 2012



Unity is certainly getting more and more popular, and with the (limited time) release of Unityunity3d1 iOS and Android for free, the number of people interested in Unity is sure to rise.  Along with the increase in popularity of Unity, there has been an influx of Unity books.



Therefore I put together this post to compile all of the books and their primary details into a single page.  This makes picking out which book to buy/read as easy as possible.



Hope you find it useful.  I am trying to keep this post as comprehensive as possible, so if I missed a book or made a mistake, please let me know!




The Unity Book Round-up

General ,

29. March 2012


If you frequent developer related forums, this question probably caused your eyes to roll and the bile to rise in your throat.  People with absolutely no development experience get the game creation bug and of all things want to start with MMOs.  Amazingly enough, there is now a very simple answer…  start here.





See, the folks over at Mozilla decided to create an HTML5 MMO to showcase the use of Web Sockets a technology for easy two way communication between a webpage and server ( that isn’t without it’s controversy ).  Additionally it makes use of HTML features including Canvas, local storage, web workers and audio.  Node is used on the server side.  Here is the game in action on my desktop:






It is graphically simple, but when I played there were 73 other players online and it played fairly flawlessly.  I also fired it up on my Android Phone ( Samsung Galaxy Note ) and here are the results:









And… failure.  It never leaves the connecting to server screen.  Perhaps it’s just me, but for now it simply doesn’t work on my phone.  Will try later on my ICS tablet and iPhone, it may just be launch day jitters so I will try again later.




Failures aside, why am I talking about a primitive browser based MMO on this site?  Well, that’s because Mozilla made the complete source code available on GitHub!



Here for example is the code from character.js for defining the players character.  As you can see, once you get over the Javascriptisms, it’s quite clean and easy to follow:


define(['entity', 'transition', 'timer'], function(Entity, Transition, Timer) { var Character = Entity.extend({ init: function(id, kind) { var self = this; this._super(id, kind); // Position and orientation this.nextGridX = -1; this.nextGridY = -1; this.orientation = Types.Orientations.DOWN; // Speeds this.atkSpeed = 50; this.moveSpeed = 120; this.walkSpeed = 100; this.idleSpeed = 450; this.setAttackRate(800); // Pathing this.movement = new Transition(); this.path = null; this.newDestination = null; this.adjacentTiles = {}; // Combat = null; this.unconfirmedTarget = null; this.attackers = {}; // Health this.hitPoints = 0; this.maxHitPoints = 0; // Modes this.isDead = false; this.attackingMode = false; this.followingMode = false; }, clean: function() { this.forEachAttacker(function(attacker) { attacker.disengage(); attacker.idle(); }); }, setMaxHitPoints: function(hp) { this.maxHitPoints = hp; this.hitPoints = hp; }, setDefaultAnimation: function() { this.idle(); }, hasWeapon: function() { return false; }, hasShadow: function() { return true; }, animate: function(animation, speed, count, onEndCount) { var oriented = ['atk', 'walk', 'idle']; o = this.orientation; if(!(this.currentAnimation && === "death")) { // don't change animation if the character is dying this.flipSpriteX = false; this.flipSpriteY = false; if(_.indexOf(oriented, animation) >= 0) { animation += "_" + (o === Types.Orientations.LEFT ? "right" : Types.getOrientationAsString(o)); this.flipSpriteX = (this.orientation === Types.Orientations.LEFT) ? true : false; } this.setAnimation(animation, speed, count, onEndCount); } }, turnTo: function(orientation) { this.orientation = orientation; this.idle(); }, setOrientation: function(orientation) { if(orientation) { this.orientation = orientation; } }, idle: function(orientation) { this.setOrientation(orientation); this.animate("idle", this.idleSpeed); }, hit: function(orientation) { this.setOrientation(orientation); this.animate("atk", this.atkSpeed, 1); }, walk: function(orientation) { this.setOrientation(orientation); this.animate("walk", this.walkSpeed); }, moveTo_: function(x, y, callback) { this.destination = { gridX: x, gridY: y }; this.adjacentTiles = {}; if(this.isMoving()) { this.continueTo(x, y); } else { var path = this.requestPathfindingTo(x, y); this.followPath(path); } }, requestPathfindingTo: function(x, y) { if(this.request_path_callback) { return this.request_path_callback(x, y); } else { log.error( + " couldn't request pathfinding to "+x+", "+y); return []; } }, onRequestPath: function(callback) { this.request_path_callback = callback; }, onStartPathing: function(callback) { this.start_pathing_callback = callback; }, onStopPathing: function(callback) { this.stop_pathing_callback = callback; }, followPath: function(path) { if(path.length > 1) { // Length of 1 means the player has clicked on himself this.path = path; this.step = 0; if(this.followingMode) { // following a character path.pop(); } if(this.start_pathing_callback) { this.start_pathing_callback(path); } this.nextStep(); } }, continueTo: function(x, y) { this.newDestination = { x: x, y: y }; }, updateMovement: function() { var p = this.path, i = this.step; if(p[i][0] < p[i-1][0]) { this.walk(Types.Orientations.LEFT); } if(p[i][0] > p[i-1][0]) { this.walk(Types.Orientations.RIGHT); } if(p[i][1] < p[i-1][1]) { this.walk(Types.Orientations.UP); } if(p[i][1] > p[i-1][1]) { this.walk(Types.Orientations.DOWN); } }, updatePositionOnGrid: function() { this.setGridPosition(this.path[this.step][0], this.path[this.step][1]); }, nextStep: function() { var stop = false, x, y, path; if(this.isMoving()) { if(this.before_step_callback) { this.before_step_callback(); } this.updatePositionOnGrid(); this.checkAggro(); if(this.interrupted) { // if Character.stop() has been called stop = true; this.interrupted = false; } else { if(this.hasNextStep()) { this.nextGridX = this.path[this.step+1][0]; this.nextGridY = this.path[this.step+1][1]; } if(this.step_callback) { this.step_callback(); } if(this.hasChangedItsPath()) { x = this.newDestination.x; y = this.newDestination.y; path = this.requestPathfindingTo(x, y); this.newDestination = null; if(path.length < 2) { stop = true; } else { this.followPath(path); } } else if(this.hasNextStep()) { this.step += 1; this.updateMovement(); } else { stop = true; } } if(stop) { // Path is complete or has been interrupted this.path = null; this.idle(); if(this.stop_pathing_callback) { this.stop_pathing_callback(this.gridX, this.gridY); } } } }, onBeforeStep: function(callback) { this.before_step_callback = callback; }, onStep: function(callback) { this.step_callback = callback; }, isMoving: function() { return !(this.path === null); }, hasNextStep: function() { return (this.path.length - 1 > this.step); }, hasChangedItsPath: function() { return !(this.newDestination === null); }, isNear: function(character, distance) { var dx, dy, near = false; dx = Math.abs(this.gridX - character.gridX); dy = Math.abs(this.gridY - character.gridY); if(dx <= distance && dy <= distance) { near = true; } return near; }, onAggro: function(callback) { this.aggro_callback = callback; }, onCheckAggro: function(callback) { this.checkaggro_callback = callback; }, checkAggro: function() { if(this.checkaggro_callback) { this.checkaggro_callback(); } }, aggro: function(character) { if(this.aggro_callback) { this.aggro_callback(character); } }, onDeath: function(callback) { this.death_callback = callback; }, /** * Changes the character's orientation so that it is facing its target. */ lookAtTarget: function() { if( { this.turnTo(this.getOrientationTo(; } }, /** * */ go: function(x, y) { if(this.isAttacking()) { this.disengage(); } else if(this.followingMode) { this.followingMode = false; = null; } this.moveTo_(x, y); }, /** * Makes the character follow another one. */ follow: function(entity) { if(entity) { this.followingMode = true; this.moveTo_(entity.gridX, entity.gridY); } }, /** * Stops a moving character. */ stop: function() { if(this.isMoving()) { this.interrupted = true; } }, /** * Makes the character attack another character. Same as Character.follow but with an auto-attacking behavior. * @see Character.follow */ engage: function(character) { this.attackingMode = true; this.setTarget(character); this.follow(character); }, disengage: function() { this.attackingMode = false; this.followingMode = false; this.removeTarget(); }, /** * Returns true if the character is currently attacking. */ isAttacking: function() { return this.attackingMode; }, /** * Gets the right orientation to face a target character from the current position. * Note: * In order to work properly, this method should be used in the following * situation : * S * S T S * S * (where S is self, T is target character) * * @param {Character} character The character to face. * @returns {String} The orientation. */ getOrientationTo: function(character) { if(this.gridX < character.gridX) { return Types.Orientations.RIGHT; } else if(this.gridX > character.gridX) { return Types.Orientations.LEFT; } else if(this.gridY > character.gridY) { return Types.Orientations.UP; } else { return Types.Orientations.DOWN; } }, /** * Returns true if this character is currently attacked by a given character. * @param {Character} character The attacking character. * @returns {Boolean} Whether this is an attacker of this character. */ isAttackedBy: function(character) { return ( in this.attackers); }, /** * Registers a character as a current attacker of this one. * @param {Character} character The attacking character. */ addAttacker: function(character) { if(!this.isAttackedBy(character)) { this.attackers[] = character; } else { log.error( + " is already attacked by " +; } }, /** * Unregisters a character as a current attacker of this one. * @param {Character} character The attacking character. */ removeAttacker: function(character) { if(this.isAttackedBy(character)) { delete this.attackers[]; } else { log.error( + " is not attacked by " +; } }, /** * Loops through all the characters currently attacking this one. * @param {Function} callback Function which must accept one character argument. */ forEachAttacker: function(callback) { _.each(this.attackers, function(attacker) { callback(attacker); }); }, /** * Sets this character's attack target. It can only have one target at any time. * @param {Character} character The target character. */ setTarget: function(character) { if( !== character) { // If it's not already set as the target if(this.hasTarget()) { this.removeTarget(); // Cleanly remove the previous one } this.unconfirmedTarget = null; = character; } else { log.debug( + " is already the target of " +; } }, /** * Removes the current attack target. */ removeTarget: function() { var self = this; if( { if( instanceof Character) {; } = null; } }, /** * Returns true if this character has a current attack target. * @returns {Boolean} Whether this character has a target. */ hasTarget: function() { return !( === null); }, /** * Marks this character as waiting to attack a target. * By sending an "attack" message, the server will later confirm (or not) * that this character is allowed to acquire this target. * * @param {Character} character The target character */ waitToAttack: function(character) { this.unconfirmedTarget = character; }, /** * Returns true if this character is currently waiting to attack the target character. * @param {Character} character The target character. * @returns {Boolean} Whether this character is waiting to attack. */ isWaitingToAttack: function(character) { return (this.unconfirmedTarget === character); }, /** * */ canAttack: function(time) { if(this.canReachTarget() && this.attackCooldown.isOver(time)) { return true; } return false; }, canReachTarget: function() { if(this.hasTarget() && this.isAdjacentNonDiagonal( { return true; } return false; }, /** * */ die: function() { this.removeTarget(); this.isDead = true; if(this.death_callback) { this.death_callback(); } }, onHasMoved: function(callback) { this.hasmoved_callback = callback; }, hasMoved: function() { this.setDirty(); if(this.hasmoved_callback) { this.hasmoved_callback(this); } }, hurt: function() { var self = this; this.stopHurting(); this.sprite = this.hurtSprite; this.hurting = setTimeout(this.stopHurting.bind(this), 75); }, stopHurting: function() { this.sprite = this.normalSprite; clearTimeout(this.hurting); }, setAttackRate: function(rate) { this.attackCooldown = new Timer(rate); } }); return Character; });




So now, the next time somebody asks you “I want to create an MMO, now what?”, you have an easy answer.  Send them over to Browser Quest.



Oh, and for the record… starting your game development career off with an MMO is still a right stupid idea! Smile



EDIT: As per a comment on reddit, this code doesn’t appear to be released under a specific license yet.  Keep that in mind before using any of this code in your own project.  Of course, this should be rectified shortly.

News ,

Month List

Popular Comments

PlayStation Mobile SDK Tutorial: Working with sprite sheets
Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

7. May 2012


As the title suggests, this thread is going to be about using sprite sheets with PS Studio.  For those of you unfamiliar with the term, a spritesheet is a single image with multiple sprites.  You generally group your sprites together on a single sheet as it is much more efficient for loading and generally performs better than loading one texture per sprite.


EDIT(5/11/2012): If you are working with an actual Vita device and using the beta SDK, there is a bug on the Vita XML that prevents this from working. Read this post for a simple workaround. Hopefully in time Sony fixes this and you no longer need to apply the bug fix. Note, the problem only occurs on an actual device.


Instead of showing you how to generate a spritesheet, I am going to recycle a previous post I made on creating a spritesheet using Daz3D.  Fortunately Daz Studio is still available for free if you want to follow along.  Of course you can create your spritesheet however you want, or can simply download a freely available spritesheet such as those available at  Or of course you can just use my sheet which will be available later.


In this example, I am going to use a different program than the GIMP for assembling the spritesheet.  The end result of my Daz tutorial is a directory full of 128x96 images like these:




You can download a zipped copy of the sprites I rendered right here.


I generated 19 frames of walking animation in each direction.  This time I am going to use the free version of the tool TexturePacker to generate my sheet.  Download and fire up TexturePacker and you will be greeted with this interface:




The first thing you want to do is add your sprites to the sprite palette. You can either drag and drop the folder ( using Windows Explorer ) containing your sprites to the sprite panel on the right or hit the Add Sprites button and select the sprites individually.  Add all of the sprites you just created in Daz3D ( or the folder you downloaded and unzipped ) using either method.  Dropping a folder will automatically add those sprites in a folder by that name keeping things a bit more organized.  Here is the results of me dropping my walkCycle folder on the sprites panel:




As you can see, it added all of the sprites under a folder named WalkCycle and automatically layed out our sprite sheet as efficiently as possible.  As this point though, we don’t really care all that much about efficiency, so we are going to make a few small changes.  In the Texture Settings panel we want to fill in a couple options.  First drop down DataFormat and change it to “Generic XML”.  Next, under Data File, pick a directory and filename to save your sprite sheet, I choose c:\temp\walk.xml.  This will automatically set the texture file to c:\temp\walk.png.  Otherwise, these are the settings I used:





You of course can use whatever settings you want, but if you want exactly the same results as me, use the above.  Now that you are ready, hit the publish button.



The end result is a PNG file and an XML document. 


The resulting image file:




You can download the generated image file here.


It also generated an XML file with all of the sprite details.  The contents of that XML file look like:


<?xml version="1.0" encoding="UTF-8"?> <!-- Created with TexturePacker> <!-- $TexturePacker:SmartUpdate:a6e51795dbe6b13cd639951a1f67241c$ --> <!--Format: n => name of the sprite x => sprite x pos in texture y => sprite y pos in texture w => sprite width (may be trimmed) h => sprite height (may be trimmed) oX => sprite's x-corner offset (only available if trimmed) oY => sprite's y-corner offset (only available if trimmed) oW => sprite's original width (only available if trimmed) oH => sprite's original height (only available if trimmed) r => 'y' only set if sprite is rotated --> <TextureAtlas imagePath="walk.png" width="512" height="960"> <sprite n="Walk_left00" x="0" y="0" w="128" h="96"/> <sprite n="Walk_left01" x="128" y="0" w="128" h="96"/> <sprite n="Walk_left02" x="256" y="0" w="128" h="96"/> <sprite n="Walk_left03" x="384" y="0" w="128" h="96"/> <sprite n="Walk_left04" x="0" y="96" w="128" h="96"/> <sprite n="Walk_left05" x="128" y="96" w="128" h="96"/> <sprite n="Walk_left06" x="256" y="96" w="128" h="96"/> <sprite n="Walk_left07" x="384" y="96" w="128" h="96"/> <sprite n="Walk_left08" x="0" y="192" w="128" h="96"/> <sprite n="Walk_left09" x="128" y="192" w="128" h="96"/> <sprite n="Walk_left10" x="256" y="192" w="128" h="96"/> <sprite n="Walk_left11" x="384" y="192" w="128" h="96"/> <sprite n="Walk_left12" x="0" y="288" w="128" h="96"/> <sprite n="Walk_left13" x="128" y="288" w="128" h="96"/> <sprite n="Walk_left14" x="256" y="288" w="128" h="96"/> <sprite n="Walk_left15" x="384" y="288" w="128" h="96"/> <sprite n="Walk_left16" x="0" y="384" w="128" h="96"/> <sprite n="Walk_left17" x="128" y="384" w="128" h="96"/> <sprite n="Walk_left18" x="256" y="384" w="128" h="96"/> <sprite n="Walk_right00" x="384" y="384" w="128" h="96"/> <sprite n="Walk_right01" x="0" y="480" w="128" h="96"/> <sprite n="Walk_right02" x="128" y="480" w="128" h="96"/> <sprite n="Walk_right03" x="256" y="480" w="128" h="96"/> <sprite n="Walk_right04" x="384" y="480" w="128" h="96"/> <sprite n="Walk_right05" x="0" y="576" w="128" h="96"/> <sprite n="Walk_right06" x="128" y="576" w="128" h="96"/> <sprite n="Walk_right07" x="256" y="576" w="128" h="96"/> <sprite n="Walk_right08" x="384" y="576" w="128" h="96"/> <sprite n="Walk_right09" x="0" y="672" w="128" h="96"/> <sprite n="Walk_right10" x="128" y="672" w="128" h="96"/> <sprite n="Walk_right11" x="256" y="672" w="128" h="96"/> <sprite n="Walk_right12" x="384" y="672" w="128" h="96"/> <sprite n="Walk_right13" x="0" y="768" w="128" h="96"/> <sprite n="Walk_right14" x="128" y="768" w="128" h="96"/> <sprite n="Walk_right15" x="256" y="768" w="128" h="96"/> <sprite n="Walk_right16" x="384" y="768" w="128" h="96"/> <sprite n="Walk_right17" x="0" y="864" w="128" h="96"/> <sprite n="Walk_right18" x="128" y="864" w="128" h="96"/> </TextureAtlas>


You can download the xml file here. This XML file contains the details about how our original sprites are arranged within the sprite sheet and will prove useful in a moment.


Alright, now that we have our spritesheet and our sprite map XML file, lets fire up PSSuite and get down to some coding.  This tutorial assumes you have already gone through my earlier tutorials, at the very least the two Hello World tutorials.


Now create a new solution in PlayStation Studio, I called mine SpriteSheet.  We need to add a couple of references right away, add a reference to GameEngine2D, System.Xml and System.Xml.Linq. Now add your spritesheet png and xml files to the project, right click them and set their build action to content.



First we are going to create our gameloop in AppMain.cs.  I want to point something out right away… this is not the way you handle game events!  So the way things are done in this example are *NOT* the way we will do things in the future.  I went this route because you are already familiar with most of the code here.  In a (very near) future tutorial, I will show the “proper” way to handle updating game objects.


With that warning in place, lets take a look at 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; namespace SpriteSheet { public class AppMain { private static Walker walker; public static void Main (string[] args) { Director.Initialize(); Director.Instance.GL.Context.SetClearColor(255,255,255,0); walker = new Walker("walk.png","walk.xml"); var scene = new Scene(); scene.Camera.SetViewFromViewport(); var sprite = walker.Get("Walk_left00"); sprite.Position = scene.Camera.CalcBounds().Center; sprite.CenterSprite(); sprite.Scale = new Vector2(2,2); scene.AddChild(sprite); Director.Instance.RunWithScene(scene,true); System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch(); int spriteOffset = 0; timer.Start(); bool walkLeft = true; while(true) { if(timer.ElapsedMilliseconds > 100f) { string spriteName; if(walkLeft) spriteName= "Walk_left" + spriteOffset.ToString("00"); else spriteName= "Walk_right" + spriteOffset.ToString("00"); sprite.TileIndex2D = walker.Get (spriteName).TileIndex2D; if(spriteOffset >= 18) { spriteOffset = 0; walkLeft = !walkLeft; } else spriteOffset++; timer.Reset(); timer.Start(); } Sce.Pss.HighLevel.GameEngine2D.Director.Instance.Update (); Sce.Pss.HighLevel.GameEngine2D.Director.Instance.Render(); Sce.Pss.HighLevel.GameEngine2D.Director.Instance.GL.Context.SwapBuffers(); Sce.Pss.HighLevel.GameEngine2D.Director.Instance.PostSwap(); } } } }

Click here to download AppMain.cs


Most of the concepts in this code we’ve seen in a previous tutorial, so I will only highlight the new details.  First thing is we declare a Walker object, this is a class we are going to create shortly that is going to handle our sprite sheet.  As you can see, the Walker constructor takes the file name of the sprite texture and xml file you created earlier as parameters. 


Most of the remainder of this initial code is a matter of setting up our scene and sprite objects just like we did in Hello World.  A key difference is we are getting the sprite from our walker object instead of creating it from scratch or loading it from file.  The line:

sprite.Scale = new Vector2(2,2);

Is simply to double the size of our sprite in the viewport to see it better, you can easily remove this if you wish.


We create a Stopwatch ( a .NET object ) and start it counting up, then enter our infinite game loop.  Lets take a look at the new logic in our game loop


if(timer.ElapsedMilliseconds > 100f) { string spriteName; if(walkLeft) spriteName= "Walk_left" + spriteOffset.ToString("00"); else spriteName= "Walk_right" + spriteOffset.ToString("00"); sprite.TileIndex2D = walker.Get (spriteName).TileIndex2D; if(spriteOffset >= 18) { spriteOffset = 0; walkLeft = !walkLeft; } else spriteOffset++; timer.Reset(); timer.Start(); }


This is basically the “guts” of our game loop.  What we are doing here is waiting for the timer to reach 1/10th of a second.  Every tenth of a second we want to move on to the next frame of animation.  However we have only 19 ( walk_left00 to walk_left18 ) frames of animation in each direction and once we hit the end of our walk cycle, we want to walk in the other direction.  So what we do here is loop through each frame of animation until we hit walk_left18 or walk_right18, at which point we flip directions by inverting the value of the walkLeft bool.  This results in changing the spriteName text prefix, otherwise we simply increment to the next frame.  Then we start our timer over again to begin the process again a tenth of a second later.  One more time, this is not the proper way to handle updates!


The key line in all of that was:

sprite.TileIndex2D = walker.Get (spriteName).TileIndex2D;


This line actually tells our sprite to refer to a different sprite within our spritesheet, causing the animation.  That will make more sense in a second when we look at the code for Walker.cs.  Speaking of which, lets create it now!


To create a new cs file we either click the New icon ( image) or hit CTRL + N.  In the resulting dialog select General on the left, Empty Class on the right and name it Walker.cs, like such:




Now open up Walker.cs and enter the following code:


using System; using System.Linq; using System.Xml; using System.Xml.Linq; using System.Collections.Generic; using Sce.Pss.Core.Graphics; using Sce.Pss.HighLevel.GameEngine2D; using Sce.Pss.HighLevel.GameEngine2D.Base; namespace SpriteSheet { public class Walker { private TextureInfo _textureInfo; private Texture2D _texture; private System.Collections.Generic.Dictionary<string,Sce.Pss.HighLevel.GameEngine2D.Base.Vector2i> _sprites; public Walker (string imageFilename, string imageDetailsFilename) { XDocument doc = XDocument.Load ("application/" + imageDetailsFilename); var lines = from sprite in doc.Root.Elements("sprite") select new { Name = sprite.Attribute("n").Value, X1 = (int)sprite.Attribute ("x"), Y1 = (int)sprite.Attribute ("y"), Height = (int)sprite.Attribute ("h"), Width = (int)sprite.Attribute("w") }; _sprites = new Dictionary<string,Sce.Pss.HighLevel.GameEngine2D.Base.Vector2i>(); foreach(var curLine in lines) { _sprites.Add(curLine.Name,new Vector2i((curLine.X1/curLine.Width),9-(curLine.Y1/curLine.Height))); } _texture = new Texture2D("/Application/" + imageFilename,false); _textureInfo = new TextureInfo(_texture,new Vector2i(4,10)); } ~Walker() { _texture.Dispose(); _textureInfo.Dispose (); } public Sce.Pss.HighLevel.GameEngine2D.SpriteTile Get(int x, int y) { var spriteTile = new SpriteTile(_textureInfo); spriteTile.TileIndex2D = new Vector2i(x,y); spriteTile.Quad.S = new Sce.Pss.Core.Vector2(128,96); return spriteTile; } public Sce.Pss.HighLevel.GameEngine2D.SpriteTile Get(string name) { return Get (_sprites[name].X,_sprites[name].Y); } } }

Click here to download Walker.cs


Our Walker class is in charge of loading and handling our spritesheet.  TextureInfo and Texture2D you have already been exposed to and in this case, nothing is different here.  Next up we declare a Dictionary of <string,Vector2i> named _sprites.  The Vector2i type is declared in GameEngine2D.Base and is simple a pair of ints. _sprites is used to store the x,y location of each individual sprite in our spritesheet, accessed by the sprites filename.


Let’s take a look first at the constructor.  This code is pretty straight forward if you have ever worked with Linq or XML.  We are simply opening our XML file, the filename of which we passed in from AppMain.  Note that we added “application/” to our paths, all your files on Vita are located under this subdirectory.  Next we read the XML file and extract each <sprite> entry into a new anonymous type composed of Name, X1, Y1, Height and Width.  Name represents the file name of the source image, X1 and Y1 represent the pixel coordinates within the generated spritesheet, while Height and Width are the dimensions of the sprite within the sprite sheet.  Given that all of our sprites are the same size, these values are of little importance.


Now that we’ve parsed out our XML file, we populate our _sprites dictionary using these values ( minus height and width which we don’t need past this point ).  However, we don’t actually want the x and y pixel coordinates of our image, but instead the offset within the spritesheet texture.  We can determine this value by dividing the X and Y values by the sprite Width and Height respectively.  Keep in mind, this works because all our sprites are the same size ( 128x96 ) and would require different logic if you had sprites of differing sizes in your sprite sheet.  You may notice I subtract 9 from the height value… this is because GameEngine2D SpriteTile’s locations start from the bottom left instead of the top left!  I prefer top right and this is how I generated the sheet, so invert the values.  If you design your sprite sheets to start at the bottom left ( so the first frame is at the bottom left corner ), you wont need to perform this calculation.  Now you may be wondering… why 9?  Well that’s the number of sprites we have in each row on the sprite sheet (10, counting from 0 equals 9).


Finally we load our texture using the filename we passed in to the constructor.  Again we prepend “Application/” to our filename.  The last thing of note here is the second value in our TextureInfo constructor.  This Vector2i informs the TextureInfo the dimensions of our sprite sheet, telling it at there are 4 columns of 10 rows of sprites.  It doesn’t matter that the last row isn’t full of sprites ( there are only 2 in the spritesheets bottom row ), if you try to access them you will simply get an empty space or whatever the background colour of your spritesheet is.


Our destructor is nothing special, just cleans up like a good little citizen should.  Remember it’s your responsibility to dispose of any objects you own and are no longer using.  C# is garbage collected, but its still easy to run out of memory in a hurry if you don’t keep things tidy.


Finally we have a pair of Get() methods.  The one takes a sprite name, looks it up in our _sprite dictionary, retrieves the X and Y offset of the sprite within the texture, then passes those values into the other Get() method.  This Get() method then creates a new SpriteTile assigning it our already created _textureInfo.  Next it sets the tiles index within the texture using the passed in coordinates to represent our currently selected sprite, sets the tiles dimensions to 128x96 (pixels) and returns our sprite.  Please notice there is absolutely no error checking or handling here in order to keep things short.  You really should have a wee bit more error checking in your own code! Winking smile


The end result of all this activity:





Click here to download the entire project source. In addition to the full project source, the zip also includes all the image files used in this tutorial.

Programming , , , , ,

blog comments powered by Disqus

Month List

Popular Comments