Game Development Community

Better Key Binding

by Clint Herron · in Torque Game Builder · 12/02/2006 (3:45 pm) · 4 replies

Hey all!

This is just a small thing that I learned today that I thought I would share with anyone who is interested. It might be old-hat-news to some of you people who have been working with Torque for a while, but perhaps some of you will find this helpful.

If you read things like the Mini Platformer Tutorial, you'll notice that it's very popular to do key bindings like the following:

function playerClass::onLevelLoaded(%this, %scenegraph)
{
     $pGuy = %this;
      
      moveMap.bindCmd(keyboard, "left", "playerLeft();", "playerLeftStop();");
      moveMap.bindCmd(keyboard, "right", "playerRight();", "playerRightStop();");
}

function playerLeft()
{
    $pGuy.moveLeft = true;
}

function playerLeftStop()
{
    $pGuy.moveLeft = false;
}

function playerRight()
{
     $pGuy.moveRight = true;
}

function playerRightStop()
{
     $pGuy.moveRight = false;
}

This is all well and good for single-player games, but it gets to be a pain once you start doing games like two players on the same keyboard, as well as making AI that walk around by simulating keypresses, because it would be nice to use the same code as the player.

Also, it's ugly to have a whole nice object-oriented file, and to have your keybindings be the only global functions in the script.

So something that I realized I could do today is this:

function playerClass::onLevelLoaded(%this, %scenegraph)
{      
      moveMap.bindCmd(keyboard, "left", %this @ ".Left();", %this @ ".LeftStop();");
      moveMap.bindCmd(keyboard, "right", %this @ ".Right();", %this @ ".RightStop();");
}

function playerClass::Left( %this )
{
    %this.moveLeft = true;
}

function playerClass::LeftStop( %this )
{
    %this.moveLeft = false;
}

function playerClass::Right( %this )
{
     %this.moveRight = true;
}

function playerClass::RightStop( %this )
{
     $pGuy.moveRight = false;
}

Voila! No more globals!

For those who may not understand what the %this @ ".Left();" thing is all about, I'll break it down for you.

Basically, in script, data blocks (such as the player) are kept track of as numbers. This number changes every time you run your game, and we can't know what that number will be. However, we can get that number at runtime by looking at the value of the variable that references the object (in this case, the variable %this). Also, that number is interchangeable for the name of the object. So if we know the number of the object (such as 1234), then we can say things like 1234.schedule( 1000, "safeDelete();" );, to schedule that object to delete itself in 1 second. We don't need the name of the object at all -- we just need the number.

So all we're doing in the %this @ ".Left();" example is, at runtime, we're getting the number of the player's object, and using string concatenation (the @ symbol) to bind that object's Left() method to the proper key. Basically, at runtime %this @ ".Left();" gets converted into "1234.Left();".

If it's too confusing, don't worry about it. But if you're like me, and like to keep things very object-oriented (especially for when making multiplayer games or combining player and NPC code), then you might appreciate this.

This technique is probably too confusing for beginners, so I don't think the newbie tutorials should be changed to use it -- but it's certainly nice for organizing larger projects.

Cheers!

--clint

#1
12/02/2006 (4:42 pm)
It gets messier if you support joysticks. I've set up my game such that any joystick 0 thru 3 can be assigned to any player 0 thru 3. It works but I'm unhappy with how bulky my code is for it.
#2
12/02/2006 (5:11 pm)
I've had success doing what you're doing Clint, but unfortunately it seems that some input events can only be hooked-up with "bind" calls rather than "bindCmd". Such as:

playerMap.bind("xinput0", "triggerl", "Player::throttleXInput"); // FAILS with namespace
playerMap.bind("xinput0", "triggerl", %player@".throttleXInput"); // FAILS
playerMap.bindCmd("xinput0", "triggerl", "echo(gag);", ""); // CRASHES using bindCmd

// works, but end up needing globals:
playerMap.bind("xinput0", "triggerl", "throttleXInput0");

I can understand why an input with a continuous range of values wouldn't work with the binary structure of bindCmd, but it's unfortunate I can't get an object handle into bind... This could just be my clumsy integration of the xbox 360 resource though, I haven't tried a regular gamepad yet.

- Matthew Durante
#3
12/02/2006 (5:46 pm)
Matthew:

In reference to this one:

playerMap.bind("xinput0", "triggerl", %player@".throttleXInput"); // FAILS

Why does it fail? Is it a syntax issue? Because it looks like it should be:

playerMap.bind("xinput0", "triggerl", %player@".throttleXInput();");

(with the parenthesis)

I could be very wrong though -- I'm still new to this stuff.

--clint
#4
12/02/2006 (7:06 pm)
Clint,

In the case of:
playerMap.bind("xinput0", "triggerl", %player@".throttleXInput");
the console prints:
2943.throttleXInput: Unknown command.

In the case of:
playerMap.bind("xinput0", "triggerl", %player@".throttleXInput();");
the console prints:
3024.throttleXInput();: Unknown command.

If you look at http://tdn.garagegames.com/wiki/Torque_Console_Objects it appears that bind only wants a function name, whereas bindCmd wants complete scripts (hence the parans and semicolon in the cmd strings).

- Matthew Durante