Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon
4. June 2012

 

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

 

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

 

 

First lets take a look at our AppMain.cs

 

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

 

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

 

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

 

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

 

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

 

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

 

 

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

 

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

 

Jet

 

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

 

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

 

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

 

 

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

 

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

 

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

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

 

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

 

1:

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

We declared out spriteList member.

 

2:

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

 

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

 

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

 

 

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

 

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

 

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

 

 

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

 

Running our SpriteList demo

 

You can download all of the project code here.

Programming


15. May 2012

 

In Part 1 we covered creating and displaying our UI.  Now we are going to do something with it.  We are going to cover quite a bit of different material in this tutorial.  First and foremost we are going to need a server to talk to, lucky enough, I already have one!

 

 

That tutorial series was about creating a high score server using Node, then consuming it from a C++ SFML app.  We are going to reuse the server logic from that tutorial.  If you have zero experience with Node, I recommend you read it, just ignore the C++ bits, we will cover them here.  If you don’t care about Node at all, simply download and save server.js as well as HighScores.txt.

 

Then all you need to do is download and extract node, once extracted at a command line run change to the node directory and run node.exe c:\path\to\where\you\save\server.js, and your server is up and running, like such:

 

image

 

Press CTRL + C to exit.

 

Let’s take a quick look at Server.js

 

var dgram = require('dgram'), fileSystem = require('fs'), highScores, server; //Load high scores from file fileSystem.readFile("HighScores.txt", 'utf8', function(err,data){ if(err){ //Error occurred loading file, spit out error message then die console.log("Error occurred loading file"); process.exit(); } console.log("Loading high scores from file"); try{ // use JSON to turn file contents back into a Javascript array object highScores = JSON.parse(data); }catch(e) { // Exception occurred parsing file contents as JSON, error out and die. console.log("Exception occured parsing data"); process.exit(); } // Now sort the high scores by score, high to low highScores.Scores.sort(function(a,b){ return b.Score - a.Score; }); // Display the sorted high scores to the console console.log(highScores); }); //Create a UDP socket server = dgram.createSocket('udp4'); console.log("Socket created"); // Add a handler for incoming traffic on the socket. This will be called each time something connects to the socket server.on("message",function (msg,rinfo) { //console.log(parseInt(msg).toString()); console.log(rinfo); // SFML sends two packets, one with the size of the following packet ( as a 4 byte number ) // We don't need it, so as a crude-hack, we ignore any 4 byte packets if(rinfo.size != 4) { console.log("Received message:" + msg.toString()); // Socket data comes in as a JSON encoded array of objects, turn back into a JS object var jsonData,i; try{ jsonData = JSON.parse(msg); } catch( exception ) { console.log("Invalid JSON request received"); return; // Non lethal error, just stop processing packet } // The action parameter determines what you should do with this packet switch(jsonData.action) { // action==AddScore, add the score to the highscore array if it's higher than an existing score case "AddScore": console.log("AddScore called\n"); // Make sure highscore has been initialized... order can be a weird thing in node if(highScores != undefined){ // Loop through current highScores ( which should be sorted ) // and insert score if a lower match found for(i=0;i < highScores.Scores.length;++i) { if(highScores.Scores[i].Score < jsonData.score){ highScores.Scores.splice(i,0,{"Name" : jsonData.name, "Score" : jsonData.score}); console.log("Inserted highscore by: " + jsonData.name); break; // match found, stop looping } } } // Display newly created highscore array console.log(highScores.Scores); break; case "GetScores": console.log("Get Scores called"); // Turn highscores back into a JSON encoded string var highScoreAsString = JSON.stringify(highScores); // Create a buffer to hold that string var responseBuffer = new Buffer(highScoreAsString.length); // Write the string to the buffer responseBuffer.write(highScoreAsString); // Send it back to the client, using the addressing information passed in via rinfo server.send(responseBuffer,0,responseBuffer.length,rinfo.port,rinfo.address, function(err, sent){ if(err) console.log("Error sending response"); else console.log("Responded to client at " + rinfo.address + ":" + rinfo.port ); }); break; } } // // }); // Called when socket starts listening for packets. besides logging, currently serves no purpose server.on("listening", function () { var address = server.address(); console.log("server listening " + address.address + ":" + address.port); }); // Finally, bind the server to port 1000. 1000 was randomly chosen. Think of this as saying GO! // Now we are listening for UDP connections on port 1000 server.bind(1000); // Now, lets show off a bit, with the worlds simplest web server. // Dynamically creates webpage showing current high scores. var webServer = require('http'); webServer.createServer(function(req,res){ res.writeHead(200, {'Content-Type': 'text/html'}); res.write("<html><body><h1>High Scores</h1><ul>"); for(i=0;i < highScores.Scores.length;++i) { res.write(highScores.Scores[i].Name + "&nbsp;&nbsp;" + highScores.Scores[i].Score + "<br />"); } res.write("</ul></body></html>"); res.end(); }).listen(80);

 

The code is pretty well commented as to what it does.  For more details, read all three parts of the prior tutorial.

 

 

Now that we have our server up and running, we need to communicate with it.  As you may have noticed, the server made heavy use of JSON, which is a simple Javascript based data markup format.  This is the format we are going to send and receive our data in.

 

Here for example, is the simple JSON we are going to create for a Add High Score request:

 

{ "action":"AddScore", "name":"Mike","score":90210}

 

Normally there is a JSON serializer bundled with the .NET framework, unfortunately it hasn’t been included in the PlayStation Suite SDK, so we need one.  Fortunately there is a very simple one available, the aptly named simple-json.  We actually only need to download a single file, SimpleJson.cs.  Download that file and save it somewhere.

 

We could simply add the cs file to our project, but I find it a bit cleaner to put it into a library of it’s own, so that is what we are going to do.  In PlayStation Suite, right click your solution and select Add->Add New Project:

 

image

 

In the resulting dialog, on the left hand panel select C#->PlayStation Suite, then in the middle pane select PlayStation Suite Library Project, finally name it and click OK.  I went with the name JSON.

 

image

 

Make certain you select a PS Suite library project, an ordinary library will not work!

 

Now that we have our newly created library, we need to add the SimpleJson.cs file we downloaded.  Simply right click your JSON project, select Add Files, and select SimpleJson.cs.  Now that the file has been added, right click the JSON project and choose Build JSON.  It should compile without error.

 

We now need to add a reference to the JSON project to our Networking project.  Expand Networking, right click References and select “Edit References…”

 

In the resulting dialog, switch to the Projects tab then click the checkbox next to JSON, finally click the OK button.

 

image

 

Our project can now access the SimpleJson class.  Now we have to wire-up our app so the UI actually does something.  Open up MainWindow.cs and change it as follows:

 

using System; using System.Collections.Generic; using Sce.Pss.Core; using Sce.Pss.Core.Imaging; using Sce.Pss.Core.Environment; using Sce.Pss.HighLevel.UI; using System.Net; using System.Net.Sockets; namespace HighScoreApp { public partial class MainWindow : Scene { public class ScoreEntry { public string Name; public int Score; } public class ScoreArray { public IList<ScoreEntry> Scores { get; set; } } public MainWindow() { InitializeWidget(); buttonAddScore.ButtonAction += delegate(object sender, TouchEventArgs e) { Dictionary<string,object> jsonRequest = new Dictionary<string, object>(); jsonRequest["action"] = "AddScore"; jsonRequest["name"] = textBoxName.Text; jsonRequest["score"] = Int32.Parse(textBoxScore.Text); // No error handling, will blow up easily, use tryparse string jsonObj = SimpleJson.SimpleJson.SerializeObject(jsonRequest); labelResults.Text = jsonObj; using(Socket sock = CreateOpenSocket()){ sock.Send(System.Text.Encoding.UTF8.GetBytes(jsonObj)); sock.Shutdown(SocketShutdown.Both); sock.Close(); } }; buttonGetScores.ButtonAction += delegate(object sender, TouchEventArgs e) { var jsonRequest = new Dictionary<string, object>(); jsonRequest["action"] = "GetScores"; jsonRequest["name"] = ""; jsonRequest["score"] = 0; string jsonString = SimpleJson.SimpleJson.SerializeObject(jsonRequest); using(var sock = CreateOpenSocket()) { sock.Send(System.Text.Encoding.UTF8.GetBytes(jsonString)); byte[] buffer = new byte[1024]; sock.Receive(buffer); string responseJSON = System.Text.UnicodeEncoding.ASCII.GetString(buffer); var results = SimpleJson.SimpleJson.DeserializeObject<ScoreArray>(responseJSON); foreach(var score in results.Scores) { labelResults.Text += score.Name + " " + score.Score.ToString() + "\r\n"; } sock.Shutdown(SocketShutdown.Both); sock.Close(); } }; } private Socket CreateOpenSocket() { String client_name = Dns.GetHostName(); IPHostEntry client_host = Dns.GetHostEntry(client_name); IPAddress client_ip = client_host.AddressList[0]; IPEndPoint endpoint = new IPEndPoint(client_ip, 1000); var sock = new System.Net.Sockets.Socket( System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); sock.Connect(endpoint); return sock; } } }

 

The first thing we added was the System.Net and System.Net.Sockets using statements, we need these for the networking calls we are going to perform.  Next we declare a pair of really simple classes, ScoreEntry and ScoreArray.  Although JSON is passed around as strings, it needs to be mapped back to usable C# objects.  Our network call is going to return an array of Scores, so we need to have a corresponding C# datatype for SimpleJson to deserialize to.

 

Next up is our constructor, its very critical that you keep InitializeWidget() where it is.  This method is what causes the generated code in MainWindow.designer.cs to execute, and this is where all of our various buttons, labels and text boxes are defined.  Be sure to call InitializeWidgets before you use any of those widgets!

 

Now we want to wire up our two buttons to actually do something when clicked.  You could use an ON_click style method, but I instead went inline with an anonymous method.  First we create a Dictionary object that is going to be translated into our JSON request string.  It’s merely a set of name value/pairs representing the name, high score as well as the action we want to perform.  As you can see, we get the values from our two EditableText widgets, and if you named them differently in designer, you need to update the variable name here accordingly.  Notice in this example I have ZERO hardening, so if you pass invalid values, things will go horribly wrong.

 

Now that we have created our Dictionary of key/value pairs, we pass it to SimpleJson.SerializeObject, which then turns it into an JSON string.  Just for debugging purposes, we display the resulting string to our labelResults.  Now we actually make the network request.  To simplify things a bit, and make the code reusable we created the CreateOpenSocket method, which creates an socket and connects to it.  We will see this method in a moment, again notice the lack of error handling, bad me!

 

Once our socket is created, we convert our string to raw bytes and send them across the socket.  At this point, we are done with it, so we shut it down and close it.  It’s pretty easy to leak ports, so be careful about shutting down your sockets when you are done with them.   At this point, you can now send high score requests to the server, like such:

 

image

 

Fill in a value for name and score, then click Add Score, if everything went right, you should see the JSON string that we passed across the network displayed below our form.  Additionally, if you tab over to your node command window, you should see:

 

image

 

This shows that your score was received, sorted and added to the high score list on the server.  Now lets look at the code behind the GetScores button, you will see that much of it is identical.  Again we start by making a Dictionary for our JSON request object, the format is identical, but in this case the action is GetScores, and the name and score values don’t matter ( although they need to be there ).  Again we serialize to a JSON string and create a UDP socket using CreateOpenSocket().

 

This time though, we are waiting for a reply.  We send our socket just like before, but this time we call Receive() after.  Again, there is no error handling and receive will block your thread preventing execution until finished, in a production environment, you would make things much more tolerant.

 

Receive takes a byte buffer into which to copy the data that it is received.  When you run the Simulator, you may get a firewall prompt, make sure you allow it, or you will never get a response here even though you are connecting on the local machine.  The contents of that buffer are actually just an ASCII string of JSON code, so we simply decode it back to a string.

 

We then take that JSON string and deserialize it, this is where we make use of those two simple classes we defined before, they are the format SimpleJson will deserialize the string into.  After running DeserializeObject, results will contain a ScoreArray, which is simply a List<> of ScoreEntry.  We simply loop through the ScoreEntrys and display the name and score out to our labelResults.  Finally, just like before, we clean up after ourselves and shutdown the socket.

 

Now if you run the code and click the GetScores button, the high scores list will display, including any scores you added using the AddScore button.  These scores will last until you shut down server.js.

 

image

 

And if you flip over to node, you will see:

 

image

 

Finally, we look at the CreateOpenSocket() method.  This is straight .NET code and has nothing at all to do with PS SDK.  First we need the DNS value of the server we are connecting to.  Generally it would be something like “someserver.com”, but since we are running client and server on the same machine, we want our own host name.  We then take that host name and DNS resolve it to an IP address, which is a two step process.  Then we take the resulting IPAddress entry and create an IPEndPoint.  The 1000 value is the port to connect on.  This value was hard coded in the server.js file.  If for some reason you want to connect on a different port, you need to change this line:

server.bind(1000);

 

in server.js.  Next we create our actual socket, in this case UDP.  Why did we use UDP instead of TCP?  No reason really.  TCP is more reliable but a bit slower, but guarantees in order response. None of this matters to use here, so I used UDP.  You can change to TCP by changing a single line here and in server.js.  Finally we connect to our socket and return it.  It is the responsibility of the caller to close the open socket down.

 

 

Now, you have a fully functional networked GUI based highscore client and server.

 

You can download the complete sources here.  That archive includes the Node scripts, so all you need is to download Node in order to run it.

Programming


15. May 2012

 

This part of the tutorial is going to look at using UIComposer to create a simple UI, then the code to actually run the resulting GUI.  This two part tutorial was originally just one part, covering how to make a UDP socket request.  However, in putting together the required code, I had to build an interface, which resulted in a rather long post.  Therefore I have split this into two parts.  This part covers creating the UI, the next part covers wiring up the UI to actually do something and of course, the networking.

 

Once you are done both parts of this tutorial you will have a fully functional networked high score client with a primitive GUI.

 

Now, let’s create our application, fire up UIComposer.

 

File->Create New Project

 

image

 

 

In the Create New Project dialog, name and path it.  I’m going with HighScoreApp.

 

image

 

 

File->Create New Layout

image

 

 

Assuming you are developing for Vita, leave it at 960x544, I named it MainWindow.

 

image

 

 

Now drag in controls from the widget panel:

 

image

 

 

Start with a Panel, the size of the entire window:

 

image

 

 

Drag and drop Label, Button and EditableText widgets to create the following result:

 

image

 

 

Now the most important part is to match my naming or your code will end up being different.  The top two labels names don’t matter.  On the next line, name them from left to right: textBoxName, textBoxScore, buttonAddScore and buttonGetScores.

 

You set the variable name by selecting the widget, then filling in the following form:

 

image

 

 

Variable Name is the field that you are most interested in.

 

Finally, at the bottom of the form, drag in a Label widget to fill the rest of the available space.  Here are the settings I used:

 

 

image

 

 

Most importantly, make sure Variable Name is set to labelResults.

 

Now that we’ve created our screen, lets build it.  Select File->Build or hit F5:

 

image

 

 

Your results should look like:

 

 

image

 

 

Now we are theoretically done with UIComposer, lets fire up PS Studio.  Create a new solution, I called mine Networking.  Add a reference to Sce.Pss.HighLevel.UI.  Now we want to import our newly created UI files.

 

Right click your project and select Add->Add Files…

 

image

 

 

Navigate to the files you just created in UIComposer and add them.  The key here is to Add them as a link.  You don’t have to do this, but if you do, if you go back to the UIComposer and make a change ( and Build ), it will be automatically brought into Studio.

 

 

image

 

 

Add MainWindow.cs and MainWindow.composer.cs.  MainWindow.composer.cs is the system generated file that wires together your windows code; you do not edit it.  Instead, it makes use of partial classes and you do your editing in MainWindow.cs.

 

Now add the following 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.UI; namespace Networking { public class AppMain { public static void Main (string[] args) { GraphicsContext graphics = new GraphicsContext(); UISystem.Initialize(graphics); HighScoreApp.MainWindow window = new HighScoreApp.MainWindow(); UISystem.SetScene(window); while(true) { SystemEvents.CheckEvents(); List<TouchData> touchData = Touch.GetData(0); UISystem.Update (touchData); graphics.SetViewport(0, 0, graphics.Screen.Width, graphics.Screen.Height); graphics.SetClearColor(new Vector4(0,0,0,1)); graphics.SetClearDepth(1.0f); graphics.Clear(); UISystem.Render (); graphics.SwapBuffers(); } } } }

 

This is about the simplest code you can create to display a UI on screen.  First we create our GraphicsContext, then initialize the UISystem singleton with it.  The UISystem singleton is contained in Sce.Pss.HighLevel.UI.  Next we create our UIComposer generated class, HighScoreApp.MainWindow.  If you are wondering where this name came from, the namespace was defined by the Project Name you specified in UIComposer, while the class name is the Class Name from the New Layout dialog.  Just like when working with Director from GameEngine2D, we add the window using SetScene().  Note, these are different scenes and cannot be interchanged.

 

Next we loop infinitely.  In our app loop, we check for new events, check what the current touch status is and pass the results to UISystem.Update().  Again, UISystem follows the same basic premise as the Director singleton, so it’s use should be familiar at this point.  We then clear the screen, tell our UISystem to draw, then finally tell the graphics to SwapBuffers displaying our screen.

 

Here is the results of our actions:

 

image

 

In the next section we will actually put the GUI to use doing something.  In the next part we are going to make a UDP socket connection to a high score server, to add new scores as well as retrieve the current high scores.

 

On to Part 2

Programming


11. May 2012

 

In the last tutorial Working with Sprite Sheets I said that I would cover updating your scene in an upcoming tutorial. This is that tutorial.

 

In this tutorial we are going to cover three different ways that you can update your scene.  As you saw earlier it is possible to update things directly inside your game loop, either directly or using a timer.  However GameEngine2D has a number of superior scheduling options built in, and we will look at 3 different options.  The first is using a Node’s Schedule method, the second is to derive from Node and override Update() and using the Scheduler singleton.  The last is to declare a ActionBase derived object, to create a re-usable updater.  We are going to create 3 bouncing smiling faces, each controlled by a different system.

 

 

We are going to start with the following basic skeleton:

 

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.Core.Imaging; // for Font using Sce.Pss.HighLevel.GameEngine2D; using Sce.Pss.HighLevel.GameEngine2D.Base; namespace Updating { public class AppMain { public static void Main (string[] args) { Director.Initialize(); Scene scene = new Scene(); scene.Camera.SetViewFromViewport(); Director.Instance.RunWithScene(scene); } } }

 

That is about as stripped down a functional program as you can make.  Be sure to add a reference to GameEngine2D.  The Imaging using statement is needed for the Font object that we will use later.  Nothing here should be new to you if you have gone through the earlier tutorials.

 

 

Alright, lets get started using the Schedule method.  First a quick explanation of how Update() works.  Remember in earlier tutorials when you called Director.Instance.Update()?  Well this triggers off a chain reaction of events.  This results in the current scene ( the one specified in the RunWithScene call ) having it’s Update call, which in turn results in all the (registered)Nodes in the Scene having their Update() methods called.  This process happens once per frame. Schedule() marks the node to be called during update, and provides a method to be called when the update occurs.  In this case we are going to declare that method using a Lambda.  Let’s take a look:

 

First off, we declare our sprite:

Texture2D texture = new Texture2D("/Application/smile.png",false); TextureInfo ti = new TextureInfo(texture); SpriteUV sprite = new SpriteUV(ti); sprite.Quad.S = new Vector2(128,128); sprite.Position = scene.Camera.CalcBounds().Center; sprite.Position = new Vector2(sprite.Position.X - 256,sprite.Position.Y); sprite.CenterSprite();

 

Everything here you’ve seen before.  The position is all relative to the middle of the screen.  We will be rendering 3 sprites, this one will be positioned off to the left. Now for the new stuff:

 

bool goingUp = true; sprite.Schedule( (dt) => { if(goingUp) { sprite.Position = new Vector2(sprite.Position.X, sprite.Position.Y + 3); if(sprite.Position.Y >= 390) goingUp = false; } else { sprite.Position = new Vector2(sprite.Position.X, sprite.Position.Y - 3); if(sprite.Position.Y <= 64) goingUp = true; } });

 

Schedule takes a (lambda) function that is called every time Update() is called.  dt is the elapsed time since Update() was last called, although we aren’t using it this time.  Our actual method is pretty straight forward, we have a bool that determines which direction we are going.  We add 3 to our Y direction until we get near the the top of the screen, then flip direction.  Otherwise we are going down, by decrementing by 3 until we get to the bottom.  ( 64 is half of our sprite height ).  The Schedule() method is probably the simplest way to update a Node ( SpriteUV is derived from Node ) object.

 

Next we look at handling updates by deriving from Node ( actually, SpriteUV again ) and overriding the Update() method.  Create a new class, I am calling mine BouncingSmile.cs but you can call it whatever you want.  Enter the following code:

 

 

using System; using Sce.Pss.Core; using Sce.Pss.Core.Graphics; using Sce.Pss.HighLevel.GameEngine2D; using Sce.Pss.HighLevel.GameEngine2D.Base; namespace Updating { public class BouncingSmile : SpriteUV { private bool _goingUp; public BouncingSmile () { Texture2D texture = new Texture2D("/Application/smile.png",true); this.TextureInfo = new TextureInfo(texture); this.Quad.S = new Vector2(128,128); _goingUp = true; } public override void Update (float dt) { if(_goingUp) { this.Position = new Vector2(this.Position.X, this.Position.Y + 3); if(this.Position.Y >= 390) _goingUp = false; } else { this.Position = new Vector2(this.Position.X, this.Position.Y - 3); if(this.Position.Y <= 64) _goingUp = true; } } } }

 

As you can see, the Update() method is basically identical to our lambda method earlier.  Otherwise we are basically just handling the creation of our sprite, just like we did back in the Hello World sample.

 

Now that we have a class with an Update() method defined, lets go back to AppMain.cs and put it to use.

 

BouncingSmile bs = new BouncingSmile(); bs.Position = scene.Camera.CalcBounds().Center; bs.CenterSprite(); Scheduler.Instance.ScheduleUpdateForTarget(bs,1,false);

 

We declare our BouncingSmile object object ( whose constructor took care of creating it’s Sprite ), then position it in the middle of the screen.  Finally we register our object so that it’s Update method is going to be called.  We do this using the Scheduler singleton.  ScheduleUpdateForTarget results in the target ( bs ) having it’s Update() called every frame.  In addition to the target to update, you pass in the priority ( how early in the Scheduler’s update phase it will be called ) and whether or not to start paused.  The end result of this is virtually identical to the earlier Schedule() method.  Our next method is much different however.

 

We are now going to create another class, this one called BounceAction.cs.  Create it, then add the following code:

 

using System; using Sce.Pss.Core; using Sce.Pss.HighLevel.GameEngine2D; using Sce.Pss.HighLevel.GameEngine2D.Base; namespace Updating { public class BounceAction : ActionBase { private bool goingUp; public BounceAction () { goingUp = true; } public override void Update (float dt) { base.Update (dt); if(goingUp) { this.Target.Position = new Vector2(this.Target.Position.X, this.Target.Position.Y + 3); if(this.Target.Position.Y >= 390) goingUp = false; } else { this.Target.Position = new Vector2(this.Target.Position.X, this.Target.Position.Y - 3); if(this.Target.Position.Y <= 64) goingUp = true; } } } }

 

We are creating a new class derived from ActionBase.  Actions are exactly what they sound like, runnable events.  ActionBase is the very base class of action types available.  Unlike our earlier examples, BounceAction isn’t actually tied to any single object, it can be applied to any Node derived object, making it very easy to create reusable/retargetable actions.  Once again, the code looks virtually identical to our prior examples. 

 

Now head back to AppMain.cs and add the following code:

 

Texture2D texture2 = new Texture2D("/Application/smile.png",false); TextureInfo ti2 = new TextureInfo(texture); SpriteUV sprite2 = new SpriteUV(ti); sprite2.Quad.S = new Vector2(128,128); sprite2.Position = scene.Camera.CalcBounds().Center; sprite2.Position = new Vector2(sprite2.Position.X + 256,sprite2.Position.Y); sprite2.CenterSprite(); BounceAction ba = new BounceAction(); ActionManager.Instance.AddAction(ba,sprite2); ba.Run();

 

The majority of that code is just us creating another sprite object.  The key part are the last three lines.  What we are doing here is creating a BounceAction, then registering it with the ActionManager singleton, applying it to our new sprite.  Finally we trigger our action to actually run.  Just like the Scheduler singleton, ActionManager is called once per frame.  The power of using ActionManager is that the action isn’t tied to the thing being acted upon.  There are some powerful things you can do with ActionManager, like queuing up actions up, or blending between actions, etc…

 

 

So there were three different different ways to handle updates with your PS Suite GameEngine2D node objects.  In a moment I will post the completed source for AppMain.cs.  There is however one new concept in the following code, Labels.  In the comments for my earlier Hello World example, I said there were better ways to actually display Hello World on screen, and that way is using the Label class.

 

In order to make a Label, you first need a Font, which in turn needs a FontMap texture. 

 

Font font = new Font(FontAlias.System,16,FontStyle.Bold); FontMap fontMap = new FontMap(font,512);

 

Then it is just a matter of creating your Label:

Label label1 = new Label("Derived from Node",fontMap); label1.Position = new Vector2(0,0);

 

Then add it to the scene to be displayed:

scene.AddChild(label1);

 

And that… is the easiest way to display text on screen.

 

Alright, now lets put everything together.

 

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.Core.Imaging; // for font using Sce.Pss.HighLevel.GameEngine2D; using Sce.Pss.HighLevel.GameEngine2D.Base; namespace Updating { public class AppMain { public static void Main (string[] args) { Director.Initialize(); Font font = new Font(FontAlias.System,16,FontStyle.Bold); FontMap fontMap = new FontMap(font,512); Scene scene = new Scene(); scene.Camera.SetViewFromViewport(); //Center sprite BouncingSmile bs = new BouncingSmile(); bs.Position = scene.Camera.CalcBounds().Center; bs.CenterSprite(); Label label1 = new Label("Derived from Node",fontMap); label1.Position = new Vector2(bs.Position.X - 80, bs.Position.Y + 200); Scheduler.Instance.ScheduleUpdateForTarget(bs,1,false); //Left sprite Texture2D texture = new Texture2D("/Application/smile.png",false); TextureInfo ti = new TextureInfo(texture); SpriteUV sprite = new SpriteUV(ti); sprite.Quad.S = new Vector2(128,128); sprite.Position = scene.Camera.CalcBounds().Center; sprite.Position = new Vector2(sprite.Position.X - 256,sprite.Position.Y); sprite.CenterSprite(); bool goingUp = true; sprite.Schedule( (dt) => { if(goingUp) { sprite.Position = new Vector2(sprite.Position.X, sprite.Position.Y + 3); if(sprite.Position.Y >= 390) goingUp = false; } else { sprite.Position = new Vector2(sprite.Position.X, sprite.Position.Y - 3); if(sprite.Position.Y <= 64) goingUp = true; } }); Label label2 = new Label("Using lambda",fontMap); label2.Position = new Vector2(sprite.Position.X - 60, sprite.Position.Y + 200); //Right sprite Texture2D texture2 = new Texture2D("/Application/smile.png",false); TextureInfo ti2 = new TextureInfo(texture); SpriteUV sprite2 = new SpriteUV(ti); sprite2.Quad.S = new Vector2(128,128); sprite2.Position = scene.Camera.CalcBounds().Center; sprite2.Position = new Vector2(sprite2.Position.X + 256,sprite2.Position.Y); sprite2.CenterSprite(); BounceAction ba = new BounceAction(); ActionManager.Instance.AddAction(ba,sprite2); ba.Run(); Label label3 = new Label("ActionBase",fontMap); label3.Position = new Vector2(sprite2.Position.X - 50, sprite2.Position.Y + 200); scene.AddChild(sprite); scene.AddChild(bs); scene.AddChild (sprite2); scene.AddChild(label1); scene.AddChild(label2); scene.AddChild(label3); Director.Instance.RunWithScene(scene); } } }

 

And here is our finished application:

smilebounce

 

As always, you can download the entire project here. Oh, and this time I actually remember to test it on my Vita! Smile

Programming


11. May 2012

 

Alright, I have been programming long enough I shouldn’t have made this mistake, but… I did.  In the comments section for Working with Spritesheets, there were a couple comments about being unable to run on the device.  I swore when I was testing this I ran it on my actual Vita, but apparently I did not.

 

I really should have, because frankly it doesn’t work.  It just hangs and makes your Vita extremely unhappy with you in the process.  Running it through the debugger, I found it was dying a hideous death on this line:

XDocument doc = XDocument.Load ("application/" + imageDetailsFilename);

 

Hmm, nothing out of the ordinary there.  So off to Google I go!  Ends up that this is a bug in the SDK, welcome to the wonderful world of beta software!  From the same thread, the fix is fairly straight forward.  In Walker.cs change the beginning of the constructor to look like this:

 

//XDocument doc = XDocument.Load ("application/" + imageDetailsFilename); FileStream fileStream = File.Open( "/Application/" + imageDetailsFilename,FileMode.Open); StreamReader fileStreamReader = new StreamReader(fileStream); string xml = fileStreamReader.ReadToEnd(); fileStreamReader.Close(); fileStream.Close(); XDocument doc = XDocument.Parse(xml);

 

You will also need to add SYstem.IO to your using declarations. Now everything will work fine when deployed to an actual Vita.  Hopefully Sony fix the bug in Xdocument.Load soon.

 

 

In the future I will make sure I test all tutorials on my device before pushing them live!

Programming


AppGameKit Studio

See More Tutorials on DevGa.me!

Month List