Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


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

blog comments powered by Disqus

Month List

Popular Comments