Sign in to follow this  

I wrote a fighting-game move-combo system

This topic is 4844 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
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
Ahh!!! Even more so then your combo system I think your quest demo looks soooooo cool! When are you releasing it? Do you have a version with more functionality? The graphics feel and everything was so awsome! I'm going to go play with it some more.

~Wave

Share this post


Link to post
Share on other sites
Raccoon: Please print the contents of Quest.log. It'll give me an idea of when the game crashed. I have no idea what RegisterWaitForInputIdle does but I'd guess you ran out of memory or something? What system are you running it on?

Wavewash: Thanks, glad to hear it. That build was the latest when I released it. So far I optimized things quite a bit so having enemies onscreen doesn't slow things down as much... I made some other little changes, and I'm adding a second enemy... a guy that explodes when you get near him. I'd like the game to involve fighting but don't have models with the right animations, so I'm just doing projectile attacks now.

Share this post


Link to post
Share on other sites

Quest.log - Record of Quest errors and warnings

10:51:06am - Startup
10:51:06am - DirectInput keyboard initialized successfully.
10:51:06am - DirectInput mouse initialized successfully.
10:51:06am - Enumerating display modes...
10:51:06am - Direct3D8 object created.
10:51:06am - Adapter display mode found.
10:51:06am - Setting the display mode...
10:51:06am - Device caps read.
10:51:06am - Attempting to create Direct3D device... success!
10:51:06am - Checking device format.
10:51:06am - Created z-buffer.
10:51:06am - Direct3D was initialized successfully.
10:51:06am - Display mode set.
10:51:06am - Default font loaded.
10:51:06am - error in Renderer::LoadTexture(...)
error: invalid file or resource
10:51:06am - error in Renderer::LoadTexture(...)
error: invalid file or resource
10:51:06am - error in Renderer::LoadTexture(...)
error: invalid file or resource
10:51:06am - error in Mesh::Load(Type=FILE,Filename="Models\Knight.md2")
error: file or resource not found
10:51:06am - error in Renderer::LoadTexture(...)
error: invalid file or resource
10:51:06am - error in Mesh::Load(Type=FILE,Filename="Models\Knight.md2")
error: file or resource not found
10:51:06am - error in Renderer::LoadTexture(...)
error: invalid file or resource
10:51:06am - error in Mesh::Load(Type=FILE,Filename="Models\Knight.md2")
error: file or resource not found
10:51:06am - error in Renderer::LoadTexture(...)
error: invalid file or resource
10:51:06am - error in Mesh::Load(Type=FILE,Filename="Models\Eva.md2")
error: file or resource not found
10:51:06am - error in Renderer::LoadTexture(...)
error: invalid file or resource
10:51:06am - error in Mesh::Load(Type=FILE,Filename="Models\Knight.md2")
error: file or resource not found
10:51:06am - error in Renderer::LoadTexture(...)
error: invalid file or resource
10:51:06am - error in Mesh::Load(Type=FILE,Filename="Models\Knight.md2")
error: file or resource not found
10:51:06am - error in Renderer::LoadTexture(...)
error: invalid file or resource
10:51:06am - error in Mesh::Load(Type=FILE,Filename="Models\Knight.md2")
error: file or resource not found
10:51:06am - error in Renderer::LoadTexture(...)
error: invalid file or resource
10:51:06am - error in Mesh::Load(Type=FILE,Filename="Models\Knight.md2")
error: file or resource not found
10:51:06am - error in Renderer::LoadTexture(...)
error: invalid file or resource
10:51:06am - error in Mesh::Load(Type=FILE,Filename="Models\Knight.md2")
error: file or resource not found
10:51:06am - error in Renderer::LoadTexture(...)
error: invalid file or resource
10:51:06am - error in Mesh::Load(Type=FILE,Filename="Models\Knight.md2")
error: file or resource not found
10:51:06am - error in Renderer::LoadTexture(...)
error: invalid file or resource
10:51:06am - error in Renderer::LoadTexture(...)
error: invalid file or resource
10:51:06am - error in Renderer::LoadTexture(...)
error: invalid file or resource
10:51:06am - error in Renderer::LoadTexture(...)
error: invalid file or resource
10:51:06am - error in Renderer::LoadTexture(...)
error: invalid file or resource
10:51:06am - INITIALIZATION COMPLETE.


It can't load the data.. Hmm! Seems like winzip didn't make folder

Share this post


Link to post
Share on other sites
Quote:
Original post by rakoon2
*** Source Snippet Removed ***
It can't load the data.. Hmm! Seems like winzip didn't make folder

Exactly. You need to preserve the folders for it to work. Or you can do it manually:

Backgrounds\BlueSky.bmp
Fonts\MSSansSerifShadow.bmp
Models\Knight.bmp
Models\Knight.md2
Models\Eva.bmp
Models\Eva.md2
Particles\DefaultGlow.bmp
Particles\DefaultGlowAlpha.bmp
Tiles\Grass.bmp
Tiles\Dirt.bmp

Share this post


Link to post
Share on other sites
bah, the firewall at work blocks devimg :D

so i can't update my statement for now.. but as far as i can see, it's cool..

too add such stuff i normally use some concatenation thing (if there is no limit).. like overloading operator () and add like this:

myCombo = Combo(Step..)(Step..)(Step..)....

or


myCombo = Combo(Step..)
.add(Step..)
.add(Step..)
.add(Step..)


we could use stream design, too..

myCombo << Step.. << Step.. << Step.. << Step.. ;

or what ever:D

well.. you get the idea:D

but else, if limited, constructor/method overloading is best, yeah.


you could even overload "operator," :D

Share this post


Link to post
Share on other sites
Quote:
Original post by davepermen
too add such stuff i normally use some concatenation thing (if there is no limit).. like overloading operator () and add like this:

myCombo = Combo(Step..)(Step..)(Step..)....

or


myCombo = Combo(Step..)
.add(Step..)
.add(Step..)
.add(Step..)


we could use stream design, too..

myCombo << Step.. << Step.. << Step.. << Step.. ;

Ooo, nice ideas... nothing like that occured to me, not even using the C++-style operators (I avoid such things usually). Specifically, the suggestion I could implement would be using + operators to add things together.

MoveCombo[0] = step(0, 1, keypress(KEY_A,KEY_D)) +
step(0, 1, keyrelease(KEY_A,KEY_D), keypress(KEY_W));


Quote:
you could even overload "operator," :D

I don't think you can overload the comma operator.

Share this post


Link to post
Share on other sites
I like the idea of the whole combo/move system here, but have you though about making the combos dynamic? For example, say you have multiple characters the player can be, each with their own move set. If you implemented each character in code, that could get very tedious. On my current project, I'm using configuration files for each of the playable characters. These file contain the moves for the individual character as well as other pertinent animation.

The configuration files give me much more control over the character and how they react. I've chosen this path because of the modular nature of the engine I've written. I can easily add/remove/change characters without having to rebuild the application. It's very cool. :-)

Share this post


Link to post
Share on other sites
Quote:
Original post by Slick-Player
u should make the charecter shoot or something

He does, he shoots a fireball. Of course there will be many more attacks in the future. I uploaded a new build that includes a second enemy, and changed one of the combos... tap W, then D, then A, then press W and D at the same time, and you'll fire a fireball.
Quote:
the guy who has the shield doesnt atack u

Yeah, I want him to swing a sword but that model has no sword model or animation. All models are placeholders right now, but they don't have the animations I need.
Quote:
and wen u die u go to -50 and so on

This is a really early build and I haven't yet implemented death for the player (though enemies do die).
Quote:
did u use a book for that?

No.
Quote:
Original post by grasshopa55
I like the idea of the whole combo/move system here, but have you though about making the combos dynamic? For example, say you have multiple characters the player can be, each with their own move set. If you implemented each character in code, that could get very tedious. On my current project, I'm using configuration files for each of the playable characters. These file contain the moves for the individual character as well as other pertinent animation.

There will indeed be multiple characters, with different moves. One will specialize in fire-based attacks, so he's the one with the fireball. But I did plan on just hard-coding them. Since the attacks themselves need to be hard-coded, and setting the moves and firing the attacks is very simple, there seems little point in moving it out of the code. For coming up with the combos, I don't need to actually test it; I just press keys on the keyboard and see what seems good for an attack.

Share this post


Link to post
Share on other sites
Quote:
Original post by CGameProgrammer
There will indeed be multiple characters, with different moves. One will specialize in fire-based attacks, so he's the one with the fireball. But I did plan on just hard-coding them. Since the attacks themselves need to be hard-coded, and setting the moves and firing the attacks is very simple, there seems little point in moving it out of the code. For coming up with the combos, I don't need to actually test it; I just press keys on the keyboard and see what seems good for an attack.


For a simple game, I see your point, but what if you wanted to do a full blown fighter, Capcom/SNK/Sammy style hard-coding may be much. I know in the old days, they would hard-cord all of that, but now, with total number of playable characters numbering in the 20's, you could image just how immense that is. For my project, I'm looking at about 10 playable characters, so I could hard code the move sets, but, as I mentioned before, I prefer a more modular approach.

Anyway, all opinions aside, your little demo is well done. Good luck.

Share this post


Link to post
Share on other sites
Well I can always move it out of the code later. Since the code for setting a combo is so simple, it would be really easy reading the parameters from a file:

// in the file:
"FIREBALL"
{
min 0, max 1: W pressed
D pressed, W released
A pressed, D released
W D pressed, A released
}
// in the code:
MoveCombo[MOVE_FIREBALL] = LoadCombo("FIREBALL");

Share this post


Link to post
Share on other sites
[quote]Original post by CGameProgrammer
[quote]Original post by davepermen
quote]


-

Yeah but i think you should make it more simpler. ALl the combos are nice :P but i would preffer pressin ctrl and shift maybe a space bar 2 jump,, just suggesting stick with the original game controls that are out instores

hope that helps

otherwise good job:) u should complete it 2 make it rich :)

Share this post


Link to post
Share on other sites
Quote:
Original post by Slick-Player
i would preffer pressin ctrl and shift maybe a space bar 2 jump

I'll need more keys than that, but using ctrl, shift, and alt is a good idea because you can have all of them pressed while also having regular keys pressed... you can't reliably use more than 2-3 regular keypresses at the same time, as more won't be detected, but the shift keys are handled differently.

On my keyboard, Ctrl, Z, and Alt can be easily handled from my left hand, and Shift and X can be hit pretty easily too. So I'll use those keys from now on.

Share this post


Link to post
Share on other sites

This topic is 4844 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.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this