Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon
13. June 2012

 

In this tutorial we are going to look at the various Gesture detectors built into the UI layer of the PlayStation Mobile SDK.  They all work fairly consistently, so this tutorial is going to be pretty light in explanation. 

 

There are 6 gesture detectors built in to the PS SDK; FlickGestureDetector, DragGestureDetector, TapGestureDetector, LongPressGestureDetector, PinchGestureDetector and DoubleTapGestureDetector.  DoubleTapGestureDetector barely works on the simulator and doesn’t work at all on the actual Vita, so we aren’t going to use it. 

 

Let’s just right in to coding, in this tutorial we are going to create an ImageBox UI widget that you can use with all these various GestureDetectors to flick, scale and drag all over the screen.  Here is the code:

 

using System; using System.Collections.Generic; using Sce.Pss.Core; using Sce.Pss.Core.Environment; using Sce.Pss.Core.Graphics; using Sce.Pss.Core.Input; using Sce.Pss.HighLevel.UI; namespace UIEffects { public class AppMain { private static GraphicsContext _graphics; private static ImageBox _imageBox; private static Label _label; private static FlickGestureDetector _flickGestureDetector; private static DragGestureDetector _dragGestureDetector; private static TapGestureDetector _tapGestureDector; private static LongPressGestureDetector _longPressGestureDetector; private static PinchGestureDetector _pinchGestureDetector; static private bool quit = false; private static float _origWidth; private static float _origHeight; private static float _origX; private static float _origY; // Note, no housecleaning, all event handlers leak! public static void Main(string[] args) { _graphics = new GraphicsContext(); UISystem.Initialize(_graphics); Sce.Pss.HighLevel.UI.Panel dialog = new Panel(); dialog.Width = _graphics.GetViewport().Width; dialog.Height = _graphics.GetViewport().Height; _label = new Label(); _label.Width = dialog.Width; _label.Height = 35; _label.Font = new Sce.Pss.Core.Imaging.Font(Sce.Pss.Core.Imaging.FontAlias.System,32,Sce.Pss.Core.Imaging.FontStyle.Regular); _label.SetPosition(0,0); _label.HorizontalAlignment = HorizontalAlignment.Center; _label.TextColor = new UIColor(0,0,0,255); _imageBox = new Sce.Pss.HighLevel.UI.ImageBox(); _imageBox.Image = new ImageAsset("/Application/image.png"); _imageBox.PivotType = PivotType.MiddleCenter; _imageBox.Width = _imageBox.Image.Width;///2; _imageBox.Height = _imageBox.Image.Height;///2; _imageBox.SetPosition(dialog.Width/2,//-_imageBox.Width/2, dialog.Height/2);///2-_imageBox.Height/2); _origWidth = _imageBox.Width; _origHeight = _imageBox.Height; _origX = _imageBox.X; _origY = _imageBox.Y; _flickGestureDetector = new FlickGestureDetector(); _flickGestureDetector.Direction = FlickDirection.Horizontal; _flickGestureDetector.FlickDetected += delegate(object sender, FlickEventArgs e) { if(e.Speed.X < 0) // Left new MoveEffect(_imageBox,800f,0f,_imageBox.Y,MoveEffectInterpolator.Elastic).Start (); else new MoveEffect(_imageBox,800f,dialog.Width,_imageBox.Y,MoveEffectInterpolator.Elastic).Start (); _label.Text = "Flicked"; }; _dragGestureDetector = new DragGestureDetector(); _dragGestureDetector.DragDetected += delegate(object sender, DragEventArgs e) { var newPos = new Vector2(e.Source.X,e.Source.Y) + e.Distance; e.Source.SetPosition(newPos.X,newPos.Y); _label.Text = "Dragged"; }; _tapGestureDector = new TapGestureDetector(); _tapGestureDector.TapDetected += delegate(object sender, TapEventArgs e) { e.Source.SetPosition(dialog.Width/2,dialog.Height/2); _label.Text = "Tapped"; }; _longPressGestureDetector = new LongPressGestureDetector(); _longPressGestureDetector.MinPressDuration = 3000f; _longPressGestureDetector.LongPressDetected += delegate(object sender, LongPressEventArgs e) { MessageDialog.CreateAndShow(MessageDialogStyle.Ok,"Press results", "Hey, let go of me! " + (e.ElapsedTime/1000).ToString() + " seconds elapsed!"); _label.Text = "Long pressed"; }; _pinchGestureDetector = new PinchGestureDetector(); _pinchGestureDetector.PinchDetected += delegate(object sender, PinchEventArgs e) { Vector2 newSize = new Vector2(e.Source.Width * e.Scale,e.Source.Height * e.Scale); e.Source.SetSize(newSize.X,newSize.Y); _label.Text = "Pinched"; }; _imageBox.AddGestureDetector(_flickGestureDetector); _imageBox.AddGestureDetector(_dragGestureDetector); _imageBox.AddGestureDetector(_tapGestureDector); _imageBox.AddGestureDetector(_longPressGestureDetector); _imageBox.AddGestureDetector(_pinchGestureDetector); dialog.AddChildLast(_imageBox); dialog.AddChildLast(_label); Sce.Pss.HighLevel.UI.Scene scene = new Sce.Pss.HighLevel.UI.Scene(); scene.RootWidget.AddChildLast(dialog); UISystem.SetScene(scene,new SlideTransition(2000.0f,FourWayDirection.Left,MoveTarget.NextScene,SlideTransitionInterpolator.Linear)); while(!quit) { _graphics.SetClearColor(255,255,255,0); _graphics.Clear(); if((GamePad.GetData(0).Buttons & GamePadButtons.Cross) == GamePadButtons.Cross) { //Reset _imageBox.SetSize(_origWidth,_origHeight); _imageBox.SetPosition(_origX,_origY); } if((GamePad.GetData(0).Buttons & GamePadButtons.Triangle) == GamePadButtons.Triangle) { quit = true; } UISystem.Update(Touch.GetData(0)); UISystem.Render(); _graphics.SwapBuffers(); } } } }

 

 

First we create our Graphics context and initialize our UISystem, then create a Panel named dialog that is going to be our active window.  We then create a label for output purposes, as well as an ImageBox that is going to contain our Jet sprite ( oh yeah… add an image to your project Smile I am reusing the Jet sprite from the last tutorial but you can use anything ).  The Image property of our ImageBox contains an ImageAsset, which we construct by simply passing it the filename of the graphic we want to use.  Next we store the original location of the _imageBox, so we can revert back to defaults later if desired.

 

The first gesture detector we are going to look at is the FlickGestureDetector.  This controls rapidly sliding your finger in one direction on the screen… think Angry birds.  The heart of the flick handling is the FlickDetected delegate, repeated here:

 

_flickGestureDetector.FlickDetected += delegate(object sender, FlickEventArgs e) { if(e.Speed.X < 0) // Left new MoveEffect(_imageBox,800f,0f,_imageBox.Y,MoveEffectInterpolator.Elastic).Start (); else new MoveEffect(_imageBox,800f,dialog.Width,_imageBox.Y,MoveEffectInterpolator.Elastic).Start (); _label.Text = "Flicked"; };

 

All we are doing here is detecting if the speed is a positive or negative value.  If it is a negative value, it means we are flicking right to left, otherwise it is left to right.  Obviously if you were interested in velocity, you would take the actual value into account.  We indicated with the line:

    _flickGestureDetector.Direction = FlickDirection.Horizontal;

that we are only interested in flicking along the horizontal axis.  Therefore any vertical flicks are going to be ignored. 

 

The direction simply determines what the Y coordinate is going to be in our MoveEffect call, either 0 ( far left ) or dialog.Width ( far right ).  MoveEffect causes the passed widget ( _imageBox ), to be moved over time (800ms in this case) to the passed coordinates.  The MoveEffectInterpolator determines how the move is going to happen, we choose Elastic which will cause it to have a bit of a spring effect.  For no effect at all, choose the Linear interpolator.  Essentially, if we flick left, we send the jet to the left side of the screen, while if we flick right, we send the jet to the right side of the screen.

 

Next up is the DragGestureDetector.  Dragging is when you tap, hold, then move the widget.  If we detect a drag, we simply want to translate our _imageBox location to the position the user dragged to.  We accomplish this with this code:

 

_dragGestureDetector = new DragGestureDetector(); _dragGestureDetector.DragDetected += delegate(object sender, DragEventArgs e) { var newPos = new Vector2(e.Source.X,e.Source.Y) + e.Distance; e.Source.SetPosition(newPos.X,newPos.Y); _label.Text = "Dragged"; };

 

As you can see, the various Gesture detectors use remarkably similar handlers.  In this case all we do is get the current position of the dragged widget ( the value in e.Source will be the widget being acted upon ) and add the distance it’s been dragged.

 

Now we add a TapGestureDetector.  In the event of a tap, we simply want to relocated the sprite back to the center of the screen.  Here is the code:

 

_tapGestureDector = new TapGestureDetector(); _tapGestureDector.TapDetected += delegate(object sender, TapEventArgs e) { e.Source.SetPosition(dialog.Width/2,dialog.Height/2); _label.Text = "Tapped"; };

 

Nothing really new there.  Again, e.Source represents the Widget object that is being tapped.  When a tap occurs we simply set it’s location to the center of the screen and log the tapped activity.

 

Next up is the LongPressGestureDetector.  A long press is a tap, followed by a hold for a given threshold of time, in our case 3000 milliseconds.  Once the long press occurs and hits the threshold, the delegate is fired.

 

_longPressGestureDetector = new LongPressGestureDetector(); _longPressGestureDetector.MinPressDuration = 3000f; _longPressGestureDetector.LongPressDetected += delegate(object sender, LongPressEventArgs e) { MessageDialog.CreateAndShow(MessageDialogStyle.Ok,"Press results", "Hey, let go of me! " + (e.ElapsedTime/1000).ToString() + " seconds elapsed!"); _label.Text = "Long pressed"; };

 

In the delegate, all we do is pop up a MessageDialog window.  There are two ways of working with MessageDialog, you can create one directly and then show it, or like we’ve done here, you can use the static CreateAndShow method.

 

Finally we have the PinchGesture Detector.  A pinch is a two finger concurrent tap, followed by either sliding your fingers closer together, or further apart.  Ultimately the value of most importance is the e.Scale, which determines if they pinched larger ( out ) or smaller ( in ) and how much.  By multiplying the e.Scale value against the widget’s current size, it will either scale larger or smaller.

 

_pinchGestureDetector.PinchDetected += delegate(object sender, PinchEventArgs e) { Vector2 newSize = new Vector2(e.Source.Width * e.Scale,e.Source.Height * e.Scale); e.Source.SetSize(newSize.X,newSize.Y); _label.Text = "Pinched"; };

 

One really confusing thing I ran into is the PinchGestureDetector’s PinchEndDetector is never fired!  Be aware of this; although truth is, you don’t really need it in the end.  As I mentioned at the beginning, there is also a DoubleTapGestureDetector, but it doesn’t work.

 

Finally, we wire all of these GestureDetectors to our ImageBox widget using this code:

_imageBox.AddGestureDetector(_flickGestureDetector); _imageBox.AddGestureDetector(_dragGestureDetector); _imageBox.AddGestureDetector(_tapGestureDector); _imageBox.AddGestureDetector(_longPressGestureDetector); _imageBox.AddGestureDetector(_pinchGestureDetector);

 

You can have multiple widgets using the same gestures, and as you can see in this example, a single widget can have multiple detectors attached.  The remaining code is all stuff we have covered in prior tutorials.

 

Here is the result in action.  Since the Simulator doesn’t support multitouch, I took a video of my Vita in action.

 

Pinching, zooming, tapping, dragging and pressing on the PlayStation Vita with the PSSDK

 

 

Once again, you can download the full project here.

Programming


9. June 2012

 

As I have been working with Cocos2D-html ( and PlayStation GameEngine2D which is based on cocos2D ) lately, I decided to take a look at what the cocos2D book situation was like.  While about half as large as my Unity 3D book round-up there is still a nice, growing collection of cocos2D related books.

 

The cocos2D book round-up is a collection of every currently published book on the subject of cocos2D.  It contains all of the details you may want to know when selecting a book ( author, page count, rating, links to Amazon, Safari Books Online and the publisher, publish date, table of contents, publisher description, etc ) all on a single page.  So if you are in the market for a book on cocos2D, or just want to see what all is available, start here.

 

I am to be as comprehensive as possible, so if I’ve missed a book on the subject, or made a mistake, please let me know!

General


9. June 2012

 

 

It was originally announced that the next version of Visual Studio Express would only be able to develop Metro applications, meaning developers writing traditional desktop, services or3749668_300 console code would have to buy the full Visual Studio.  This was a pretty serious blow to not only poor indie developers but also various open source projects.

 

 

Luckily, Microsoft has seen the error of their way and announced Visual Studio 2012 Express for Desktop.

 

 

Adhering to the core principles we’ve set for our Express products, Visual Studio Express 2012 for Windows Desktop will provide a simple, end-to-end development experience for developing Windows desktop applications targeted to run on all versions of Windows supported by Visual Studio 2012. With this new Express edition, developers will be able to use C++, C#, or Visual Basic to create Windows desktop and console applications.  Developers will also get access to new advances available across the Express family in Visual Studio 2012, such as the latest compilers and programming language tools, integrated unit testing, and the ability for small development teams to collaborate via Team Explorer and TFS Express.

 

It’s nice to see them put all the languages together in a single Express version as well.  Nice to see Microsoft seeing the error of their ways and not trying to force Metro down developers throats.  Now if only they would reverse their stance on XNA (or Silverlight). :(

News


8. June 2012

 

Just a quick note to say I’ve organized my recent Cocos2D posts into a traditional tutorial series, like this site’s C++ and PlayStation Mobile tutorials.  You can now access them from the right hand navigation list, or by clicking here

 

Right now we are at 3 tutorials, plus one related post.  More tutorials are on the way.

News


8. June 2012

 

This tutorial is going to look at the basics of sprites and an area that initially confuses many people ( myself included! ), the positioning system used by Cocos2D.  I am going to build off the code we wrote in Tutorial 2, with a few minor changes.  First thing you need to do is make a copy of the MyFirstApp folder and paste it as the aptly named MySecondApp, so you end up with a folder looking like this:

 

image

 

Now we need to make a few changes. First, you can delete the file MyFirstApp.js within the MySecondApp directory as we are going to create a new one now.  Create another js file, MySecondApp.js.  We will worry about the contents in a few minutes.

 

First we need to make a couple small alterations to our existing code.  Open up Cocos2d.js and scroll to the very bottom.  We want to change:

 

appFiles:['MyFirstApp.js']

to

appFiles:['MySecondApp.js']

Now open up main.js and change:

 

var myApp = new cocos2dApp(MyFirstAppScene);

 

to

 

var myApp = new cocos2dApp(MySecondAppScene);

 

That is all the changes we need to make to our boilerplate code.  In future tutorials, I will just assume you have already performed this step.

 

Finally before we get to coding, we need a sprite to work.  In your newly created folder, create a directory named images and save an image file there.  I am using this sprite, which has the file name Jet.png.  You of course can use whatever sprite you want.

jet

 

Now lets fill in the code for MySecondApp.js

 

var MySecondApp = cc.Layer.extend(
{
    init:function()
    {
        var layer1 = cc.LayerColor.create(
            new cc.Color4B(128, 128, 128, 255), 600, 600),
            jetSprite = cc.Sprite.create("./images/Jet.png");

        layer1.setPosition(new cc.Point(0.0,0.0));
        layer1.addChild(jetSprite);
        jetSprite.setPosition(new cc.Point(0.0,0.0));

        this.addChild(layer1);
        return true;
    }
});


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

 

Much of this code should look virtually identical to MyFirstApp.js, as not much has changed.  Obviously our variable name has changed, but onEnter() is otherwise identical.

 

In our init() function, we are simply creating a background layer that is a solid gray color with the dimensions of 600x600 ( the same as our Canvas tag ).  We then create a cc.Sprite object using our Jet.png.  This is all that is involved in loading and creating a sprite object.  Now it's just a matter of adding the sprite to our layer, and adding our layer to MySecondApp’s layer. 

 

Lets load up our new app in the web browser and see what’s what!

image

 

As you can see, we have a 600x600 solid layer of gray and if you look closely in the bottom corner of our screen, you can see 1/4 of our jet sprite.  Depending on your background, this result might seem remarkably odd, as there are two factors at play here.

 

First, as I said in a previous tutorial, Cocos2D puts the origin at the bottom left hand corner, not the more traditional top left.  So if you have ever worked with textures or bitmap graphics before ( outside of OpenGL ), this is going to seem a little backwards to you.

 

Why would they do it this way?  I am guessing that  they followed the OpenGL way of doing things.  Now, why would OpenGL buck the trend and set the origin to the bottom left?  Well that involves a trip back to high school math and Cartesian coordinates.  If you think in those terms, suddenly starting from the bottom left makes a bit more sense.  Remember this graph:

 

2D_Cartesian_Coordinates

 

In this particular case, if you are dealing with positive coordinates, you are in the upper right hand quadrant.  So if something is at say, position (2,2), it is above and to the right of the origin.

 

In the end it doesn’t really matter why, just be aware that the positions in Cocos2D start at the bottom left and you will be fine.

 

The next factor here is, where is the sprite’s anchor point.  Its all nice and good to say our jet is at (0,0)… but what part of our Jet is at (0,0)?  This is where the anchor point comes into effect.  The anchor point is where sprite translations ( rotating, translating, etc ) are performed relative to.  In Cocos2D, by default the pivot point is in the middle of the sprite, like illustrated by the red dot:

jetpivot

 

It is traditional to place the anchor point at the top left corner, but there are some real advantages to placing it at the center.  The least of which is rotation.  Consider the effects of rotating a sprite 1 degree per frame.

 

Here is the result with the pivot about the center:

(The code for the above animation is available here )

 

 

And here is the same image with the sprite anchor point moved to the top left:

 

(The code for the above animation is available here )

 

 

If you want to alter the anchor point, you can do it with this code:

jetSprite.setAnchorPoint(cc.PointMake(0,1));

 

The parameter to setAnchorPoint() is the new origin, in OpenGL coordinates.  0,0 == Bottom left, 0,1 == Top left, 1,0 == Top right, 1,1 == Bottom right.  Truth of the matter is, you should just adjust to the Cocos way of doing things unless you have a very good reason.

 

Alright… back to example, doing things the Cocos2D way.

 

Let say for example we wanted our sprite to be at the top right hand corner, we would accomplish this with the following code:

var size = cc.Director.sharedDirector().getWinSize(); jetSprite.setPosition(new cc.Point( size.width - jetSprite.getContentSizeInPixels().width/2, size.height - jetSprite.getContentSizeInPixels().height/2) );

 

Now here is the updated init() method with the Jet sprite centered to the screen.

 init:function()
    {
        var layer1 = cc.LayerColor.create(
            new cc.Color4B(128, 128, 128, 255), 600, 600),
            jetSprite = cc.Sprite.create("./images/Jet.png");

        layer1.setPosition(new cc.Point(0.0,0.0));
        layer1.addChild(jetSprite);

        var size = cc.Director.getInstance().getWinSize();
        jetSprite.setPosition(new cc.Point(size.width/2,size.height/2));

        this.addChild(layer1);
        return true;
    }

 

And finally, our end results:

image

 

Again, you can download the entire project here.

 

 

Read Tutorial 4

Programming


AppGameKit Studio

See More Tutorials on DevGa.me!

Month List