Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

6. June 2012

 

As promised in the last tutorial, in this tutorial we are going to jump in and do some coding.  We are going to implement the obligatory Hello World example using Cocos2D HTML, with a twist ( literally ).  I warn you up front, this is going to look a great deal more complex than it actually is.  There is a certain amount of boilerplate code you need to get up and running, but it really isn’t all that difficult in the end.  In a future game, you will just do a series of copy and pastes and be off to the races.

 

The following goes into a lot of detail, more so than future tutorials.  There is however a TL;DR summary at the end, so if you prefer, simply read the code examples then jump ahead to the summary at the bottom of the post for an understanding of exactly what’s going on.

 

To make things a bit easier on yourself, create your game project in the same folder you extracted the Cocos2D-html archive to.  If you followed my directions exactly in tutorial 1, this directory will be c:\wamp\www.  Create a new folder for your project, I called mine MyFirstApp. 

 

Now that your directory is created, we are going to be creating a couple files.  First we need to create an HTML web page that is going to host our game.  Following convention ( and so Apache will serve it as the default ), I called mine index.html.  Enter the following code:

 

<html> <body style="padding:0; margin: 0; background: #fff;"> <div style="text-align: center; font-size: 0"> <canvas id="gameCanvas" width="600" height="600"> Your browser does not support the canvas tag </canvas> </div> </body> </html> <script src="cocos2d.js"></script>

 

This is pretty much stock HTML code.  The styling in the body tag is so our canvas tag will be flush with the left side of the screen, and because I wanted a white background.  Only two items here are really important.  First is the canvas tag, we need at least one canvas tag, as this is where Cocos2D draws everything.  Why 600 pixels?  That’s (edit- was) the content width of my blog.  So obviously, make your canvas whatever size you want it to be.  Finally we include our cocos2d.js script.

 

This segues nicely into the next task, go ahead and create another text file, this one called cocos2d.js.  Inside that file we type:

 

(function () {
    var d = document;
    var c = {
        COCOS2D_DEBUG:2, //0 to turn debug off, 1 for basic debug, and 2 for full debug
        box2d:false,
        showFPS:true,
        frameRate:60,
        tag:'gameCanvas', //the dom element to run cocos2d on
        engineDir:'../cocos2d/',
        appFiles:['MyFirstApp.js']
    };
    window.addEventListener('DOMContentLoaded', function () {
        //first load engine file if specified
        var s = d.createElement('script');
        s.src = c.engineDir + 'platform/jsloader.js';
        d.body.appendChild(s);
        s.c = c;
        s.id = 'cocos2d-html5';
    });
})();

 

You may notice that this code block ends with ();  This is a self executing anonymous function.  Basically that means after it is finished being defined, it will immediately be called.

 

So what exactly are we doing here?  First we care creating a configuration variable called c.  This file consists of a number of configuration settings for initializing the Cocos2D engine.  Lets take a quick look at each value:

 

COCOS2D_Debug: This controls the debug level Cocos2D engine. In release set to 0

box2d: Tells Cocos2D if it needs to initialize the box2d physics engine

showFPS: Enable/disable a frames per second counter being displayed each frame

tag: This is the HTML element that cocos2D will render to.  In our case, our canvas tag

engineDir: This is the directory the cocos2D libraries are installed in.  In our case, up one directory from cocos2d.js in the folder cocos2d

appFiles: This is an array containing all of the your script files.  If you add a js file to your project, add it to this array.  This causes it to be loaded by the loader.

 

Next we register an event handler that will be fired once the DOM has been loaded ( meaning the page is done loading and JavaScript is setup and ready ).  We then create a <SCRIPT> tag in our HTML page with the id ‘cocos2d-html5’ and add a reference to jsloader.js script as well as our configuration variable c.

 

At this point, the jsloader.js script now loads all of the various libraries required by cocos2d into the HTML file.  Once it is done loading all of the library files, it calls a user defined script named main.js. You need to create this file in the root of your project.

 

Now let’s go ahead and create main.js:

 

var cocos2dApp = cc.Application.extend({
    config:document.querySelector('#cocos2d-html5')['c'],
    ctor:function (scene) {
        this._super();
        this.startScene = scene;
        cc.COCOS2D_DEBUG = this.config['COCOS2D_DEBUG'];
        cc.setup(this.config['tag']);
        cc.Loader.shareLoader().onloading = function () {
            cc.LoaderScene.shareLoaderScene().draw();
        };
        cc.Loader.shareLoader().onload = function () {
            cc.AppController.shareAppController().didFinishLaunchingWithOptions();
        };
        cc.Loader.shareLoader().preload([
        ]);
    },
    applicationDidFinishLaunching:function () {
        var director = cc.Director.getInstance();
        director.setDisplayStats(this.config['showFPS']);
        director.setAnimationInterval(1.0 / this.config['frameRate']);
        director.runWithScene(new this.startScene());

        return true;
    }
});
var myApp = new cocos2dApp(MyFirstAppScene);

 

We are creating a new object by extending cc.Application.  If you are familiar with C++, Java or C# you may be looking at this code and thinking it is both vaguely familiar and different at the same time.  There is a good reason for that.  JavaScript isn’t really “object oriented” in the way you are familiar with, it is prototype based.  This is beyond the scope of what I can explain here, but basically there are no classes.  Instead there are “prototypes” that you clone and extend.  So essentially you define something once, which is then used as a prototype for creating “objects” in the future.  Clear as mud?

 

Essentially what we are doing here is defining our cocos2DApp object to extend the cc.Application prototype.  This is a pretty common behavior in working with Cocos2D, so hopefully you can wrap your head around it.  We then implement the ctor ( constructor ) and applicationDidFinishingLaunching.  Again, if you are used to C++ like languages, you can think of cocos2DApp as being derived from cc.Application, then overriding the default constructor and a virtual method.  If you aren’t a C++/C# or Java programmer, forget everything I just said. Smile

 

First, up is the ctor, which simply results in the creation of the prototype cc.Application which cocos2DApp extends.  super() can be a confusing concept to understand, for more details read this. ( warning, NSFW language ).  One thing to keep in mind, in JavaScript, the constructor ( or ctor ) is just another function, unlike in other languages. 

 

We pass the type of Scene we want to start our app with, we will be creating this in a moment.  This is value the parameter scene holds.  We then call _super() as described earlier.  Next we setup cocos using the values we set in the value c earlier in cocos2d.js.  Next we invoke the global Loader object via cc.Loader.shareLoader().  onloading is a call back that will be called when the loader is executing, which effectively renders the loading screen.  preLoad is an important function for loading user data files, which we will user in a later tutorial.

 

In applicationDidFinishLaunching ( which is predictably called when the application finishes launching! ), we initialize the Director object. Director is a global object that basically controls the execution of everything, we will be seeing a lot of it later on.  For now just realize that Director is very important.  Why will become clear in time.  The .getInstance() call is where the singleton part of the equation comes in.  It’s well beyond the scope of what I can go into, but for now just think about it as a globally unique shared variable.

 

The heart of our cocos2dApp though is the applicationDidFinishLaunching function.  This is a function that will be called once your cocos2dApp class is loaded, cocos2d is setup and it is what gets the party started.  First we tell the ( all important! ) Director if we want to display the frames per second counter on screen and that we want to update using the framerate specified in c configuration in cocos2d.js.  Next we tell Director to start running using the scene type we passed in to the constructor.  The director only ever has one scene at a time active, and as I said earlier, Cocos2D organizes your game Scene by scene.  This is the point where your (only) Scene is created and execution is started. 

 

Finally we create an instance of cocos2dApp named myApp and pass it the scene MyFirstAppScene.

 

Now lets create that scene object, create a file named MyFirstApp.js as such:

var MyFirstApp = cc.Layer.extend({
    init:function()
    {
        this._super();

        var s = cc.Director.getInstance().getWinSize();

        var layer1 = cc.LayerColor.create(new cc.Color4B(255, 255, 0, 255), 600, 600);
        layer1.setAnchorPoint(new cc.Point(0.5,0.5));

        
        var helloLabel = cc.LabelTTF.create("Hello world", "Arial", 30);
        helloLabel.setPosition(new cc.Point(s.width/2,s.height/2));
        helloLabel.setColor(new cc.Color3B(255,0,0));
        var rotationAmount = 0;
        var scale = 1;
        helloLabel.schedule(function()
            {
                this.setRotation(rotationAmount++);
                if(rotationAmount > 360)
                    rotationAmount = 0;
                this.setScale(scale);
                scale+= 0.05;
                if(scale > 10)
                    scale =1;
            });

        layer1.addChild(helloLabel);
        this.addChild(layer1);

        
        return true;
    }

});

var MyFirstAppScene = cc.Scene.extend({
    onEnter:function(){
        this._super();
        var layer = new MyFirstApp();
        layer.init();
        this.addChild(layer);
    }
})

Just like we did when creating our cocos2dApp, we are creating MyFirstApp by extending a Cocos2D prototype, in this case ccLayer.  If you have ever worked in a graphic application like Photoshop or GIMP, the concept of a layer should already be familiar to you.  If the term is new to you, think of a layer as an image or graphic, that might be transparent in parts.  You can layer ( thus the name ) layers over top of each other to create a compound image.  This image illustrates the concept as well as any I could find:

 

 

 

You can think of MyFirstApp as representing the entire collection of layers, which in the end will ultimately be drawn to our HTML canvas tag.

 

As I said, our canvas can be made out of a collection of layers, and the first thing we want to do is create a solid yellow layer.  That is what this call does:

var layer1 = cc.LayerColor.create(new cc.Color4B(255, 255, 0, 255), 600, 600);

We are creating a layer 600x600 in size ( the same as our canvas tag ), filled with the RGBA color 255,255,0,255.  These numbers represent the red, green, blue and alpha values respectively, as represented by a value from 0-255.  The alpha value can be thought of as the amount of transparency in the layer, in this case 100% opaque.  As you can see, we created our color with a call to cc.Color4B, you will see this naming convention quite often. 

 

Now that we have a solid yellow layer, we need to position it.  Positioning in Cocos2D might be a bit different from what you are used to.  Instead of positioning elements from the their top left, they are positioned by their center point by default.  Additionally Cocos2D coordinates start at the bottom left of the screen instead of the top left.  Therefore when we set the position of our layer, we want it to be the center of the screen.

 

Now we want to create our HelloWorld text, which we do by creating a cc.LabelTTF ( TTF = True Text Font ), with our string “Hello world” using the font family Arial and font size of 30.  We too want the Hello World label centered to the screen and we want it to be red in color.  We then declare two local variables rotationAmount and scale, which we will use in a second.

 

The next line is key, we are declaring a lambda ( or anonymous ) function that we pass as a parameter to the schedule function.  End end result is, the following code will be executed every update ( which we set to 60 times per second earlier ) as long as our scene is active.  Essentially what happens is Director calls our Scene ( MyFirstApp ) to update 60 times per second, and our Scene in turn calls the update function of each node that registered to be updated.  This line is registering the function that is going to be called during updating.  ( This is greatly simplifying the actual process, but will do for now ).  Essentially, all this function does is rotate the text and scale it until it is 10 times in size, at which point it starts over.

 

Finally, we add our Hello World label to our layer, and add our layer to our MyFirstApp variable.

 

Now we create our scene, MyFirstAppScene.  This is the value we passed in to the constructor cocos2dApp earlier.  MyFirstAppScene extends cc.Scene and overrides the OnEnter method, which is called when the scene is activated ( in the RunWithScene call ).  In this case, we simply call up to cc.Scene with a call to this._super(), then create an instance of our layer MyFirstApp and add it to our scene.  This causes our layer to be created and displayed.

 

Here are the end results of all our activity:

 

 

TL;DR overview of what just happened:

 

  1. index.html is loaded in the web browser
  2. Our canvas tag is defined
  3. cocos2d.js then loaded ( within index.html )
  4. cocos2.js creates a SCRIPT tag in our index.html, injects configuration settings as well as a reference to jsloader.js
  5. jsloader loads all of the required cocos2D libraries, then invokes main.js
  6. main.js loads cocos2D, preloads assets, initializes the director then runs our scene
  7. our scene MyFirstAppScene creates a new layer MyFirstApp

 

Your game is now running.  We will take a closer look at program execution and handling input shortly.

 

You can download the whole project here.  Simply extract it to your www folder and open index.html in your browser.

 

 

Read Tutorial 3

Programming , ,

6. June 2012

 

I can always tell when Diablo 3 servers are being patched as I get an influx of visitors viewing this page.  I wrote that when I was experiencing crashes when they last patched the Diablo 3D3Boom servers.  It would appear the server patch crashes are still occurring.

 

To people having this problem, this post is a much more concise description of the problems I had, and the fix.  So if you are unable to log in to Diablo after a server patch, or the patcher is giving you “Diablo III is already running” or “Diablo III has stopped working”, this might be the fix to your problem.

 

Basically what it boiled down to is the Blizzard installer gave me the en-GB ( Great Britain ) installer instead of the en-US one.  This is all fine and good, until Blizzard releases a patch.  The problem is, the US patch is released first, causing all the people connecting to the US servers with a non-US client to crash until the other servers are patched.

 

It may be possible to work around the problem by manually editing the patch_url in your agent.db file, I had some initial success this way.

 

However, the *best* solution, is to reinstall using the client native the server you are playing on.  Granted, this is a giant pain in the backside, especially if your internet connection has usage caps!

 

That said, the actual BEST solution, is for Blizzard to fix this problem, or failing that, roll out all the patches at once!

 

Hopefully this helped a few of you get over the Diablo 3 patching woes!  If nothing else, it allowed me to create that Diablo mushroom cloud graphic. Smile

 

 

EDIT: For those that don’t want to re-install, you can try editing the patch_url manually.  Worst case scenario… you will have to re-install! Smile

 

To do so, follow these instructions.  Of course, these instructions are for switching to the Asian server, so substitute en-US for American, and en-GB for European servers.  Also, be sure to note that there are multiple copies of the file you need to edit, not just one!

Totally Off Topic

5. June 2012

 

As part of the same document that announced the PlayStation Mobile compatible phones fromplaystation-mobile-on-htc HTC, Sony also released a list of developers currently working on PlayStation Mobile content.  Give it a look and I believe that you will agree that it is fairly impressive:

 

 

 

 

List of Third Party Game Developers and Publishers

 


JAPAN


ACQUIRE Corp.
ARC SYSTEM WORKS CO.,LTD. 
ARTDINK CORPORATION
ASCII MEDIA WORKS Inc. 
ASGARD Co.,Ltd. 
ASOBIMO,Inc.
CYBERFRONT Corporation
D3PUBLISHER INC.
eitarosoft, inc.
ENTERBRAIN, INC. 
FromSoftware, Inc
G STYLE CO.,LTD.
Gameloft
GungHo Online Entertainment, Inc.  
GUST CO.,LTD.
HAMSTER Corporation
IDEA FACTORY Co., Ltd.  

 


 

EUROPE/PAL

 
Atomicom Ltd.
Beatnik Games Ltd. 
Big Head Games Ltd.
Crash Lab Ltd.
Futurlab Ltd. 
Green Hill
Honeyslug Ltd.
Icon Games Entertainment Ltd. 
Omni Systems Ltd.
Origin8 Technologies Ltd.
Playerthree Ltd. 
IMAGEEPOCH INC.
Index Corporation 
Kadokawa Games, Ltd.
Kadokawa Shoten Publishing Co., Ltd. 
KAYAC Inc.
MAGES. Inc.
MarvelousAQL Inc.
NCM Entertainment Corporation / Forever Entertainment S.A. Group
NEX ENTERTAINMENT CO.,LTD.
Nippon Ichi Software, Inc.
Pygmy Studio.Co., Ltd.
Q Entertainment Inc.
SEGA Corporation
SPIKE CHUNSOFT Co.,Ltd.
SUCCESS Corporation
SYNC Inc.
TECMO KOEI GAMES Co., Ltd. 
34  companies in total
22 companies in total
Pompom Software Ltd.
Quirkat Inc.
Retroburn Game Studios Ltd.
Ripstone Ltd. 
SFB Games Ltd.
Spinning Head Software Ltd. 
Thumbs Up
Tikipod Ltd.
Triangle Factory
Vlambeer
Wired Productions Ltd.

 


 

 

Some pretty impressive names in there, from Sony, From Software and GameLoft to Nippon, Tecmo and Sega.  If you want, you can download the original PDF here.  The scei.co.jp press release is available here.  With heavy weight support behind their effort, Sony may become a big player in the ( currently horrible? ) Android game market.

 

 

So, if Sony had this many developers lined up and working on PlayStation Mobile games, why didn’t they mention that at the E3 press conference???  I am sure nobody would have minded a few minutes axed out of the Wonderbook demonstration!

News

5. June 2012

 

At E3, Sony announced that Sony PlayStation Suite would be rebranded as PlayStation mobile.  More importantly, they announced that HTC would be the first 3rd party Androidatt-htc-one-x manufacturer to release a PlayStation certified phone, but they never went into any details about what that phones would be supported.  It appeared to be an HTC One that he was holding on stage.

 

Fortunately, that information is available on Sony’s Japanese corporate site:

 

The license program to expand PS Mobile, dedicated for portable hardware manufacturers. SCE will not only license logos but also provide necessary development support. As of June 5, 2012, the line-up of PlayStation™Certified devices include the HTC One series of smartphones, HTC One™ X, HTC One™ S, and HTC One™ V. Content developed with official version of PlayStation®Mobile SDK will be available on those devices later this year, also Xperia™ arc, Xperia™ acro, Xperia™ PLAY, Xperia™ acro HD, Xperia™ S, Xperia™ ion, Xperia™ acro S from Sony Mobile Communications AB, and "Sony Tablet" S and "Sony Tablet" P from Sony Corporation.

 

It seems strange that the information was here, but not as part of the official announcement!

 

Anyways, there you have it.  PlayStation Mobile is coming to the HTC One X, S and V phones, in addition to Sony’s existing lineup.  Now lets just hope they sign a deal with Samsung soon!

Smile

News

4. June 2012

 

As was just announced at E3, Sony has rebranded PlayStation Suite to PlayStation.mobile. 

 

All of our existing tutorials will will still work exactly the same, and I will rebrand them as I come to them. I aim to make this site “the site” for all your PlayStation Mobile programming needs.

 

Code wise, absolutely nothing has changed, but if you are coming here from a search engine and you run into a post referring to the  PlayStation Suite SDK, you now know why.

 

 

Other than the change to PlayStation Mobile, Sony has now officially announced a partnership with HTC, bring PlayStation mobile to more devices!  Long rumoured, it is nice to know that there are going to be PlayStation based HTC phones!

 

So, if you are interested in developing for PlayStation Mobile, head over here for a list of tutorials to get you started!

News , ,

Month List

Popular Comments

A complete PlayStation Vita game from scratch: Part Five
Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


13. September 2012

 

In this section we are going to implement the Ball and the Paddle class.  Let’s jump right in with the Ball class

 

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

 

Once again, we will take it from the top.

private PhysicsBody _physicsBody;
// Change this value to make the game faster or slower
public const float BALL_VELOCITY = 5.0f;

 

These are Ball’s two member variables.  The first one is a reference to the PhysicsBody that represents the ball in the physics engine.  The second is the velocity the game ball is going to move at.  You can optionally change the difficulty of this game by increasing that value.

 

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

 

We pass the PhysicsBody in to the constructor, so we take a reference to it.  We then load our ball texture, scale it to match the texture size in pixels, set it’s pivot point to the center, then locate it in the middle of the screen.  The constructor ends with pretty much the same code as we used in ResetBall() in the game scene.  This code is pretty thoroughly explained in the comment.  Finally we register this class to receive updates each frame.

 

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;

}

… speaking of updates, here is our Update() method.  This will be called once per frame by the Scheduler object ( which itself is called by the Director object ) and is passed the value dt, which is the elapsed time since the last time update was called, in seconds.

 

Most of the code in Update is to deal with the ball hitting the left or right screen at an exceedingly boring angle.  Without this code, you can run in to a situation where the ball will simply bounce back and forth for hours.  Here, if the Y velocity isn’t at least 20% off center, we add a further 20% to it.  Finally we set the velocity to our fixed BALL_VELOCITY, over riding the physics engine. 

 

~Ball()
{
    this.TextureInfo.Texture.Dispose();
    this.TextureInfo.Dispose();
}

Clean up…

 

Now lets take a look at the code behind the paddle objects.

 

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

 

Alright… from the top

 

public enum PaddleType { PLAYER, AI };

private PaddleType _type;
private PhysicsBody _physicsBody;
private float _fixedY;

PaddleType is a simple enum specifying the different paddle types, the player or the computer.  It just adds a bit of readability to the code.  Like the ball, we take a reference to the PhysicsBody controlling the paddle movement, _fixedY is for, well, fixing the Y position, we will see this in action shortly.

 

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

 

Our constructor is pretty straight forward.  We cache our PaddleType and PhysicsShape values.  Then we loaded the paddle texture, set its scale and pivot.  Then if it is an AI paddle, we position it 10 pixels from the top of the screen in the center, while if it is the player we position it 10 from the bottom of the screen.  In both cases, we add half of the sprites height to the equation, since we set the sprite pivot point to it’s center and the pivot point is where transforms are performed relative to.  Next we copy the sprites Y position into _fixedY.  We apply a small bit of motion so that paddles start off moving, then schedule this class to receive updates.

 

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

 

In the ball Update method we start off making sure the rigid body controlling the paddle hasn’t rotated as a result of collisions… pong paddles don’t rotate!  Next we check if we are controller the player or the AI.  If it’s the player, we check the state of the left or right gamepad, and if either is pressed we either add or subtract Force from the paddle.  As a result, two presses left will have double the force, while a press left and a press right effectively has no force.  In the event of the AI, instead of being fed by input, we check the current ball location and apply force as a result.  You can add a great deal more logic here to make the AI perform better.  The catch is, it is easy to make Pong unbeatable, so don’t make it too good!  Finally we want to make sure the Physics system hasn’t moved the AI on the Y axis and if it has, move it back.

 

For the record, the paddles could be implemented as a Joint constrained to the X axis.  This has advantages and disadvantages.  First it would prevent the need to fix the Y axis.  However, you would lose the ability to bounce off the side bumpers.

 

Finally, we update the paddle’s position to match it’s rigid body’s position.  Then in the destructor, we clean things up.  That is more or less the entire logic of the game. 

 

Now lets take a look at the ScoreBoard logic.

 

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

Scoreboard is a simple dynamic sprite.  It keeps track of and displays the game score on screen.  It works just like the titlescreen, logic-wise, except that it generates it’s own image.  All of that logic is handled in UpdateImage, so let’s take a closer look at it.

 

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

The scoreboard starts life as an Image object 110x100 pixels in size.  We create a 50 point font using the only font included, System. We then draw the score in white on the image.  We then create a Texture2D to hold the image and a TextureInfo to hold the texture.  We then copy the pixels from our image to our texture using SetPixels and passing in the image as a byte array via ToBuffer.  At this point we are done with the image and font objects, so we dispose of them.

 

The only thing that remains is the game over screen, which we will look at now:

 

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

 

This code is almost identical to the title screen logic, so we wont go through it in detail.  Basically, you pass a boolean in to the constructor to let it know who won, the player or AI.  If the Player won, we display the winner.png graphic, while if the AI won, we display the loser.png graphic.  In the Update method, we check for a touch and if one occurs, set the TitleScene menu as the active scene.

 

And… that is it.  A complete simply Pong clone made with the PlayStation Mobile SDK.  It is by no means perfect, but it is a complete game and can be used as the foundation of a much better game.  I hope you enjoyed the series.  In the next part, we will simply be putting all of the code together on a single page, as well as a link to download the entire project, assets and all.

 

Programming , , ,

blog comments powered by Disqus

Month List

Popular Comments