I wrote a fighting-game move-combo system

This topic is 5158 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Recommended Posts

Today I wrote code for doing movement combos like console fighting games often have. It's very versatile... some test combinations I did were pressing W, then releasing it while pressing A and D at the same time... or press A, then S, then D, then S again, then A again... you can test them by playing this; either one will fire a fireball. Using the arrow keys to move, up arrow to jump. Anyway I figured other people might find the code useful... it won't work "out of the box" since it uses too many of my other files, but it's easy to adapt. Here's MoveCombo.h which has most of it. The sample game uses this code:
using namespace move;
MoveCombo[0] = combo( 1,
2,
step(1, keypress(KEY_W)),
step(3, keyrelease(KEY_W), keypress(KEY_A), keypress(KEY_D)) );
MoveCombo[1] = combo( 1,
5,
step(1, keypress(KEY_A)),
step(2, keypress(KEY_S), keyrelease(KEY_A)),
step(2, keypress(KEY_D), keyrelease(KEY_S)),
step(2, keypress(KEY_S), keyrelease(KEY_D)),
step(2, keypress(KEY_A), keyrelease(KEY_S)) );

What that actually means, I'll describe line-by-line for the first combination:
MoveCombo[0] = combo( 1,

'1' means one second -- each step of the move needs to be completed within one second of the previous step.
                      2,

There are 2 steps.
                      step(1, keypress(KEY_W)),

The first step is for the player to press the W key. The '1' indicates how many keys are in this step.
                      step(3, keyrelease(KEY_W), keypress(KEY_A), keypress(KEY_D)) );

This means the player should then release W, press A, and press D, all in the same step. That means they all need to be entered simultaneously (within 1/8 of a second). So first the player presses W, then less than a second later he needs to release W and press A and D, all at the same time. If he released W, then pressed A, then pressed D, it wouldn't work (unless he does it really quickly, since there's 1/8 second tolerance). The code for casting the fireball is:
if( MoveCombo[0].IsEntered(Keyboard) ||
MoveCombo[1].IsEntered(Keyboard) )
{
// fire the fireball
}

As for MoveCombo.h, it uses my keyboard class for input... all you need to know is:
• 'Time' is a static global (of the base 'object' class) that's just "float(timeGetTime()) * 0.001f" which means it's the current time in seconds.
• The keycodes (KEY_W, etc.) are identical to the already defined codes (VK_W, etc.).
• Keyboard->PressTime and Keyboard->ReleaseTime are arrays that indicate the time when a given key was pressed or released. They actually remember the last 8 times every key was pressed and released... PressTime[KEY_W][0] returns the last time the W key was pressed, whereas PressTime[KEY_W][1] returns the time of the previous time it was pressed. If it's only been pressed once, this will be 0. So for the A-S-D-S-A combo, A and S are pressed twice, so it has to know the times of each of those presses.
Let me know if anyone finds any use from this, or has any questions.

Share on other sites
Make sure you allow for a way for keystrokes to not affect the combo. I notice in this system you need to press the exact sequence of keys in the correct order, which you might think is the way it always has to be at first, but in fact most of the time, an incorrect key will not affect the combo at all. Directions are the best example of this. If you've got an analog input such as an analog stick on a joypad, you're probably going to hit a diagonal by mistake quite often. Fighting games ignore bogus directions most of the time, as long as the right one is pressed at some point. There are a lot of examples of this kind of thing though. If I hit an unmapped key for example, it shouldn't affect it. If I pause the game during a combo, it shouldn't affect it either. Really, unless a keystroke indicates to the game that it should do a different move instead of the next part of the combo, it should ignore it. In a fighting game, I should be able to press "a, b, b, x, y" for a combo that's simply "a, y", and as long as b and x don't indicate any kind of move at that time, it should get through. You need to allow for this kind of thing in your system.

Oh, and just a small note, when I alt+tab out of your app, then switch back to it, it crashes.

Share on other sites
Actually, extra keystrokes do not affect it, as long as each step can be completed. If a move combo is pressing X and Y at the same time, pressing X, Y, and Z will complete the move. If the combo is pressing X, then releasing it while pressing Y, pressing X and Z, then Y, will complete the combo, as will pressing X, then pressing Y and Z.

In the A-S-D-S-A combo, if you press A-S-D-F-S-A, that won't work, but only because you're supposed to press the 2nd S when D is released -- you can press something else at the same time if you want, but the original step still needs to be satisfied.

Share on other sites
Holy shit, where'd you get that EVA? It looks awesome :D

The combo system looks pretty good, although it looks like it needs a keyboard class with timed keypresses to work (but that's understandable since you are enforcing the 0.125s tolerance)

Just curious - how'd you go about implementimg features such as requirement to hold a key for a minimum amount of time?

Share on other sites
Quote:
 Original post by UfoZHoly shit, where'd you get that EVA? It looks awesome :D

Here. I've just been using Quake2 MD2s; I need to write the loading code for more model formats.

Quote:
 Just curious - how'd you go about implementimg features such as requirement to hold a key for a minimum amount of time?

I didn't, yet. It didn't occur to me to use move combos that involve things like that... but it would be easy to do. Currently, there is a maximum amount of time allowed between steps -- given by the first parameter to the combo constructor. All I'd need to do is add a minimum time to each as well... and maybe make the maximum time step-specific as well. So to create a combo where you hold down the W key for at least half a second, then quickly tap the S and D keys, you can do this:
MoveCombo[2] = combo( 4,                      step(0.5, 1.0, 1, keypress(KEY_W)),                      step(0.0, 0.25, 2, keypress(KEY_S), keyrelease(KEY_W)),                      step(0.0, 1.0, 1, keyrelease(KEY_S)),                      step(0.0, 1.0, 1, keypress(KEY_D)) );

The first step is to press the W key. The minimum and maximum times for the step are 0.5 and 1.0, so step 2 needs to be completed at least half a second later, and not longer than one second later. For step 2 the W key gets released, and the S key gets pressed. Within 1/4 of a second after that, the S key needs to be released. Then when the D key gets pressed, the move is activated.

Share on other sites
great work. the only thing i dislike is the use of varargs.. i don't like to have to say how much parameters.. thats the compilers job in my eyes.

but all in all, great work. espencially great that you share such info with us. i don't know of much other info about combo systems and always had this somehow in my backbrain in a "list of unsolved misteries" :D

Share on other sites
Quote:
 Original post by davepermengreat work. the only thing i dislike is the use of varargs.. i don't like to have to say how much parameters.. thats the compilers job in my eyes.

Thanks. I agree the language should have built-in variable argument support so it wouldn't be necessary specifying how many arguments were passed... The vararg macros just use assembly code to pull the data from the stack.

I could write multiple constructors to handle the various possible numbers of parameters, since I did put in a hard cap (8 or 16) and they're all the same type (keys or steps). In fact I think I'll do that; the constructors will call the var-arg constructor, passing the number of parameters to it. Like:
step :: step ( float Min, float Max, key K1, key K2 ){    Create(Min, Max, 2, K1, K2);}

Share on other sites
I've updated MoveCombo.h and uploaded a new build in Quest.zip. I implemented the timed keypress idea I talked about, along with using multiple constructors so the number of arguments doesn't need to be specified. The new move combo is:
MoveCombo[2] = combo( step(0.5f, 1, keypress(KEY_W)),                      step(0, 0.25f, keypress(KEY_S), keyrelease(KEY_W)),                      step(0, 1, keyrelease(KEY_S)),                      step(0, 1, keypress(KEY_D)) );

So to test this new combo, press W, hold it for between half a second and one second, then release it and tap S. S can only be held down 1/4 of a second or less before you need to release it and press D.

So in summary, hold down W for just under one second, then release it and tap S then D.

The reason those last two steps aren't combined into one is so you're required to release S before pressing D. If they were the same step, it can be done in any order as long as it's done almost simultaneously. That's why steps can use multiple keypresses -- so if you're required to press several keys at the same time, it won't matter if X gets pressed just before Y, or vice versa.

Oh, and I fixed the Alt+Tab issue. You can now Alt+Tab safely both in windowed mode and in fullscreen mode (run "Quest.exe -fullscreen").

Share on other sites
For dealing with the vararg business, you could just use a single array parameter instead, since the data are all the same type each time you want to invoke the vararg. (Assuming 'keypress' and 'keyrelease' have a common supertype, anyway.)

Also, you should think about having a way to serialize and deserialize combos - either in a nice human-readable format (you could go with XML for example, and no longer need the count values) or a packed binary format.

Share on other sites
Nice! But I get:
Quest.exe caused an Access Violation at location 00410a5c in module Quest.exe Reading from location 00000000.Registers:eax=00000000 ebx=7ffd6000 ecx=00000000 edx=04d3aa38 esi=00484a60 edi=00484a60eip=00410a5c esp=001289c0 ebp=0012a390 iopl=0         nv up ei pl zr na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200246Call stack:00410A5C  Quest.exe:00410A5C004094FC  Quest.exe:004094FC0040273B  Quest.exe:0040273B00427695  Quest.exe:0042769500427484  Quest.exe:0042748400471043  Quest.exe:004710437C816D4F  kernel32.dll:7C816D4F  RegisterWaitForInputIdle

If I run your game.. :/ ?

1. 1
Rutin
48
2. 2
3. 3
4. 4
5. 5
JoeJ
19

• 11
• 16
• 9
• 10
• 13
• Forum Statistics

• Total Topics
633003
• Total Posts
3009845
• Who's Online (See full list)

There are no registered users currently online

×