This tutorial is part of the Marmalade SDK tutorials collection. To see the tutorials index click here
A little rushed this weekend but I did kind of commit myself to producing another tutorial this weekend, so here goes.
This tutorial is going to cover handling touch and multi-touch pointer events using the Marmalade SDK. Thankfully the Marmalade SDK makes this pretty simple as you will soon see.
As usual if you just want the code then grab it from here. if you want the details then read on. If you just want the details on how to use the CInput class then jump straight to the “Using CInput” section
Oh its worth noting that week by week I am going to keep updating the original Iw2D DrawSprite example with the systems that we put together. This week for example, I have added a new CInput class. I like to try and keep code for separate systems in their own neat little classes because a) it makes the code easier to understand b) you can strip out the code and use it as-is with no modification c) its good programming practice. I would ordinarily create systems such as CInput using a singleton, but for the sake of readability and so that you don’t have to run off finding out about the likes of singletons, I have simply declared the concrete version of CInput as a global (slap on the wrist for me, but whatever makes the code easier to understand)
Marmalade Events and Callbacks
The Marmalade pointer system handles screen touches using an event system. This means that when something happens to the pointer (the users finger or a stylus touches the phone or tablets screen for example) the system calls a function to handle it. The Marmalade SDK uses what are called Callbacks to implement this type of event notification system. A call back is basically a function that we define that gets called back by the Marmalade system when the event occurs. The system usually passes some parameters to the call back function to let us know what the heck it wants. Here’s a quick example:
// // HandleSingleTouchButtonCB - The system will call this callback when the user moves their finger on the screen // void HandleSingleTouchButtonCB(s3ePointerEvent* event) { // Please add code to do something about this important event }
In this example HandleSingleTouchButtonCB gets called by the system when the user touches the screen. Behold, the system has also provided us with some data about the event by way of a pointer to the s3ePointerEvent data type. We can now query this data to find out what the system wants to tell us.
Taking a quick look at the s3ePointerEvent data type we discover that there is some pretty interesting stuff in there that we can use:
typedef struct s3ePointerTouchEvent { /** * ID of the touch. The ID given to a touch is equal to the number * of simultaneous touches active at the time the touch began. This ID * can be between 0 and S3E_POINTER_TOUCH_MAX-1 inclusive */ uint32 m_TouchID; /** Whether the touch started (1) or ended (0).*/ uint32 m_Pressed; /** Position X. */ int32 m_x; /** Position Y. */ int32 m_y; } s3ePointerTouchEvent;
Ok, so some of you may be thinking, how do I tell the Marmalade SDK to use my callback when such an event does occur? Because call backs are so useful the Marmalade SDK has lots of function for registering a callback with the system. The one we are interested in for the pointer is called:
S3E_API s3eResult s3ePointerRegister(s3ePointerCallback cbid, s3eCallback fn, void* userData);
- cbid – This represents a constant that identifies which pointer event you would like to be notified about
- fn – The address of the callback function that you would like Marmalade to call when the event occurs
Here’s how we would set one up to listen for single touch events
s3ePointerRegister(S3E_POINTER_BUTTON_EVENT, (s3eCallback)HandleSingleTouchButtonCB, NULL);
Now that is registered, whenever the system receives a screen touched event you will know about it as HandleSingleTouchButtonCB() will be called
Oh and be nice to the system and don’t forget to unregister the call back when you are done using it with, such as when you exit the game:
s3ePointerUnRegister(S3E_POINTER_BUTTON_EVENT, (s3eCallback)HandleSingleTouchButtonCB);
Types of Marmalade Pointer Events
The Marmalade SDk currently handles a number of pointer event types:
- Touch Event – Called when the user touches the screen (S3E_POINTER_BUTTON_EVENT)
- Motion Event – Called when the user moves their finger or stylus on the screen (S3E_POINTER_MOTION_EVENT)
- Multi-touch Touch Event – Called when the user touches the screen (S3E_POINTER_TOUCH_EVENT)
- Multi-touch Motion Event – Called when the user moves their finger or stylus on the screen (S3E_POINTER_TOUCH_MOTION_EVENT)
To be a good Marmalade developer you should handle all four of these events
Handling the Four Marmalade Pointer Events
In the Input example (CInput.cpp) we have declared four call backs to handle all four events:
// // HandleMultiTouchButtonCB - For multitouch devices the system will call this callback when the user touches the screen. This callback is called once for each screen touch // void HandleMultiTouchButtonCB(s3ePointerTouchEvent* event) { // Check to see if the touch already exists CTouch* touch = g_Input.findTouch(event->m_TouchID); if (touch != NULL) { // Yes it does, so update the touch information touch->active = event->m_Pressed != 0; touch->x = event->m_x; touch->y = event->m_y; } } // // HandleMultiTouchMotionCB - For multitouch devices the system will call this callback when the user moves their finger on the screen. This callback is called once for each screen touch // void HandleMultiTouchMotionCB(s3ePointerTouchMotionEvent* event) { // Check to see if the touch already exists CTouch* touch = g_Input.findTouch(event->m_TouchID); if (touch != NULL) { // Updates the touches positional information touch->x = event->m_x; touch->y = event->m_y; } } // // HandleSingleTouchButtonCB - The system will call this callback when the user touches the screen // void HandleSingleTouchButtonCB(s3ePointerEvent* event) { CTouch* touch = g_Input.getTouch(0); touch->active = event->m_Pressed != 0; touch->x = event->m_x; touch->y = event->m_y; } // // HandleSingleTouchMotionCB - The system will call this callback when the user moves their finger on the screen // void HandleSingleTouchMotionCB(s3ePointerMotionEvent* event) { CTouch* touch = g_Input.getTouch(0); touch->x = event->m_x; touch->y = event->m_y; }
Eik! I know, looks a bit messy, but callbacks usually do look a bit out of place. That said I do love callbacks!
The call back functions are very small and very simple. They basically pull the event data (pointer position and button status) and move them into a CTouch array inside the CInput class, where we can later access them in our code. Note the use of my nasty global concrete version of CInput g_Input. Ordinarily I would use a singleton for stuff like this (mental note, topic for another blog)
Now that callbacks are more or less out of the way we will proceed with looking at the CInput class in a little more detail
The CInput Class
Firstly lets take a look at the CInput initialisation code:
bool CInput::Init() { // Check to see if the device that we are running on supports the pointer Available = s3ePointerGetInt(S3E_POINTER_AVAILABLE) ? true : false; if (!Available) return false; // No pointer support // Clear out the touches array for (int t = 0; t < MAX_TOUCHES; t++) { Touches[t].active = false; Touches[t].id = 0; } // Determine if the device supports multi-touch IsMultiTouch = s3ePointerGetInt(S3E_POINTER_MULTI_TOUCH_AVAILABLE) ? true : false; // For multi-touch devices we handle touch and motion events using different callbacks if (IsMultiTouch) { s3ePointerRegister(S3E_POINTER_TOUCH_EVENT, (s3eCallback)HandleMultiTouchButtonCB, NULL); s3ePointerRegister(S3E_POINTER_TOUCH_MOTION_EVENT, (s3eCallback)HandleMultiTouchMotionCB, NULL); } else { s3ePointerRegister(S3E_POINTER_BUTTON_EVENT, (s3eCallback)HandleSingleTouchButtonCB, NULL); s3ePointerRegister(S3E_POINTER_MOTION_EVENT, (s3eCallback)HandleSingleTouchMotionCB, NULL); } return true; // Pointer support }
Like any good programmer we are checking to ensure that the pointer system is available on the device that we are running on, with so many different handsets out there, who knows if there are some with no pointer support?
// Check to see if the device that we are eunning on supports the pointer Available = s3ePointerGetInt(S3E_POINTER_AVAILABLE) ? true : false;
Its better to know up front and inform the user that your game or app is not compatible with their phone because it does not support the pointer.
Next, we determine if the device supports multi-touch. Note that many Android phones and tablets do not support multi-touch, so you will have to think carefully about your game or apps design before targeting Android.
// Determine if the device supports multi-touch IsMultiTouch = s3ePointerGetInt(S3E_POINTER_MULTI_TOUCH_AVAILABLE) ? true : false;
Lastly, we register two callbacks depending upon whether or not the device supports multi-touch:
// For multi-touch devices we handle touch and motion events using different callbacks if (IsMultiTouch) { s3ePointerRegister(S3E_POINTER_TOUCH_EVENT, (s3eCallback)HandleMultiTouchButtonCB, NULL); s3ePointerRegister(S3E_POINTER_TOUCH_MOTION_EVENT, (s3eCallback)HandleMultiTouchMotionCB, NULL); } else { s3ePointerRegister(S3E_POINTER_BUTTON_EVENT, (s3eCallback)HandleSingleTouchButtonCB, NULL); s3ePointerRegister(S3E_POINTER_MOTION_EVENT, (s3eCallback)HandleSingleTouchMotionCB, NULL); }
Ok, so now we have initialised the input system using g_Input.Init(); we need to ensure that the pointer system gets regularly updated. To do that we call g_Input.Update();
This method is very simple:
void CInput::Update() { // Update the pointer if it is available if (Available) s3ePointerUpdate(); }
Update() simply calls the Marmalade SDK’s s3ePointerUpdate() function to update the pointer system and call our callbacks when pointer events occur. Note that this must be called every game frame, so ensure that its placed somewhere in your main loop (near the beginning if possible)
Using the Cinput Class
If we now turn our attention towards Main.cpp, we can take a quick look at what has changed from DrawSprite_Iw2D.
Well the first thing is the inclusion of the CInput.h header file.
Next we initialise the Cinput class:
// Initialise the input system g_Input.Init();
Because the example supports multi-touch we create a few variables to hold the position of our two sprites so we can move them independently
int sprite1_pos_x = surface_width / 2; int sprite1_pos_y = surface_height / 2; int sprite2_pos_x = surface_width / 2; int sprite2_pos_y = surface_height / 2;
In our main loop we update the sprites based on where the user touches the screen:
// Update pointer system g_Input.Update(); if (g_Input.getTouchCount() != 0) { // Get the first touch position CTouch* touch = g_Input.getTouch(0); if (touch != NULL) { sprite1_pos_x = touch->x; sprite1_pos_y = touch->y; sprite2_pos_x = sprite1_pos_x; sprite2_pos_y = sprite1_pos_y; } // if multi-touch is available then move 2nd sprite to 2nd touch position if (g_Input.isMultiTouch()) { if (g_Input.getTouchCount() > 1) { touch = g_Input.getTouch(1); if (touch != NULL) { sprite2_pos_x = touch->x; sprite2_pos_y = touch->y; } } } }
Ok, this bit of code is no longer a bit of code and looks a bit meaty. However, it is very simple to understand.
Firstly we check to see if there has been any touches by getting the touch count from CInput. If there are touches present then we get the first touch and set both sprites to the position of the touch. This will move both sprites to wherever the user taps the screen.
The second part checks to see if there has been more than one touch, if so then we get the 2nd touch and move the 2nd sprite to its position.
Note that I am just getting the touches by their index in the touches list and not by their ID. In a proper multi-touch system you should ideally track touches by their ID and not their index in the touches list. But for this example, this way suffices.
And finally we draw our sprites at their new positions:
// Draw two sprites DrawSprite(image1, sprite1_pos_x, sprite1_pos_y, -sprite_angle, (iwfixed)(IW_GEOM_ONE * 2)); DrawSprite(image2, sprite2_pos_x, sprite2_pos_y, sprite_angle, IW_GEOM_ONE);
Multi-touch Simulation using the Simulator
The Marmalade SDK simulator will allow you to simulate multi-touch functionality in your application but you firstly need to enable it. To enable this functionality you need to:
- Go to the simulator menu and select Configuration Pointer
- Tick “Report multi-touch available” and “enable multi-touch simulation mode”
Now that you have enabled multi-touch simulation you can use the middle mouse button to place touches. You can move the touches around by holding the middle mouse button down over the placed touch and move it. To remove a multi-touch touch, simply click the middle mouse button over the touch again.
Well that concludes this tutorial. I hope you all find it of some use. You can download the associated touch code project from here
Happy coding and stay away from rickety old bridges!
This post got rather long so I did not cover how one would handle things like detecting gestures or taps / double taps. I will work on a further tutorial outlining the details in the near future
Great tutorial. Anxious for your gestures tutorial though.
And thanks for sharing and teaching 🙂
Excellent tutorial again, thanks for going to the effort of writitng it.
Can I ask when we will see a guide on displaying an image that is larger than the screen?
I always seem to run into memory issues, even for an image that is at the iPhone4 retina resolution.
Again, excellent work.
@pumartin: Have you increased the amount of memory available to Marmalade, the default is pretty small? To increase the amount of memory available edit the data\app.icf file and add this section:
[S3E]
MemSize=20971520
This will give you 20MB to play with
Well… That got it working!
To be honest I thought I’d allocated enough memory using that method previously – though it did keep telling me that i had much less than the 20971520 suggested to allocate when I was trying it? Something like 16231625 it said was the maximum I could set only last week?
Either way, I’m very happy to have this working now. Along with your continued tutorials I think I’ll ba having a good go at this Marmalade malarkey 🙂
Glad that you sorted it. Not sure about the memory size discrepancy.
Glad to hear that you will be sticking with Marmalade (no pun intended :)) I will try and get a new tutorial done this week then more at the weekend with any luck.
Just to say thank-you for these articles they truely are invaluable (and inspirational). Please keep them comming.
Best Regards
Don’t know whether I am doing something wrong, but when I tried to build and (x86)Debug the project, the simulator just hangs (Not responding). Please guide me, so that I can trace the events happening in the program.
Awaiting your reply.
Regards
I will try to help. Here are a couple of questions:
* Are you using this unmodified version of the source code archive attached to this post? If not which source are you using?
* Which version of the Marmalade SDK are you using?
* Are there are errors or warnings output to the Visual Studio output window?
1) Unmodified. Just downloaded and tried. Just hangs the simulator.
2) Marmalade SDK 5.1
3) Just one, given below
IntelliSense: #error directive: “Wrong compiler used. Please run s3eConfig to configure Visual Studio” c:\users\abc\desktop\touch\build_touch_vc10\temp_defines_debug.h 112 10
I don’t understand, it is perfectly configured, and when I try to run examples of Marmalade, all work perfectly.
It feels like it is going in an infinite loop.
By the way, thanks a lot for helping. Looking forward for the solution.
Regards.
Could you try deleting data\development.icf and then re-creating the project from the MKB file?
Tried deleting the development.icf file. Still no success. 🙁
Do you open the Touch.mkb by double clicking on it in explorer?
I took a look around the net and found a few places that mention this problem:
http://www.madewithmarmalade.com/node/253
http://www.madewithmarmalade.com/devnet/forum/5104
http://www.madewithmarmalade.com/node/4148
Already visited all the links before posting here. None solved the problem.. 🙁 Just seems like it is running in an infinite loop. Can you mail me your IM, so that we can interact efficiently.
Send me your info using our contact form at http://www.pocketeers.co.uk/?page_id=8
Hi there,
How do I detect when the user has released a touch?
Checking for touch->active==0 (or false) never seems to trigger?
Hmm, odd, works at my end. Try adding the following code into the HandleMultiTouchButtonCB and HandleSingleTouchButtonCB callbacks
if (event->m_Pressed != 0)
s3eDebugOutputString(“pressed”);
else
s3eDebugOutputString(“released”);
Hmmm, that works as it should (showing expected result in debug output), though using:
if (touch->active==0)
{ previous_touch=0;
s3eDebugOutputString(“Not touched”);
}
in the main() code does not pick up the fact that the user has let go?
No, the example provided is just a basic introduction to using the Marmalade SDK systems that provide access to touch and multi-touch data. I do not detect when the user releases the touch. I have a future tutorial that will cover taps and gestures that builds on top of the CInput class. If you need it urgently I will see if I can put a bite size tutorial together
Aha, I see.
Thats very kind of you but I wouldnt want to get in the way of how you see the tutorials panning out and their order.
Maybe just a quick point in the general direction here and ill have a go myself?
Im guessing I can set a flag within the Input code that can be picked up within my main code.
Again, thanks for this site and the help within it.
Its nice being able to get an answer to a query in such a short time.
Long may you continue 🙂
Hi there, I have it working now.
I am now able to detect when the user has let go and hold this position – this enables me to ‘drag’ a larger than screen size image around the screen.
Rather than anything complicated Im able to detect this state with an ‘else’ in line with the:
if (g_Input.getTouchCount() != 0)
{
}
code.
Thanks very much!
That’s great to hear, nice one.
Hey, Solved the problem.. Issues related to ATI card…
This helped me –
http://www.madewithmarmalade.com/node/3874
Anyways, Thanks a lot for your help.. Appreciate the work you are doing! God bless you 🙂
Ah, nice one, glad you sorted the issue. Hopefully get the chance to do a couple more blogs this weekend
Hi, just wanted to say thanks for the tutorials, there really helping me get to grips with using Marmalade. Just a observation from looking over the source code, I don’t think you use the release function in main(), so it is essential to use it?
In this instance calling release() wont matter, but I should really have included it as its good programming practice to ensure that every Init() has a Release().
Hey,
I just wanted to say that I found a bug in your example code. I had the problem that releasing a touch was not always triggered correctly. After releasing two touches at the same time the last of the two stayed active. The Problem is in the findTouch method:
_________________________________________________________________________
CTouch* CInput::findTouch(int id)
{
if (!Available)
return NULL;
// Attempt to find the touch by its ID and then return it
// If the touch does not exist then it is recorded in the touches list
for (int t = 0; t < MAX_TOUCHES; t++)
{
if (Touches[t].id == id)
return &Touches[t];
if (!Touches[t].active)
{
Touches[t].id = id;
return &Touches[t];
}
}
return NULL;
}
_________________________________________________________________________
The problem is that if a touch is inactive, it will be chosen, even if there is an active touch with the same ID later in the list. The solution is pretty simple (but the reason for this behavior was not easy to find…). If we first look for the touch with the right ID and then only if there is none, look for an empty one, it will work. This means that we need two loops:
_________________________________________________________________________
CTouch* CInput::findTouch(int id)
{
if (!Available)
return NULL;
// Attempt to find the touch by its ID and then return it
// If the touch does not exist then it is recorded in the touches list
for (int t = 0; t < MAX_TOUCHES; t++)
{
if (Touches[t].id == id)
return &Touches[t];
}
for (int t = 0; t < MAX_TOUCHES; t++)
{
if (!Touches[t].active)
{
Touches[t].id = id;
return &Touches[t];
}
}
return NULL;
}
_________________________________________________________________________
At least for me it works. It might not be the most sophisticated solution, but it was my first idea to solve it. Just in case that anybody has the same problem…