Flapper Boriel: 4px "Scroll" and sprite animation

2026-05-16 09:34:03 by juntelart

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 screenBuffer and a single memcopy at 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_HALF and ATTR_LAST_HALF) on even and odd frames (worldCol Mod 2).

  • The halfTile pixel 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


Back to posts