#26800 - Typhin - Sun Sep 26, 2004 8:48 am
Okay, I tried searching, but when I realized I was getting into threads that died about a year and a half ago, I decided I'd just go ahead and ask.
Is there any easy way to recognize certain sequences of inputs? For example, moves in a fighting game? A certain margin of error is needed, not to mention the timing issues involved. Saving the state from the previous check would mean that a move like Ryu's Hadoken would have to be executed in 1/20th of a second.
So far, the only idea I have involves checking for the first input of a sequence (like down), allowing a certain amount of time (ten frames, maybe) to pass while waiting for the next input (like down-forward) and ruling out any move that doesn't start with those two, again letting time pass while waiting for the next input of any matching sequences, and only performing the move when one finishes.
--Typhin, who's a little stumped by this.
#26803 - tepples - Sun Sep 26, 2004 2:32 pm
Read up on finite state machines. Then for each move, construct a finite state machine that will detect it.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.
#26807 - keldon - Sun Sep 26, 2004 3:27 pm
Aargh, I just looked at a search for Finite State machines .. I think a link would do him justice =D
http://forum.gbadev.org/viewtopic.php?t=3753&highlight=finite+state+machine
Tepples, maybe this should appear in the FAQ ( or at least a link to it ). Only problem is that unless you know what FSM's are, then you most likely don't know that it is what you wish to search for =)
#26820 - Typhin - Sun Sep 26, 2004 9:02 pm
I didn't find that post while searching, but it is essentially what I had described. Except that I'm trying to figure out a good way to include a "fudge factor" into the moves so each input doesn't have to happen within the next frame. In the example shown (unless I'm misreading it), the entire sequence would have to take place within three frames, or 1/20th of a second. Even then, there has to be some kind of margin for error, so the only way I can think of to use the example shown is basically to include a count of how many frames the character has been in each state and cancel the move if they're doing it too slow, as well as keep tabs on multiple moves that start the same way. Perhaps the "state" would contain a series of bitflags corresponding to possible moves at that point. At the first "down", flags for all mves starting with "down" are set, and everything else is cleared. The next input checks against the sequences necessary for each of the remaining moves, clearing any that are wrong. (Down/forward clears bits for moves that need down/back, for example.) And then a move is executed only if it successfully gets to the end of a single move.
--Typhin, who'd never heard the term "Finite State Machine" before this forum, but knew the basic concepts already.
#26822 - sajiimori - Sun Sep 26, 2004 9:57 pm
The key is to completely isolate each state machine, and let it work according to its own rules. Pass the current input to each of the relevant state machines, and let them decide what to do independently.
Code: |
void update_states()
{
KeyState s = get_new_key_state();
update_hadouken(s);
update_shoryuken(s);
...
update_regular_moves(s);
}
|
An FSM for a particular move might resemble this pseudocode:
Code: |
// Specify how many frames can pass before entering the
// next part of the sequence.
#define HADOUKEN_MAX_TIMING_GAP 5
SequenceStep hadouken_sequence[] =
{
down, down_forward, forward_and_punch, end
};
struct
{
int current_step;
int ticks_since_last_step;
} hadouken_state;
void update_hadouken(KeyState s)
{
if(input_satisfies_current_step(s)
{
reset_ticks_since_last_step();
increment_current_step();
if(at_end_of_sequence())
{
do_hadouken_if_possible();
reset_current_step();
}
}
else
{
increment_ticks_since_last_step();
if(too_many_ticks_have_passed())
reset_current_step();
}
}
|
Chances are your various moves will have a lot of code in common, so you could share most of it and just have the timing and sequences be different for each.
#26823 - LOst? - Sun Sep 26, 2004 10:01 pm
sajiimori wrote: |
The key is to completely isolate each state machine, and let it work according to its own rules. Pass the current input to each of the relevant state machines, and let them decide what to do independently.
Code: |
void update_states()
{
KeyState s = get_new_key_state();
update_hadouken(s);
update_shoryuken(s);
...
update_regular_moves(s);
}
|
An FSM for a particular move might resemble this pseudocode:
Code: |
// Specify how many frames can pass before entering the
// next part of the sequence.
#define HADOUKEN_MAX_TIMING_GAP 5
SequenceStep hadouken_sequence[] =
{
down, down_forward, forward_and_punch, end
};
struct
{
int current_step;
int ticks_since_last_step;
} hadouken_state;
void update_hadouken(KeyState s)
{
if(input_satisfies_current_step(s)
{
reset_ticks_since_last_step();
increment_current_step();
if(at_end_of_sequence())
{
do_hadouken_if_possible();
reset_current_step();
}
}
else
{
increment_ticks_since_last_step();
if(too_many_ticks_have_passed())
reset_current_step();
}
}
|
Chances are your various moves will have a lot of code in common, so you could share most of it and just have the timing and sequences be different for each. |
sajiimori, you are one of the best programmers I have seen.
#26827 - sajiimori - Sun Sep 26, 2004 10:30 pm
Wow, thank you! o_O
#26838 - sajiimori - Mon Sep 27, 2004 7:45 am
Just for practice, here's a working (?) version in Lua. This language is pretty cool for having such humble origins. Things that are conceptually linear (like pressing down, down+forward, forward+punch) can be written in a linear fashion, rather than jumping through the usual C hoops with FSMs and all that. Code: |
-- 'sequence' is a list of command sets to do the move.
-- 'max_gap' is the maximum number of ticks between steps.
-- 'execute_move' actually does the move, if possible.
-- Presumes "local by default" Lua patch.
-- Caller of coroutine provides command sets
-- that correspond to player input. These commands become
-- "return values" of coroutine.yield().
function special_move_coroutine(sequence,
max_gap, execute_move)
while true do
ticks_since_last = 0
for step in list_values(sequence) do
while ticks_since_last <= max_gap do
if coroutine.yield() == step then
ticks_since_last = 0
break
end
ticks_since_last = ticks_since_last + 1
end
if ticks_since_last > max_gap then break end
end
if not step then execute_move() end
end
end |
#26853 - expos1994 - Mon Sep 27, 2004 2:46 pm
Hey, I just thought I'd share my combo code.
I have a structure that defines button_combos. These can be loaded on the fly from Const definitions. Basically there's an index that increments each time a button in the combodef array is pressed. When a timer(counter) reaches a certain value without the next button being pressed, the index resets. If the last button in combodef gets pressed before the timer has a chance to reset, the combo is executed.
I have tested this and it works, but I haven't used it extensively so there is probably some cleanup and functionality that could be added to it. You will need a method of detecting button state changes that detects when a button has been pressed and unpressed (if you know what I mean). If you want to look at my way of doing that, send me a pm and I'll send you the code.
Code: |
struct COMBO
{
u16 index;
u16 combodef[6];
u16 numofbuttons;
u16 type;
}combo[3]; //Three different combos.
u16 numofcombos = 1;
u16 combotimer[3]; |
Code: |
void check_combos()
{
u16 comboloop;
u16 b_loop; //Button_loop. Loops through all GBA buttons
for(comboloop = 0; comboloop < 2; comboloop++)
{
if (combotimer[comboloop] == 20) //the amount of time to press the next button in a combo. 20 gives ~1/3sec to press a button, this could be variable.
{
combo[comboloop].index = 0; //Reset combo.
combotimer[comboloop] = 0;
}
else
{
for (b_loop = 0; b_loop < 9; b_loop++)
{
if ((changed_keys[b_loop] == PRESSED) && (combo[comboloop].combodef[combo[comboloop].index] == b_loop)) //If a pressed button is the next button in a combo.
{
if (combo[comboloop].index + 1 == combo[comboloop].numofbuttons)
{
player.combo_executed = YES;
combo[comboloop].index = 0;
}
else
{
combo[comboloop].index++;
combotimer[comboloop] = 0;
}
}
//else
}
}
combotimer[comboloop]++;
}
} |