Game Development Community

dev|Pro Game Development Curriculum

Wiimote integration on Mac

by Nicolas Buquet · 08/22/2008 (6:12 am) · 6 comments

Download Code File

This resource allow you to use the wiimote (and only the wiimote; I don't use the nunchuk) on Mac OSX without any other bluetooth adapter than the integrated one.

It works from 10.4 to 10.5 (direct bluetooth framework needed wasn't available before 10.4).
Tested with XCode 2.4 and 3.1, on mac OSX?4 and X.5, on a MacMini (core Solo and Core Duo), Mac Book Pro, and PowerBook G4 (a long time ago).

Get the linked file and uncompress it. You will get a 'wiimac' folder to copy into the 'engine' folder of TGE 1.52.

Then, in XCode, right-click on your root TGE project ('torque_xcode_2_2_UB') and 'add existing files', choosing the wiimac folder.

In XCode, visit this new folder, and check the '.m' AND '.mm' files; uncheck the '.h' files.

Be sure that the 'macCocoaWii.mm' file will compile with the objectiveC++ compiler (by get Infos on this file).

In the 'frameworks' section, in sub-section 'System Frameworks', right-click 'add existing framework', and select '/Developer/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks/IOBluetooth.framework' to add to your project.

In 'Project Settings', select 10.4 as your base SDK.

//--------------------------------------------------------------------------------
In 'platform/event.h' :
//--------------------------------------------------------------------------------
- in 'enum KeyCodes' section,
- between 'KEY_BUTTON31'
- and 'KEY_ANYKEY',
- add
WIIMOTEKEY_A      = 0x0120,
WIIMOTEKEY_B      = 0x0121,
WIIMOTEKEY_PLUS   = 0x0122,
WIIMOTEKEY_MINUS  = 0x0123,
WIIMOTEKEY_HOME   = 0x0124,
WIIMOTEKEY_1      = 0x0125,
WIIMOTEKEY_2      = 0x0126,
WIIMOTEKEY_LEFT   = 0x0127,
WIIMOTEKEY_RIGHT  = 0x0128,
WIIMOTEKEY_UP     = 0x0129,
WIIMOTEKEY_DOWN   = 0x012A,

- in 'enum JoystickCodes' section,
- after 'SI_RPOV2', add
,
SI_WIIMOTEMOVE_YAW   = 0x218,
SI_WIIMOTEMOVE_PITCH = 0x219

- in 'enum InputDeviceTypes' section,
- after 'JoystickDeviceType', add
, WiimoteDeviceType

//--------------------------------------------------------------------------------
In 'platformMacCarb/macCarbEvents.cc' :
//--------------------------------------------------------------------------------
- after the 'include' section, add :
typedef UInt16 WiiButtonType;
#include "WiiRemote_codes.h"
extern void wiimote_stop();

- in function '_OnAppEvent',
- replace the 'case kEventAppQuit:' with :
case kEventAppQuit:
         // quit command from menu

            [b]wiimote_stop();[/b]

         Platform::postQuitMessage(0);
         propagateEvent = true;
         break;

- at the end of the file, add :
// got data from Wii controller

// void _OnWiiDataNB(float ax, float ay, float az, bool aBtnDown, bool aBtnUp, bool bBtn)

void _OnWiiDataNB(float ax, float ay, float az, float roll, float pitch, unsigned short btnChangedToDown, unsigned short btnChangedToUp)
{
//	fprintf(stderr, "_OnWiiDataNB : %f %f %f %d %d\n",ax, ay, az, btnChangedToDown, btnChangedToUp);
	InputEvent event;
	
	event.deviceType = WiimoteDeviceType;
	event.deviceInst = 0;
	event.objInst    = 0;
	event.modifier   = 0;
	event.ascii      = 0;
	event.action     = SI_MOVE;

        event.objType = SI_XAXIS;
        event.fValue  = F32(ax);
        Game->postEvent(event);

        event.objType = SI_YAXIS;
        event.fValue  = F32(ay);
        Game->postEvent(event);

        event.objType = SI_ZAXIS;
        event.fValue  = F32(az);
        Game->postEvent(event);

        event.objType = SI_WIIMOTEMOVE_YAW;
        event.fValue  = F32(roll);
        Game->postEvent(event);

        event.objType = SI_WIIMOTEMOVE_PITCH;        
        event.fValue  = F32(pitch);
        Game->postEvent(event);

	event.deviceType = WiimoteDeviceType;
	event.deviceInst = 0;
	event.objType    = SI_BUTTON;
	event.modifier   = 0;
	event.ascii      = 0;
        event.action     = SI_MAKE;
        event.fValue     = 1.0;
	
	if( btnChangedToDown & kWiiRemoteAButton )     {   event.objInst    = WIIMOTEKEY_A;        Game->postEvent(event);   }
	if( btnChangedToDown & kWiiRemoteBButton )     {   event.objInst    = WIIMOTEKEY_B;        Game->postEvent(event);   }
	if( btnChangedToDown & kWiiRemotePlusButton )  {   event.objInst    = WIIMOTEKEY_PLUS;     Game->postEvent(event);   }
	if( btnChangedToDown & kWiiRemoteMinusButton ) {   event.objInst    = WIIMOTEKEY_MINUS;    Game->postEvent(event);   }
	if( btnChangedToDown & kWiiRemoteHomeButton )  {   event.objInst    = WIIMOTEKEY_HOME;     Game->postEvent(event);   }
	if( btnChangedToDown & kWiiRemoteOneButton )   {   event.objInst    = WIIMOTEKEY_1;        Game->postEvent(event);   }
	if( btnChangedToDown & kWiiRemoteTwoButton )   {   event.objInst    = WIIMOTEKEY_2;        Game->postEvent(event);   }
	if( btnChangedToDown & kWiiRemoteLeftButton )  {   event.objInst    = WIIMOTEKEY_LEFT;     Game->postEvent(event);   }
	if( btnChangedToDown & kWiiRemoteRightButton ) {   event.objInst    = WIIMOTEKEY_RIGHT;    Game->postEvent(event);   }
	if( btnChangedToDown & kWiiRemoteUpButton )    {   event.objInst    = WIIMOTEKEY_UP;       Game->postEvent(event);   }
	if( btnChangedToDown & kWiiRemoteDownButton )  {   event.objInst    = WIIMOTEKEY_DOWN;     Game->postEvent(event);   }
 
    event.action     = SI_BREAK;
    event.fValue     = 0.0;

	if( btnChangedToUp & kWiiRemoteAButton )     {   event.objInst    = WIIMOTEKEY_A;        Game->postEvent(event);   }
	if( btnChangedToUp & kWiiRemoteBButton )     {   event.objInst    = WIIMOTEKEY_B;        Game->postEvent(event);   }
	if( btnChangedToUp & kWiiRemotePlusButton )  {   event.objInst    = WIIMOTEKEY_PLUS;     Game->postEvent(event);   }
	if( btnChangedToUp & kWiiRemoteMinusButton ) {   event.objInst    = WIIMOTEKEY_MINUS;    Game->postEvent(event);   }
	if( btnChangedToUp & kWiiRemoteHomeButton )  {   event.objInst    = WIIMOTEKEY_HOME;     Game->postEvent(event);   }
	if( btnChangedToUp & kWiiRemoteOneButton )   {   event.objInst    = WIIMOTEKEY_1;        Game->postEvent(event);   }
	if( btnChangedToUp & kWiiRemoteTwoButton )   {   event.objInst    = WIIMOTEKEY_2;        Game->postEvent(event);   }
	if( btnChangedToUp & kWiiRemoteLeftButton )  {   event.objInst    = WIIMOTEKEY_LEFT;     Game->postEvent(event);   }
	if( btnChangedToUp & kWiiRemoteRightButton ) {   event.objInst    = WIIMOTEKEY_RIGHT;    Game->postEvent(event);   }
	if( btnChangedToUp & kWiiRemoteUpButton )    {   event.objInst    = WIIMOTEKEY_UP;       Game->postEvent(event);   }
	if( btnChangedToUp & kWiiRemoteDownButton )  {   event.objInst    = WIIMOTEKEY_DOWN;     Game->postEvent(event);   }
 
}

extern void wiimote_rumble ( int );
extern void wiimote_calibrate ( int, int, int, int, int, int, int, int, int );
extern void wiimote_setLeds ( int, int, int, int );

void _onWiiDiscoveryError()
{
	Con::evaluatef("onWiiDiscoveryError();");
}

void _onWiiDiscoveryStart()
{
	Con::evaluatef("onWiiDiscoveryStart();");
}

void _onWiiDiscoveryStop()
{
	Con::evaluatef("onWiiDiscoveryStop();");
}

void _onWiiConnected()
{
//	Con::printf("Wii conn ect ed !!!");
	Con::evaluatef("onWiiConnected();");
}

void _onWiiDisconnected()
{
	Con::evaluatef("onWiiDisconnected();");
}

ConsoleFunction(wiiRumbleOn, void, 1, 1, "Wii remote starts rumble")
{
    wiimote_rumble( 1 );
}

ConsoleFunction(wiiRumbleOff, void, 1, 1, "Wii remote stops rumble")
{
    wiimote_rumble( 0 );
}

ConsoleFunction(wiiCalibrate, void, 10, 10, "Wii calibration : x1 y1 z1 with Wii button A up, x2 y2 z2 with IR sensor down, x3 y3 z3 with left side down")
{
    wiimote_calibrate( dAtoi(argv[1]), dAtoi(argv[2]), dAtoi(argv[3]), dAtoi(argv[4]), dAtoi(argv[5]), dAtoi(argv[6]), dAtoi(argv[7]), dAtoi(argv[8]), dAtoi(argv[9]) );
}

ConsoleFunction(wiisetLeds, void, 5, 5, "Wii leds light on/off (led1 led2 led3 led4)")
{
    wiimote_setLeds( dAtoi(argv[1]), dAtoi(argv[2]), dAtoi(argv[3]), dAtoi(argv[4]) );
}

//--------------------------------------------------------------------------------
In 'platformMacCarb/macCarbMain.cc' :
//--------------------------------------------------------------------------------
- after the includes, add :
extern void wiimote_Start( void );

- in the 'main' function,
- find and modify as this :
#if !defined(TORQUE_MULTITHREAD)
   // Install a one-shot timer to run the game, then call RAEL to install
   // the default application handler (which can't be called directly).
   EventLoopTimerRef timer;
   InstallEventLoopTimer(GetCurrentEventLoop(), 0, 0, 
                     NewEventLoopTimerUPP(_MacCarbRAELCallback), NULL, &timer);
    
   RunApplicationEventLoop();

[b]   wiimote_Start();[/b]

#else
   // Put the Torque application loop in one thread, and the event listener loop
   // in the other thread. The event loop must use the process's initial thread.

   // We need to cache a ref to the main event queue because GetMainEventQueue
   // is not thread safe pre 10.4 . 
   platState.mainEventQueue = GetMainEventQueue();

   // We need to install event handlers for interthread communication.
   // Events and some system calls must happen in the process's initial thread.
   MacCarbInstallTorqueCarbonEventHandlers();
   
	
   TorqueMainThread mainLoop;
   mainLoop.start();
   
[b]   wiimote_Start();[/b]

   if(!platState.headless)
   {
      //printf("starting RAEL\n");
      RunApplicationEventLoop();
   }
   mainLoop.join();

#endif

//--------------------------------------------------------------------------------
In 'sim/actionMap.cc' :
//--------------------------------------------------------------------------------
- in the 'ActionMap::getDeviceTypeAndInstance' function,
- find and modify as this :
} else if (dStrnicmp(pDeviceName, "joystick", dStrlen("joystick")) == 0) {
      deviceType      = JoystickDeviceType;
      offset = dStrlen("joystick");
[b]   } else if (dStrnicmp(pDeviceName, "wiimote", dStrlen("wiimote")) == 0) {
      deviceType      = WiimoteDeviceType;
      offset = dStrlen("wiimote");[/b]
   } else {
      return false;

- in the 'ActionMap::getDeviceName' function,
- find and modify as this :
case JoystickDeviceType:
      dSprintf(buffer, 16, "joystick%d", deviceInstance);
      break;

[b]     case WiimoteDeviceType:
      dSprintf(buffer, 16, "wiimote%d", deviceInstance);
      break;[/b]

     default:

- in the 'CodeMapping gVirtualMap[]' definition,
- between '{ "button31", SI_BUTTON, KEY_BUTTON31 },'
- and '//-------------------------------------- MOVE EVENTS', add this :
{ "wiimote_a",     SI_BUTTON, WIIMOTEKEY_A     },
   { "wiimote_b",     SI_BUTTON, WIIMOTEKEY_B     },
   { "wiimote_+",     SI_BUTTON, WIIMOTEKEY_PLUS  },
   { "wiimote_-",     SI_BUTTON, WIIMOTEKEY_MINUS },
   { "wiimote_home",  SI_BUTTON, WIIMOTEKEY_HOME  },
   { "wiimote_1",     SI_BUTTON, WIIMOTEKEY_1     },
   { "wiimote_2",     SI_BUTTON, WIIMOTEKEY_2     },
   { "wiimote_left",  SI_BUTTON, WIIMOTEKEY_LEFT  },
   { "wiimote_right", SI_BUTTON, WIIMOTEKEY_RIGHT },
   { "wiimote_up",    SI_BUTTON, WIIMOTEKEY_UP    },
   { "wiimote_down",  SI_BUTTON, WIIMOTEKEY_DOWN  },

- in the 'CodeMapping gVirtualMap[]' definition,
- in '// Mouse/Joystick axes:' section,
- between '{ "slider", SI_MOVE, SI_SLIDER },'
- and '//-------------------------------------- POV EVENTS', add this :
{ "yaw",   SI_MOVE,   SI_WIIMOTEMOVE_YAW      },
   { "pitch", SI_MOVE,   SI_WIIMOTEMOVE_PITCH      },

That should compile (crossing finger).

After that, in the script section :

//--------------------------------------------------------------------------------
In 'client/default_bind.cs' :
//--------------------------------------------------------------------------------
- you an add the following bindings :
moveMap.bind(wiimote0, "xaxis", accelerate);
moveMap.bind(wiimote0, "yaxis", WiiSteering);
moveMap.bind(wiimote0, "zaxis", accZ);

moveMap.bind(wiimote0, "yaw", wii_yaw);
moveMap.bind(wiimote0, "pitch", wii_pitch);

moveMap.bind( wiimote0, "wiimote_b", wii_b );
moveMap.bind( wiimote0, "wiimote_+", wii_plus );
moveMap.bind( wiimote0, "wiimote_-", wii_minus );
moveMap.bind( wiimote0, "wiimote_home", wii_home );
moveMap.bind( wiimote0, "wiimote_1", wii_1 );
moveMap.bind( wiimote0, "wiimote_2", wii_2 );
moveMap.bind( wiimote0, "wiimote_left", wii_left );
moveMap.bind( wiimote0, "wiimote_right", wii_right );
moveMap.bind( wiimote0, "wiimote_up", wii_up );
moveMap.bind( wiimote0, "wiimote_down", wii_down );

- and add the following functions :
function onWiiDiscoveryStart() { echo("Wii Discovery START..."); }
//--------------------------------------------------------------------------------
function onWiiDiscoveryStop() { echo("Wii Discovery STOP..."); }
//--------------------------------------------------------------------------------
function onWiiConnected() { echo("Wii CONNECTED"); 
	%t = 1000;
	%dt = 350;
	schedule( %t, 0, "wiiRumbleFor", 0.2 );
	schedule( %t, 0, "wiiSetLeds", 1, 0, 0, 0 );
	%t += %dt;
	schedule( %t, 0, "wiiRumbleFor", 0.2 );
	schedule( %t, 0, "wiiSetLeds", 0, 1, 0, 0 );
	%t += %dt;
	schedule( %t, 0, "wiiRumbleFor", 0.2 );
	schedule( %t, 0, "wiiSetLeds", 0, 0, 1, 0 );
	%t += %dt;
	schedule( %t, 0, "wiiRumbleFor", 0.2 );
	schedule( %t, 0, "wiiSetLeds", 0, 0, 0, 1 );
	%t += %dt;
	schedule( %t, 0, "wiiSetLeds", 0, 0, 0, 0 );
}
//--------------------------------------------------------------------------------
function onWiiDisconnected() { echo("Wii DISCONNECTED"); }
//--------------------------------------------------------------------------------
function wiiRumbleFor( %len )
{
    echo("wiiRumbleFor" SPC %len );
	wiiRumbleOn();
	schedule( %len*1000, 0, "wiiRumbleOff" );
}
//--------------------------------------------------------------------------------
function onWiiDiscoveryError()
{
    echo("Wii Discovery ERROR");
}
//--------------------------------------------------------------------------------
function onWiiDiscoveryStop()
{
   echo( "Wii Discovery Stop");
}
//--------------------------------------------------------------------------------

- you can call the rumble function :
wiiRumbleFor( 0.25 ); // in seconds

And now you should be ready to use the basic Wiimote functionnality on your Mac !

Your bluetooth must be ON.

When you start your application, it will scan automatically for Wiimote that are in discovery mode (4 leds flashing when your press buttons 1 & 2 together).
You will see messages in the console window.

If the application doesn' connect to your wiimote, press the red reset button on the wiimote in the battery closet.

#1
01/09/2009 (1:35 pm)
I crossed my fingers and it didn't compile. That's not strictly true - it compiled, but it didn't link. I had similar problems working with Rob Terrell's code, which turned out to be not using the c++ compiler for the objective C bits. Unfortunately, that doesn't seem to be the problem here.

Basically, the symbols declared extern in macCarbEvents.cc can't be found. I've tried declaring them extern "C", just in case there was still an issue about which compiler was being used; this also doesn't work.

Linkers are a bit of a mystery to me. How would it know to look in macCocoaWii.mm ( or I guess macCocoa.o at this point) to find what it needs? Is there still a bit of configuration I've missed that tells the linker where it will find things?
#2
01/12/2009 (1:19 am)
Did you try to clean all your project and recompile from scratch ?

What is your linker error exactly ?

Hope I can help you.
#3
01/12/2009 (12:26 pm)
Thanks for the response.

There's five errors, but they're all the same type of error from the looks of it. One example is:

"wiimote_rumble(int)", referenced from:
cwiiRumbleOn(SimObject*, int, char const**) in macCarbEvents.o
cwiiRumbleOff(SimObject*, int, char const**) in macCarbEvents.o

Then at the bottom it says "symbol(s) not found". The other 4 symbols that weren't found are wiimote_Start(), wiimote_calibrate(int, int, int, int, int, int, int, int, int), wiimote_stop(), and wiimote_setLeds(int, int, int, int).

These are all methods that were defined in macCocoaWii.mm, and then declared as external linkages in either macCarbEvents.cc or macCarbMain.cc. When I look at the info for macCocoaWii.mm, the filetype is set to "sourcecode.cpp.objcpp", which is what I assumed the problem would be - you get the same link error if you use the c compiler instead of the c++ compiler.

I have tried cleaning everything and rebuilding from scratch, always with the same results. Everything seems to have compiled but the linker simply can't find the compiled symbols. I can only think of 3 things that could cause this problem:
1. There is no definition for these sybols.
2. The definition exists but the linker didn't look for it in the right place.
3. The definition is not recognized by the linker.

I think we can rule out 1, because you've provided definitions in macCocoaWii.mm and it compiles. Cause 2 seems suspicious to me, but I can't rule out 3 because I really don't know anything about objective-C.
#4
01/13/2009 (2:32 am)
Hi John,

problem resolved : I had put bad files in the ZIP code file. There were remaining conditional compile via
#ifdef __NB__WIIMOTE_MAC
at the beginning of the .m and .mm files.

I defined this symbol in my "torqueConfig.h".

I remove them, and now, these files compile COMPLETELY, and so, the missing functions are now defined and exported. And the linker finds them.

So, you just have to re-download the wiimac.zip, and replace the old folder with the new one.

Sorry for the inconvenience.

Nicolas Buquet
www.buquet-net.com/cv/
#5
01/13/2009 (5:53 am)
Inconvenience? It seems very convenient that you are doing all this work for us!

Compile problem fixed. I didn't download the new zip, but instead removed the conditional compile you mentioned. I've now got the fps starter running and the wiimote registering. (Push the 1 and 2 buttons on the startup screen and LED 1 lights).

It's not working as a controller yet, but I'm assuming this is because I need to do some configuration to map wii events to the game controls. When I attempt to do this in the game options control dialog, it is able to sense the the button presses so I know there are events being sent. This dialog won't map the IR tracking or accelerometers but I'm guessing this is more like calibrating a joystick than mapping a button press. At any rate, this seems like the sort of problem I can track down myself.

Thanks for all your help and hard work.
#6
01/13/2009 (11:46 am)
I never tried to map the Wiimote commands with the option control dialog.

I do it with the default_bind.cs, the same way as other controls (with bind or bindCmd).

IR tracking wasn't implemented in this version.

And there was a bug in Apple that make the bluetooth connection drops on first attempt (the bug still exists in 10.5)

So, in general, 2 connection tryouts are necessary.

The wiimote reset in the battery room is stronger than resetting with 1 & 2 buttons.

If the wiimote can't reconnect, you have to go to the System Preferences / Bluetooth, and delete the wiimote from the connected or preferred devices.