Lots of pretty awesome news out of Xamarin ( the C#/.NET for iOS/Android guys )

22. February 2013

 

Monotouch and Monodroid ( long since renamed ) are two products that I have *almost* purchased half a hundred times.  If you’ve not heard of them, they are a native port of C# and most of the .NET libraries to iOS and Android.  They have been having a good run and are the underpinnings of a number of very successful projects, such as PlayStation Mobile and Unity3D.  I really like .NET too but… and there is always a BUT.

 

It was 400$ for the basic version.  That’s 400$ for each platform too by the way.  That’s a pretty hard pill to swallow, especially when most of the competing products are free.  Or frankly, you could pick up the full Unity package for less than that!  Monotouch always offered a trial version, but it was limited to the emulator/simulator and if you have ever used the Android emulator on Windows, you know how vile that is!

 

Well, great news! 

 

First off, there is now a free version available that lets you deploy to device!  That said, you can’t p/Invoke to 3rd party code.  I honestly am not sure how much of a limitation that is.  Generally that means you can’t run native code, but still can run .NET assemblies.  If that’s the limitation, it isn’t a huge one.  If it means no libraries though… well, that’s a bit more painful.  I wonder if you can run Monogame, libGDX or PlayN in the free version?  Will look into that and get back to you.

 

Second, there has been a price drop.  It’s now 299$ per platform, per year.  Somehow, that 100$ makes a huge difference for me.  I don’t really like annual subscriptions though, I really wish people would move back towards version releases…  All the same, cheaper is always nice.

 

Third, there’s a new IDE, Xamarin Studio.

382920489522

A little too XCode for my tastes, but I’ll be sure to check it out.  MonoDevelop is nice enough, but never really felt… comfortable if that makes sense.

 

Fourth, and this one is a biggie to many people.  You can build for iOS from Visual Studio on Windows.  Don’t get too excited, you still need a networked Mac to run the native toolchain, but you can do 99% of your work, debugging and testing in Visual Studio.

 

 

Very cool developments!  You can read more about it here.

 

EDIT:

 

A few points of clarification.

First is regarding the 299/year.  It’s not as bad as it sounds, the tools continue to work after a year is up, just no more updates.  That is much more reasonable and developer friendly.

Second is more details on the free version:

Xamarin Starter allows developer to build and publish simple apps, which contain no more than 32k of compiled user code (IL), and which do not call out to native third party libraries (i.e., developers may not P/Invoke into C/C++/Objective-C/Java.

Still not sure where that would leave Monogame, as it isn’t a native library, but it does no doubt make pInvoke calls to OpenGL.

General, News, Programming , ,




monoGame 3.0 stable released

21. January 2013

 

This one is a big one.  If you have never heard of it, monoGame started life as a way to port XNA applications to the various Mono targets (including iOS and Android), built on top of OpenGL.  With Microsoft basically retiring XNA, monoGame has basically become the future of XNA.

The biggest and most obvious addition in this release is 3D support, but there are a number of other great new features:image

 

What's New?
  • 3D (many thanks to Infinite Flight Studios for the code and Sickhead Games in taking the time to merge the code in)
  • New platforms: Windows 8, Windows Phone 8, OUYA, PlayStation Mobile (including Vita).
  • Custom Effects.
  • PVRTC support for iOS.
  • iOS supports compressed Songs.
  • Skinned Meshs
  • VS2012 templates.
  • New Windows Installer
  • New MonoDevelop Package/AddIn
  • A LOT of bug fixes
  • Closer XNA 4 compatibility
  •  

    The also added a new installer that will install a binary release of Monogame on Windows for use with Visual Studio.  MonoDevelop users can update with the Add-in Manager.

    Head on over here for the release announcement.

     

    Nice work Monogame team!

    News , , , ,




    PlayStation Mobile Tutorial: Messing with a GameEngine2D sprite’s UV coordinates

    19. November 2012

     

    Over on the PlayStation Mobile forums, a user asked:

    For example, if I had a 100 x 100 image, I could use the source rectangle to  draw, say, the left part of the image 50 x 100.  This is useful when I have two images that represent the same object, e.g. a fancy progress meter, one light, the other dark.  SpriteUV has Quad which allows me to stretch and skew the image, but not crop.

    Am I missing something? If not, what are my options?

     

    My answer wasn’t a short one, so I decided to essentially turn it in to a tutorial post in and of itself.  So, if you want to learn how to do this, or how UV coordinates work in the first place, read on!

     

    The answer is, yes, you can very much do this with PSM Studio, but the way might not be completely intuitive.  Each sprite has a TRS set ( Translation/Rotation/Scale ) that represents its UV coordinates within its textures.    Essentially when you work with a SpriteTile, it’s handling all this magic for you.  The good news is, you can manipulate the UV values of a SpriteUV to accomplish exactly the effects described in the question above.  If what you are looking to do is create a sprite sheet animation however, there is a much better way.

     

    Alright, let’s jump right in with some code:

     

    using System;
    using Sce.PlayStation.Core;
    using Sce.PlayStation.Core.Graphics;
    using Sce.PlayStation.Core.Input;
    using Sce.PlayStation.HighLevel.GameEngine2D;
    using Sce.PlayStation.HighLevel.GameEngine2D.Base;
    
    namespace Test
    {
        public class AppMain
        {
            public static void Main (string[] args)
            {
                Director.Initialize();
                Scene scene = new Scene();
                scene.Camera.SetViewFromViewport();
                
                SpriteUV sprite = new SpriteUV(new TextureInfo("/Application/watch.png"));
                sprite.Scale = new Vector2(Director.Instance.GL.Context.Screen.Width,
                                           Director.Instance.GL.Context.Screen.Height);
                sprite.Schedule((dt) => {
                    var g = GamePad.GetData(0);
                    if((g.Buttons & GamePadButtons.Up) == GamePadButtons.Up){
                        // Top left
                        sprite.UV.S = new Vector2(0.5f,0.5f);
                        sprite.UV.T = new Vector2(0.0f,0.5f);
                    }
                    else if((g.Buttons & GamePadButtons.Left) == GamePadButtons.Left){
                        // Bottom left
                        sprite.UV.S = new Vector2(0.5f,0.5f);
                        sprite.UV.T = new Vector2(0.0f,0.0f);
                    }
                    else if ((g.Buttons & GamePadButtons.Down) == GamePadButtons.Down){
                        // Bottom right                
                        sprite.UV.S = new Vector2(0.5f,0.5f);
                        sprite.UV.T = new Vector2(0.5f,0.0f);
                    }
                    else if ((g.Buttons & GamePadButtons.Right) == GamePadButtons.Right){
                        // Top right
                        sprite.UV.S = new Vector2(0.5f,0.5f);
                        sprite.UV.T = new Vector2(0.5f,0.5f);
                    }
                    else if((g.Buttons & GamePadButtons.Cross) == GamePadButtons.Cross){
                        // Back to full screen
                        sprite.UV.S = new Vector2(1.0f,1.0f);
                        sprite.UV.T = new Vector2(0.0f,0.0f);
                    }
                },0);
                
                scene.AddChild(sprite);
                Director.Instance.RunWithScene(scene);
            }
        }
    }

     

    Run this code and you will see:

    full

     

    Now press an arrow key ( here is the results for UP ) and you will see a fraction of the original texture:

    topleft

    Press X to go back to the full texture dimensions.

     

    So… what’s happening here?

     

    First off, we Initialize our Director singleton, create a Scene and set the camera up to match the device screen dimensions.  We then create a SpriteUV and TextureInfo in a single line, loading an image of the famous Watchmen logo.  We then scale the sprite to also match the dimensions of the screen.

     

    It’s in the Schedule lambda the bulk of our UV manipulation logic takes place.  As you can see, we get the GamePad state, and handle the user pressing the d-pad or X button ( S key on simulator ).  In the event the user presses a direction button, we manipulate the sprite’s UV.S and UV.T values depending on which direction the user pressed, as indicated by the comment in each if statement.  If the user presses the X button, we set the UV.S and UV.T values back to full size and the origin respectively.  Finally we add the sprite to the scene, then run the scene.

     

    The heart of this example is the UV property, which is a TRS object.  If the term UV is unfamiliar to you, they are simply texture coordinates on a face.  They are called UV simply because there are two coordinates, U and V. The UV values  describe how a texture is mapped to a polygon face.  U and V values go from 0 to 1 and start in the bottom left corner of an image.  This means (0,0) is the bottom left corner of the texture, (1,1) is the top right coordinate of the texture while (0.5,0.5) is the very center of the texture.

     

    So, using the SpriteUV.UV property, we can alter the Translation ( position ), Rotation and Scale of the selection within SpriteUV’s texture that is going to be drawn.  Think of these values as describing the location of a select box within the texture that are going to be copied and drawn as the texture.  Here is a bad diagram to attempt to explain what’s happening.

     

    image

     

    The background is our SpriteUV texture.  By default the UV.T is (0,0), meaning drawing will start from the bottom left.  By default the UV.S values will be (1,1), meaning no scaling will occur.  Using these defaults, when we draw our sprite, the entire image will be drawn.

     

    Now consider the blue selection rectangle.  This has a UV.T value of (0.3,0.3) and a UV.S value of (0.5,0.5) …. okay… my drawing to scale kind sucked, just imagine that the blue rectange was a quarter of the entire image size.  These values mean that at position 0.3,0.3… or 30% right and 30% up from the origin in the bottom left, we start our selection.  Due to our S(cale) value of (0.5,0.5) it means our selection size is half the height and half the width of the texture.  In this example we are not making use of TR.R(otation).

     

    Therefore, assuming the following diagram was to scale, with a Translation UV value of 0.3, 0.3 and Scale UV value of 0.5 and 0.5, when we draw our texture we will see:

    image

     

    And that is how you can use UV values to draw a portion of a sprite’s texture.  You may notice that SpriteUV has a pair of properties, FlipU and FlipV.  All these do is reverse the direction of the U or V coordinate, for example, flipping U would make (0,0) be at the bottom right of the image instead of the bottom left.

     

    You can download the complete archive here.  Obviously the Watchman logo is protected under copyright and cannot be used commercially in any way whatsover, period, ever. For real.

    Programming , , ,




    A complete PlayStation Vita game from scratch: Summary

    13. September 2012

     

    This post is a simple summary of all the code used in the complete Vita game tutorial series, all on a single page.  Additionally, you can download the complete project here.

     

    Here is the end result of all of this code in action:

    Vita pong!

     

     

    And here is the code:

     

    AppMain.cs

    using System;
    using Sce.PlayStation.HighLevel.UI; 
    using Sce.PlayStation.HighLevel.GameEngine2D;
    
    namespace Pong
    {
        public class AppMain
        {
            public static void Main (string[] args)
            {
                Director.Initialize();
                UISystem.Initialize(Director.Instance.GL.Context);
                Director.Instance.RunWithScene(new TitleScene());                
            }
        }
    }

     

    TitleScene.cs

    using System;
    using Sce.PlayStation.Core;
    using Sce.PlayStation.Core.Graphics;
    using Sce.PlayStation.Core.Audio;
    using Sce.PlayStation.HighLevel.GameEngine2D;
    using Sce.PlayStation.HighLevel.GameEngine2D.Base;
    using Sce.PlayStation.Core.Input;
     
    namespace Pong
    {
        public class TitleScene : Scene
        {
            private TextureInfo _ti;
            private Texture2D _texture;
            
            private Bgm _titleSong;
            private BgmPlayer _songPlayer;
            
            public TitleScene ()
            {
                this.Camera.SetViewFromViewport();
                _texture = new Texture2D("Application/images/title.png",false);
                _ti = new TextureInfo(_texture);
                SpriteUV titleScreen = new SpriteUV(_ti);
                titleScreen.Scale = _ti.TextureSizef;
                titleScreen.Pivot = new Vector2(0.5f,0.5f);
                titleScreen.Position = new Vector2(Director.Instance.GL.Context.GetViewport().Width/2,
                                                  Director.Instance.GL.Context.GetViewport().Height/2);
                this.AddChild(titleScreen);
                
                Vector4 origColor = titleScreen.Color;
                titleScreen.Color = new Vector4(0,0,0,0);
                var tintAction = new TintTo(origColor,10.0f);
                ActionManager.Instance.AddAction(tintAction,titleScreen);
                tintAction.Run();
                
                _titleSong = new Bgm("/Application/audio/titlesong.mp3");
                
                if(_songPlayer != null)
                _songPlayer.Dispose();
                _songPlayer = _titleSong.CreatePlayer();
                
                Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);
    
                // Clear any queued clicks so we dont immediately exit if coming in from the menu
                Touch.GetData(0).Clear();
            }
            
            public override void OnEnter ()
            {
                _songPlayer.Loop = true;
                _songPlayer.Play();
            }
            public override void OnExit ()
            {
                base.OnExit ();
                _songPlayer.Stop();
                _songPlayer.Dispose();
                _songPlayer = null;
            }
            
            public override void Update (float dt)
            {
                base.Update (dt);
                var touches = Touch.GetData(0).ToArray();
                if((touches.Length >0 && touches[0].Status == TouchStatus.Down) || Input2.GamePad0.Cross.Press)
                {
                    Director.Instance.ReplaceScene(new MenuScene());
                }
            }
        
            ~TitleScene()
            {
                _texture.Dispose();
                _ti.Dispose ();
            }
        }
    }
     

    MenuScene.cs

    using System;
    using Sce.PlayStation.Core;
    using Sce.PlayStation.Core.Graphics;
    using Sce.PlayStation.Core.Input;
    
    using Sce.PlayStation.HighLevel.GameEngine2D;
    using Sce.PlayStation.HighLevel.GameEngine2D.Base;
    
    using Sce.PlayStation.HighLevel.UI;
    
    namespace Pong
    {
        public class MenuScene : Sce.PlayStation.HighLevel.GameEngine2D.Scene
        {
            private Sce.PlayStation.HighLevel.UI.Scene _uiScene;
            
            public MenuScene ()
            {
                this.Camera.SetViewFromViewport();
                Sce.PlayStation.HighLevel.UI.Panel dialog = new Panel();
                dialog.Width = Director.Instance.GL.Context.GetViewport().Width;
                dialog.Height = Director.Instance.GL.Context.GetViewport().Height;
                
                ImageBox ib = new ImageBox();
                ib.Width = dialog.Width;
                ib.Image = new ImageAsset("/Application/images/title.png",false);
                ib.Height = dialog.Height;
                ib.SetPosition(0.0f,0.0f);
                
                Button buttonUI1 = new Button();
                buttonUI1.Name = "buttonPlay";
                buttonUI1.Text = "Play Game";
                buttonUI1.Width = 300;
                buttonUI1.Height = 50;
                buttonUI1.Alpha = 0.8f;
                buttonUI1.SetPosition(dialog.Width/2 - 150,200.0f);
                buttonUI1.TouchEventReceived += (sender, e) => {
                    Director.Instance.ReplaceScene(new GameScene());
                };
                
                Button buttonUI2 = new Button();
                buttonUI2.Name = "buttonMenu";
                buttonUI2.Text = "Main Menu";
                buttonUI2.Width = 300;
                buttonUI2.Height = 50;
                buttonUI2.Alpha = 0.8f;
                buttonUI2.SetPosition(dialog.Width/2 - 150,250.0f);
                buttonUI2.TouchEventReceived += (sender, e) => {
                Director.Instance.ReplaceScene(new TitleScene());
                };        
                    
                dialog.AddChildLast(ib);
                dialog.AddChildLast(buttonUI1);
                dialog.AddChildLast(buttonUI2);
                _uiScene = new Sce.PlayStation.HighLevel.UI.Scene();
                _uiScene.RootWidget.AddChildLast(dialog);
                UISystem.SetScene(_uiScene);
                Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);
            }
            public override void Update (float dt)
            {
                base.Update (dt);
                UISystem.Update(Touch.GetData(0));
                
            }
            
            public override void Draw ()
            {
                base.Draw();
                UISystem.Render ();
            }
            
            ~MenuScene()
            {
                
            }
        }
    }

    GameScene.cs

    using System;
    using Sce.PlayStation.Core;
    using Sce.PlayStation.HighLevel.GameEngine2D;
    using Sce.PlayStation.HighLevel.GameEngine2D.Base;
    using Sce.PlayStation.HighLevel.Physics2D;
    using Sce.PlayStation.Core.Audio;
    
    namespace Pong
    {
        public class GameScene : Scene
        {
        private Paddle _player,_ai;
        public static Ball ball;
        private PongPhysics _physics;
        private Scoreboard _scoreboard;
        private SoundPlayer _pongBlipSoundPlayer;
        private Sound _pongSound;
            
            // Change the following value to true if you want bounding boxes to be rendered
            private static Boolean DEBUG_BOUNDINGBOXS = false;
            
            public GameScene ()
            {
                this.Camera.SetViewFromViewport();
                _physics = new PongPhysics();
    
                
                ball = new Ball(_physics.SceneBodies[(int)PongPhysics.BODIES.Ball]);
                _player = new Paddle(Paddle.PaddleType.PLAYER, 
                                     _physics.SceneBodies[(int)PongPhysics.BODIES.Player]);
                _ai = new Paddle(Paddle.PaddleType.AI, 
                                 _physics.SceneBodies[(int)PongPhysics.BODIES.Ai]);
                _scoreboard = new Scoreboard();
                
                this.AddChild(_scoreboard);
                this.AddChild(ball);
                this.AddChild(_player);
                this.AddChild(_ai);
                
                
                // This is debug routine that will draw the physics bounding box around the players paddle
                if(DEBUG_BOUNDINGBOXS)
                {
                    this.AdHocDraw += () => {
                        var bottomLeftPlayer = _physics.SceneBodies[(int)PongPhysics.BODIES.Player].AabbMin;
                        var topRightPlayer = _physics.SceneBodies[(int)PongPhysics.BODIES.Player].AabbMax;
                        Director.Instance.DrawHelpers.DrawBounds2Fill(
                            new Bounds2(bottomLeftPlayer*PongPhysics.PtoM,topRightPlayer*PongPhysics.PtoM));
    
                        var bottomLeftAi = _physics.SceneBodies[(int)PongPhysics.BODIES.Ai].AabbMin;
                        var topRightAi = _physics.SceneBodies[(int)PongPhysics.BODIES.Ai].AabbMax;
                        Director.Instance.DrawHelpers.DrawBounds2Fill(
                            new Bounds2(bottomLeftAi*PongPhysics.PtoM,topRightAi*PongPhysics.PtoM));
    
                        var bottomLeftBall = _physics.SceneBodies[(int)PongPhysics.BODIES.Ball].AabbMin;
                        var topRightBall = _physics.SceneBodies[(int)PongPhysics.BODIES.Ball].AabbMax;
                        Director.Instance.DrawHelpers.DrawBounds2Fill(
                            new Bounds2(bottomLeftBall*PongPhysics.PtoM,topRightBall*PongPhysics.PtoM));
                    };
                }
                
                //Now load the sound fx and create a player
                _pongSound = new Sound("/Application/audio/pongblip.wav");
                _pongBlipSoundPlayer = _pongSound.CreatePlayer();
                
                Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);
            }
            
            private void ResetBall()
            {
                //Move ball to screen center and release in a random directory
                _physics.SceneBodies[(int)PongPhysics.BODIES.Ball].Position = 
                    new Vector2(Director.Instance.GL.Context.GetViewport().Width/2,
                                Director.Instance.GL.Context.GetViewport().Height/2) / PongPhysics.PtoM;
                
                System.Random rand = new System.Random();
                float angle = (float)rand.Next(0,360);
            
                if((angle%90) <=15) angle +=15.0f;
            
                _physics.SceneBodies[(int)PongPhysics.BODIES.Ball].Velocity = 
                    new Vector2(0.0f,5.0f).Rotate(PhysicsUtility.GetRadian(angle));
            }
            
            public override void Update (float dt)
            {
                base.Update (dt);
                
                if(Input2.GamePad0.Select.Press)
                    Director.Instance.ReplaceScene(new MenuScene());
                
                //We don't need these, but sadly, the Simulate call does.
                Vector2 dummy1 = new Vector2();
                Vector2 dummy2 = new Vector2();
                
                //Update the physics simulation
                _physics.Simulate(-1,ref dummy1,ref dummy2);
                
                //Now check if the ball it either paddle, and if so, play the sound
                if(_physics.QueryContact((uint)PongPhysics.BODIES.Ball,(uint)PongPhysics.BODIES.Player) ||
                    _physics.QueryContact((uint)PongPhysics.BODIES.Ball,(uint)PongPhysics.BODIES.Ai))
                {
                    if(_pongBlipSoundPlayer.Status == SoundStatus.Stopped)
                        _pongBlipSoundPlayer.Play();
                }
                
                //Check if the ball went off the top or bottom of the screen and update score accordingly
                Results result = Results.StillPlaying;
                bool scored = false;
                
                if(ball.Position.Y > Director.Instance.GL.Context.GetViewport().Height + ball.Scale.Y/2)
                {
                    result = _scoreboard.AddScore(true);
                    scored = true;
                }
                if(ball.Position.Y < 0 - ball.Scale.Y/2)
                {
                    result =_scoreboard.AddScore(false);
                    scored = true;
                }
                
                // Did someone win?  If so, show the GameOver scene
                if(result == Results.AiWin) 
                    Director.Instance.ReplaceScene(new GameOverScene(false));
                if(result == Results.PlayerWin) 
                    Director.Instance.ReplaceScene(new GameOverScene(true));
                
                //If someone did score, but game isn't over, reset the ball position to the middle of the screen
                if(scored == true)
                {
                    ResetBall ();
                }
                
                //Finally a sanity check to make sure the ball didn't leave the field.
                var ballPB = _physics.SceneBodies[(int)PongPhysics.BODIES.Ball];
                
                if(ballPB.Position.X < -(ball.Scale.X/2f)/PongPhysics.PtoM ||
                   ballPB.Position.X > (Director.Instance.GL.Context.GetViewport().Width)/PongPhysics.PtoM)
                {
                    ResetBall();
                }
            }
            
            ~GameScene(){
                _pongBlipSoundPlayer.Dispose();
            }
        }
    }

    Ball.cs

    using System;
    using Sce.PlayStation.Core;
    using Sce.PlayStation.Core.Graphics;
    using Sce.PlayStation.HighLevel.GameEngine2D;
    using Sce.PlayStation.HighLevel.GameEngine2D.Base;
    using Sce.PlayStation.HighLevel.Physics2D;
    
    namespace Pong
    {
        public class Ball : SpriteUV
        {
            private PhysicsBody _physicsBody;
            // Change this value to make the game faster or slower
            public const float BALL_VELOCITY = 5.0f;
            
            public Ball (PhysicsBody physicsBody)
            {
                _physicsBody = physicsBody;
                
                this.TextureInfo = new TextureInfo(new Texture2D("Application/images/ball.png",false));
                this.Scale = this.TextureInfo.TextureSizef;
                this.Pivot = new Sce.PlayStation.Core.Vector2(0.5f,0.5f);
                this.Position = new Sce.PlayStation.Core.Vector2(
                    Director.Instance.GL.Context.GetViewport().Width/2 -Scale.X/2,
                    Director.Instance.GL.Context.GetViewport().Height/2 -Scale.Y/2);
                
                
                //Right angles are exceedingly boring, so make sure we dont start on one
                //So if our Random angle is between 90 +- 25 degrees or 270 +- 25 degrees
                //we add 25 degree to value, ie, making 90 into 115 instead
                System.Random rand = new System.Random();
                float angle = (float)rand.Next(0,360);
            
                if((angle%90) <=25) angle +=25.0f;
                this._physicsBody.Velocity = new Vector2(0.0f,BALL_VELOCITY).Rotate(PhysicsUtility.GetRadian(angle));;
                
                Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);
            }
            
            public override void Update (float dt)
            {
    
                this.Position = _physicsBody.Position * PongPhysics.PtoM;
    
                
                // We want to prevent situations where the balls is bouncing side to side
                // so if there isnt a certain amount of movement on the Y axis, set it to + or - 0.2 randomly
                // Note, this can result in the ball bouncing "back", as in it comes from the top of the screen
                // But riccochets back up at the user.  Frankly, this keeps things interesting imho
                var normalizedVel = _physicsBody.Velocity.Normalize();
                if(System.Math.Abs (normalizedVel.Y) < 0.2f) 
                {
                    System.Random rand = new System.Random();
                    if(rand.Next (0,1) == 0)
                        normalizedVel.Y+= 0.2f;
                    
                    else
                        normalizedVel.Y-= 0.2f;
                }
                
                // Pong is a mess with physics, so just fix the ball velocity
                // Otherwise the ball could get faster and faster ( or slower ) on each collision
                _physicsBody.Velocity = normalizedVel * BALL_VELOCITY;
    
            }
            
            ~Ball()
            {
                this.TextureInfo.Texture.Dispose();
                this.TextureInfo.Dispose();
            }
        }
    }

    Paddle.cs

    using System;
    using Sce.PlayStation.Core;
    using Sce.PlayStation.Core.Graphics;
    using Sce.PlayStation.HighLevel.GameEngine2D;
    using Sce.PlayStation.HighLevel.GameEngine2D.Base;
    using Sce.PlayStation.HighLevel.Physics2D;
    
    namespace Pong
    {
        public class Paddle : SpriteUV
        {
            public enum PaddleType { PLAYER, AI };
            
            private PaddleType _type;
            private PhysicsBody _physicsBody;
            private float _fixedY;
            
            public Paddle (PaddleType type, PhysicsBody physicsBody)
            {
                _physicsBody = physicsBody;
                _type = type;
    
                this.TextureInfo = new TextureInfo(new Texture2D("Application/images/Paddle.png",false));
                this.Scale = this.TextureInfo.TextureSizef;
                this.Pivot = new Sce.PlayStation.Core.Vector2(0.5f,0.5f);
                
                if(_type== PaddleType.AI)
                {
                    this.Position = new Sce.PlayStation.Core.Vector2(
                        Director.Instance.GL.Context.GetViewport().Width/2 - this.Scale.X/2,
                        10 + this.Scale.Y/2);                    
                }
                else
                {
                    this.Position = new Sce.PlayStation.Core.Vector2(
                        Director.Instance.GL.Context.GetViewport().Width/2 - this.Scale.X/2,
                        Director.Instance.GL.Context.GetViewport().Height - this.Scale.Y/2 - 10);
                }
                
                // Cache the starting Y position, so we can reset and prevent any vertical movement from the Physics Engien
                _fixedY = _physicsBody.Position.Y;
                
                // Start with a minor amount of movement
                _physicsBody.Force = new Vector2(-10.0f,0);
                
                Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);
            }
            
            // This method will fix the physics bounding box to the sprites current position
            // Not currently used, was used for debug, left in for interest sake only
            private void ClampBoundingBox()
            {
                var bbBL = new Vector2(Position.X- Scale.X/2, Position.Y- Scale.Y/2) / PongPhysics.PtoM;
                var bbTR = new Vector2(Position.X+ Scale.X/2, Position.Y+ Scale.Y/2) / PongPhysics.PtoM;
                _physicsBody.AabbMin = bbBL;
                _physicsBody.AabbMax = bbTR;
                
            }
            public override void Update (float dt)
            {
                // Reset rotation to prevent "spinning" on collision
                _physicsBody.Rotation = 0.0f;
                
                
                if(_type == PaddleType.PLAYER)
                {
                    if(Input2.GamePad0.Left.Down)
                    {
                        _physicsBody.Force = new Vector2(-30.0f,0.0f);
                    }
                    if(Input2.GamePad0.Right.Down)
                    {
                        _physicsBody.Force = new Vector2(30.0f,0.0f);
                    }
                }
                else if(_type == PaddleType.AI)
                {
                    if(System.Math.Abs (GameScene.ball.Position.X - this.Position.X) <= this.Scale.Y/2)
                        _physicsBody.Force = new Vector2(0.0f,0.0f);
                    else if(GameScene.ball.Position.X < this.Position.X)
                        _physicsBody.Force = new Vector2(-20.0f,0.0f);
                    else if(GameScene.ball.Position.X > this.Position.X)
                        _physicsBody.Force = new Vector2(20.0f,0.0f);
                }
                
                //Prevent vertical movement on collision.  Could also implement by making paddle Kinematic
                //However, lose ability to use Force in that case and have to use AngularVelocity instead
                //which results in more logic in keeping the AI less "twitchy", a common Pong problem
                if(_physicsBody.Position.Y != _fixedY)
                    _physicsBody.Position = new Vector2(_physicsBody.Position.X,_fixedY);
                
                this.Position = _physicsBody.Position * PongPhysics.PtoM;
            }
            
            ~Paddle()
            {
                this.TextureInfo.Texture.Dispose ();
                this.TextureInfo.Dispose();
            }
        }
    }

    Scoreboard.cs

    using System;
    
    using Sce.PlayStation.Core.Imaging;
    using Sce.PlayStation.Core.Graphics;
    using Sce.PlayStation.Core;
    using Sce.PlayStation.HighLevel.GameEngine2D;
    using Sce.PlayStation.HighLevel.GameEngine2D.Base;
    
    namespace Pong
    {
        public enum Results { PlayerWin, AiWin, StillPlaying };
        
        public class Scoreboard : SpriteUV
        {
            public int playerScore = 0;
            public int aiScore = 0;
            
            public Scoreboard ()
            {
                this.TextureInfo = new TextureInfo();
                UpdateImage();
                
                this.Scale = this.TextureInfo.TextureSizef;
                this.Pivot = new Vector2(0.5f,0.5f);
                this.Position = new Vector2(Director.Instance.GL.Context.GetViewport().Width/2,
                                            Director.Instance.GL.Context.GetViewport().Height/2);
                
            }
            
            private void UpdateImage()
            {
                Image image = new Image(ImageMode.Rgba,new ImageSize(110,100),new ImageColor(0,0,0,0));
                Font font = new Font(FontAlias.System,50,FontStyle.Regular);
                image.DrawText(playerScore + " - " + aiScore,new ImageColor(255,255,255,255),font,new ImagePosition(0,0));
                image.Decode();
    
                var texture  = new Texture2D(110,100,false,PixelFormat.Rgba);
                if(this.TextureInfo.Texture != null)
                    this.TextureInfo.Texture.Dispose();
                this.TextureInfo.Texture = texture;
                texture.SetPixels(0,image.ToBuffer());
                font.Dispose();
                image.Dispose();
            }
            public void Clear()
            {
                playerScore = aiScore = 0;
                UpdateImage();
            }
            
            public Results AddScore(bool player)
            {
                if(player)
                    playerScore++;
                else
                    aiScore++;
                if(playerScore > 3) return Results.PlayerWin;
                if(aiScore > 3) return Results.AiWin;
                
                UpdateImage();
    
                return Results.StillPlaying;
            }
        }
    }

    GameOver.cs

    using System;
    using Sce.PlayStation.Core;
    using Sce.PlayStation.Core.Graphics;
    using Sce.PlayStation.Core.Audio;
    using Sce.PlayStation.HighLevel.GameEngine2D;
    using Sce.PlayStation.HighLevel.GameEngine2D.Base;
    using Sce.PlayStation.Core.Input;
    
    namespace Pong
    {
        public class GameOverScene : Scene
        {
            private TextureInfo _ti;
            private Texture2D _texture;
            
            public GameOverScene (bool win)
            {
                this.Camera.SetViewFromViewport();
                if(win)
                    _texture = new Texture2D("Application/images/winner.png",false);
                else
                    _texture = new Texture2D("Application/images/loser.png",false);
                _ti = new TextureInfo(_texture);
                SpriteUV titleScreen = new SpriteUV(_ti);
                titleScreen.Scale = _ti.TextureSizef;
                titleScreen.Pivot = new Vector2(0.5f,0.5f);
                titleScreen.Position = new Vector2(Director.Instance.GL.Context.GetViewport().Width/2,
                                                   Director.Instance.GL.Context.GetViewport().Height/2);
                this.AddChild(titleScreen);
                
                Scheduler.Instance.ScheduleUpdateForTarget(this,0,false);
                
                Touch.GetData(0).Clear();
            }
            
            public override void Update (float dt)
            {
                base.Update (dt);
                int touchCount = Touch.GetData(0).ToArray().Length;
                if(touchCount > 0 || Input2.GamePad0.Cross.Press)
                {
                    Director.Instance.ReplaceScene( new TitleScene());
                }
            }
            
            ~GameOverScene()
            {
                _texture.Dispose();
                _ti.Dispose ();
            }
        }
    }

    Programming , , ,




    A complete PlayStation Vita game from scratch: Part Five

    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 , , ,




    A closer look at the Loom game engine. Part one, getting started
    Home >

    A closer look at the Loom game engine. Part one, getting started

    28. February 2013

    I have decided to take a closer look at the Loom game engine that I mentioned recently, as I am an absolute sucker for new game engines, especially ones that are currently free.  You can consider these posts a cross between a diary and a review as I dive in to what it's like using Loom.  Of course, I am also documenting the process, so if you are just getting started with Loom hopefully these posts prove useful for you.

     

    Alright, let's jump right in.

     

    Quick summary to those that didn't read the above link.  Loom is a 2D game engine, based around a custom scripting language Loomscript, which is a bastard love child of ActionScript and C#. Behind the scenes it is written in C++ over the Cocos2D-x library (and others) and you can get full access to the source code. Full disclosure, I've never actually used ActionScript before, but I have a fair bit of JavaScript and C# experience as well as a copy of the book The ActionScript 3.0 Bible so hopefully I can puzzle things out as I go.

     

    Getting Started -- Installation and creating your first application

     

    First things first head on over to TheEngine.co site sign up and download Loom.  Be sure to use a valid email address and password, you are going to need them later.  Log in and download the version most applicable to you.

    Loom1

     

    In this particular case, I'm using MacOS, but it should simply be a matter of substituting the words "command prompt" wherever you see "Terminal".

     

    Now that it's installed, welcome to an interesting part of Loom.  It is command line driven.  During installation it will have installed itself to somewhere in your systems path (/usr/bin/loom in my case on Mac OS), simply open a terminal/command prompt and change to the directory where you want to create a Loom project.

    Now type loom login

    When prompted for credentials, use the username and password you signup with.  If all went well you should see:

    Loom2

     

    If you don't get Login successful! well, good luck finding out if TheEngine.co has good support or not. :)

     

    Now we actually want to create a project.  From the same terminal window type:

    loom new ProjectName

    like so:

    Loom3

     

    Voila, a new project should have just been created for you.

     

    Now simply change into your project directory and type loom run

    Loom will now download the latest SDK and compile your code like so:

    Loom4

     

    And assuming everything went well, your skeleton application will run:

    Loom5

    And you've just created and run your first loom application in about 4 minutes work.

     

    Now for the first annoyance… for some reason, after calling loom run once you close your application window, the script is hung.  You need to CTRL+C to stop execution of the script in your terminal window.  Small oversight, but kinda irritating… at first it just seemed like it was simply hung up, especially as my LoomTest application didn't get focus on execution, so I wasn't even aware it was running at first!

     

    Anyways, that's a pretty small beef and something easily fixed in time.

     

     

    Setting up an Editor

     

    Command line tools are all nice and good, but I am spoiled from years of Visual Studio development… I like working in an IDE or at least an editor.  Fortunately Loom has configurations for a very good one available, Sublime Text.

     

    This part is completely optional, but I am now going to configure Sublime Text to work with Loom.

    Initially all I am going to do is add LoomScript syntax highlighting, I don't really mind switching to a terminal window to build/run my application.  To do so, head over to this thread and download the linked bundle zip file.  I assume you have already installed SublimeText, if not, do so now.

    Extract the zip file.  Inside the extracted folder is another folder called LoomScript.  Copy it.

    (The following is MacOS specific)

    Open Finder then hit Command Key + Shift + G

    In the field enter ~/Library/Application\ Support/Sublime\ Text\ 2/Packages/

    Paste the folder LoomScript in this directory.

    Now start Sublime Text and select File->Open… and navigate to the folder you created your Loom project in.  If should look like this with complete syntax highlighting.

    Loom6

     

    This mostly just gives pretty syntax, if you want closer to a full IDE experience, be sure to read this post!

     

    TheEngine.co apparently have a full IDE in the works, but for now, IDE should prove a usable solution.  I am going to continue investigating getting code completion working though, a feature I really like.

     

    Learning Loom

     

    This part may not seem immediately obvious… so now you have your game created, how do you actually go about learning Loom?  

    Well, there are two ways.  First off, there are a series of examples you can download and dissect from right here.

    Second, in your terminal, change to your project directory and simply type loom docs 

    The documentation will then be loaded in your default browser like so:

    Loom7

     

    Well, we are now setup and running with our first primitive app.  That's enough for now, in the next part I will get down to writing some actual code.

     

    You can read the next part right here







    blog comments powered by Disqus