FUN LABS @ BOSTON CHILDREN'S MUSEUM 2026
SkyRocket
A space-themed foam rocket game designed for children ages 4–8. Shoot rockets through planet holes to earn points and trigger a comet LED light show!
A space-themed foam rocket game designed for children ages 4–8. Shoot rockets through planet holes to earn points and trigger a comet LED light show!
SkyRocket was designed and built for the Cornerstone of Engineering Fun Labs showcase at the Boston Children's Museum in April 2026. The project challenged our team to design, prototype, and build an engaging interactive game that young children could enjoy safely and intuitively — with no instructions needed.
Our team of four first-year Northeastern engineering students (class of 2029) went through a full engineering design process: from a low-fidelity cardboard prototype to a polished painted plywood final build with electronics, all in one semester.
SkyRocket is a foam rocket launcher game with an outer-space theme. Kids use foam rocket launchers to aim at one of three planet-shaped holes on a painted plywood board. When a rocket goes through, a team member helps them press a large interactive button that triggers a "comet" LED light show — a sequence of orange lights racing across a strip of 109 NeoPixel LEDs.
Push button
Foam rockets + launchers
109 NeoPixel LED strip
Raspberry Pi Pico
Painted plywood boards
rubber band
Our project went through three major prototype phases, each informed by peer feedback and real testing.
Low-fidelity prototype — Feb 2026
Plain brown cardboard structure with LED lights and a Pico Board. Peer confirmation used for scoring. Identified issues: table too tall for children, holes hard to aim at, rockets hard to control.
High-fidelity prototype — Mar 2026
Cardboard decorated with space theme (black with white stars). Motion sensor integrated for scoring. Guide areas added under holes. Table height lowered. Holes resized and angled for better playability.
Final prototype — Apr 2026
Upgraded to painted plywood for durability and aesthetics. Motion sensor replaced with large push button (sensor had too many false positives). Planets painted on the board and separate planet backboard added. Comet LED animation added.
Switching from cardboard to plywood dramatically improved durability and visual appeal. The disassembleable design made transport easy. The interactive button proved far more engaging for children than the motion sensor approach. The space theme resonated immediately with young visitors.
The motion sensors available to us could not reliably detect every rocket pass, producing both missed detections and false alarms. We pivoted to a simpler button-based system, which ultimately made the game more interactive and child-friendly. We also initially considered a safety net to catch rockets, but decided it would reduce the fun — removing it was the right call.
Simpler is often better. Our most reliable and enjoyable solution (a button) was simpler than our original plan (motion sensors). Early testing with real children would have saved iteration time. Cross-functional planning (splitting electronics, decoration, and assembly tasks clearly across the team) was essential to meeting deadlines.
Code
import machine, neopixel, time, math
# ── Hardware ──────────────────────────────────────────────────────────────────
n = 108
np = neopixel.NeoPixel(machine.Pin(0), n)
button = machine.Pin(15, machine.Pin.IN, machine.Pin.PULL_UP)
led = machine.Pin(14, machine.Pin.OUT)
led.value(1)
# ── Helpers ───────────────────────────────────────────────────────────────────
def clear():
for i in range(n):
np[i] = (0, 0, 0)
np.write()
def get_press_count():
"""
Called immediately after the first press is detected.
Waits for button release, then counts any additional presses
within a 500 ms window. Returns total press count (1, 2, or 3+).
"""
count = 1
# Wait for the first press to be released
while button.value() == 0:
time.sleep_ms(10)
time.sleep_ms(50) # debounce
deadline = time.ticks_add(time.ticks_ms(), 500)
while time.ticks_diff(deadline, time.ticks_ms()) > 0:
if button.value() == 0:
count += 1
while button.value() == 0: # wait for release
time.sleep_ms(10)
time.sleep_ms(50) # debounce
# Reset the window on each new press so rapid tapping still counts
deadline = time.ticks_add(time.ticks_ms(), 500)
time.sleep_ms(10)
return count
# ── Mode 1 — Aurora Borealis (1 click) ───────────────────────────────────────
# Three overlapping sine waves (green / teal / purple) drift across the strip.
# Drift speed eases out: starts fast, decelerates to a slow crawl, then fades.
def aurora(duration_ms=5000):
TWO_PI = 2 * math.pi
offset = 0.0
speed_start = 0.14 # fast initial drift (rad/frame)
speed_end = 0.018 # slow final drift (rad/frame)
speed = speed_start
start = time.ticks_ms()
while True:
elapsed = time.ticks_diff(time.ticks_ms(), start)
if elapsed >= duration_ms:
break
# Ease-out: linearly interpolate speed from fast → slow
t_norm = elapsed / duration_ms # 0.0 → 1.0
speed = speed_start + (speed_end - speed_start) * t_norm
for i in range(n):
t = (i / n) * TWO_PI + offset
g = int((math.sin(t) + 1) * 75) # green 0 – 150
b = int((math.sin(t + 2.094) + 1) * 60) # teal 0 – 120
r = int((math.sin(t + 4.189) + 1) * 40) # purple 0 – 80
np[i] = (r, g, b)
np.write()
offset += speed
time.sleep_ms(28)
# Smooth fade-out (continues drifting at final slow speed while dimming)
for step in range(20, 0, -1):
f = step / 20
offset += speed_end
for i in range(n):
t = (i / n) * TWO_PI + offset
g = int((math.sin(t) + 1) * 75 * f)
b = int((math.sin(t + 2.094) + 1) * 60 * f)
r = int((math.sin(t + 4.189) + 1) * 40 * f)
np[i] = (r, g, b)
np.write()
time.sleep_ms(28)
clear()
# ── Mode 2 — Comet + Explosion (2 clicks) ────────────────────────────────────
# Orange comet streaks across the strip, then explodes at the end.
COMET_COLOR = (200, 30, 1)
TAIL_LENGTH = 12
def comet():
# --- Travel across the strip ---
for pos in range(n):
clear()
np[pos] = COMET_COLOR
for t in range(1, TAIL_LENGTH + 1):
idx = pos - t
if idx < 0:
break
fade = 1 - (t / TAIL_LENGTH)
np[idx] = (
int(COMET_COLOR[0] * fade),
int(COMET_COLOR[1] * fade),
int(COMET_COLOR[2] * fade),
)
np.write()
time.sleep_ms(12)
# --- Explosion: ring expands outward from last LED ---
for radius in range(1, 20):
clear()
for off in range(-radius, radius + 1):
idx = (n - 1) - abs(off)
if 0 <= idx < n:
fade = (1 - abs(off) / radius) if radius > 0 else 1
b = int(255 * fade)
np[idx] = (b, int(b * 0.4), 0)
np.write()
time.sleep_ms(40)
# --- White-hot flash at the impact point ---
for _ in range(2):
for i in range(max(0, n - 8), n):
np[i] = (255, 255, 200)
np.write()
time.sleep_ms(60)
clear()
np.write()
time.sleep_ms(60)
# --- Full-strip orange glow fades out ---
for b in range(225, 0, -15):
for i in range(n):
np[i] = (b, int(b * 0.3), 0)
np.write()
time.sleep_ms(20)
clear()
# ── Mode 3 — Warp / Hyperspace (3 clicks) ────────────────────────────────────
# Stars appear and accelerate across the strip, building to a hyperjump
# with dense streaks, a blinding white flash, then a blue fade-out.
def warp():
W = (160, 210, 255) # blue-white star color
def draw_stars(positions, trail):
clear()
for pos in positions:
for t in range(trail):
idx = int(pos) - t
if 0 <= idx < n:
f = 1 - t / trail
np[idx] = (int(W[0]*f), int(W[1]*f), int(W[2]*f))
np.write()
# Phase 1 — Stars spread across the strip and accelerate
GAP = 14
positions = [float(i * GAP % (n + GAP)) for i in range(n // GAP + 2)]
speed = 1.5
for _ in range(75):
draw_stars(positions, trail=5)
positions = [(p + speed) % (n + GAP) for p in positions]
speed = min(speed + 0.13, 10.0)
time.sleep_ms(14)
# Phase 2 — Hyperjump: 3 dense sweeps of fast streaks
for _ in range(3):
GAP2 = 10
stars = [float(-i * GAP2) for i in range(n // GAP2 + 3)]
while not all(p > n + 5 for p in stars):
draw_stars(stars, trail=8)
stars = [p + 12 for p in stars]
time.sleep_ms(7)
# Phase 3 — Blinding white flash
for i in range(n):
np[i] = (255, 255, 255)
np.write()
time.sleep_ms(130)
# Phase 4 — Blue cooldown fade
for b in range(255, 0, -8):
for i in range(n):
np[i] = (int(b * 0.4), int(b * 0.65), b)
np.write()
time.sleep_ms(12)
clear()
# ── Main loop ─────────────────────────────────────────────────────────────────
# Detects falling edge on button, counts successive presses, dispatches mode.
last_state = 1
while True:
current_state = button.value()
if current_state == 0 and last_state == 1:
count = get_press_count()
print("Button presses detected:", count)
if count == 1:
print("→ Aurora")
aurora()
elif count == 2:
print("→ Comet launched")
comet()
elif count >= 3:
print("→ Warp speed")
warp()
time.sleep_ms(50) # short guard after any animation finishes
last_state = button.value()