Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

22. March 2013

 

So, some exciting personal news today, I can finally unveil what I've been working on the last several months, my new book PlayStation Mobile Cookbook!  A special thanks to my reviewers and supporters at Sony and the entire team at Packt, it was a pleasure working with you all on this book.

 

If you have never read one of Packt's "cookbook style" books, its basically composed of a series of recipes illustrating how to perform a particular task

followed by a detailed description of what's happening as well as various tips and tricks.  In this case, its contains over 60 different sample applications illustrating how to do… well, just about everything you want to with the PlayStation Mobile SDK.  If you've run through any of my tutorials, you should have a pretty good idea of what to expect.

 

For a better idea of the contents of the book, here is the Table of Contents:

 

  • Preface

 
  • Chapter 1: Getting Started

    • Introduction
    • Accessing the PlayStation Mobile portal
    • Installing the PlayStation Mobile SDK
    • Creating a simple game loop
    • Loading, displaying, and translating a textured image
    • "Hello World" drawing text on an image
    • Deploying to PlayStation certified Mobile Android devices
    • Deploying to a PlayStation Vita
    • Manipulating an image dynamically
    • Working with the filesystem
    • Handling system events
 
  • Chapter 2: Controlling Your PlayStation Mobile Device

    • Introduction
    • Handling the controller's d-pad and buttons
    • Using the Input2 wrapper class
    • Using the analog joysticks
    • Handling touch events
    • Using the motion sensors
    • Creating onscreen controls for devices without gamepads
    • Configuring an Android application to use onscreen controls
 
  • Chapter 3: Graphics with GameEngine2D

    • Introduction
    • A game loop, GameEngine2D style
    • Creating scenes
    • Adding a sprite to a scene
    • Creating a sprite sheet
    • Using a sprite sheet in code
    • Batching a sprite with SpriteLists
    • Manipulating a texture's pixels
    • Creating a 2D particle system
 
  • Chapter 4: Performing Actions with GameEngine2D

    • Introduction
    • Handling updates with Scheduler
    • Working with the ActionManager object
    • Using predefined actions
    • Transitioning between scenes
    • Simple collision detection
    • Playing background music
    • Playing sound effects
 
  • Chapter 5: Working with Physics2D

    • Introduction
    • Creating a simple simulation with gravity
    • Switching between dynamic and kinematic
    • Creating a (physics!) joint
    • Applying force and picking a physics scene object
    • Querying if a collision occurred
    • Rigid body collision shapes
    • Building and using an external library
 
  • Chapter 6: Working with GUIs

    • Introduction
    • "Hello World" – HighLevel.UI style
    • Using the UI library within a GameEngine2D application
    • Creating and using hierarchies of widgets
    • Creating a UI visually using UIComposer
    • Displaying a MessageBox dialog
    • Handling touch gestures and using UI effects
    • Handling language localization
 
  • Chapter 7: Into the Third Dimension

    • Introduction
    • Creating a simple 3D scene
    • Displaying a textured 3D object
    • Implementing a simple camera system
    • A fragment (pixel) shader in action
    • A vertex shader in action
    • Adding lighting to your scene
    • Using an offscreen frame buffer to take a screenshot
 
  • Chapter 8: Working with the Model Library

    • Introduction
    • Importing a 3D model for use in PlayStation Mobile
    • Loading and displaying a 3D model
    • Using BasicProgram to perform texture and shader effects
    • Controlling lighting using BasicProgram
    • Animating a model
    • Handling multiple animations
    • Using bones to add a sword to our animated model
 
  • Chapter 9: Finishing Touches

    • Introduction
    • Opening and loading a web browser
    • Socket-based client and server networking
    • Accessing (Twitter) data over the network using REST and HttpWebRequest
    • Copying and pasting using Clipboard
    • Embedding and retrieving a resource from the application assembly
    • Configuring your application using PublishingUtility
    • Creating downloadable content (DLC) for your application
 
  • Appendix: Publishing Your Application

    • Introduction
 
  • Index

 

The book is quite literally at the printers right now, I'll post more details once it's available on Amazon, on Safari Books Online or in stores for purchase.  It should be in the next couple of days.  You can of course order the book on the Packt website.

 

Are trying to figure out what to get your grandmother for her birthday?  I have the perfect recommendation! :)

 

Hope you enjoy the book!

, ,

24. February 2013

 

A new beta release of the PlayStation Mobile SDK has just been released.

 

First off, it’s very important to realize this is a *BETA RELEASE*, meaning you can publish with this version.  This version also cannot be installed side by side with the release version.

 

Key changes in this version are:

 

Changes in Version 1.10.00

  • PS3(TM) wireless controller is available on PC Simulator.
  • Screen can be scaled and rendered automatically when actual device screen size is different from specified size in the program.
  • ADPCM format supported in Sound class.
  • New APIs added to SoundPlayer and BgmPlayer.
  • Camera and Location APIs added.
  • Improved CPU performance on PlayStation(R)Vita.
  • Fixed some issues of HTTP/HTTPS connection.
  • Fixed other issues.

 

Some pretty nice changes in this release.  The use of a PS3 controller with the Simulator was a huge missing feature, it’s nice to see it added as you can now test analog controls in the simulator.  Adding GPS and Camera SDKs is also quite nice, as of course are performance fixes.

 

You can read the full details here.

News ,

19. November 2012

 

Over on the PlayStation Mobile forums, a user asked:

For example, if I had a 100 x 100 image, I could use the source rectangle to  draw, say, the left part of the image 50 x 100.  This is useful when I have two images that represent the same object, e.g. a fancy progress meter, one light, the other dark.  SpriteUV has Quad which allows me to stretch and skew the image, but not crop.

Am I missing something? If not, what are my options?

 

My answer wasn’t a short one, so I decided to essentially turn it in to a tutorial post in and of itself.  So, if you want to learn how to do this, or how UV coordinates work in the first place, read on!

 

The answer is, yes, you can very much do this with PSM Studio, but the way might not be completely intuitive.  Each sprite has a TRS set ( Translation/Rotation/Scale ) that represents its UV coordinates within its textures.    Essentially when you work with a SpriteTile, it’s handling all this magic for you.  The good news is, you can manipulate the UV values of a SpriteUV to accomplish exactly the effects described in the question above.  If what you are looking to do is create a sprite sheet animation however, there is a much better way.

 

Alright, let’s jump right in with some code:

 

using System;
using Sce.PlayStation.Core;
using Sce.PlayStation.Core.Graphics;
using Sce.PlayStation.Core.Input;
using Sce.PlayStation.HighLevel.GameEngine2D;
using Sce.PlayStation.HighLevel.GameEngine2D.Base;

namespace Test
{
    public class AppMain
    {
        public static void Main (string[] args)
        {
            Director.Initialize();
            Scene scene = new Scene();
            scene.Camera.SetViewFromViewport();
            
            SpriteUV sprite = new SpriteUV(new TextureInfo("/Application/watch.png"));
            sprite.Scale = new Vector2(Director.Instance.GL.Context.Screen.Width,
                                       Director.Instance.GL.Context.Screen.Height);
            sprite.Schedule((dt) => {
                var g = GamePad.GetData(0);
                if((g.Buttons & GamePadButtons.Up) == GamePadButtons.Up){
                    // Top left
                    sprite.UV.S = new Vector2(0.5f,0.5f);
                    sprite.UV.T = new Vector2(0.0f,0.5f);
                }
                else if((g.Buttons & GamePadButtons.Left) == GamePadButtons.Left){
                    // Bottom left
                    sprite.UV.S = new Vector2(0.5f,0.5f);
                    sprite.UV.T = new Vector2(0.0f,0.0f);
                }
                else if ((g.Buttons & GamePadButtons.Down) == GamePadButtons.Down){
                    // Bottom right                
                    sprite.UV.S = new Vector2(0.5f,0.5f);
                    sprite.UV.T = new Vector2(0.5f,0.0f);
                }
                else if ((g.Buttons & GamePadButtons.Right) == GamePadButtons.Right){
                    // Top right
                    sprite.UV.S = new Vector2(0.5f,0.5f);
                    sprite.UV.T = new Vector2(0.5f,0.5f);
                }
                else if((g.Buttons & GamePadButtons.Cross) == GamePadButtons.Cross){
                    // Back to full screen
                    sprite.UV.S = new Vector2(1.0f,1.0f);
                    sprite.UV.T = new Vector2(0.0f,0.0f);
                }
            },0);
            
            scene.AddChild(sprite);
            Director.Instance.RunWithScene(scene);
        }
    }
}

 

Run this code and you will see:

full

 

Now press an arrow key ( here is the results for UP ) and you will see a fraction of the original texture:

topleft

Press X to go back to the full texture dimensions.

 

So… what’s happening here?

 

First off, we Initialize our Director singleton, create a Scene and set the camera up to match the device screen dimensions.  We then create a SpriteUV and TextureInfo in a single line, loading an image of the famous Watchmen logo.  We then scale the sprite to also match the dimensions of the screen.

 

It’s in the Schedule lambda the bulk of our UV manipulation logic takes place.  As you can see, we get the GamePad state, and handle the user pressing the d-pad or X button ( S key on simulator ).  In the event the user presses a direction button, we manipulate the sprite’s UV.S and UV.T values depending on which direction the user pressed, as indicated by the comment in each if statement.  If the user presses the X button, we set the UV.S and UV.T values back to full size and the origin respectively.  Finally we add the sprite to the scene, then run the scene.

 

The heart of this example is the UV property, which is a TRS object.  If the term UV is unfamiliar to you, they are simply texture coordinates on a face.  They are called UV simply because there are two coordinates, U and V. The UV values  describe how a texture is mapped to a polygon face.  U and V values go from 0 to 1 and start in the bottom left corner of an image.  This means (0,0) is the bottom left corner of the texture, (1,1) is the top right coordinate of the texture while (0.5,0.5) is the very center of the texture.

 

So, using the SpriteUV.UV property, we can alter the Translation ( position ), Rotation and Scale of the selection within SpriteUV’s texture that is going to be drawn.  Think of these values as describing the location of a select box within the texture that are going to be copied and drawn as the texture.  Here is a bad diagram to attempt to explain what’s happening.

 

image

 

The background is our SpriteUV texture.  By default the UV.T is (0,0), meaning drawing will start from the bottom left.  By default the UV.S values will be (1,1), meaning no scaling will occur.  Using these defaults, when we draw our sprite, the entire image will be drawn.

 

Now consider the blue selection rectangle.  This has a UV.T value of (0.3,0.3) and a UV.S value of (0.5,0.5) …. okay… my drawing to scale kind sucked, just imagine that the blue rectange was a quarter of the entire image size.  These values mean that at position 0.3,0.3… or 30% right and 30% up from the origin in the bottom left, we start our selection.  Due to our S(cale) value of (0.5,0.5) it means our selection size is half the height and half the width of the texture.  In this example we are not making use of TR.R(otation).

 

Therefore, assuming the following diagram was to scale, with a Translation UV value of 0.3, 0.3 and Scale UV value of 0.5 and 0.5, when we draw our texture we will see:

image

 

And that is how you can use UV values to draw a portion of a sprite’s texture.  You may notice that SpriteUV has a pair of properties, FlipU and FlipV.  All these do is reverse the direction of the U or V coordinate, for example, flipping U would make (0,0) be at the bottom right of the image instead of the bottom left.

 

You can download the complete archive here.  Obviously the Watchman logo is protected under copyright and cannot be used commercially in any way whatsover, period, ever. For real.

Programming , , ,

19. October 2012

 

Sony, actually… and file this under the category of longest most unwieldy names ever, Sony Computer Entertainment Worldwide Studios Europe External Development Studio (yeah… really) in collaboration with Creative England and Birmingham Science Park have put together a contestimage called PlayStation Pioneers, with a grand prize of £25,000.  That’s about 40K in real money! Smile

 

So, how then do you go about winning this money?  Well, here are the details:

  • The opportunity is open to UK-based developers only
  • The goal is to take a concept/prototype and develop a playable ‘vertical slice’ suitable for consumer trial/user testing in summer 2013.
  • The deadline for submissions is 31st October 2012
  • Five finalists will then be selected to exhibit at this year’s LAUNCH conference taking place at Birmingham Science Park on 13th and 14th November 2012 and present their concepts to a panel.
  • £25,000 will be awarded to the winning entry
  • XDev will have the first right of refusal in regard to publishing the final game.
  • The winner will be announced by the panel at LAUNCH on 14th November 2012.

 

What isn’t explicitly listed in those requirements, your project will be for PlayStation Mobile.

 

That deadline is fast approaching (less than 2 weeks!).  Keep in mind though, you are submitting a *concept* by October 31st, not a complete game. 

 

The other line of note you should be aware of was:

XDev will have the first right of refusal in regard to publishing the final game

In other words, if they like you game, they get first crack at being the publisher.  Given that most indie developers would love to have a publisher, this shouldn’t be a huge deal, but is certainly something you should be aware of.

 

 

If you have never heard of it before, PlayStation Mobile is cross platform game development system based around Mono.  It is C# based, built over OpenGL ES and able to target the PlayStation Vita, as well as select Android mobile devices ( most Sony Android devices, plus select devices from HTC, Asus and a few other manufacturers ).  This site has a series of tutorials you can used to get started.

 

You can read the original announcement thread right here.

 

If you are interested in proceeding, be sure to read this guide (PDF link).  It gives more details of what is involved.  From that document, here is what is recommended in your proposal:

1. Concept summary (ideally one page).
2. A walkthrough/storyboard detailing the proposed ‘vertical slice’ playable.
3. Artwork/Visualisation that is representative of proposed final quality.
4. Design briefs (prototypes where possible) explaining key features.
5. A summary schedule and risk assessment re delivery of the ‘vertical slice’ playable to be delivered for full
consumer trial/user testing in summer 2013.
6. A commercial/financial business model, illustrating market potential for the concept on PS Mobile certified
platforms; (PS Vita, Xperia, Sony Tablet S etc.)
7. Details of relevant prior experience.

 

The PDF however has no more legal issues regarding publishing or IP ownership.

News ,

13. September 2012

 

This post is a simple summary of all the code used in the complete Vita game tutorial series, all on a single page.  Additionally, you can download the complete project here.

 

Here is the end result of all of this code in action:

Vita pong!

 

 

And here is the code:

 

AppMain.cs

using System;
using Sce.PlayStation.HighLevel.UI; 
using Sce.PlayStation.HighLevel.GameEngine2D;

namespace Pong
{
    public class AppMain
    {
        public static void Main (string[] args)
        {
            Director.Initialize();
            UISystem.Initialize(Director.Instance.GL.Context);
            Director.Instance.RunWithScene(new TitleScene());                
        }
    }
}

 

TitleScene.cs

using System;
using Sce.PlayStation.Core;
using Sce.PlayStation.Core.Graphics;
using Sce.PlayStation.Core.Audio;
using Sce.PlayStation.HighLevel.GameEngine2D;
using Sce.PlayStation.HighLevel.GameEngine2D.Base;
using Sce.PlayStation.Core.Input;
 
namespace Pong
{
    public class TitleScene : Scene
    {
        private TextureInfo _ti;
        private Texture2D _texture;
        
        private Bgm _titleSong;
        private BgmPlayer _songPlayer;
        
        public TitleScene ()
        {
            this.Camera.SetViewFromViewport();
            _texture = new Texture2D("Application/images/title.png",false);
            _ti = new TextureInfo(_texture);
            SpriteUV titleScreen = new SpriteUV(_ti);
            titleScreen.Scale = _ti.TextureSizef;
            titleScreen.Pivot = new Vector2(0.5f,0.5f);
            titleScreen.Position = new Vector2(Director.Instance.GL.Context.GetViewport().Width/2,
                                              Director.Instance.GL.Context.GetViewport().Height/2);
            this.AddChild(titleScreen);
            
            Vector4 origColor = titleScreen.Color;
            titleScreen.Color = new Vector4(0,0,0,0);
            var tintAction = new TintTo(origColor,10.0f);
            ActionManager.Instance.AddAction(tintAction,titleScreen);
            tintAction.Run();
            
            _titleSong = new Bgm("/Application/audio/titlesong.mp3");
            
            if(_songPlayer != null)
            _songPlayer.Dispose();
            _songPlayer = _titleSong.CreatePlayer();
            
            Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);

            // Clear any queued clicks so we dont immediately exit if coming in from the menu
            Touch.GetData(0).Clear();
        }
        
        public override void OnEnter ()
        {
            _songPlayer.Loop = true;
            _songPlayer.Play();
        }
        public override void OnExit ()
        {
            base.OnExit ();
            _songPlayer.Stop();
            _songPlayer.Dispose();
            _songPlayer = null;
        }
        
        public override void Update (float dt)
        {
            base.Update (dt);
            var touches = Touch.GetData(0).ToArray();
            if((touches.Length >0 && touches[0].Status == TouchStatus.Down) || Input2.GamePad0.Cross.Press)
            {
                Director.Instance.ReplaceScene(new MenuScene());
            }
        }
    
        ~TitleScene()
        {
            _texture.Dispose();
            _ti.Dispose ();
        }
    }
}
 

MenuScene.cs

using System;
using Sce.PlayStation.Core;
using Sce.PlayStation.Core.Graphics;
using Sce.PlayStation.Core.Input;

using Sce.PlayStation.HighLevel.GameEngine2D;
using Sce.PlayStation.HighLevel.GameEngine2D.Base;

using Sce.PlayStation.HighLevel.UI;

namespace Pong
{
    public class MenuScene : Sce.PlayStation.HighLevel.GameEngine2D.Scene
    {
        private Sce.PlayStation.HighLevel.UI.Scene _uiScene;
        
        public MenuScene ()
        {
            this.Camera.SetViewFromViewport();
            Sce.PlayStation.HighLevel.UI.Panel dialog = new Panel();
            dialog.Width = Director.Instance.GL.Context.GetViewport().Width;
            dialog.Height = Director.Instance.GL.Context.GetViewport().Height;
            
            ImageBox ib = new ImageBox();
            ib.Width = dialog.Width;
            ib.Image = new ImageAsset("/Application/images/title.png",false);
            ib.Height = dialog.Height;
            ib.SetPosition(0.0f,0.0f);
            
            Button buttonUI1 = new Button();
            buttonUI1.Name = "buttonPlay";
            buttonUI1.Text = "Play Game";
            buttonUI1.Width = 300;
            buttonUI1.Height = 50;
            buttonUI1.Alpha = 0.8f;
            buttonUI1.SetPosition(dialog.Width/2 - 150,200.0f);
            buttonUI1.TouchEventReceived += (sender, e) => {
                Director.Instance.ReplaceScene(new GameScene());
            };
            
            Button buttonUI2 = new Button();
            buttonUI2.Name = "buttonMenu";
            buttonUI2.Text = "Main Menu";
            buttonUI2.Width = 300;
            buttonUI2.Height = 50;
            buttonUI2.Alpha = 0.8f;
            buttonUI2.SetPosition(dialog.Width/2 - 150,250.0f);
            buttonUI2.TouchEventReceived += (sender, e) => {
            Director.Instance.ReplaceScene(new TitleScene());
            };        
                
            dialog.AddChildLast(ib);
            dialog.AddChildLast(buttonUI1);
            dialog.AddChildLast(buttonUI2);
            _uiScene = new Sce.PlayStation.HighLevel.UI.Scene();
            _uiScene.RootWidget.AddChildLast(dialog);
            UISystem.SetScene(_uiScene);
            Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);
        }
        public override void Update (float dt)
        {
            base.Update (dt);
            UISystem.Update(Touch.GetData(0));
            
        }
        
        public override void Draw ()
        {
            base.Draw();
            UISystem.Render ();
        }
        
        ~MenuScene()
        {
            
        }
    }
}

GameScene.cs

using System;
using Sce.PlayStation.Core;
using Sce.PlayStation.HighLevel.GameEngine2D;
using Sce.PlayStation.HighLevel.GameEngine2D.Base;
using Sce.PlayStation.HighLevel.Physics2D;
using Sce.PlayStation.Core.Audio;

namespace Pong
{
    public class GameScene : Scene
    {
    private Paddle _player,_ai;
    public static Ball ball;
    private PongPhysics _physics;
    private Scoreboard _scoreboard;
    private SoundPlayer _pongBlipSoundPlayer;
    private Sound _pongSound;
        
        // Change the following value to true if you want bounding boxes to be rendered
        private static Boolean DEBUG_BOUNDINGBOXS = false;
        
        public GameScene ()
        {
            this.Camera.SetViewFromViewport();
            _physics = new PongPhysics();

            
            ball = new Ball(_physics.SceneBodies[(int)PongPhysics.BODIES.Ball]);
            _player = new Paddle(Paddle.PaddleType.PLAYER, 
                                 _physics.SceneBodies[(int)PongPhysics.BODIES.Player]);
            _ai = new Paddle(Paddle.PaddleType.AI, 
                             _physics.SceneBodies[(int)PongPhysics.BODIES.Ai]);
            _scoreboard = new Scoreboard();
            
            this.AddChild(_scoreboard);
            this.AddChild(ball);
            this.AddChild(_player);
            this.AddChild(_ai);
            
            
            // This is debug routine that will draw the physics bounding box around the players paddle
            if(DEBUG_BOUNDINGBOXS)
            {
                this.AdHocDraw += () => {
                    var bottomLeftPlayer = _physics.SceneBodies[(int)PongPhysics.BODIES.Player].AabbMin;
                    var topRightPlayer = _physics.SceneBodies[(int)PongPhysics.BODIES.Player].AabbMax;
                    Director.Instance.DrawHelpers.DrawBounds2Fill(
                        new Bounds2(bottomLeftPlayer*PongPhysics.PtoM,topRightPlayer*PongPhysics.PtoM));

                    var bottomLeftAi = _physics.SceneBodies[(int)PongPhysics.BODIES.Ai].AabbMin;
                    var topRightAi = _physics.SceneBodies[(int)PongPhysics.BODIES.Ai].AabbMax;
                    Director.Instance.DrawHelpers.DrawBounds2Fill(
                        new Bounds2(bottomLeftAi*PongPhysics.PtoM,topRightAi*PongPhysics.PtoM));

                    var bottomLeftBall = _physics.SceneBodies[(int)PongPhysics.BODIES.Ball].AabbMin;
                    var topRightBall = _physics.SceneBodies[(int)PongPhysics.BODIES.Ball].AabbMax;
                    Director.Instance.DrawHelpers.DrawBounds2Fill(
                        new Bounds2(bottomLeftBall*PongPhysics.PtoM,topRightBall*PongPhysics.PtoM));
                };
            }
            
            //Now load the sound fx and create a player
            _pongSound = new Sound("/Application/audio/pongblip.wav");
            _pongBlipSoundPlayer = _pongSound.CreatePlayer();
            
            Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);
        }
        
        private void ResetBall()
        {
            //Move ball to screen center and release in a random directory
            _physics.SceneBodies[(int)PongPhysics.BODIES.Ball].Position = 
                new Vector2(Director.Instance.GL.Context.GetViewport().Width/2,
                            Director.Instance.GL.Context.GetViewport().Height/2) / PongPhysics.PtoM;
            
            System.Random rand = new System.Random();
            float angle = (float)rand.Next(0,360);
        
            if((angle%90) <=15) angle +=15.0f;
        
            _physics.SceneBodies[(int)PongPhysics.BODIES.Ball].Velocity = 
                new Vector2(0.0f,5.0f).Rotate(PhysicsUtility.GetRadian(angle));
        }
        
        public override void Update (float dt)
        {
            base.Update (dt);
            
            if(Input2.GamePad0.Select.Press)
                Director.Instance.ReplaceScene(new MenuScene());
            
            //We don't need these, but sadly, the Simulate call does.
            Vector2 dummy1 = new Vector2();
            Vector2 dummy2 = new Vector2();
            
            //Update the physics simulation
            _physics.Simulate(-1,ref dummy1,ref dummy2);
            
            //Now check if the ball it either paddle, and if so, play the sound
            if(_physics.QueryContact((uint)PongPhysics.BODIES.Ball,(uint)PongPhysics.BODIES.Player) ||
                _physics.QueryContact((uint)PongPhysics.BODIES.Ball,(uint)PongPhysics.BODIES.Ai))
            {
                if(_pongBlipSoundPlayer.Status == SoundStatus.Stopped)
                    _pongBlipSoundPlayer.Play();
            }
            
            //Check if the ball went off the top or bottom of the screen and update score accordingly
            Results result = Results.StillPlaying;
            bool scored = false;
            
            if(ball.Position.Y > Director.Instance.GL.Context.GetViewport().Height + ball.Scale.Y/2)
            {
                result = _scoreboard.AddScore(true);
                scored = true;
            }
            if(ball.Position.Y < 0 - ball.Scale.Y/2)
            {
                result =_scoreboard.AddScore(false);
                scored = true;
            }
            
            // Did someone win?  If so, show the GameOver scene
            if(result == Results.AiWin) 
                Director.Instance.ReplaceScene(new GameOverScene(false));
            if(result == Results.PlayerWin) 
                Director.Instance.ReplaceScene(new GameOverScene(true));
            
            //If someone did score, but game isn't over, reset the ball position to the middle of the screen
            if(scored == true)
            {
                ResetBall ();
            }
            
            //Finally a sanity check to make sure the ball didn't leave the field.
            var ballPB = _physics.SceneBodies[(int)PongPhysics.BODIES.Ball];
            
            if(ballPB.Position.X < -(ball.Scale.X/2f)/PongPhysics.PtoM ||
               ballPB.Position.X > (Director.Instance.GL.Context.GetViewport().Width)/PongPhysics.PtoM)
            {
                ResetBall();
            }
        }
        
        ~GameScene(){
            _pongBlipSoundPlayer.Dispose();
        }
    }
}

Ball.cs

using System;
using Sce.PlayStation.Core;
using Sce.PlayStation.Core.Graphics;
using Sce.PlayStation.HighLevel.GameEngine2D;
using Sce.PlayStation.HighLevel.GameEngine2D.Base;
using Sce.PlayStation.HighLevel.Physics2D;

namespace Pong
{
    public class Ball : SpriteUV
    {
        private PhysicsBody _physicsBody;
        // Change this value to make the game faster or slower
        public const float BALL_VELOCITY = 5.0f;
        
        public Ball (PhysicsBody physicsBody)
        {
            _physicsBody = physicsBody;
            
            this.TextureInfo = new TextureInfo(new Texture2D("Application/images/ball.png",false));
            this.Scale = this.TextureInfo.TextureSizef;
            this.Pivot = new Sce.PlayStation.Core.Vector2(0.5f,0.5f);
            this.Position = new Sce.PlayStation.Core.Vector2(
                Director.Instance.GL.Context.GetViewport().Width/2 -Scale.X/2,
                Director.Instance.GL.Context.GetViewport().Height/2 -Scale.Y/2);
            
            
            //Right angles are exceedingly boring, so make sure we dont start on one
            //So if our Random angle is between 90 +- 25 degrees or 270 +- 25 degrees
            //we add 25 degree to value, ie, making 90 into 115 instead
            System.Random rand = new System.Random();
            float angle = (float)rand.Next(0,360);
        
            if((angle%90) <=25) angle +=25.0f;
            this._physicsBody.Velocity = new Vector2(0.0f,BALL_VELOCITY).Rotate(PhysicsUtility.GetRadian(angle));;
            
            Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);
        }
        
        public override void Update (float dt)
        {

            this.Position = _physicsBody.Position * PongPhysics.PtoM;

            
            // We want to prevent situations where the balls is bouncing side to side
            // so if there isnt a certain amount of movement on the Y axis, set it to + or - 0.2 randomly
            // Note, this can result in the ball bouncing "back", as in it comes from the top of the screen
            // But riccochets back up at the user.  Frankly, this keeps things interesting imho
            var normalizedVel = _physicsBody.Velocity.Normalize();
            if(System.Math.Abs (normalizedVel.Y) < 0.2f) 
            {
                System.Random rand = new System.Random();
                if(rand.Next (0,1) == 0)
                    normalizedVel.Y+= 0.2f;
                
                else
                    normalizedVel.Y-= 0.2f;
            }
            
            // Pong is a mess with physics, so just fix the ball velocity
            // Otherwise the ball could get faster and faster ( or slower ) on each collision
            _physicsBody.Velocity = normalizedVel * BALL_VELOCITY;

        }
        
        ~Ball()
        {
            this.TextureInfo.Texture.Dispose();
            this.TextureInfo.Dispose();
        }
    }
}

Paddle.cs

using System;
using Sce.PlayStation.Core;
using Sce.PlayStation.Core.Graphics;
using Sce.PlayStation.HighLevel.GameEngine2D;
using Sce.PlayStation.HighLevel.GameEngine2D.Base;
using Sce.PlayStation.HighLevel.Physics2D;

namespace Pong
{
    public class Paddle : SpriteUV
    {
        public enum PaddleType { PLAYER, AI };
        
        private PaddleType _type;
        private PhysicsBody _physicsBody;
        private float _fixedY;
        
        public Paddle (PaddleType type, PhysicsBody physicsBody)
        {
            _physicsBody = physicsBody;
            _type = type;

            this.TextureInfo = new TextureInfo(new Texture2D("Application/images/Paddle.png",false));
            this.Scale = this.TextureInfo.TextureSizef;
            this.Pivot = new Sce.PlayStation.Core.Vector2(0.5f,0.5f);
            
            if(_type== PaddleType.AI)
            {
                this.Position = new Sce.PlayStation.Core.Vector2(
                    Director.Instance.GL.Context.GetViewport().Width/2 - this.Scale.X/2,
                    10 + this.Scale.Y/2);                    
            }
            else
            {
                this.Position = new Sce.PlayStation.Core.Vector2(
                    Director.Instance.GL.Context.GetViewport().Width/2 - this.Scale.X/2,
                    Director.Instance.GL.Context.GetViewport().Height - this.Scale.Y/2 - 10);
            }
            
            // Cache the starting Y position, so we can reset and prevent any vertical movement from the Physics Engien
            _fixedY = _physicsBody.Position.Y;
            
            // Start with a minor amount of movement
            _physicsBody.Force = new Vector2(-10.0f,0);
            
            Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);
        }
        
        // This method will fix the physics bounding box to the sprites current position
        // Not currently used, was used for debug, left in for interest sake only
        private void ClampBoundingBox()
        {
            var bbBL = new Vector2(Position.X- Scale.X/2, Position.Y- Scale.Y/2) / PongPhysics.PtoM;
            var bbTR = new Vector2(Position.X+ Scale.X/2, Position.Y+ Scale.Y/2) / PongPhysics.PtoM;
            _physicsBody.AabbMin = bbBL;
            _physicsBody.AabbMax = bbTR;
            
        }
        public override void Update (float dt)
        {
            // Reset rotation to prevent "spinning" on collision
            _physicsBody.Rotation = 0.0f;
            
            
            if(_type == PaddleType.PLAYER)
            {
                if(Input2.GamePad0.Left.Down)
                {
                    _physicsBody.Force = new Vector2(-30.0f,0.0f);
                }
                if(Input2.GamePad0.Right.Down)
                {
                    _physicsBody.Force = new Vector2(30.0f,0.0f);
                }
            }
            else if(_type == PaddleType.AI)
            {
                if(System.Math.Abs (GameScene.ball.Position.X - this.Position.X) <= this.Scale.Y/2)
                    _physicsBody.Force = new Vector2(0.0f,0.0f);
                else if(GameScene.ball.Position.X < this.Position.X)
                    _physicsBody.Force = new Vector2(-20.0f,0.0f);
                else if(GameScene.ball.Position.X > this.Position.X)
                    _physicsBody.Force = new Vector2(20.0f,0.0f);
            }
            
            //Prevent vertical movement on collision.  Could also implement by making paddle Kinematic
            //However, lose ability to use Force in that case and have to use AngularVelocity instead
            //which results in more logic in keeping the AI less "twitchy", a common Pong problem
            if(_physicsBody.Position.Y != _fixedY)
                _physicsBody.Position = new Vector2(_physicsBody.Position.X,_fixedY);
            
            this.Position = _physicsBody.Position * PongPhysics.PtoM;
        }
        
        ~Paddle()
        {
            this.TextureInfo.Texture.Dispose ();
            this.TextureInfo.Dispose();
        }
    }
}

Scoreboard.cs

using System;

using Sce.PlayStation.Core.Imaging;
using Sce.PlayStation.Core.Graphics;
using Sce.PlayStation.Core;
using Sce.PlayStation.HighLevel.GameEngine2D;
using Sce.PlayStation.HighLevel.GameEngine2D.Base;

namespace Pong
{
    public enum Results { PlayerWin, AiWin, StillPlaying };
    
    public class Scoreboard : SpriteUV
    {
        public int playerScore = 0;
        public int aiScore = 0;
        
        public Scoreboard ()
        {
            this.TextureInfo = new TextureInfo();
            UpdateImage();
            
            this.Scale = this.TextureInfo.TextureSizef;
            this.Pivot = new Vector2(0.5f,0.5f);
            this.Position = new Vector2(Director.Instance.GL.Context.GetViewport().Width/2,
                                        Director.Instance.GL.Context.GetViewport().Height/2);
            
        }
        
        private void UpdateImage()
        {
            Image image = new Image(ImageMode.Rgba,new ImageSize(110,100),new ImageColor(0,0,0,0));
            Font font = new Font(FontAlias.System,50,FontStyle.Regular);
            image.DrawText(playerScore + " - " + aiScore,new ImageColor(255,255,255,255),font,new ImagePosition(0,0));
            image.Decode();

            var texture  = new Texture2D(110,100,false,PixelFormat.Rgba);
            if(this.TextureInfo.Texture != null)
                this.TextureInfo.Texture.Dispose();
            this.TextureInfo.Texture = texture;
            texture.SetPixels(0,image.ToBuffer());
            font.Dispose();
            image.Dispose();
        }
        public void Clear()
        {
            playerScore = aiScore = 0;
            UpdateImage();
        }
        
        public Results AddScore(bool player)
        {
            if(player)
                playerScore++;
            else
                aiScore++;
            if(playerScore > 3) return Results.PlayerWin;
            if(aiScore > 3) return Results.AiWin;
            
            UpdateImage();

            return Results.StillPlaying;
        }
    }
}

GameOver.cs

using System;
using Sce.PlayStation.Core;
using Sce.PlayStation.Core.Graphics;
using Sce.PlayStation.Core.Audio;
using Sce.PlayStation.HighLevel.GameEngine2D;
using Sce.PlayStation.HighLevel.GameEngine2D.Base;
using Sce.PlayStation.Core.Input;

namespace Pong
{
    public class GameOverScene : Scene
    {
        private TextureInfo _ti;
        private Texture2D _texture;
        
        public GameOverScene (bool win)
        {
            this.Camera.SetViewFromViewport();
            if(win)
                _texture = new Texture2D("Application/images/winner.png",false);
            else
                _texture = new Texture2D("Application/images/loser.png",false);
            _ti = new TextureInfo(_texture);
            SpriteUV titleScreen = new SpriteUV(_ti);
            titleScreen.Scale = _ti.TextureSizef;
            titleScreen.Pivot = new Vector2(0.5f,0.5f);
            titleScreen.Position = new Vector2(Director.Instance.GL.Context.GetViewport().Width/2,
                                               Director.Instance.GL.Context.GetViewport().Height/2);
            this.AddChild(titleScreen);
            
            Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);
            
            Touch.GetData(0).Clear();
        }
        
        public override void Update (float dt)
        {
            base.Update (dt);
            int touchCount = Touch.GetData(0).ToArray().Length;
            if(touchCount > 0 || Input2.GamePad0.Cross.Press)
            {
                Director.Instance.ReplaceScene( new TitleScene());
            }
        }
        
        ~GameOverScene()
        {
            _texture.Dispose();
            _ti.Dispose ();
        }
    }
}

Programming , , ,

Month List

Popular Comments