Game Development Community

Key combo example.

by David Bigerstaff · in Torque Game Builder · 01/13/2006 (7:47 am) · 9 replies

This is a small tutorial explaining how I implemented player key combos resulting in different actions. Like the systems used in arcade beat 'em ups. Go easy on me if something can be done better, i'm sure it can, and i'd like to know where. I like making tutorials as it helps me learn, and if another person can make use of this then all the better.

I have this as my ActionMap:
new ActionMap(moveMap);
moveMap.bind(keyboard, left, "moveLeft");
moveMap.bind(keyboard, right, "moveRight");
moveMap.bind(keyboard, up, "jump");
moveMap.bind(keyboard, down, "duck");
moveMap.push();

With the following functions for movement:
function moveLeft(%val)
{
	$moveLeft = %val;
	//Update combo when only key is pressed.
	if (%val)
        {
          setCombo("Left");
        }
}

function moveRight(%val)
{
	$moveRight = %val;
	//Update combo only when key is pressed.
        if (%val)
        {
          setCombo("Right");
        }
}

function jump(%val)
{
	$jump = %val;

	//Update combo only when key is pressed.
        if (%val)
        {
          setCombo("Jump");
        }
}

function duck(%val)
{
   $duck = %val;
   //Update combo only when key is pressed.
   if (%val)
   {
      setCombo("Duck");
   }
}
All of the above functions are basically the same. The movement of my character is based off the Platformer tutorial available on TDN here.

The %val in each of the movement functions is changed whenever the corresponding key is pressed. %val = 1 on key down, and 0 on key up.

To make sure that the current combo is updated only when a key is pressed and not also when the key is released an If statement is used. then the setCombo is called with a string of which key is pressed.
function setCombo(%key)
{
  if ($keyPressOne $= "")
  {
    $keyPressOne = %key;
  }
  else if ($keyPressTwo $= "")
  {
    $keyPressTwo = %key;
  }
  else
  {
    $keyPressThree = %key;
  }
  schedule(1000,0,resetCombo);
}
A string of the key pressed is passed into this function. If there is no current combo started the first key press is saved into $keyPressOne, the next time this function is called the value is saved in the next global variable until, in my example, the three key variables are saved. This could easily be altered to save a longer chain of keys.

The final line, schedule(), calls resetCombo function every 1 second. This means that the player has one second to complete their combination of keystrokes before the system is reset.
function resetCombo()
{
  $keyPressOne = "";
  $keyPressTwo = "";
  $keyPressThree = "";

  echo("Reset");
}
This function just resets the values to nothing so that a new chain of keystrokes can be monitored. I also placed a call to this function when I initially create my player, only to get the variables set up.

The above should let you monitor which 3 keys have been used in the last 1 second and store them in the appropriate variables...

Continued in next post.

#1
01/13/2006 (7:48 am)
Now onto using this to achieve actions.
function t2dSceneGraph::onUpdateScene(%this)
{
        detectCombo();
}

Everytime the game updates (is that the right phrase? correct me please) the detectCombo() will be called. This causes the game to continually check if a combo is recognised.

function detectCombo()
{
   //Detect which combo has been executed.
   
   //Left, Right, Jump
   if ($keyPressOne $= "Left" && $keyPressTwo $= "Right" && $keyPressThree $= "Jump")
   {
      echo("Left Right Jump combo recognised");
      resetCombo();
   }
   //Right, Right, Left
   else if ($keyPressOne $= "Right" && $keyPressTwo $= "Right" && $keyPressThree $= "Left")
   {
      echo("Right Right Left combo recognised");   
      resetCombo();
   }
   //Duck, Jump
   else if ($keyPressOne $= "Duck" && $keyPressTwo $= "Jump")
   {
      echo("Super Jump!");
      resetCombo();
   }
}

In here we're just checking the values of each of the key pressed variables, if all three, and in the bottom case two, keys match a pre-set combination then the result of the combo can be produced. In this case it's just a simple echo() letting me know that the combo was successful. When a combo is recognised the key variables are reset once again, ready to begin monitoring for another combo.

Well, that's the way I implemented it. I'm begining to wonder if there's a better way to detectCombo() and setCombo() if any of you guys can see a way to improve it let me know, and if i think of anything else i'll add it.
#2
01/13/2006 (8:39 am)
Very nice! I know the T2D TDN page is moderately unwieldy at the moment, but this is exactly the type of stuff that could be put up over there. Eventually we will get things cleaned up so there is an actual section for this, but for now you could just create a link for it on the main page.
#3
01/13/2006 (8:42 am)
Looks pretty good. I would put detectCombo() and the end of setCombo() since the only time a combo can happen is after a key press. That way it is only trying to detect if a combo has been achieved after a new key press rather than every scene update.
#4
01/13/2006 (12:39 pm)
I didn't run this, but I think you'll run into problems doing it this way. The issue is, you schedule a reset of all the variables every time a key is hit.

So, as an example, if i slam my keyboard and hit a bunch of keys 1 second before i start doing the combo, it will reset my keys during my pressing of the combo. This may not be a problem in your case(maybe you only get one shot at trying to do the combo), but for a fighting game this would obviously be a real problem.

Additionally, keeping track of the variables in sets of 3, a lot of combos would also be missed in the case of a game where you get more than one shot at a combo. For example, if you hit 'up up down right", that would normally be read as an "up down right", however in tihs case it would be "up up right" if "up up down" wasn't found as a combo.
#5
01/13/2006 (1:50 pm)
I think personally I'd do it as a simset, adding in each key press as it gets made. A schedule can be set to remove the earlier keypresses after a set period of time.

It also gets a bit easier because in each keypress you simply have:
keyComboSimSet.add(%key);
This would make it much easier to have multi-key combos rather than having to check $keyPress1, $keyPress2 and so-on.

I'd had toyed with the idea of a fighting game, but it would take more time than I currently have, so discussions of this nature are quite interesting :)
#6
01/13/2006 (2:32 pm)
I've updated my code with Owen's and Aaron's advice.

I now dont need to use onUpdateScene for my example and placed detectCombo() at the bottom of setCombo().

With regards to always calling a resetCombo(). My initial thought was that you'd only have 1 second to perform that combo, then you would have to try again. But i think your point was that if somone was mashing the keys and in the middle of that they hit a combo it wouldnt go off? and that does make sense, i know first hand how to play beat'em ups only by button mashing :P In regards to this at the bottom of setCombo() i now have
$reset = schedule(1000,0,resetCombo);
And each keypress function now has an extra line in it, cancel(), such as.
function jump(%val)
{
	$jump = %val;

	//Update combo only when key is pressed.
        if (%val)
        {
          cancel($reset);
          setCombo("Jump");
          //detectCombo();
        }
}
The effect of this is that the combo only gets reset if no key has been pressed for 1 second.

Because of this the if statement in setCombo() has to be changed to handle there already being 3 keys set, i changed it to the following, adding a new else if, and a different else.
if ($keyPressOne $= "")
  {
    $keyPressOne = %key;
  }
  else if ($keyPressTwo $= "")
  {
    $keyPressTwo = %key;
  }
  else if ($keyPressThree $="")
  {
    $keyPressThree = %key;
  }
  else
  {
    $keyPressOne = $keyPressTwo;
    $keyPressTwo = $keyPressThree;
    $keyPressThree = %key;
  }
Another change was needed in my detectCombo() incase Duck and Jump were in $keyPressTwo and Three respectively.

I've not touched simsets since getting t2d so the thought of using them never came into my head. Just before reading your post Phillip i was thinking of using an array and searching it. However i'll have a read up on simsets and see what they're all about :)
#7
01/14/2006 (4:17 pm)
Just thought i'd post an update.

I reworked the code so that the combos werent stored in three different variables. I figured that an array (or simset on phillip's advice) would be better than having separate variables. Then i remembered back to a c++ lecture i had a few years ago that strings were just arrays.

Anyways so what i've got now instead of three global strings holding a key stroke each, is one global string holding all values.

setCombo has changed to the following. Quite a lot smaller :)
function setCombo(%key)
{
   $combo = $combo @ %key;
   detectCombo();
}

And the detect combo is using the strstr($combo,%string) function to check for a sequence of keys for a combo. This will return -1 if %string is not found.

I'm still not quite happy with the detectCombo code, so i'll see what i can do with that.

Is this way any better than using simsets or arrays? I liked the easyness of using existing string functions.
#8
07/09/2009 (8:30 am)
could you post the final code for the setCombo() detectCombo(). I'm having trouble figuring out what you did to get Duck and jump to work in $keyPressTwo and Three.
#9
07/15/2009 (4:37 am)
I would like to notice few things:
* constant key-pressing without any delay longer then 1 second would cause your string ($combo) becoming longer and longer. Setting maximum number of keys pressed and cutting beginning (oldest keys) if length hits limit would solve it
* using "@" operator will glue recorded keys together and let you only use strstr like functions but in TGB (and in whole Torque Script) you can use SPC and then use getWordCount($combo) to count collected keys, getWord($combo, <position>) so you would be able to iterate through collection or after getting desired key position just check next two in vector