Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


11. September 2012

 

In this part, we are going to create the GameScene.cs, the backbone of our game.  Most of the topics we cover here we have actually seen already in prior tutorials.  Let’s jump right in:

 

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

 

Alright, taking it from the top:

 

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;

 

Here we declare a number of member variables.  We create a Ball object, a Scoreboard object and two Paddle objects, all of which are derived from SpriteUV.  We will cover these in more detail shortly.  We also create an instance of the PhysicsScene we just defined in the last part, PongPhysics.  Next we declare a SoundPlayer and Sound for playing our pong “blip”.  Finally we have a const Boolean value, DEBUG_BOUNDINGBOXS, which you switch to true if you want object bounding boxes on the screen.  This is debug code I originally used to debug a problem and it proved handy so I left it in.

 

At the beginning of the constructor, we:

 

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

 

First we configure our Scene’s camera then create our PongPhysics object. Here we create our Ball, Paddles and Scoreboard objects.  For the Ball, we pass in the index value of the ball rigid body, that we defined in the handy enum PongPhysics.BODIES.  We also pass the PhysicsBody for each Paddle.  We also pass in the Paddle.PaddleType we want to create, telling it whether to create a player or ai paddle.  Obviously, we want one of each.  We then create a Scoreboard object, we will cover each of these shortly.  Finally, we add all four objects to the scene.

 

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

 

This is the code that runs if you define DEBUG_BOUNDINGBOXS to true.  Basically it registers an AdHocDraw method, which is a drawing routine that runs outside the normal page draw lifecycle, a way to define custom drawing behavior without having to inherit and override the Draw method.  Basically we simply get the bounding box of each physics object, translate it to screen space and draw it on screen using the DrawHelper.DrawBounds2Fill method.

 

_pongSound = new Sound("/Application/audio/pongblip.wav");
_pongBlipSoundPlayer = _pongSound.CreatePlayer();

Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);

Next would load our sound effect and create a SoundPlayer for it.  Finally, we register our scene to receive updates.

 

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

 

This simple method resets the game ball back to the center of the screen.  We then set a random direction for it, by getting a random degree from 0 to 360.  We then make sure that the angle is within 15 degrees of 90 or 270 degrees, meaning it is going straight at the side boards.  In this case, we add 15 degrees to the direction to keep this from happening.  This has the unfortunate side effect of overly punishing the top player, but I am ok with that… that’s the AI. Smile  You can easily add more logic to this method to remove this limitation, or simply randomize the addition or subtraction.  Finally, we take our calculated angle, convert it to radians and rotate our velocity vector by it. 

 

Now lets look at our Update method in chunks.

 

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

 

Here we first check to see if the user has pressed the Select button, if so, we replace the game scene with the menu scene, end result is it shows the main menu.  The Simulate method ( we will see shortly ), requires a pair of Vector2’s, neither of which we need so we call them Dummy1 and Dummy2.  Speaking of Simulate, we call it.  This is the call that runs the physics simulation, translating all of the rigid bodies in the scene.  The parameters you pass in are for interacting with touch, which are meaningless for us in this demo.  Next we check if a collision occurred between the ball and either paddle using the method QueryContact, and playing our blip sound if one does.

 

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

 

Here we are simply checking if the ball went off the top or bottom of the screen.  If so, we update the scoreboard and set the scored flag to true.

 

if(result == Results.AiWin) 
    Director.Instance.ReplaceScene(new GameOverScene(false));
if(result == Results.PlayerWin) 
    Director.Instance.ReplaceScene(new GameOverScene(true));

 

Next we check if either the player or AI won and if so, and depending on which, we display the win or lose screen with the GameOverScene.

 

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

 

If a score occurred, but the game isn’t over, we reset the ball to the center of the screen.  Finally we make sure that the ball didn’t somehow “escape” the screen, if it did, we reset it.  Generally this *should* never happen.

 

 

That then is the heart of your game, as you can see, most of the game logic is moved into the Paddle and Ball classes, that we will see in the next part.

 

Programming , , ,

blog comments powered by Disqus

Month List

Popular Comments