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 (); } } }