Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon
11. September 2012

 

In this recipe, we are going to show how to create a GUI using Moai.  This functionality isn’t actually built directly into Moai, it is provided by a third party library, so we have to do a bit of setup first.

 

There are a few ways you can get moaigui.  First, it is already included in your moai install in the samples directory.  In the samples folder, look for a contrib folder, in my case it was located at C:\moai-sdk\samples\contrib.  Select the folder moaigui and copy it into your project directory.  Otherwise you can get the newest version using github. To do so ( assuming you have git installed and configured ), open a console window, change directory to your project and type:

git clone https://code.google.com/p/moaigui/

 

This will clone the newest source of the moaigui as a subdirectory in your project.  Alright, that done, on with the code:

 

screenWidth = MOAIEnvironment.screenWidth
screenHeight = MOAIEnvironment.screenHeight
if screenWidth == nil then screenWidth =1280 end
if screenHeight == nil then screenHeight = 800 end

MOAISim.openWindow("Window",screenWidth,screenHeight)

viewport = MOAIViewport.new()
viewport:setSize(screenWidth,screenHeight)
viewport:setScale(screenWidth,screenHeight)

package.path = './moaigui/?.lua;' .. package.path
require "gui/support/class"
local moaigui = require "gui/gui"
local resources = require "gui/support/resources"
local filesystem = require "gui/support/filesystem"
local inputconstants = require "gui/support/inputconstants"
local layermgr = require "layermgr"


local gui = moaigui.GUI(screenWidth,screenHeight)


gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "fonts"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "gui"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "media"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "themes"))

layermgr.addLayer("gui",99999, gui:layer())
gui:setTheme("basetheme.lua")
gui:setCurrTextStyle("default")

function onButtonClick(event,data)
    label1:setText("You clicked the button")


end



function onLessProgressButtonClick(event,data)
    local curProgress = progress:getProgress()
    if(curProgress > 0) then
        progress:setProgress(curProgress-10)
    end

end

function onMoreProgressButtonClick(event,data)
    local curProgress = progress:getProgress()
    if(curProgress < 100) then
        progress:setProgress(curProgress+10)
    end
end


button = gui:createButton()
button:setPos(0,0)
button:setDim(100,25)
button:setText("This is a button")
button:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onButtonClick)
button:registerEventHandler(button.EVENT_TOUCH_ENTERS,nil,onButtonClick)

progress = gui:createProgressBar()
progress:setPos(0,25)
progress:setDim(100,25)
progress:setText("This is a progress bar")

button2 = gui:createButton()
button2:setPos(0,50)
button2:setDim(49,25)
button2:setText("Less Progress")
button2:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onLessProgressButtonClick)
button2:registerEventHandler(button.EVENT_TOUCH_ENTERS,nil,onLessProgressButtonClick)


button3 = gui:createButton()
button3:setPos(51,50)
button3:setDim(49,25)
button3:setText("More Progress")
button3:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onMoreProgressButtonClick)
button3:registerEventHandler(button.EVENT_TOUCH_ENTERS,nil,onMoreProgressButtonClick)
button3:registerEventHandler(button.EVENT_TOUCH_TAP,nil,onMoreProgressButtonClick)

label1 = gui:createLabel()
label1:setPos(0,75)
label1:setDim(100,25)
label1:setText("Click the top button")
label1:setTextAlignment(label1.TEXT_ALIGN_CENTER)

function onPointerEvent(x, y)
    gui:injectMouseMove(x, y)
end

function onMouseLeftEvent(down)
    if(down) then
        gui:injectMouseButtonDown(inputconstants.LEFT_MOUSE_BUTTON)
    else
        gui:injectMouseButtonUp(inputconstants.LEFT_MOUSE_BUTTON)
    end
end

function onTouchEvent(eventType,idx,x,y,tapCount)
    --gui:injectTouch(eventType,idx,x,y,tapCount)
    onPointerEvent(x, y)
        if (MOAITouchSensor.TOUCH_DOWN == eventType) then
                onMouseLeftEvent(true)
        elseif (MOAITouchSensor.TOUCH_UP == eventType) then
                onMouseLeftEvent(false)
        end
end


if MOAIInputMgr.device.pointer then
    MOAIInputMgr.device.pointer:setCallback(onPointerEvent)
    MOAIInputMgr.device.mouseLeft:setCallback(onMouseLeftEvent)
else
    MOAIInputMgr.device.touch:setCallback(onTouchEvent)
end

Run the code and you will see:

image

 

Buttons and bars and labels, oh my!

 

The top 10 lines are pretty much unchanged from earlier tutorials, except I switched to the native resolution of my device, since I am doing a lot more on device testing lately.  Obviously you can change screenWidth and screenHeight to match your preferred settings.  The new code starts at:

 

package.path = './moaigui/?.lua;' .. package.path
require "gui/support/class"
local moaigui = require "gui/gui"
local resources = require "gui/support/resources"
local filesystem = require "gui/support/filesystem"
local inputconstants = require "gui/support/inputconstants"
local layermgr = require "layermgr"

The first line looks a bit daunting, but it is important to realize that Lua doesn’t really understand file directories the way you or I do.  It uses a variable called package.path to determine where to look for source files.  We want to add the moaigui subdirectory, which is what this line does, we append that folder and tell it to include all .lua files within in the path.  We then have a series of require statements for importing various required parts of the moaigui library.  We don’t have to add paths to these values, as moaigui takes care of that for us.

 

local gui = moaigui.GUI(screenWidth,screenHeight)

Create a moaigui GUI object named gui, by passing in the screens width and height.

 

gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "fonts"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "gui"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "media"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "themes"))

These are a number of essential files for moaigui, these lines make them all available in the resource path. 

 

layermgr.addLayer("gui",99999, gui:layer())
gui:setTheme("basetheme.lua")
gui:setCurrTextStyle("default")

Add a layer to the moaigui layer manager for the gui layer.  It needs to be the topmost layer so it will receive first crack at all the input coming in.  This is why we set it at layer 99999.  Just don’t go creating a layer 100000 now, ok?  We then load a default theme ( we will look at this in a second, but don’t worry, someone else has already done the work for us ) named basetheme.lua, and set the text style to default… if you don’t do this, text wont show up, so do it.

 

function onButtonClick(event,data)
    label1:setText("You clicked the button")
end

function onLessProgressButtonClick(event,data)
    local curProgress = progress:getProgress()
    if(curProgress > 0) then
        progress:setProgress(curProgress-10)
    end
end

function onMoreProgressButtonClick(event,data)
    local curProgress = progress:getProgress()
    if(curProgress < 100) then
        progress:setProgress(curProgress+10)
    end
end

Here we create 3 button click/touch handlers, one for each button we are going to create.  The first simply updates the text on a label, while the next two advance or decrease the progress status on a progress bar by 10%.

 

button = gui:createButton()
button:setPos(0,0)
button:setDim(100,25)
button:setText("This is a button")
button:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onButtonClick)

progress = gui:createProgressBar()
progress:setPos(0,25)
progress:setDim(100,25)
progress:setText("This is a progress bar")

button2 = gui:createButton()
button2:setPos(0,50)
button2:setDim(49,25)
button2:setText("Less Progress")
button2:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onLessProgressButtonClick)


button3 = gui:createButton()
button3:setPos(51,50)
button3:setDim(49,25)
button3:setText("More Progress")
button3:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onMoreProgressButtonClick)

Speaking of buttons and progress bars, here we create 3 buttons and a progress bar.  There are a few things to note about moaigui.  First off, position is all done by percentages ( which I personally love!  so easy ).  So saying setDim(100,25), sets the dimensions to 100% width and 25% height.  Nice easy way to scale your UI to the device you are running on.  For each of the buttons, we register the click function we just declared in the call registerEventHandler().

 

label1 = gui:createLabel()
label1:setPos(0,75)
label1:setDim(100,25)
label1:setText("Click the top button")
label1:setTextAlignment(label1.TEXT_ALIGN_CENTER)

Oh, and we create a label too, that will update when the user clicks the topmost button.  Using setTextAlignment, we set the text centered.

 

function onPointerEvent(x, y)
    gui:injectMouseMove(x, y)
end

function onMouseLeftEvent(down)
    if(down) then
        gui:injectMouseButtonDown(inputconstants.LEFT_MOUSE_BUTTON)
    else
        gui:injectMouseButtonUp(inputconstants.LEFT_MOUSE_BUTTON)
    end
end

You need to feed UI events in to moaigui, so when the user hits a key, moves the mouse or taps the screen, if you want moaigui to know about it, you need to tell it.  This is done with a series of inject calls, like injectMouseMove() when the user moves the mouse.  If you don’t wire events up as moaigui is concerned, they didn’t happen.  For example, with the current setup, you can type till the cows come home, and your gui will be completely oblivious.

 

function onTouchEvent(eventType,idx,x,y,tapCount)
    --gui:injectTouch(eventType,idx,x,y,tapCount)
    onPointerEvent(x, y)
        if (MOAITouchSensor.TOUCH_DOWN == eventType) then
                onMouseLeftEvent(true)
        elseif (MOAITouchSensor.TOUCH_UP == eventType) then
                onMouseLeftEvent(false)
        end
end

Currently touch support in moaigui is a bit… in development.  So for now we emulate the user using a mouse.  This means when the user touches the screen, we create a mouse pointer event, when the user touches the screen, we create a left click event and when the user releases the screen, be set the left mouse button status to up.  I also got it working by making a few small changes to the moagui code itself, but unless someone specifically requests it, I wont go into them here.

 

if MOAIInputMgr.device.pointer then
    MOAIInputMgr.device.pointer:setCallback(onPointerEvent)
    MOAIInputMgr.device.mouseLeft:setCallback(onMouseLeftEvent)
else
    MOAIInputMgr.device.touch:setCallback(onTouchEvent)
end

Finally, we wire up a callback for each io event we want moai to tell us about.

 

One other thing I mentioned earlier was the theme file basetheme.lua.  This file is located in the /moaigui/resources/themes/ directory, and looks like this:

data = {
    textstyles = {
        default = {
            font = "arial-rounded.TTF",
            size = 44,
        },
        listselected = {
            font = "arial-rounded.TTF",
            size = 14,
            color = {0, 0, 0, 1}
        },
        listunselected = {
            font = "arial-rounded.TTF",
            size = 14,
            color = {1, 1, 1, 1}
        }
    },
    label = {

    },
    button = {
        normal = "button normal center.png",
        pushed = "button pushed center.png",
        hover = "button hover center.png",
    },
    checkbox = {
        normal = "check box.png",
        hover = "check box.png",
        pushed = "check box.png",
        selected = "check box select.png",
    },
    radiobutton = {
        normal = "radio button.png",
        hover = "radio button.png",
        pushed = "radio button.png",
        selected = "radio button select.png",
    },
    editbox = {
        normal = "edit box center.png",
    },
    vertscrollbar = {
        normal = "vert scroll center.png",
        scrollernormal = "vert scroller.png",
        scrollerhover = "vert scroller.png",
    },
    horzscrollbar = {
        normal = "horz scroll center.png",
        scrollernormal = "horz scroller.png",
        scrollerhover = "horz scroller.png",
    },
    vertslider = {
        normal = "vert slider center.png",
        slidernormal = "vert slider.png",
        sliderhover = "vert slider.png",
    },
    horzslider = {
        normal = "horz slider center.png",
        slidernormal = "horz slider.png",
        sliderhover = "horz slider.png",
    },
    progressbar = {
        normal = "progress bar center.png",
        bar = "progress bar.png",
        mask = "progress bar mask.png",
    },
    textbox = {
    
    },
    widgetlist = {
        selected = "listselected",
        unselected = "listunselected",
    },
    cursors = {
        default = {

        },
    },
}

return data

You can easily make small changes to your UI look and feel by changing this file.  As you can see, I vastly increased the default font size to improve visibility on my phone.  You can also change the button images, and other UI elements, by altering their images in the /moaigui/resources/gui directory, while additional or different fonts should go in the moaigui/resources/fonts folder.

 

In the next exciting tutorial we will put our new found GUIness to work.

 

Now you know, and knowing… is half the battle. Or was that a quarter?  No, no, definitely half.

 

Programming


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


10. September 2012

 

The C++ mobile SDK Marmalade just announced release 6.1.

 

I suppose I can’t really refer to it as a mobile SDK any more, as one of the biggest features of 6.1 is the ability to target Windows and Mac OS desktops.

 

Other new features in this release are:

 

  • Target the desktop: With Marmalade 6.1 you can now deliver your apps to current Windows (7, Vista, XP) and Mac OS X platforms, as well as to Android MIPS devices and the latest LG Smart TVs.
  • Full flexibility: With Web Marmalade’s Plugins you can now mix HTML5 with native platform code, providing full flexibility and creativity.
  • Improved: Desktop graphics support, making it easier to develop and test Marmalade titles on a wide variety of desktop hardware.
  • Updated: You can now use floating-point within the graphics pipeline, allowing more intuitive manipulation of 3D assets.

 

 

Marmalade has been used to release a number of big name projects across many devices.  There is a free trial available, with pricing ranging from 149$ a year ( with a Splashscreen ), to 500$ a year ( no splash screen ) or a negotiated commercial license with full support ( including service level agreement ) and private beta access. 

 

One of the major features of the Marmalade SDK is the ability to target iOS without the need for a Mac.  I know there are a ton of small developers out there that want to make games for the iPhone and iPad, but don’t want to drop 1000$+ on an Apple computer.  If this description fits you, Marmalade might be the perfect option!

News


10. September 2012

 

The venerable Torque3D game engine is going completely open source, with the source code released on GitHub.image Torque was originally used to create the popular Tribes series of game, then was made available for a relatively low fee.  Considering this was ages before Unity or UDK existed, that was pretty revolutionary.

 

If you recently purchased ( this month ) Torque, GarageGames will be offering a complete refund.

 

In their words:

Eleven years ago, The GarageGames founders did an incredibly innovative thing when they sold a full source game engine for $100. We are excited to continue in their footsteps by announcing that we will be releasing Torque 3D as the best open source game technology in the world. Once again, GarageGames will be changing game development.


Why are we doing this?
Nine months ago, we realigned the goals of GarageGames; making Torque 3D available via a permissive open source license is a strategic move towards fulfilling the company vision. Our first goal was to use iTorque to build a new product we call 3 Step Studio. We envisioned a game development tool that requires no programming at all and began to build it. This product is available today for free, but it’s very, very, early in development and we expect to iterate many times before it is a commercially viable product. Our second goal was to build a service division. I’m happy to say that we’ve already booked our first million dollars in service work and we expect the growth trend to continue as we make Torque 3D more accessible. You can visit our services site at
services.garagegames.com. We are very well prepared and staffed to provide support, training, and custom development.

 

Very cool, more choice is always nice, and so is free!

 

 

Not sure the exact date of the code release, it will however be under the MIT permissive license, personally one of my favorites.  ( No GPL like downsides ).  I will be sure to check this out in closer detail once the code is released.

 

Good work Garage Games.

News


9. September 2012

 

In this part of the tutorial we are going to setup our PhsyicsScene, the heart of our physics engine.  A physics engine, in really laymans terms, take a bunch of physics objects, composed of shapes and with defined physical properties like mass, force, elasticity, etc…  and simulates movement in a virtualized world.  You then take the outputs from the physics engine and update your game accordingly.

 

Let’s jump right in to the code:

 

PhysicsScene.cs

 

    using System;
    using Sce.PlayStation.Core;
    using Sce.PlayStation.HighLevel.GameEngine2D;
    using Sce.PlayStation.HighLevel.GameEngine2D.Base;
    using Sce.PlayStation.HighLevel.Physics2D;
    
    namespace Pong
    {
        
        public class PongPhysics : PhysicsScene
        {
            // PixelsToMeters
            public const float PtoM = 50.0f;
            private const float BALLRADIUS = 35.0f/2f; 
            private const float PADDLEWIDTH = 125.0f;
            private const float PADDLEHEIGHT = 38.0f;
            private float _screenWidth;
            private float _screenHeight;
            
            
            public enum BODIES { Ball = 0, Player, Ai, LeftBumper, RightBumper };
    
            
            public PongPhysics ()
            {
                _screenWidth = Director.Instance.GL.Context.GetViewport().Width;
                _screenHeight = Director.Instance.GL.Context.GetViewport().Height;
                
                // turn gravity off
                this.InitScene();
                this.Gravity = new Vector2(0.0f,0.0f);
                
                // Set the screen boundaries + 2m or 100pixel
                this.SceneMin = new Vector2(-100f,-100f) / PtoM;
                this.SceneMax = new Vector2(_screenWidth + 100.0f,_screenHeight + 100.0f) / PtoM;
                
                // And turn the bouncy bouncy on
                this.RestitutionCoeff = 1.0f;
                
                this.NumBody = 5; // Ball, 2 paddles, 2 bumpers
                this.NumShape = 3; // One of each of the above
                
                //create the ball physics object
                this.SceneShapes[0] = new PhysicsShape(PongPhysics.BALLRADIUS/PtoM);
                this.SceneBodies[0] = new PhysicsBody(SceneShapes[0],0.1f);
                this.SceneBodies[0].ShapeIndex = 0;
                this.sceneBodies[0].ColFriction = 0.01f;
                this.SceneBodies[0].Position = new Vector2(_screenWidth/2,_screenHeight/2) / PtoM;
                
                
                //Paddle shape
                Vector2 box = new Vector2(PADDLEWIDTH/2f/PtoM,-PADDLEHEIGHT/2f/PtoM);
                this.SceneShapes[1] = new PhysicsShape(box);
                
                //Player paddle
                this.SceneBodies[1] = new PhysicsBody(SceneShapes[1],1.0f);
                this.SceneBodies[1].Position = new Vector2(_screenWidth/2f,0f+PADDLEHEIGHT/2+ 10f) / PtoM;
                this.SceneBodies[1].Rotation = 0;
                this.SceneBodies[1].ShapeIndex = 1;
                
                //Ai paddle
                this.SceneBodies[2] = new PhysicsBody(SceneShapes[1],1.0f);
                float aiX = ((_screenWidth/2f)/PtoM);
                float aiY = (_screenHeight - PADDLEHEIGHT/2 - 10f)/ PtoM;
                this.SceneBodies[2].Position = new Vector2(aiX,aiY);
                this.SceneBodies[2].Rotation = 0;
                this.SceneBodies[2].ShapeIndex = 1;
                
                //Now a shape for left and right bumpers to keep ball on screen
                this.SceneShapes[2] = new PhysicsShape((new Vector2(1.0f,_screenHeight)) / PtoM);
                
                //Left bumper
                this.SceneBodies[3] = new PhysicsBody(SceneShapes[2],PhysicsUtility.FltMax);
                this.SceneBodies[3].Position = new Vector2(0,_screenHeight/2f) / PtoM;
                this.sceneBodies[3].ShapeIndex = 2;
                this.sceneBodies[3].Rotation = 0;
                this.SceneBodies[3].SetBodyStatic();
                
                //Right bumper
                this.SceneBodies[4] = new PhysicsBody(SceneShapes[2],PhysicsUtility.FltMax);
                this.SceneBodies[4].Position = new Vector2(_screenWidth,_screenHeight/2f) / PtoM;
                this.sceneBodies[4].ShapeIndex = 2;
                this.sceneBodies[4].Rotation = 0;
                this.SceneBodies[4].SetBodyStatic();
            }
        }
    }
    

Once again, let’s take it from the top.

 

// PixelsToMeters
public const float PtoM = 50.0f;
private const float BALLRADIUS = 35.0f/2f; 
private const float PADDLEWIDTH = 125.0f;
private const float PADDLEHEIGHT = 38.0f;
private float _screenWidth;
private float _screenHeight;

Most of these are convenience variables for commonly used dimensions.  The most important concept here is PtoM, which is short hand for Pixels To Meters.  By default, the Physics2D engine measures things in meters and kilograms.  So a velocity of (5,0) is 5 meters per second, and a mass of 1 is 1kg.  This value is used to map from pixels to physics units and back, and is going to be used A LOT.  The value 50.0f was chosen pretty much at random by me.

 

public enum BODIES { Ball = 0, Player, Ai, LeftBumper, RightBumper };

These are just shorthand values for accessing our various physics objects by index value.  Obviously if you changed the order of the PhysicsBodies, you need to update this enum.  This is just to make code that access the physics scene externally a bit more readable and is completely optional.

 

The remaining code is entirely in the constructor, let’s take it in chunks:

_screenWidth = Director.Instance.GL.Context.GetViewport().Width;
_screenHeight = Director.Instance.GL.Context.GetViewport().Height;

// turn gravity off
this.InitScene();
this.Gravity = new Vector2(0.0f,0.0f);

We use the screen width and height a lot, so we cache them in a much shorter variable.  Next we init our PhysicsScene, which simply sets a number of values to defaults and should be called before using the Scene ( calling it later will overwrite all of your settings ).  We then set gravity to (0,0) so there will effectively be none.  By default it’s (0,-9.8), which is 9.8 m/s down the Y axis.

 

// Set the screen boundaries + 2m or 100pixel
this.SceneMin = new Vector2(-100f,-100f) / PtoM;
this.SceneMax = new Vector2(_screenWidth + 100.0f,_screenHeight + 100.0f) / PtoM;
                
// And turn the bouncy bouncy on
this.RestitutionCoeff = 1.0f;
                
this.NumBody = 5; // Ball, 2 paddles, 2 bumpers
this.NumShape = 3; // One of each of the above

By default, the PhysicsScene is from –1000,-1000 to 1000,1000.  Notice how we divided the value by PtoM.  This turns it from pixel coordinates to physics coordinates.  Basically we are setting the scene equal to the size of the screen + 100pixels in all directions.  Once a physics object passes out of this area, it stops updating.

 

Next we set RestituionCoeff, (somewhat short) for Coefficient of Restitution.  Let’s think of it as “bounciness”.  A value of 1.0f means something should rebound at the same force it hit, while a value of 0.0 will mean no bounce at all.  A value over 1.0f will cause a whole lot of bounce.  For some exceptionally odd reason, it is set at the scene level in the Phsyics2D library, instead of at the Body level.  Next we touch on another odd decision in the library, the PhysicsScene contains three pre-allocated arrays for Shapes, Bodies and Joins ( 100 items, 250 items and 150 items in size respectively ).  NumBody and NumShape indicate how many items of each you are going to use.  Make perfectly sure that you get the sizes exactly right or you will have problems.  As you can see, we are going to have 5 SceneBodies and 3 SceneShapes in our scene.

 

//create the ball physics object
this.SceneShapes[0] = new PhysicsShape(PongPhysics.BALLRADIUS/PtoM);
this.SceneBodies[0] = new PhysicsBody(SceneShapes[0],0.1f);
this.SceneBodies[0].ShapeIndex = 0;
this.sceneBodies[0].ColFriction = 0.01f;
this.SceneBodies[0].Position = new Vector2(_screenWidth/2,_screenHeight/2) / PtoM;

First we create a new PhysicsShape at position 0 of the SceneShapes array.  This is going to be for our ball, and is created by specifying the radius of the sphere ( again, adjusted from pixels to physics coordinates ).  We then create a PhysicsBody using the PhysicsShape.  The 0.1f represents the mass, in this case 1/10th kg.  ShapeIndex is the index within the SceneShapes array that this SceneBody uses ( and yes, is overwhelmingly redundant, another design decision I don’t understand).  As we will see shortly, multiple Bodies can use the same Shape.  ColFriction is Collision Friction which you can read more about here.  Finally we position the ball within the physics world to match it’s location in the GameEngine2D world ( we will see shortly ), again, converted using PtoM.

 

//Paddle shape
Vector2 box = new Vector2(PADDLEWIDTH/2f/PtoM,-PADDLEHEIGHT/2f/PtoM);
this.SceneShapes[1] = new PhysicsShape(box);

//Player paddle
this.SceneBodies[1] = new PhysicsBody(SceneShapes[1],1.0f);
this.SceneBodies[1].Position = new Vector2(_screenWidth/2f,0f+PADDLEHEIGHT/2+ 10f) / PtoM;
this.SceneBodies[1].Rotation = 0;
this.SceneBodies[1].ShapeIndex = 1;

//Ai paddle
this.SceneBodies[2] = new PhysicsBody(SceneShapes[1],1.0f);
float aiX = ((_screenWidth/2f)/PtoM);
float aiY = (_screenHeight - PADDLEHEIGHT/2 - 10f)/ PtoM;
this.SceneBodies[2].Position = new Vector2(aiX,aiY);
this.SceneBodies[2].Rotation = 0;
this.SceneBodies[2].ShapeIndex = 1;

 

This code sets up the player and ai paddle physics bodies, this time sharing a single PhysicsShape.  Instead of passing a radius to this one, we pass a pair of vectors representing width an height, relative to the center point ( which coincidentally is explained nowhere and learned from trial and error ).  The only other difference is for the AI paddle, I saved the X and Y values to temporary variables, aiX and aiY.  Why?  Because there is an annoying bug in Expression Evaluator, that simply could not handle _screenWidth/2f.  It would treat both numbers an ints, and was showing ( in watch windows and Expression Evaluator ) the wrong values.  In fact, Expression Evaluation thinks 3.0f/2.0f = 1!   This particular (display) bug cost me much of my sanity!

 

//Now a shape for left and right bumpers to keep ball on screen
this.SceneShapes[2] = new PhysicsShape((new Vector2(1.0f,_screenHeight)) / PtoM);

//Left bumper
this.SceneBodies[3] = new PhysicsBody(SceneShapes[2],PhysicsUtility.FltMax);
this.SceneBodies[3].Position = new Vector2(0,_screenHeight/2f) / PtoM;
this.sceneBodies[3].ShapeIndex = 2;
this.sceneBodies[3].Rotation = 0;
this.SceneBodies[3].SetBodyStatic();

//Right bumper
this.SceneBodies[4] = new PhysicsBody(SceneShapes[2],PhysicsUtility.FltMax);
this.SceneBodies[4].Position = new Vector2(_screenWidth,_screenHeight/2f) / PtoM;
this.sceneBodies[4].ShapeIndex = 2;
this.sceneBodies[4].Rotation = 0;
this.SceneBodies[4].SetBodyStatic();

This code simply creates a pair of “bumpers” for the left and right side of the screen.  Something for the ball to ricochet off of.  The only thing of note is SetBodyStatic(), which tells the physics engine the object isn’t going to move, saving some valuable calculation time.  There are 3 modes in Physics2D, Dynamic ( what are ball and paddles are ), which will be fully calculated by the engine, Kinematic, which respond to direct application of motion, but not environmental effects ( like gravity ) as well as Static, which are completely stationary.  We are sticking with Dynamic because we are going to control paddle movement through the application of Force, something Kinematic doesn’t support.

 

We now have a physics scene, in the next part we will create the actual GameEngine2D scene, GameScene.

 

Programming


AppGameKit Studio

See More Tutorials on DevGa.me!

Month List