What's New in v2.2.0¶
v2.2 absorbs game-loop boilerplate into the library — inputs, camera, and character movement are now one-liners. All changes are fully backward-compatible.
New features¶
Input.axis() — compact movement input¶
# before
move_x = float(inp.key_held(f3d.Key.D)) - float(inp.key_held(f3d.Key.A))
# after
move_x = inp.axis(f3d.Key.A, f3d.Key.D) # → float in [-1, 1]
move_y = inp.axis(f3d.Key.S, f3d.Key.W)
Also available on ScriptedInput for deterministic testing.
OrbitCamera.handle_input() — camera in one call¶
# before (per-frame event parsing)
dx, dy = inp.mouse_delta()
if inp.mouse_button(1):
cam.rotate(d_azimuth=dx * 0.5, d_elevation=-dy * 0.5)
cam.zoom(-inp.scroll_delta() * 0.5)
# after
cam.handle_input(inp, dt) # drag + Q/E rotate + scroll zoom, all built in
CharacterController.move_camera_relative() — camera-space movement¶
# before — manual yaw computation
import math
yaw = math.atan2(cam_dir[1], cam_dir[0])
fx = math.cos(yaw) * move_x + ...
# after
cc.move_camera_relative(inp, cam, speed=5.5, dt=dt)
Viewer.show_grid — public property¶
viewer = f3d.Viewer(world, show_grid=True) # constructor parameter
viewer.show_grid = False # or toggle at runtime
Replaces internal viewer._renderer._show_grid access.
Viewer.draw_text — anchor-based layout¶
viewer.draw_text("Score: 100", anchor="topcenter", margin=10)
viewer.draw_text("Lives: 3", anchor="topleft", margin=10)
viewer.draw_text("GAME OVER", anchor="bottomcenter", margin=40)
viewer.draw_text("Press ENTER", anchor="bottomright", margin=10)
x and y are still accepted for pixel-exact placement; omit them to use anchor.
App render parameters at construction¶
app = f3d.App(
"My Game",
width=1920, height=1080,
substeps=4,
shadow_resolution=2048,
sky_color=(0.1, 0.15, 0.25),
show_grid=False,
max_dt=1/30,
)
Bug fix¶
Sentry hover drift (apps/game/enemies.py)¶
The hover raycast was missing layer_mask, causing sentries to detect their own body
and drift upward. Fixed with:
hit = world.raycast(origin, (0, 0, -1), max_dist=2.0,
layer_mask=CollisionLayer.DEFAULT | CollisionLayer.TERRAIN)
Migration¶
No breaking changes. All existing code runs unchanged.
| Old pattern | v2.2 alternative |
|---|---|
float(inp.key_held(D)) - float(inp.key_held(A)) |
inp.axis(A, D) |
| Per-frame camera event parsing | cam.handle_input(inp, dt) |
Manual yaw + cc.move(...) |
cc.move_camera_relative(inp, cam, speed, dt) |
viewer._renderer._show_grid = True |
viewer.show_grid = True |
draw_text(text, x=640, y=10, ...) |
draw_text(text, anchor="topcenter", margin=10) |