It is currently Sun Dec 09, 2018 2:24 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 7 posts ] 
Author Message
PostPosted: Mon Sep 24, 2018 9:49 am 
Offline

Joined: Wed Apr 04, 2018 7:29 pm
Posts: 38
Location: Montreal, Canada
Hi.

This is not directly related to NES, but more of a general game programming question.

I want to map an action to a double-tap of the A button. Of course, it needs to handle different speeds of taps. Also, id like to have an action mapped to A+B. Again, it needs some tolerance since it's possible the player wont press both on exactly the same frame.

So that got me thinking : what's the simplest/most efficient way of detecting these kind of complex button combos/sequences? How do fighting games do it usually?

Any suggestions are welcome. Thanks!

-Mat


Top
 Profile  
 
PostPosted: Mon Sep 24, 2018 10:12 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11009
Location: Rio de Janeiro - Brazil
I don't know how existing games do it, but the way I'd do it would be storing each possible sequence as a list is memory (RAM or ROM doesn't matter, whatever is more convenient), and maintain some state information about each one. At least one byte to indicate how far into the sequence the player is, but possibly a global byte to count inactivity time, so you can reset the sequences if the player takes to long to press the next button.

Every frame you'd scan all sequences, and test if the current input allows them to advance to the next step. If the input is wrong, reset the step counter to the start of the sequence. If the step counter ever reaches the end, that means the sequence has been completed and you can carry out the action associated with it. If you want to reject slow input, you can count the time since the last input change and reset all sequences if that reaches a certain threshold.


Top
 Profile  
 
PostPosted: Mon Sep 24, 2018 11:06 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 7003
Location: Canada
Well, detect a tap, then start a timeout counter that ticks down every frame. If you get another tap before then, it's a double tap.

You can do the same to detect B and wait for B+A, etc.

Though, maybe worth pointing out that you have to wait for the timeout to resolve the cases where it's not one of those, which may add a lot of lag to your input. You could do something like generically wait for a few frames of commitment on any input (e.g. keep a buffer of last few pad reads and AND them or something), but it may not be appropriate to wait that long for all actions.

For example, if the B+A action can just cancel the B or A action already started, then you don't need to do the timeout at all. You start the B action immediately when pressed, and cancel it when they get to B+A. No lag on either case, but whatever B does has to be designed to be cancellable like this. (Could also make B only cancellable for a few frames, etc.)


If you want to detect longer sequences, it generically becomes a problem of navigating a finite state graph. For one sequence this might just be implemented with a single array, where you increment the index if you get a confirmation of the next input, and restart the sequence if you don't. (...and like above you might allow 1 or 2 frames of "error" between correct entries, just another timeout situation.)

For multiple sequences either you're running that same thing many times in parallel, or you have a big finite state graph with all the loops of the sequences connected together, it can get pretty complicated to do this efficiently (but it might not be necessary to do it efficiently -- for a small enough set of moves, a separate linear detector for each move that runs every frame might be sufficient).


Top
 Profile  
 
PostPosted: Mon Sep 24, 2018 12:38 pm 
Offline
Formerly ~J-@D!~
User avatar

Joined: Sun Mar 12, 2006 12:36 am
Posts: 474
Location: Rive nord de Montréal
Super Smash Bros. games tend to immediately begin the action associated with the press, but within a certain timeout, if another key is pressed, it changes the animation (and attack). For example, you can do a dash attack or a dash grab (which has a different animation from regular grab starting from Super Smash Bros. Melee); attack is with A and grab with (Shield)+A or its dedicated key (R on N64, Z on Game Cube). Of course the game has to wait upon seeing A because you could be pressing (Shield). This is obvious with Kirby which has a fire dash-attack in Super Smash Bros. Melee: run, press A and then (Shield) within a (very) few frames, and you still see the fire from the dash-attack while grabbing. This is shown below at about 50% speed.

Image

Also, you can make an upward Smash attack with (↑-tap)+A, but (↑-tap) is also used to jump. So, if you carefully tilt and hold up (so the fighter doesn't jump) then press (Jump) and within a few frames A, you'll still do an upward Smash attack. Also, normally you can't run and make anything else than skidding, dash-attach, dash-grab or jump, but you can still run and do an upward Smash attack because of the "fail-safe" ↑+Jump+A decaying to (↑-tap)+A. The games don't seem to differentiate or care how you do a grab or a jump.

Soul Calibur II has even more variations in its moves: you can do for example ↓ + (Vert. Attack), but also (Vert. Attack) + ↓ which means you have to press, say X, and then the joystick down. I remember Cassandra having such a move; if you do it slowly enough (the game seems to have very tolerant timings except in a few places) you clearly see the regular vertical sword slash beginning, then practically half-way through changing to a down-stab to the opponent's foot.

So this shows that you can make moves with key combinations, with timeouts and without introducing lag, but it can introduce quirks visually or in the move itself, but IMHO it is perfectly acceptable.

_________________
((λ (x) (x x)) (λ (x) (x x)))


Top
 Profile  
 
PostPosted: Mon Sep 24, 2018 12:44 pm 
Offline
User avatar

Joined: Tue Apr 04, 2017 1:22 pm
Posts: 36
Location: Ohio, USA
You could make a state graph and represent it on the NES as jump/lookup tables.
For example, your state graph might look like the following:
Code:
                                              +-----------+
                            +---------> A --> | High jump |
                            |                 +-----------+
                            |
+------+              +-----------+           +---------+
| Idle | --> Down --> | Crouching | --> B --> | Kicking |
+---+--+              +---------+-+           +----+----+
    ^                           |
    |                           |
    +-- Timeout or wrong button +


This could be implemented in a series of lookup tables, with each state having its own table that tells your game which new state to transition to given some action.
Here's an example of how you could translate the above state graph into lookup tables:
Code:
ACTION_NONE    = 0
ACTION_A       = 1
ACTION_B       = 2
ACTION_UP      = 3
ACTION_DOWN    = 4
ACTION_TIMEOUT = 5

STATE_IDLE      = 0
STATE_CROUCHING = 1
STATE_KICKING   = 2
STATE_HIGHJUMP  = 3

stateTable:
   .dw idleTable      ; state 0 (idle)
   .dw crouchingTable ; state 1 (crouching)
   .dw kickingTable   ; state 2 (kicking)
   .dw highjumpTable  ; state 3 (high jump)

idleTable:
   .db STATE_IDLE      ; No action
   .db STATE_IDLE      ; A press
   .db STATE_IDLE      ; B press
   .db STATE_IDLE      ; Up press
   .db STATE_CROUCHING ; Down press
   .db STATE_IDLE      ; Timeout

crouchingTable:
   .db STATE_CROUCHING ; No action
   .db STATE_HIGHJUMP  ; A press
   .db STATE_KICKING   ; B press
   .db STATE_IDLE      ; Up press
   .db STATE_CROUCHING ; Down press
   .db STATE_IDLE      ; Timeout

kickingTable:
   .db STATE_KICKING ; No action
   .db STATE_IDLE    ; A press
   .db STATE_KICKING ; B press
   .db STATE_IDLE    ; Up press
   .db STATE_IDLE    ; Down press
   .db STATE_IDLE    ; Timeout

highjumpTable:
   .db STATE_HIGHJUMP ; No action
   .db STATE_HIGHJUMP ; A press
   .db STATE_IDLE     ; B press
   .db STATE_IDLE     ; Up press
   .db STATE_IDLE     ; Down press
   .db STATE_IDLE     ; Timeout

Then to change state depending on your player's action, a you can use a routine like this:
Code:
ChangeActionState:

; Get the current state as an index in the state table
   LDA currentState
   ASL
   TAX

; Point to the current state's action table
   LDA stateTable, x
   STA pointer
   LDA stateTable+1, x
   STA pointer+1

; Change state based on the current action
   LDY currentAction
   LDA (pointer), y
   STA currentState

In an earlier routine you would need to set the currentAction based on the currently pressed buttons, and in a later routine you could set the player's animation/damage/etc based on the currentState.

_________________
http://zutanogames.com/ <-- my dev blog


Top
 Profile  
 
PostPosted: Mon Sep 24, 2018 12:53 pm 
Offline

Joined: Sun Jan 31, 2016 9:55 pm
Posts: 48
This is similar to what Rainwarrior said, but here's some ideas from fighting games -- remember that there are multiple phases of performing a move in a fighting game.

A simple way to think about it might be like this:
1. Startup -- before the hitbox for the move comes out, there is a startup animation to telegraph that move will happen and give the other player a chance to react
2. Attack -- the actual hitboxes appear and the move is performed
3. Cooldown -- after the move finishes, the player must wait before performing another move

Move cancelling is common in fighting games. During the startup phase, button combinations are resolved, so pressing A will start the A move immediately, but allow canceling into A+A or A+B for a few frames. Sometimes the attack phase can cancel into a dodge or shield. In Street Fighter, I believe, performing certain combos of moves allows skipping the cooldown phase and starting the new move immediately. Super Smash Bros L-Cancels allow cancelling an aerial move much faster on landing.

With move cancelling implemented, if you have multiple moves that might start with the same button press, it's not a big deal to take a few frames to decide. Simply start the move it looks like based on the button press in the startup phase. For a few more frames (you'll have to play around to tell how much), allow cancelling into different moves. You can decide whether the couple of frames of the first move startup count towards the startup of the new move. This is a good way to handle button combos like A+B.

Another thing to consider is move buffering. Separate presses, like your A+A example, might be better handled using move buffering. Many games allow you to input your next move while the previous move is still going. Even some NES platformers allow you to buffer a jump a few frames before you land, I believe. Rather than cancelling the current move, it will buffer it and do the move once you are able. In this case, you can define rules not on button presses, but on moves. Say A is the button for a basic punch. You would add a rule that says if the player buffers A while performing basic punch, then buffer left-hand punch next. Then you don't have to introduce lag to decide which move to do, but create a one-two punch move if the player presses A twice in succession.


Top
 Profile  
 
PostPosted: Mon Sep 24, 2018 4:58 pm 
Offline

Joined: Sat Sep 01, 2018 3:26 pm
Posts: 1
I'm no fighting game fan, but my understanding is they are almost universally implemented with input buffers. They can express multi-stage combos (e.g. press down, then down left, then left, then A) very efficiently and they naturally allow the player to mess up and start over in the middle of the combo. For just a double tap or A+B it's probably overkill, but for complexity approaching a fighting game your best bet is probably input buffers.

Instead of just storing the button state for this frame and last frame, you instead make a relatively large circular buffer and put the new button state in it each frame, overwriting the oldest sample, and wrapping around at the end. Then each frame search backward through the buffer for an allowed combo. For example, at 60Hz a 120-sample buffer gives the player at most 2 seconds to enter a combo. Your exact comparison method may vary, and you would still need an FSM for your character so the player can't do moves that aren't legal for their current state, like doing air moves on the ground or when they're in hit stun, etc.

So if, to use an extreme example, you were implementing a combo identical to the Konami code, then every frame you start looking backwards through your input buffer (ignoring duplicate entries.) If you read these entries, in this order, then they successfully performed the combo: $80, $0, $40, $0, $1, $0, $2, $0, $1, $0, $2, $0, $4, $0, $4, $0, $8, $0, $8, $0 (which is the Konami code backwards.)

For double-tapping A and A+B specifically: you can know if the player was within the tolerance by the distance between the two button presses in the buffer -- A and A, or A and B, respectively.


P.S. Hello NesDev!


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 7 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group