Jump to content
  • Advertisement
Sign in to follow this  
CGameProgrammer

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.

If you intended to correct an error in the post then please contact us.

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 this post


Link to post
Share on other sites
Advertisement
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 this post


Link to post
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 this post


Link to post
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 this post


Link to post
Share on other sites
Quote:
Original post by UfoZ
Holy 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 this post


Link to post
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 this post


Link to post
Share on other sites
Quote:
Original post by davepermen
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.

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 this post


Link to post
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 this post


Link to post
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 this post


Link to post
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=00484a60
eip=00410a5c esp=001289c0 ebp=0012a390 iopl=0 nv up ei pl zr na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246

Call stack:
00410A5C Quest.exe:00410A5C
004094FC Quest.exe:004094FC
0040273B Quest.exe:0040273B
00427695 Quest.exe:00427695
00427484 Quest.exe:00427484
00471043 Quest.exe:00471043
7C816D4F kernel32.dll:7C816D4F RegisterWaitForInputIdle


If I run your game.. :/ ?

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!