D-Pad / Joystick GUI Ctrl for iTGB
by Dave Calabrese · 12/14/2009 (6:12 pm) · 19 comments
The idea behind this control is actually really simple - it checks the position of where the user touches from the center, and then determines an angle. This can then be used to set a linear velocity on the player, or do whatever you need with it! This control is pretty much a copy of the guiBitmapCtrl, but with a number of modifications. I'm sure it could be cleaned up further, or even turned into a control derived from GuiBitmapCtrl.
guiControlDisc.cpp
guiControlDisc.h
And there you have it! Toss that into your game and you have a working D-Pad / Joystick control setup. All you have to do now is edit the end of the .CPP file to use the COS and SIN data in a way that is appropriate for your game.
guiControlDisc.cpp
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "console/console.h"
#include "console/consoleTypes.h"
#include "dgl/dgl.h"
#include "mMathFn.h"
#include "T2D/t2dVector.h"
#include "t2dSceneObject.h"
#include "gui/controls/GuiControlDisc.h"
IMPLEMENT_CONOBJECT(GuiControlDisc);
GuiControlDisc::GuiControlDisc(void)
{
mBitmapName = StringTable->insert("");
startPoint.set(0, 0);
mWrap = false;
#ifdef _LUMA
//ability to specify source rect for image UVs
mUseSourceRect = false;
mSourceRect.set(0, 0, 0, 0);
#endif //_LUMA
}
bool GuiControlDisc::setBitmapName( void *obj, const char *data )
{
// Prior to this, you couldn't do bitmap.bitmap = "foo.jpg" and have it work.
// With protected console types you can now call the setBitmap function and
// make it load the image.
static_cast<GuiControlDisc *>( obj )->setBitmap( data );
// Return false because the setBitmap method will assign 'mBitmapName' to the
// argument we are specifying in the call.
return false;
}
void GuiControlDisc::initPersistFields()
{
Parent::initPersistFields();
addGroup("GuiControlDisc");
addProtectedField( "bitmap", TypeFilename, Offset( mBitmapName, GuiControlDisc ), &setBitmapName, &defaultProtectedGetFn, "" );
endGroup("GuiControlDisc");
#ifdef _LUMA
addGroup("Misc");
//ability to specify source rect for image UVs
addField( "useSourceRect", TypeBool, Offset( mUseSourceRect, GuiControlDisc ));
addField( "sourceRect", TypeRectI, Offset( mSourceRect, GuiControlDisc ));
endGroup("Misc");
#endif //_LUMA
}
ConsoleMethod( GuiControlDisc, setValue, void, 4, 4, "(int xAxis, int yAxis)"
"Set the offset of the bitmap.n"
"@return No return value."
)
{
object->setValue(dAtoi(argv[2]), dAtoi(argv[3]));
}
ConsoleMethod( GuiControlDisc, setBitmap, void, 3, 3, "( pathName ) Use the setBitmap method to change the bitmap this control uses.n"
"@param pathName A path to a new texture for this control. Limited to 256x256.n"
"@return No return value")
{
object->setBitmap(argv[2]);
}
bool GuiControlDisc::onWake()
{
if (! Parent::onWake())
return false;
setActive(true);
setBitmap(mBitmapName);
return true;
}
void GuiControlDisc::onSleep()
{
mTextureHandle = NULL;
Parent::onSleep();
}
//-------------------------------------
void GuiControlDisc::inspectPostApply()
{
// if the extent is set to (0,0) in the gui editor and appy hit, this control will
// set it's extent to be exactly the size of the bitmap (if present)
Parent::inspectPostApply();
if (!mWrap && (mBounds.extent.x == 0) && (mBounds.extent.y == 0) && mTextureHandle)
{
TextureObject *texture = (TextureObject *) mTextureHandle;
mBounds.extent.x = texture->bitmapWidth;
mBounds.extent.y = texture->bitmapHeight;
}
}
void GuiControlDisc::setBitmap(const char *name, bool resize)
{
mBitmapName = StringTable->insert(name);
if (*mBitmapName) {
mTextureHandle = TextureHandle(mBitmapName, BitmapTexture, true);
// Resize the control to fit the bitmap
if (resize) {
TextureObject* texture = (TextureObject *) mTextureHandle;
mBounds.extent.x = texture->bitmapWidth;
mBounds.extent.y = texture->bitmapHeight;
GuiControl *parent = getParent();
if( !parent ) {
Con::errorf( "GuiControlDisc::setBitmap( %s ), trying to resize but object has no parent.", name ) ;
} else {
Point2I extent = parent->getExtent();
parentResized(extent,extent);
}
}
}
else
mTextureHandle = NULL;
setUpdate();
}
void GuiControlDisc::setBitmap(const TextureHandle &handle, bool resize)
{
mTextureHandle = handle;
// Resize the control to fit the bitmap
if (resize) {
TextureObject* texture = (TextureObject *) mTextureHandle;
mBounds.extent.x = texture->bitmapWidth;
mBounds.extent.y = texture->bitmapHeight;
Point2I extent = getParent()->getExtent();
parentResized(extent,extent);
}
}
void GuiControlDisc::onRender(Point2I offset, const RectI &updateRect)
{
if (mTextureHandle)
{
dglClearBitmapModulation();
if(mWrap)
{
// We manually draw each repeat because non power of two textures will
// not tile correctly when rendered with dglDrawBitmapTile(). The non POT
// bitmap will be padded by the hardware, and we'll see lots of slack
// in the texture. So... lets do what we must: draw each repeat by itself:
TextureObject* texture = (TextureObject *) mTextureHandle;
RectI srcRegion;
RectI dstRegion;
float xdone = ((float)mBounds.extent.x/(float)texture->bitmapWidth)+1;
float ydone = ((float)mBounds.extent.y/(float)texture->bitmapHeight)+1;
int xshift = startPoint.x%texture->bitmapWidth;
int yshift = startPoint.y%texture->bitmapHeight;
for(int y = 0; y < ydone; ++y)
for(int x = 0; x < xdone; ++x)
{
#ifdef _LUMA
//ability to specify source rect for image UVs
if(mUseSourceRect && mSourceRect.isValidRect() )
{
srcRegion = mSourceRect;
}
else
#endif //_LUMA
srcRegion.set(0,0,texture->bitmapWidth,texture->bitmapHeight);
dstRegion.set( ((texture->bitmapWidth*x)+offset.x)-xshift,
((texture->bitmapHeight*y)+offset.y)-yshift,
texture->bitmapWidth,
texture->bitmapHeight);
dglDrawBitmapStretchSR(texture,dstRegion, srcRegion, false);
}
}
else
{
RectI rect(offset, mBounds.extent);
#ifdef _LUMA
//ability to specify source rect for image UVs
if(mUseSourceRect && mSourceRect.isValidRect() )
{
RectI srcRegion;
srcRegion = mSourceRect;
dglDrawBitmapStretchSR(mTextureHandle,rect, srcRegion, false);
}
else
#endif //_LUMA
dglDrawBitmapStretch(mTextureHandle, rect);
}
}
if (mProfile->mBorder || !mTextureHandle)
{
RectI rect(offset.x, offset.y, mBounds.extent.x, mBounds.extent.y);
dglDrawRect(rect, mProfile->mBorderColor);
}
renderChildControls(offset, updateRect);
}
void GuiControlDisc::setValue(S32 x, S32 y)
{
if (mTextureHandle)
{
TextureObject* texture = (TextureObject *) mTextureHandle;
x+=texture->bitmapWidth/2;
y+=texture->bitmapHeight/2;
}
while (x < 0)
x += 256;
startPoint.x = x % 256;
while (y < 0)
y += 256;
startPoint.y = y % 256;
}
void GuiControlDisc::onMouseDown(const GuiEvent &event)
{
doPlayerThumbMove(event);
/*
//Lock the control
mouseLock();
setUpdate();
*/
}
void GuiControlDisc::onMouseUp(const GuiEvent &event)
{
char buf[32];
dSprintf(buf, sizeof(buf), "-1");
Con::executef(2,"joystickManager",buf);
}
void GuiControlDisc::onMouseDragged(const GuiEvent &event)
{
doPlayerThumbMove(event);
}
void GuiControlDisc::onMouseLeave(const GuiEvent &event)
{
/*
mouseUnlock();
setUpdate();
char buf[32];
dSprintf(buf, sizeof(buf), "-1");
Con::executef(2,"joystickManager",buf);
*/
}
void GuiControlDisc::doPlayerThumbMove(const GuiEvent &event)
{
Point2I pos = globalToLocalCoord(event.mousePoint);
F32 xPos = (int)pos.x;
F32 yPos = (int)pos.y;
t2dVector controlDiscCenter;
t2dVector controlDiscTouchPos;
controlDiscCenter.mX = mBounds.extent.x / 2.0;
controlDiscCenter.mY = mBounds.extent.y / 2.0;
controlDiscTouchPos.mX = pos.x;
controlDiscTouchPos.mY = pos.y;
// find angle
F32 angleOut = mFmod(mAtan( controlDiscTouchPos.mY - controlDiscCenter.mY , controlDiscTouchPos.mX - controlDiscCenter.mX), (2 * 3.14159));
// normalize to 0 - 360
if ( angleOut < 0.0f )
angleOut += (2 * 3.14159);
F32 sin, cos;
mSinCos( angleOut, sin, cos );
//NOTE: Now you have your data, you can do what you want with it. Let's pretend our player is stored in gGamePlayer. You could now do:
// F32 speed = 60;
// F32 finalSin = sin*speed;
// F32 finalCos = cos*speed;
// gGamePlayer->setLinearVelocity(finalSin,finalCos);
}guiControlDisc.h
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _GuiControlDisc_H_
#define _GuiControlDisc_H_
#ifndef _GUICONTROL_H_
#include "gui/core/guiControl.h"
#endif
#ifndef _GTEXMANAGER_H_
#include "dgl/gTexManager.h"
#endif
#ifdef PUAP_OPTIMIZE
//-Mat credit for this goes to Luke Lamothe (Luma), allows you to specify a region
//in your GUI image to use, instead of always displaying the whole image, you can clip part
//of it, makes it easy for different GUIS to use one image
#define _LUMA
#endif
/// Renders a bitmap.
class GuiControlDisc : public GuiControl
{
private:
typedef GuiControl Parent;
protected:
static bool setBitmapName( void *obj, const char *data );
static const char *getBitmapName( void *obj, const char *data );
StringTableEntry mBitmapName;
TextureHandle mTextureHandle;
Point2I startPoint;
bool mWrap;
t2dVector mLastTouchPos;
#ifdef _LUMA
//ability to specify source rect for image UVs
bool mUseSourceRect;
RectI mSourceRect;
#endif //_LUMA
public:
//creation methods
DECLARE_CONOBJECT(GuiControlDisc);
GuiControlDisc();
static void initPersistFields();
//Parental methods
bool onWake();
void onSleep();
void inspectPostApply();
void setBitmap(const char *name,bool resize = false);
void setBitmap(const TextureHandle &handle,bool resize = false);
void setMouseLeaveBool(bool setMouseLeave);
void onMouseDown(const GuiEvent &);
void onMouseUp(const GuiEvent &);
void onMouseDragged(const GuiEvent &);
void onMouseLeave(const GuiEvent &event);
void doPlayerThumbMove(const GuiEvent &event);
S32 getWidth() const { return(mTextureHandle.getWidth()); }
S32 getHeight() const { return(mTextureHandle.getHeight()); }
void onRender(Point2I offset, const RectI &updateRect);
void setValue(S32 x, S32 y);
};
#endifAnd there you have it! Toss that into your game and you have a working D-Pad / Joystick control setup. All you have to do now is edit the end of the .CPP file to use the COS and SIN data in a way that is appropriate for your game.
#2
12/16/2009 (9:27 am)
Cool! Might Add This When Im Working On My Game :)
#3
12/16/2009 (3:55 pm)
video would be cool to see this in action
#4
@Anthony: I'm talking to the band to see if we can get some gameplay videos of the Shinobi Ninja game posted soon, which makes use of this code for its D-Pad.
12/16/2009 (4:01 pm)
@Edoardo: Good catch! I hacked out a bunch of our custom code that was specific to the Shinobi Ninja game when posting this, so I guess that got clipped from the resource by mistake. Added it back in - thanks!@Anthony: I'm talking to the band to see if we can get some gameplay videos of the Shinobi Ninja game posted soon, which makes use of this code for its D-Pad.
#5
I've not idea if GuiDragDropControl it's good for the cap movement
12/29/2009 (5:42 am)
It's possible add a classic joypadcap to GuiControl and move it with the thumb?I've not idea if GuiDragDropControl it's good for the cap movement
#6
12/29/2009 (1:54 pm)
Sure. I would recommend adding this as a new graphical property to the control itself, so it renders where the player touches the GuiControl at.
#7
(T2D_iPhone_1_3_1\T2D_iPhone_1_3_1\engine\compilers\Xcode_iPhone) -> Open XCode project up
And then, I add your 2 files into the gui/controls folder (T2D_iPhone_1_3_1\T2D_iPhone_1_3_1\engine\source\gui\controls)
I build the project, everything fines
But when I run TGB iPhone 1.3 again, and go to GUI Builder, I didn’t see “GuiControlDisc” in Control Palette list (both Common, All tab)
So do you mind to tell me what did I do wrong here? Many thanks in advance
01/30/2010 (1:36 am)
Hi Dave, do you mind to help me here, I’m trying to add your 2 files into GUI Builder; so I go to the compiler at (T2D_iPhone_1_3_1\T2D_iPhone_1_3_1\engine\compilers\Xcode_iPhone) -> Open XCode project up
And then, I add your 2 files into the gui/controls folder (T2D_iPhone_1_3_1\T2D_iPhone_1_3_1\engine\source\gui\controls)
I build the project, everything fines
But when I run TGB iPhone 1.3 again, and go to GUI Builder, I didn’t see “GuiControlDisc” in Control Palette list (both Common, All tab)
So do you mind to tell me what did I do wrong here? Many thanks in advance
#8
01/30/2010 (1:55 am)
It sounds like you only recompiled the iPhone engine, not the TGB editor. Open up the engine\compilers\XCode project, add the files to that as well, then compile the Torque Game Builder target.
#9
01/30/2010 (2:44 am)
Thanks Dave for your quick reply, I finally managed to add GuiControlDisc into the Control Palette list. Btw, do you mind to give me some hints, how can I somehow link the GUI to for example, a character A in the MainScreenGUI or any useful resources. Thanks a lot for your helps, I appreciate it :)
#10
That said...
All you will need to do is get the angle of the D-Pad and apply it to what the player is doing. So if you are doing this from C++, it would be something like...
Obviously, I don't recommend making your player a global variable, but that's just for a quick example. :)
01/30/2010 (4:02 am)
Being that this is for the iPhone, I would suggest doing all of your controls in C++ as they will be much slower in script. That said...
All you will need to do is get the angle of the D-Pad and apply it to what the player is doing. So if you are doing this from C++, it would be something like...
gGamePlayer->setLinearVelocity(finalSin,finalCos);
Obviously, I don't recommend making your player a global variable, but that's just for a quick example. :)
#11
01/30/2010 (10:39 am)
Thanks for your hints, I'm going to try it now :)
#12
I have gotten everything to compile and it appears in the gui builder. I am having the following errors. Can you help me out with what I need to do to fix this.
joystickManager: Unknown command.
joystickManager: Unknown command.
joystickManager: Unknown command.
joystickManager: Unknown command.
02/21/2010 (5:35 pm)
Thanks for sharing this script.I have gotten everything to compile and it appears in the gui builder. I am having the following errors. Can you help me out with what I need to do to fix this.
joystickManager: Unknown command.
joystickManager: Unknown command.
joystickManager: Unknown command.
joystickManager: Unknown command.
#13
02/21/2010 (5:37 pm)
You'll need to create a script-level command called joystickManager. This is just meant to call the script level whenever the player takes their finger off the joystick. Obviously, you could control all of this on the C++ side as well (which would actually be better).
#14
Can you supply a sample joystick manager function?
Would it be something like...
function joystickManager(%direction)
{
if (%direction==1)
echo ("left);
etc....
}
02/21/2010 (5:41 pm)
I am a C# VS 2008 programmer and I have taken a course in c++, but that was a few years ago.Can you supply a sample joystick manager function?
Would it be something like...
function joystickManager(%direction)
{
if (%direction==1)
echo ("left);
etc....
}
#15
02/21/2010 (5:48 pm)
You got it, actually. Just go with that. In my example, I return a -1, to tell the script-level function that the joystick is no longer being touched. So...function joystickManager(%direction)
{
if(%direction == -1)
<no longer on joystick>
else
<Do things based on linear direction>
}
#16
The joystick Manager only gets called when the user lets go of the joypad. How do you read the direction and what function would get called when someone is clicking on the joypad?
I have added this to the end of the doPlayerThumbMove function.
**it seems to hang**
*********************************************************************
If I was to code this in C++, where would I put the code? Would I add it to my project somewhere or to the itgb source?
02/21/2010 (11:58 pm)
Thanks for your help, I have gotten things to work except...The joystick Manager only gets called when the user lets go of the joypad. How do you read the direction and what function would get called when someone is clicking on the joypad?
I have added this to the end of the doPlayerThumbMove function.
**it seems to hang**
char buf[64]; dSprintf(buf, sizeof(buf), "%s,%s,%s", angleOut, sin, cos ); Con::executef(2,"joystickManager", buf);
*********************************************************************
If I was to code this in C++, where would I put the code? Would I add it to my project somewhere or to the itgb source?
#17
I placed that between ""'s to show that it is reading it as one argument - not three. Also, you are storing your data in the buf as %s - which is string. You probably want to instead use %f for floating point.
You need to store your data in 3 separate buffers so you can return it to the script side without causing problems. Change your code to look like this and you should see better results:
As for where to put this in your code - that depends on your game. Personally, I like to have a player class that is derived from an existing class (such as t2dAnimatedSprite), and then pass data to and from that object. Depending on the game - if there is only one main character, you're also safe treating it like a global extern so you can easily pass data to and from it, however if you have a lot of characters, you should probably instead create a special holder object derived from simset then pass data into that to then pass data into the proper objects - and then in the object code on the C++ level, handle things as appropriate. :)
As a graph, that may look like this:
02/22/2010 (3:06 am)
You've got the right idea, but the con::execute function doesn't work like that. With your code, here is what the script side is seeing:joystickManager("arg1,arg2,arg3");I placed that between ""'s to show that it is reading it as one argument - not three. Also, you are storing your data in the buf as %s - which is string. You probably want to instead use %f for floating point.
You need to store your data in 3 separate buffers so you can return it to the script side without causing problems. Change your code to look like this and you should see better results:
char buf[3][64]; dSprintf(buf[1], sizeof(buf[1]), "%f", angleOut); dSprintf(buf[2], sizeof(buf[2]), "%f", sin); dSprintf(buf[3], sizeof(buf[3]), "%f", cos); Con::executef(4,"joystickManager", buf[1], buf[2], buf[3]);
As for where to put this in your code - that depends on your game. Personally, I like to have a player class that is derived from an existing class (such as t2dAnimatedSprite), and then pass data to and from that object. Depending on the game - if there is only one main character, you're also safe treating it like a global extern so you can easily pass data to and from it, however if you have a lot of characters, you should probably instead create a special holder object derived from simset then pass data into that to then pass data into the proper objects - and then in the object code on the C++ level, handle things as appropriate. :)
As a graph, that may look like this:
joystickController->SimSetDerivative->t2dAnimatedSpriteDerivative (which then contains movement code based on input it gets from the joystickController)
#18
guiControlDisc
Let's pretend the player sprite is named player.
Torque Script
I have updated Dave' code at the following...
www.torquepowered.com/community/resources/view/19395
02/22/2010 (6:48 am)
Thanks again and this is what I was trying to do.guiControlDisc
char buf[32]; dSprintf(buf, sizeof(buf), "%f %f %f", angleOut, sin, cos); Con::executef(2,"joystickManager", buf);
Let's pretend the player sprite is named player.
Torque Script
function joystickManager(%dir)
{
if(%dir == -1)
{
%finalSin = 0;
%finalCos = 0;
}
else
{
%angleOut = getWord(%dir, 0);
%sin = getWord(%dir, 1);
%cos = getWord(%dir, 2);
%speed = 20;
%finalSin = %sin*%speed;
%finalCos = %cos*%speed;
}
player.setLinearVelocity(%finalCos,%finalSin);
}I have updated Dave' code at the following...
www.torquepowered.com/community/resources/view/19395
#19
10/31/2010 (2:32 am)
cool~~, thx for your share~~ 
Torque Owner AltiMario
... I've compilation problem add this declaration line of guiControlDisk.h (after line 61):
void doPlayerThumbMove(const GuiEvent &event);