In this post I go over what changed in the feat-4px-scroll-and-sprite-animation branch compared to the previous port version. The short version:
- Fine horizontal scroll at 4px per frame instead of character-based shifts.
- Bird sprite animation (alternating frames) for smoother motion.
- Render pipeline adjustments to avoid tearing: pre-calculation in
screenBufferand a singlememcopyat the end of the frame. - Collision logic revised to work correctly with 4px scrolling.
Motivation
The issue with the previous version was that the world moved in full character steps (8×8 pixel columns), which made horizontal motion feel pretty jerky. I wanted something smoother without touching the game's geometry — moving to 4px per frame did the trick.
Technical implementation (summary)
-
4px scroll:
-
Smooth horizontal scrolling is achieved by alternating the attributes of both the pipe columns and the floor between normal values (
ATTR_PIPE,ATTR_SKY, etc.) and special values (ATTR_FIRST_HALFandATTR_LAST_HALF) on even and odd frames (worldCol Mod 2). - The
halfTilepixel pattern (left half of the character ON) allows each character to be visually split: the left half with one color (ink/paper) and the right half with another. - This way, both pipes and floor appear to move 4px per frame, even though the attribute memory remains 32 columns wide. There is no logical resolution doubling or true per-pixel shift.
- Key code fragment (
src/draw.bas):
If worldCol Mod 2 = 1 Then
attrFront = ATTR_FIRST_HALF
attrBack = ATTR_LAST_HALF
Else
attrFront = ATTR_PIPE
attrBack = ATTR_SKY
End If
...
bufferPipeColumn(leadingCol, gap, attrFront)
bufferPipeColumn(trailingCol, gap, attrBack)
-
Sprite animation:
-
The bird sprite now has 3 frames in
src/spriteset.bas. The game alternates the frame index every N frames (e.g., every 6 frames) to create a flapping effect. -
Animation code:
Sub drawBird()
' Calculate yo-yo animation frame (0, 1, 2, 1) changing every 2 ticks
Dim animIdx As Ubyte = (worldCol / 2) Mod 4
If animIdx = 3 Then animIdx = 1
' Draw pixels based on animation frame
If animIdx = 0 Then
putChars(birdX, Int(birdYPos), 2, 2, @sprite0(0))
ElseIf animIdx = 1 Then
putChars(birdX, Int(birdYPos), 2, 2, @sprite1(0))
Else
putChars(birdX, Int(birdYPos), 2, 2, @sprite2(0))
End If
' Paint bird with Yellow Ink (6) and Blue Paper (1) -> 14
paint(birdX, Int(birdYPos), 2, 2, 14)
End Sub
Collision and logic compatibility
With sub-character scrolling, the bird can straddle two attribute columns for several frames, which broke collisions. To fix it without overcomplicating the code:
-
Collision checks now use game (geometric) coordinates: it calculates whether the bird's rectangle intersects the pipe geometry, rather than reading attributes rounded to columns. This avoids false positives/negatives during intermediate scroll phases.
-
We still only check the front pipe (the one that can actually be in contact), just using pixel positions and widths/gaps instead of column indices.
Performance and timing
I was a bit worried these changes would tank the frame rate, but it turned out fine. The extra work for 4px scrolling and sprite animation is negligible compared to hammering the attribute RAM with multiple writes. The strategy stays the same: compute the full screenBuffer before waitretrace, then do a single memcopy during the retrace window to keep things tear-free.
What players will notice
- The world moves much smoother: 4px steps instead of full character jumps.
- The bird now flaps with an animated sprite.
- Collisions feel more precise thanks to the pixel-based geometric checks.
Links
- Code for this improvement (branch): feat/4px-scroll-and-sprite-animation