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 opengameart.org.  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:

 

Walk_left00Walk_left01Walk_left02

 

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:

 

image

 

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:

 

image

 

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:

 

image

 

 

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.

image

 

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

 

The resulting image file:

 

walk

 

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 http://texturepacker.com--> <!-- $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:

 

image

 

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:

 

walkcycle

 

 

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


30. April 2012

 

BlenderOnVita

We are now going to look at creating a fully textured model in Blender and exporting to a PlayStation Studio SDK project, a simple model viewer.  We create a very simple model, UV map, texture then export it.  At this point, we process it using the PS Studio ModelConverter tool, import it into PS Studio then finally run our code in the simulator.

 

The example model is as I said, brutally simple ( it’s simply a dice, er… die, also known as a textured cube ). However the process for exporting more complex models is exactly the same.  The process isn’t really all that difficult, especially if you know your way around Blender, but if you don’t know Blender all that well don’t worry, I’ve actually captured the entire process in video form.  This video covers exactly the same thing that the rest of this tutorial does, so if you have problems following one, refer to the other and vice versa.

 

 

Modeling, texturing and exporting from Blender to Sony PlayStation Suite SDK in under 5 minutes

 

 

The above video is actually encoded at 1080p and is probably almost illegible at anything less than 720p.  You can watch it on YouTube or Vimeo in full definition.  Again, the video demonstrates exactly what I am going to show below, except the source code.  So if you are the type that prefers to learn by watching, or the type that learns by reading, you get the best of both worlds here!  Alright, let’s get started.

 

 

If you already know how to model, texture and export in COLLADA format using Blender, I suggest you skip ahead to part 2, as the remainder of this section will be quite boring for you… it goes into detail, a lot of detail. Smile

 

 

If you haven’t already, fire up Blender.  We are going to work with the default cube, if you don’t have a default cube select the Add menu –> Mesh –>Cube.  The first thing we want to do is create a new image to texture our model.

 

In the properties panel to your right ( assuming you are using the default layout, which I will for the remainder of this tutorial ), locate the Textures tab and click it.  Like such:

 

image

 

 

Now change “Type” to Image:

image

 

Scroll down slightly to the Image section ( expand it if required ) and click New:

image

 

In the resulting dialog, we specify the texture details.  I am going to name it the ultra imaginative name “Texture” but fell free to call it whatever you want.  1024x1024 is massive overkill for what we are doing, but hey… I like overkill.  When done, click OK.

 

image

 

Now we want to apply some simple texture mapping to our 3D cube.  In the 3D view, make sure you are in “Edit Mode”, either by hitting Tab until selected, or via this menu:

image

 

Now that you are in edit mode, select all the faces of your mesh by hitting “a”.  Now in the Mesh menu, you want to select Mesh->UV Unwrap…->Follow Active Quads.

image

 

Simply click OK at the resulting dialog menu.  Now we want to switch to UV editing mode.  Locate the icon to the bottom left of your view, click it and select UV/Image Editor.

image

 

We now want to make our texture the active image.  Locate the “Browse image to be linked” button, click it and select texture from the list.

 

image

 

Now press A to select all of our faces, then using hit S to scale ( then mouse move, left click when done), followed by G to translate, until our UV coordinates are all over the black image, like so:

image

 

Now we want to quickly paint our dice faces on the texture within the UV coordinates.  From the menubar select Image->Image Painting.

image

 

Now left click to mouse paint the texture like the face of a die.  You can use the left hand panel ( press “N” if not visible ) to change the paint settings.  When done you should have something like this:

image

 

Now, back in the Image menu, unclick Image Painting.  Now we want to save our completed image.  In the same menu as before, select “Save as Image” or hit F3.  In the resulting dialog, locate the directory you want to save the texture to ( make it the same place you are going to save the model ), name it, then click Save as Image:

image

 

Now with that complete, we assign our image to our texture.  Back in the Texture panel ( to the right ), locate the Image section, click the Browse Image to be Linked button and select your texture.

image

 

Now scroll down lower in the Texture panel, locate Mapping, then select the Coordinates: dropdown and select UV.

image

 

Right below that, select the Map: dropdown and select UVMap.

image

 

 

 

All right, now we have a fully texture mapped model ready for exporting.  From the File Menu ( top right ), select File->Export->COLLADA(.dae).

image

 

 

In the resulting dialog, navigate to the same location where you saved your texture, name it ( I used box.dae ), optionally select “Export only selected” then click Export COLLADA.

 

image

 

 

And we are now done with Blender.

 

 

This section has already gotten quite long so I am going to break this post into two parts.

 

 

Continue on to Part 2.

Programming Art


26. April 2012

 

I am noticing from search traffic that this is an extremely common question, people seem to want to know if you can use C++ with the PS Studio SDK.VitaCpp

 

 

Simple answer and you aren’t going to like it.

 

 

No, no you can’t.

 

 

 

I wouldn’t hold your breath either, it’s the mono runtime behind the scenes that is providing the portability across devices.  If you want native C++ support you need to have access to the full development suite.  Even becoming a PSN developer to develop PSN Mini games requires well over 1000$.

 

 

 

If for some reason you prefer C++’s syntax over C# ( this may in fact be a sign of insanity by the way… ), you *might* and I emphasize *might* be able to take advantage of this project CPlusPlus, at some point, but even then you would have to write pure CIL code.  Without native access, there would be very little point using C++ in this situation.

 

 

 

Therefore for all intents and purposes, no you cannot use C++ with PlayStation Suite. C# is the only game in town and I wouldn’t expect to see that change any time soon. 

Programming


26. April 2012

 

In this tutorial we are going to manually manage the game loop manually.  Additionally we are going to look at the various ways of handling input from the gamepad.  This tutorial builds on the code we developed in the previous tutorial.  As you may recall, we created a “Hello World” sprite and centered it to the camera.  This time, we are going to give the user the ability to control the sprite’s position and size using the gamepad.

 

 

Let’s get straight to the code:

 

using System; using System.Collections.Generic; using Sce.Pss.Core; using Sce.Pss.Core.Environment; using Sce.Pss.Core.Graphics; using Sce.Pss.Core.Input; using Sce.Pss.HighLevel.GameEngine2D; using Sce.Pss.HighLevel.GameEngine2D.Base; using Sce.Pss.Core.Imaging; namespace HelloWorld { public class AppMain { public static void Main (string[] args) { Director.Initialize(); Scene scene = new Scene(); scene.Camera.SetViewFromViewport(); var width = Director.Instance.GL.Context.GetViewport().Width; var height = Director.Instance.GL.Context.GetViewport().Height; Image img = new Image(ImageMode.Rgba,new ImageSize(width,height),new ImageColor(255,0,0,0)); img.DrawText("Hello World", new ImageColor(255,0,0,255), new Font(FontAlias.System,170,FontStyle.Regular), new ImagePosition(0,150)); Texture2D texture = new Texture2D(width,height,false,PixelFormat.Rgba); texture.SetPixels(0,img.ToBuffer()); img.Dispose(); TextureInfo ti = new TextureInfo(); ti.Texture = texture; SpriteUV sprite = new SpriteUV(); sprite.TextureInfo = ti; sprite.Quad.S = ti.TextureSizef; sprite.CenterSprite(); sprite.Position = scene.Camera.CalcBounds().Center; scene.AddChild(sprite); Director.Instance.RunWithScene(scene,true); bool gameOver = false; while(!gameOver) { Sce.Pss.HighLevel.GameEngine2D.Director.Instance.Update(); if(Input2.GamePad.GetData(0).Left.Release) { sprite.Rotate(Sce.Pss.HighLevel.GameEngine2D.Base.Math.Deg2Rad(90)); } if(Input2.GamePad0.Right.Release) { sprite.Rotate(Sce.Pss.HighLevel.GameEngine2D.Base.Math.Deg2Rad(-90)); } if((Sce.Pss.Core.Input.GamePad.GetData(0).Buttons & GamePadButtons.Up) == GamePadButtons.Up) { sprite.Quad.S = new Vector2(sprite.Quad.S.X += 10.0f,sprite.Quad.S.Y += 10.0f); sprite.CenterSprite(); } if((Sce.Pss.Core.Input.GamePad.GetData(0).Buttons & GamePadButtons.Down) == GamePadButtons.Down) { sprite.Quad.S = new Vector2(sprite.Quad.S.X -= 10.0f,sprite.Quad.S.Y -= 10.0f); sprite.CenterSprite(); } if(Input2.GamePad0.Circle.Press == true) gameOver = true; Sce.Pss.HighLevel.GameEngine2D.Director.Instance.Render(); Sce.Pss.HighLevel.GameEngine2D.Director.Instance.GL.Context.SwapBuffers(); Sce.Pss.HighLevel.GameEngine2D.Director.Instance.PostSwap(); } Director.Terminate(); } } }

 

 

 

The top portion of the code is completely unchanged, our new additions start at the line:

Director.Instance.RunWithScene(scene,true);

 

The key addition here is the second parameter “true”.  This bool is telling the Director singleton that we are going to handle the game loop ourselves, this means we need to call 4 methods manually ( described in a moment ).  Next up we create a bool gameOver, which is going to control when we exit our game loop.  Obviously we don’t want to exit right away, so we default it to false.  Speaking of game loops, that’s what the while line does, loops over and over until gameOver is set to true.

 

Now in each iteration of our loop, there are four methods we have to call, Update(), Render(), GL.Context.SwapBuffer() and PostSwap().  Update() tells the director we have moved on to the next frame, Render() draws the frame, SwapBuffers displays what Render() drew on the backbuffer to the screen (makes it visible) and finally PostSwap() tells the director we’ve finished swapping buffers and it must be called after SwapBuffers().  Those four combined represent a complete game loop, all the rest of the code handles input from the game pad.

 

Just to make something perfectly clear here, I am using 3 different ways to check for input, *you won’t do this in your code*.  I am just doing it to illustrate all of your different options in one example.  You should just pick one ( probably Input2 ) and use only it.  Lets look at them one at a time.

 

if(Input2.GamePad.GetData(0).Left.Release) { sprite.Rotate(Sce.Pss.HighLevel.GameEngine2D.Base.Math.Deg2Rad(90)); }

 

 

This method is the most likely way you will deal with Input.  Input2 is a wrapper around the Input object to make things a bit simpler.  GetData takes a parameter telling it which control ( controller 1, controller 2, etc ) you want to poll, returning a GamePadData object, representing the state the controller is in.  We are then checking if the “Left” button has been released.  In the case Left is released, we then rotate our sprite 90 degrees.  Rotate takes an angle value in radians, so we use the Math.Deg2Rad() helper function to convert from degrees to radians.  Of course you could have passed the radian value in instead of converting, 1.5709633 is 90 degrees in radians, it’s just a bit harder to look at.

 

if(Input2.GamePad0.Right.Release) { sprite.Rotate(Sce.Pss.HighLevel.GameEngine2D.Base.Math.Deg2Rad(-90)); }

 

 

This if statement is almost identical to the last one, but instead of using GetData(0), we use a handy alias (GamePad0) that represents exactly the same thing.  The only other difference is, in this case we are checking to see if the “Right” button has been released, and we are rotating by a negative value ( the other way ) if it is.

 

 

if((Sce.Pss.Core.Input.GamePad.GetData(0).Buttons & GamePadButtons.Up) == GamePadButtons.Up) { sprite.Quad.S = new Vector2(sprite.Quad.S.X += 10.0f,sprite.Quad.S.Y += 10.0f); sprite.CenterSprite(); } if((Sce.Pss.Core.Input.GamePad.GetData(0).Buttons & GamePadButtons.Down) == GamePadButtons.Down) { sprite.Quad.S = new Vector2(sprite.Quad.S.X -= 10.0f,sprite.Quad.S.Y -= 10.0f); sprite.CenterSprite(); }

 

 

This time we are using Input directly, using Input instead of Input2.  As you can see, the results are a bit more “raw”.  In this case we have to use bit masking to determine if a given button is pressed and there is no Released option.  In this case we are checking for the “Up” and “Down” buttons.  In the event that the user is pressing Up or Down, we are modifying the Scale of the quad our hello texture is pasted on.  Remember initially Quad.S is equal to the size of the screen in pixels.  If we press Up, we scale the image up 10 pixels in size, if we press down, we shrink it by 10 pixels in size.

 

 

Finally, we check ( using the Input2 method ), if the user has pressed the Circle button, in which case we set gameOver to true, causing our loop to exit and our program to end.

 

 

One thing to notice at this point is how we scaled the sprite.  Unlike rotate, we didn’t call a method, instead we modified the Quad.S(cale) property.  The actual transformation matrix of a node ( which SpriteUV is derived from ) is actually determined combining the Position, Scale, Skew and Rotation+Angle+RotationNormalize properties.  Therefore modifying any of these properties will translate the node accordingly.

 

 

Now run our game, if we press left or right, we rotate 90 degrees, while pressing up or down scales the images.  Finally press Circle to exit.

 

 

helloworld2

 

 

One last thing I suppose needs covering… how exactly do you press Left, Right, or Circle on the simulator?

Left directional key
Cursor key: ←

Up directional key
Cursor key: ↑

Right directional key
Cursor key: →

Down directional key
Cursor key: ↓

Square button
Alphabet: A

Triangle button
Alphabet: W

Circle button
Alphabet: D

Cross button
Alphabet: S

SELECT button
Alphabet: Z

START button
Alphabet: X

L button
Alphabet: Q

R button
Alphabet: E

 

 

Sadly, you cannot currently emulate the analog sticks using the simulator.  Obviously you can on the Vita device.

 

EDIT: Oops, forgot to include the project source code, which you can download here.

Programming


24. April 2012

 

 

There is a charter somewhere that states all tutorial series must start with the ubiquitous Hello World tutorial, and who am I to break the charter?  So that is exactly what we are going to do here, a simple Hello World. This is as much about getting up and running with PS Studio than it is about C# coding, as there are a couple small gotchas.  By the end of this tutorial you should be able to create, configure and run an application.  In future tutorials, I will assume you have these abilities.

 

If you haven’t already, download and install PlayStation®Suite SDK from here.  The install is pretty straight forward, take the defaults, next next next, done.  If you want some idea of what you just installed check out this post.  Now fire up PssStudio from your start menu.  Once loaded, select File->New-Solution, like this:

 

image

 

 

In the “New Solution” dialog, on the left hand side expand C# then select PlayStation Suite.  Select PlayStation Suite Application on the right, then fill in whatever name you want ( I  am using HelloWorld ).  This should automatically fill in Solution Name, it is your choice if you want to create a subdirectory or not, in this case I will.  Fill out the dialog like this:

 

image

 

 

Click OK, and your solution will be created.  Now we need to add an app.cfg file to your application, or it will fail to run.  Note, this is not that same as a .NET application configuration file.  In the Solution Explorer, right click your project name ( HelloWorld in my case ), then select Add->New File… like such:

 

 

image

 

 

Choose Misc on the left, then Empty Text File, name it app.cfg and click New.

 

image

 

The newly created file will open in the text editor, fill it in with the following:

 

memory: resource_heap_size : 16384 managed_heap_size : 16384 input: gamepad : false touch : false motion : false

 

 

This file is telling what kind of machine your application is targeting.  If you forget this step, the simulator pss.exe will just max out a CPU core, never responding.  Now in the Solution panel, locate and double click AppMain.cs, this is where our application code will reside.

 

image

 

 

The actual process of doing Hello World on Vita is actually incredibly involved, as you need to do almost everything yourself.  In this example however, I am going to take advantage of GameEngine2D, which is an included 2D Game engine that makes many of the drudge worthy tasks much easier.  That means we need to add a reference to GameEngine2D.  Adding a reference is simply saying “I am going to use the code included in this library” In order to add a reference to GameEngine2D, right click on References and choose Edit References… like such:

 

image

 

Then a dialog will pop up, locate Sce.Pss.HighLevel.GameEngine2D and check the box to it’s left.  Then click OK.

 

image

 

 

Now we can add a using entry to tell our code to use the newly referenced library.  This is where we run into a bug in MonoDevelop ( PS Studio ).  Look at the following auto-completion screenshot:

 

image

 

 

Hmmm… where is HighLevel?  We added the reference, it should be there.  Well, on adding a new reference it seems the intellisense/auto-completion isn’t updated properly.  I live by auto-complete so this is a big deal to me.  Simply close and re-open your solution and presto:

 

 

image

 

 

Thankfully, you don’t really need to add references all that often, so while it’s inconvenient to have to exit and restart, its not the end of the world.  PS Studio loads quickly enough to barely notice.

 

 

Alright, finally time for the code!

 

 

using System; using System.Collections.Generic; using Sce.Pss.Core; using Sce.Pss.Core.Environment; using Sce.Pss.Core.Graphics; using Sce.Pss.Core.Input; using Sce.Pss.HighLevel.GameEngine2D; using Sce.Pss.HighLevel.GameEngine2D.Base; using Sce.Pss.Core.Imaging; namespace HelloWorld { public class AppMain { public static void Main (string[] args) { Director.Initialize(); Scene scene = new Scene(); scene.Camera.SetViewFromViewport(); var width = Director.Instance.GL.Context.GetViewport().Width; var height = Director.Instance.GL.Context.GetViewport().Height; Image img = new Image(ImageMode.Rgba, new ImageSize(width,height), new ImageColor(255,0,0,0)); img.DrawText("Hello World", new ImageColor(255,0,0,255), new Font(FontAlias.System,170,FontStyle.Regular), new ImagePosition(0,150)); Texture2D texture = new Texture2D(width,height,false, PixelFormat.Rgba); texture.SetPixels(0,img.ToBuffer()); img.Dispose(); TextureInfo ti = new TextureInfo(); ti.Texture = texture; SpriteUV sprite = new SpriteUV(); sprite.TextureInfo = ti; sprite.Quad.S = ti.TextureSizef; sprite.CenterSprite(); sprite.Position = scene.Camera.CalcBounds().Center; scene.AddChild(sprite); Director.Instance.RunWithScene(scene); } } }

 

 

PHEW!  Pretty long for a Hello World eh?  Truth of the matter is, had I not used GameEngine2D it would have easily been 4 or 5 times longer!  We will cover what is going on behind the scenes ( the stuff GameEngine2D is handling ) in a later post.  For now, just assume this is the way it works, at least while you are getting started.  Now lets take a quick walk through the code and look at what’s happening here.

 

First we add our additional includes, Sce.Pss.HighLevel.GameEngine2D and Sce.Pss.HighLevel.GameEngine2D.Base.  Our app consists of a single method, Main, as the event loop is actually managed by the Director.

 

Speaking of which, that is what the first line does, initializes the Director singleton.  A singleton is a globally available object that is allocated on it’s first use.  Director is the heart of your game, even though most of the complexity is hidden away.  Remember, you have full source code for GameEngine2D available if you want to peek behind the curtain.

 

Next up we create a Scene.  Again, Scene is an abstraction provided by GameEngine2D which can be thought of as a collection of visible “stuff”, the bits and pieces that compose what we want to display to the user and the Camera used to display them.  We then call SetViewFromViewport() which creates a camera sized to the window. Generally without GameEngine2D, you would have to do this yourself, setting up an orthographic project.

 

The next two lines get the width and height, as reported by the Director’s OpenGL context.  We then create an Image the same dimensions as our viewport.  The Image class is used to hold binary image data, such as from a PNG or JPG file, but in this case we are create a new blank image.  Once we create our Image, we call it’s DrawText method to draw our “Hello World” message.  ImageColor represents the color we want to draw the image ( red in this case ), Font represents the font to draw the text in ( there aren’t really many options here ), while ImagePosition represents the location within the image to draw the text at.

 

Now that we have created an image, we need to create a texture out of it.  Textures are most often thought of as the images mapped around 3D objects and in a way, that is still what we are doing here.  GPUs have no concept of image files or pixels, they deal only with textures.  That is why we copy our image data into the texture, using the SetPixels method of Texture2D and the ToBuffer() method of Image, to turn the images pixel data into an array Texture2D can use.  At this point we are done with our Image, so we Dispose() it.

 

We next create a TextureInfo object and assign our newly created Texture to it.  TextureInfo caches UV location information about the texture, as well as taking ownership of the texture itself.  More importantly, it’s a TextureInfo object that Sprite expects, so that is what we create.  Speaking of sprite, that is what we create next in the form of a SpriteUV.

 

Since modern GPUs don’t really deal with pixels anymore, everything is pretty much made out of textured polygons and SpriteUV is no exception.  It is essentially a rectangular polygon that faces the camera with our image textured on it, all sprites are.  Next up we set the sprite’s Quad ( the rectangle the texture is plastered to ) to be equal to the size of our texture, which in this case is the same size as our view.  We now position our sprite smack in the middle of the view.

 

Now that we created our Image data, copied that image data into a Texture2D that was then assigned to a TextureInfo object, which in turn was assigned to a SpriteUV, we are now ready to add that fully textured sprite to our scene, which we do by calling AddChild().  Yeah, it sounds very convoluted, but you will find it natural very soon and generally you just load your textures from file anyways, greatly simplifying this process.

 

Anyways, now that we have our scene populated with our texture, which is the same size as the screen, we go ahead and tell the Director to do it’s thing, via RunWithScene().  You can think of this as the game loop, although the internals of what it’s doing are hidden from you.

 

 

Don’t worry, it’s really not as complicated as it seems.  Now lets take a look at the fruits of our labour.  To run it in PS Studio, select the Run menu and either choose Start Without Debugging or Start Debugging, depending if you want to be able to debug or not, like such:

 

image

 

 

And presto, all our hard work resulted in…

 

image

 

May not be much to look at, but you successfully created a game you can run on a Vita!

 

Speaking of which, if you need details of running on an actual device instead of in the simulator, read this post.

 

The complete project file for this tutorial are available for download here.

 

 

 

Continue on to Hello World Part 2: Hello Harder.

Programming


AppGameKit Studio

See More Tutorials on DevGa.me!

Month List