Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

30. April 2012
BlenderOnVita

 

This is part 2 of our series looking at getting a fully textured model from Blender to the PlayStation Vita, or at least, the simulator.

 

In part 1 we created and exported our textured model, a simple 6 sided die.  Now we are going to convert it into a format the PlayStation Suite can make use of.  To do this, we make use of a command line tool called ModelConverter. ModelConverter was installed when you installed the PSSDK.  If you haven’t already installed it, go install it now.  For details about what is installed by the SDK and where take a look at this post.  I will assume you installed to the default drive and directory, if you haven’t adjust your directories accordingly.  If you aren’t using a 64bit copy of Windows, it is Program Files instead of Program Files (X86).

 

 

ModelConverter is located at C:\Program Files (X86)\SCE\Pss\tools\ModelConverter, open a dos prompt and CD to that directory.  Now you want to run modelconverter and point it at your dae file.  I saved mine ( and associated textures ) in c:\temp, so I run “modelconverter c:\temp\box.dae”.  The results should look like:

 

image

 

( ModelConverter can handle Fbx, dae, xsi and x file formats ).

 

You have now created a new file named Box.mdx, in the same folder as your COLLADA file.  We now want to use this model in PlayStation Suite Studio.  Let’s fire it up, and create a new solution called ModelViewer.  If you don’t know how to create a project using PS Suite Studio, take a quick look at this post.  Now that we have our project, double click AppMain.cs and enter the following code (replacing whatever is there already):

 

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.Model; namespace ModelViewer { public class AppMain { private static GraphicsContext graphics; private static BasicModel model; private static BasicProgram program; private static float cameraAngleX = 0.0f; private static float cameraAngleY = 0.0f; public static void Main (string[] args) { Initialize (); while (true) { SystemEvents.CheckEvents (); Update (); Render (); } } public static void Initialize () { // Set up the graphics system graphics = new GraphicsContext (); program = new BasicProgram(); model = new BasicModel("/Application/box.mdx",0); } public static void Update () { // Query gamepad for current state var gamePadData = GamePad.GetData (0); if((gamePadData.Buttons & GamePadButtons.Up) != 0) cameraAngleX += 1.1f; if((gamePadData.Buttons & GamePadButtons.Down) != 0) cameraAngleX -= 1.1f; if((gamePadData.Buttons & GamePadButtons.Left) != 0) cameraAngleY += 1.1f; if((gamePadData.Buttons & GamePadButtons.Right) != 0) cameraAngleY -= 1.1f; if(cameraAngleX < 0.0f) cameraAngleX = 360.0f; if(cameraAngleX > 360.0f) cameraAngleX = 0.0f; if(cameraAngleY < 0.0f) cameraAngleY = 360.0f; if(cameraAngleY > 360.0f) cameraAngleY = 0.0f; } public static void Render () { // Clear the screen graphics.SetClearColor (1.0f, 1.0f, 1.0f, 0.0f); graphics.Clear (); Matrix4 projectionMatrix = Matrix4.Perspective(FMath.Radians(45.0f),graphics.Screen.AspectRatio, 1.0f, 100000.0f); Matrix4 viewMatrix = Matrix4.LookAt( new Vector3(0.0f,5.0f,5.0f), new Vector3(0.0f,0.0f,0.0f), new Vector3(0.0f,1.0f,0.0f)); Vector3 litDirection = new Vector3( 0.5f, -1.0f, -1.0f ).Normalize(); Vector3 litColor = new Vector3( 1.0f, 1.0f, 1.0f ); BasicParameters parameters = program.Parameters; parameters.Enable(BasicEnableMode.Lighting, true); parameters.SetViewMatrix(ref viewMatrix); parameters.SetLightCount(1) ; parameters.SetLightDirection(0, ref litDirection); parameters.SetLightDiffuse( 0, ref litColor); parameters.SetLightSpecular(0, ref litColor); parameters.SetProjectionMatrix(ref projectionMatrix); Matrix4 world = Matrix4.RotationXyz(FMath.Radians(cameraAngleX),FMath.Radians(cameraAngleY),0.0f); graphics.SetViewport(0,0,graphics.Screen.Width,graphics.Screen.Height ); graphics.Enable(EnableMode.Blend); graphics.Enable(EnableMode.CullFace) ; graphics.SetCullFace(CullFaceMode.Back, CullFaceDirection.Ccw) ; graphics.Enable(EnableMode.DepthTest); graphics.SetDepthFunc(DepthFuncMode.LEqual, true); model.SetWorldMatrix(ref world); model.Update(); model.Draw(graphics,program); graphics.SwapBuffers (); graphics.Disable(EnableMode.CullFace); graphics.Disable(EnableMode.DepthTest); } } }

 

 

EDIT: You also need to add a reference to Sce.Pss.HighLevel.Model.  If you don’t know how to add a reference, refer to this post.

 

 

I am not going to go into a ton of detail about the code for two reasons.  One, its mostly just a stripped down version of the BasicModelSample included in the SDK and two, I am going to cover 3D in a bit more detail in a later tutorial.  So here is the ultra quick version of what’s happening.

 

In Main, we have our primary event loop, which calls Inititalize(), then simply loops forever, getting events, then calling Update() and Render().  Initialize sets up our required objects, the GraphicsContext, BasicProgram and most importantly, our BasicModel.  The parameter to the BasicModel is the file path to where our mdx file is stored ( after being imported into Studio, not on your hard drive ).  If you named your model anything other than box.dae, you need to change it here!

 

Next up is Update(), which is called once per frame before drawing.  All I am doing here is checking the status of the gamepad ( not the analog pads ) and if it is up or down, I set the cameraAngleX variable to increment or decrement 1.1 degrees in that direction.  I do the same thing then for left and right, this time updating the variable cameraAngleY.  Finally I make sure that we aren’t trying to rotate more than 360 degrees, or less than 0 degree.  For more details on handling input, see here.

 

Next we come to Render, which is responsible for drawing the frame to the screen.  First we set up the required viewing matrixes for our screen, create a light and a BasicParameters object that is going to tell our renderer how to render ( via program ).  The lighting details are part of this parameters object, as are our view and projection matrix.  Next we set up our world matrix, representing “our” or more accurately “our camera’s” location in the world, by transforming it relative to our rotation values we set with earlier in update. This effectively rotates the camera relative to our two rotation variables.

 

Finally we set up our graphics object, which is actually used to render our scene.   Set the various rendering options, update our model using our world matrix, render the model via it’s Draw() method, passing in our graphics and program object ( which contains are parameters object ).  Finally we tell it to SwapBuffers, which causes the scene we’ve been rendering to be displayed on screen.

 

I know that grossly glossed over what was happening here, but to go into much more detail would require a couple dozen more pages.  We will cover this stuff later, I promise.

 

 

Ok, now that we have code capable of displaying our model, we need to import it into Studio.  In the Solution window on the left hand side of Studio, right click the ModelViewer project and select Add Files…

 

image

 

 

In the resulting dialog, select your model and texture, in my case box.dae and dice.png.  Click Open.

 

image

 

Once you have selected your file, the following dialog will appear:

 

image

 

Choose “Add a link” if you want to keep your modeling project separate from your code, this personally is what I do.  The nice thing with this setup is if you run modelconverter on your model again, the changes are automatically updated in Studio.  If you choose copy, you will have to reimport each time you change.  The choice ultimately is yours.

 

 

Now is the very key part.  You need to tell PlayStation Suite Studio how to handle these files.  Your png texture will automatically be set right, but your mdx file will not.  Files are actually copied into different directory when your project is built, depending on if you are creating a debug or deployment build.  This is the “/Application” folder we saw earlier.  You need to tell PS Studio that your mdx file is a content file, and should be copied to the right folder when you do a build.  To do this, right click the file box.mdx in the Solution Viewer and select Build Action->Content, like such:

 

image

 

 

Now you should be able to run your application.  If everything worked properly, you should see the following.  Use the gamepad(arrow keys in simulator) to rotate the camera left, right, up and down.

 

 

BlendToPssResults

 

 

And now, you have successfully exported a fully textured model from Blender to a PlayStation Vita application!

 

 

One thing you may notice is your texture looks rather washed out, I found this as well, and currently am unable to figure out why.  The results are even worse when you export as FBX, while astonishingly your results are vastly improved exporting as X ( DirectX format ), as you can see below:

 

DifferentVersions

 

 

I am not completely sure what is responsible at this point, my code, Blender or modelconverter, but as it stands , X format gives basically identical results to what you started with in Blender.  Once I figure out what is responsible, I will update here.

 

 

That concludes how to export from Blender to PlayStation and trust me, it is nowhere near as complex as it looks.  As you can see in the video, the entire process takes less than 4 minutes.

 

EDIT:  Oops, forgot the project code.  Here is a zip of the project file.

Art, Programming , , , ,

29. April 2012

 

 

One of the most bizarre things missing from the PlayStation simulator was support for analog imagesticks, or gamepad support in general.  I had just assumed that you would be able to plug a gamepad into your PC to control the simulator, but I was wrong.  A keyboard only solution made owning an actual device pretty much an absolute requirement.  Fortunately a user on the PlayStation Suite Developer Forums YamatoKei released a code driven solution, which in his own words:

 

 

 

The simulator's input layout and limitations weren't to my taste, so I made myself a way around it:

Run a TCP server that reads PC's gamepad state and sends it back on a client request. And add a simple utility class/lib to connect to that server + decode data transparently from within the game. With a one-line change to revert-back to using the native input on real devices.

Uploaded here: http://dl.dropbox.com/u/1969613/openglForum/PadServer.7z

Code to use:

static ILXPAD pad1 = new ILXPAD_Net();
//static ILXPAD pad1 = new ILXPAD_Vita();
...
pad1.start();
...
pad1.update();
...
camera.roty -= pad1.RStick.X;
bool jump = pad1.Cur.CROSS;

Cheers. :Wink:

 

Just what the doctor ordered!

 

[Download Link]

 

Buyer beware, I haven’t actually tried this out, so if it doesn’t work, destroys your computer or causes a horde of angry cows to raze your house, I take no responsibility.

Programming ,

28. April 2012

 

I am going to continue to develop a number of PlayStation Suite SDK tutorials and finding them could start getting confusing.  Therefore I have put together an index page off all tutorials I have created and will continue to update it as I create more.  Tutorials are ordered in more or less chronological order that a developer should read them in.  I will put this in the side bar shortly, for now you can access the PS tutorial index here.

Programming ,

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

Month List

Popular Comments