Saturday, December 17, 2011

How to draw your own buttons

In this tutorial I am presenting a way for the case where you don’t want to use the high level library to add buttons in a game (or whatever else application) and want to draw your own instead.

Suppose you are making a game for the iPhone using the OpenGL ES. Of course you can use UIKit buttons and simply lay them over your OpenGL view. Using UIKit buttons works fine and is probably the easiest way to add buttons to your game. However, if you are trying to achieve the best performance possible, you should avoid putting a UIView on top of your OpenGL view. This is especially true if you want to use the controls in the game, unless your game is a “Find The Differences”-like game.

And since everything in my engine is texture everything that you can imagine can be considered as a button. From graphics and single alphanumeric characters (this is very useful for the “write your name” menu) to words and entire phrases.

Three types of buttons are fully supported:

  • Buttons that fire a single signal of the touch event even when the button is kept pressed

  • Continuous sending of signals to the core mechanism of the game as long as the button is kept pressed

  • One signle signal only if the button is released after it has been pressed.



We do need a procedure for this because the operating system (iOS or Dalvik) triggers one single event when we click on the screen, no matter if we keep our finger on the screen.

As a first step we have to introduce a new structure (or class, in java) where we are going to store whatever is needed for a clickable object or Option, to speak more general.


public class Option
{
int x;
int y;
int width;
int height;
int type;
int prevState;
}


Where x,y are the (x,y) coordinates of the upper left corner of the option in actual screen coordinates, calculated in the initialization procedure of the clickable object that is bound with this option.
‘width’ and’ height’ is the width and the height of the object , again in screen coordinates.

The field ‘type’ stores the information of the type (what else?) of the option according to the three different cases as I described above. I have defined them as follow:


final static int OPTION_TYPE_INSTANT = 0;
final static int OPTION_TYPE_CONTINUOUS = 1;
final static int OPTION_TYPE_PRESSED = 2;


The ‘type’ property of the option can be changed on runtime to achieve other effect for the same button in different game situations.
In the field ‘prevState’ is stored the previous state of the clickable object. You don’t have to deal with it since it is calculated automatically from the procedure that I am going to attach here. This state is defined as 0 for previously not pressed and 1 for previously pressed.


final static int PREVIOUSLY_NOT_PRESSED = 0;
final static int PREVIOUSLY_PRESSED = 1;


So,I am ready to reveal how our function will look like


boolean hasTouchedOnOption ( Option option )


The function returns a ‘boolean’ (BOOL for iOS). This means that in the current iteration of the game loop an option is either active or not active.

Do we need anything else? As you can guess we need a ‘touch_x’ and a ‘touch_y’ variable that represent the coordinates of the point where the click has taken place. We need also a ‘pointerStates’ variable that will do exactly what it says.

In my engine there are two states for the pointer. A finger is either on the screen or it is not on the screen.


final static int POINTER_RELEASED = 0;
final static int POINTER_PRESSED = 1;

int pointerStates = POINTER_RELEASED;


I chose to declare all these three variables as global variables. They are initialized in the function that catches the touch events directly from the underlying hardware.

iPhone: touchesBegan, touchesMoved, touchesEnded
Android: public boolean onTouchEvent

I am pasting here my implementation for Android


@Override
public boolean onTouchEvent(MotionEvent event)
{
int action = event.getAction();
if ( action == MotionEvent.ACTION_OUTSIDE ||
action == MotionEvent.ACTION_UP )
{
touch_x = -1;
touch_y = -1;
pointerStates = POINTER_RELEASED;
}
else if ( action == MotionEvent.ACTION_DOWN ||
action == MotionEvent.ACTION_MOVE )
{
touch_x = (int) event.getX();
touch_y = (int) event.getY();
pointerStates = POINTER_PRESSED;
}
return super.onTouchEvent(event);
}


Now it is time to open the curtain and reveal the body of the hasTouchedOnOption function

protected static boolean hasTouchedOnOption ( Option option )
{
if (pointerStates == POINTER_RELEASED )
{
if ( option.type == OPTION_TYPE_PRESSED )
{
if ( option.prevState == PREVIOUSLY_PRESSED)
{
option.prevState = PREVIOUSLY_NOT_PRESSED;
return true;
}
}
option.prevState = PREVIOUSLY_NOT_PRESSED;
return false;
}

if ( option.type == OPTION_TYPE_INSTANT )
{
if ( option.prevState == PREVIOUSLY_PRESSED )
{
return false;
}
}

// [OPTION] [TOUCH]
if ( touch_x > option.x + option.width )
{
option.prevState = PREVIOUSLY_NOT_PRESSED;
return false;
}

// [TOUCH] [OPTION]
if ( touch_x < option.x )
{
option.prevState = PREVIOUSLY_NOT_PRESSED;
return false;
}

// [ OPTION]
//
// [TOUCH]
if ( touch_y > option.y + option.height )
{
option.prevState = PREVIOUSLY_NOT_PRESSED;
return false;
}

// [TOUCH]
//
// [OPTION]
if ( touch_y < option.y )
{
option.prevState = PREVIOUSLY_NOT_PRESSED;
return false;
}

option.prevState = PREVIOUSLY_PRESSED;
if ( option.type != OPTION_TYPE_PRESSED )
return true;

return false;
}


I think there is not a lot of stuff that needs detailed explanation. [OPTION] and [TOUCH] is the relative position of the touch and the option. The function is extremely quick and it needs from 2 to 10(in the worst case) operations to return a result.

But where is the best place to call this function?

It depends on the nature of your application. Either directly inside the functions that transfer the touches from the OS(see above) or inside the main game loop.

2 comments:

  1. Hello, the code shown above is very much impressive but I am having problems in functions. I am doing the programming in iOS so it get me little bit confused with the boolean values so can you help with this issue.

    ReplyDelete
  2. dear Marie,
    the code in my entire blog is a pseudo-programming language. It is a near-java syntax but my scope is to give an implementation of the algorithm that I describe and not a compilable source-code for each platform. Of course in iOS you have to use BOOL instead of boolean and in order to use the samples without huge modifications you have to use C++ syntax instead of Objective-C.

    ReplyDelete