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:
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 ();
}
}
}
4aad5fad-df08-4079-b59a-d00a189420e1|2|5.0|96d5b379-7e1d-4dac-a6ba-1e50db561b04
Programming
PSSDK, PlayStation Mobile, Tutorial, C#