Pages

Monday, February 14, 2011

Tandem sprite movement in Allegro 5

My friends and I worked on a flixel game called "Deaf Bastard," which featured a composer who moved notes. The player controlled two sprites simultaneously. Pressing left made one note move left, and made the other note move right. The former was called the "direct note," and the latter was called the "inverse note." Our last build here.



We hit a technical challenge early on, because flixel as-is checked for collisions after positions were updated. Both of the notes' positions were incremented even if one note collided against a wall. So you could bang the direct note repeatedly against the ceiling, and the inverse note would "creep" down. Without preservation of note positions, the game mechanics were inconsistent.

Compounding this difficulty was the seeming complexity of determining which note would collide against a wall first. Each note was independently updated. The distance traversed in each frame was hard to predict, and therefore hard to undo.

I never could figure it out, but with Allegro 5 I tried again.


This implementation uses two features to avoid creep and to maintain consistent note movement: states to permit a note's position to be updated (that is, to be moved), and events triggered by released keys.

During each iteration of the game loop, we first determine the direction of the note. If the note is moving down, it is affected by a positive velocity along the y-axis. We predict the note's future position by adding the note's (current) y-position and its speed. There is a collision if the note passes the floor boundary, designated by the #define symbol HEIGHT.


All of my drawing functions assume the sprite's position is at its center. In order to calculate boundaries, I have to add or subtract SPRITE_HEIGHT/2 or SPRITE_WIDTH/2 as needed.

If there is an imminent collision, we do some bookkeeping: zero the note's speed, and reset its position to be flush with the floor. We set the note's vertical state to DO_NOT_UPDATE_V, which denies permission to update position, at least in this loop (explained next). We also prevent the player from moving the note with the left or right key. Since this is a floor collision, the player can't move left or right until he releases the up key (explained in last code snippet). 


This is the crux of the matter: one note collided, which means the other note should not move. We use a state variable to control permission. In our final code snippet, we look at reenabling the player's left-right input if he releases the up key. This by the design of the mechanic: the player should not be able to move notes further if they collide against any wall.



We need the zerospeed() function call because the next iteration of the game loop leads directly back to the notes updating their positions. The player releases the up key, fine; but in the next frame the note's y-speed remains non-zero, therefore yspd > 0, therefore predy > HEIGHT, therefore lockedkeys = true. Perpetually so, which means the player is never able to move the notes left or right. You can see this for yourself by commenting out zerospeed() above and re-compiling.

Files
deaf.c
deaf.h

Compilation
gcc deaf.c -o deaf.out -L/usr/local/lib -lallegro -lallegro_main -lallegro_primitives


(This is assuming your shared libraries are in /usr/local/lib/ directory.)


Further Investigations
  • Does it work when notes are placed in different initial positions?
  • Implement checkpath().
  • Go back and do it in flixel.
Reference
Game Programming All-in-One, 3e by Jonathan Harbour [Amazon Affiliate Link]

Credits
Original flixel version: All of the architecture, design and initial coding was done by Josh Helpert, with co-design and graphics by dcb.

Update. Added a link to the swf file.
Update August 2011. Added Reference section and an Amazon affiliate link.

No comments:

Post a Comment