Game Development Community

Getting MoveMap to Imitate Keyboard Repeat Rate

by Deozaan · in Torque Game Builder · 07/06/2009 (6:36 pm) · 6 replies

moveMap.bind(keyboard, "up", keyboardUp);

Using the MoveMap to bind keyboard has its problems:

function keyboardUp()
{
   moveCameraUp();
}

1: It will call the function on press and on release.
2: It will not call the function while holding down.

An alternative to get #2 but not #1 is to do something like:

function keyboardUp(%val)
{
   $pressingUp = %val;
}

function cameraClass::onUpdate(%this)
{
   if ($pressingUp)
   {
      moveCameraUp();
   }
}

What I would like is actually a mix of them both. I would like the function to be called much like a standard keyboard repeat rate works: Press the button (and hold it down) and you get one character. After the key is held down for a second, it quickly repeats several times until the key is released.

Basically, I have code to move the camera (sceneWindow2D) around the world using the arrow keys. The way it moves is that it snaps into specific locations (think of those old RPGs where you were forced to move within a square tile, and were not allowed to move just a pixel or two). I want to be able to hold down the key for moving long distances, but not make it instantly travel more than one space without giving a pause for the player to let go of the key.

Any tips on how to accomplish this?

#1
07/07/2009 (11:47 am)
I'm still new to torque and I'm sure there are more experienced people who have solved your problem but one way that you might want to go about is to have something like:

function keyboardUp(%val)  
{  
   moveCameraUp();
   $pressingUp = %val;
   schedule(1000, 0, "moveCameraUpQuickly");
}

function moveCameraUpQuickly()
{
    if ($pressingUp)  
    {  
       moveCameraUp();
       schedule(10, 0, "moveCameraUpQuickly");
    }
}

You'd have to work out the details but the idea would be that if the user pressed up once, that would move the camera up once but by the time moveCameraUpQuickly was called, $pressingUp would no longer be true. If the user held down the up key, moveCameraUpQuick gets called and keeps calling itself while the upkey is held down. The reason I didn't use a while loop is that I'm not sure if a keyboard event can interrupt a while loop so I think you need to have a small break in order to avoid being stuck in an infinite loop.
#2
07/07/2009 (11:58 am)
@Nate: I was thinking I'd have to do something along those lines but hadn't yet had the time to figure out how it would work.

Maybe someone with more experience in TGB will come up with a more clever trick, but until then I say your suggestion is brilliant.

Thanks so much!
#3
07/07/2009 (12:45 pm)
Just for future reference/information, here's what I ended up with. This code is just pasted in from all over the place. It doesn't really look like this inside my source files, but the relevant stuff is here:
// Keyboard & MoveMap Stuff
// I had to initialize $Keyboard or else the rest wouldn't work.
if (!isObject($Keyboard))
{
   $Keyboard = new ScriptObject()
   {
      canSaveDynamicFields = true;
   };
}   

moveMap.bind(keyboard, "up", keyboardUp);
moveMap.bind(keyboard, "down", keyboardDown);

// keyboard movemap functions

function keyboardUp(%val)
{
   $Keyboard.up = %val;
   
   if (%val)
   {
      moveCameraUp();
      
      // schedule to repeat if the key on the keyboard is being held down.
      schedule(500, 0, "moveCameraUpRepeat");
   }
}

function keyboardDown(%val)
{
   $Keyboard.down = %val;
   
   if (%val)
   {
      moveCameraDown();
      
      // schedule to repeat if the key on the keyboard is being held down.
      schedule(500, 0, "moveCameraDownRepeat");
   }
}

//camera movement functions

function moveCameraDown()
{
   // logic to figure out where to move the camera and move it goes here
}

function moveCameraDownRepeat()
{
   // if the key isn't still pressed, do nothing
   if (!$Keyboard.down)
   {
      return;
   }
   
   // logic to figure out where to move the camera and move it goes here

   // schedule to repeat this again soon.
   schedule(100, 0, "moveCameraDownRepeat");
}

function moveCameraUp()
{
   // logic to figure out where to move the camera and move it goes here
}

function moveCameraUpRepeat()
{
   // if the key isn't still pressed, do nothing
   if (!$Keyboard.up)
   {
      return;
   }
   
   // logic to figure out where to move the camera and move it goes here

   // schedule to repeat this again soon.
   schedule(100, 0, "moveCameraUpRepeat");
}

One final interesting thing I quickly realized is that I have no schedule control. That is, if I quickly tap the button a few times within the initial 500ms and then hold the key down, it will actually make a few schedules to rapidly move the camera around.

In other words, double- or triple-tapping will make the camera scroll twice or three times as fast. I'm still trying to decide if this is a bug I want to squash or if I want to embrace this functionality as a feature, as it could potentially come in very handy for my particular game idea.

EDIT: Upon looking more closely at your code example, Nate, I decided I liked better the idea of including the schedule for the repeat within the keyboard event handle function rather than at the end of the moveCameraUp/Down function. It really does fit better with the keyboard event handler since the mouse scroll wheel also calls the moveCameraUp/Down functions and would never need to schedule a repeat.
#4
07/07/2009 (1:20 pm)
Quote: One final interesting thing I quickly realized is that I have no schedule control. That is, if I quickly tap the button a few times within the initial 500ms and then hold the key down, it will actually make a few schedules to rapidly move the camera around.

I'm not sure if you already know this but it took me a while to figure out that you could create means of controlling scheduled events and I thought I'd point it out if you've overlooked it. If you want there to be only one scheduled event of a certain type, you can use the schedule IDs to cancel the previous event.

function foo
{
  if ($ThereCanBeOnlyOne)
    cancel($ThereCanBeOnlyOne);

  $ThereCanBeOnlyOne = schedule(500, 0, "someFunction");
}

function somefunction
{
  //Some stuff here

  $ThereCanBeOnlyOne = 0;
}

This would insure that there would only be one instance of somefunction pending. If you already knew this, I'm sure that you'd need no reminder but it took me a bit before I stumbled upon schedule IDs and how to use them.
#5
07/07/2009 (1:26 pm)
Thanks again Nate, you've been a good help!
#6
02/10/2011 (1:15 am)
Thanks guys. Very nice. A push and hold key. After 1 second, the key starts repeating, until released.

function moveUp(%val)
{
  if(%val)
  {
       sendUp();
       $keyPress = %val;
       schedule(1000, 0, sendItUp);     // 1000 = 1 sec
  }
  else
       $keyPress = 0;    // key released, stop calling function
}

function sendItUp()
{
  if($keyPress)
  {
       sendUp();
       schedule(100, 0, sendItUp);		// call until %val = 0
  }
}

function sendUp()
{
    echo("Y = ", $y_val);
}