McRogueFace Quick Reference
A handy cheat sheet for common McRogueFace operations. Every snippet on this page runs against the current engine.
Scene Management
# Create and activate scenes
scene = mcrfpy.Scene("menu")
scene.activate()
current = mcrfpy.current_scene # property, not a function
# Access scene UI elements
scene.children.append(element)
# Set input handler on scene
scene.on_key = on_key
UI Elements
Caption (Text)
caption = mcrfpy.Caption(text="Hello World", pos=(100, 100))
caption.font_size = 24
caption.fill_color = mcrfpy.Color(255, 255, 255)
caption.pos = (120, 140) # Reposition
# font is read-only after construction - pass it up front
styled = mcrfpy.Caption(text="Styled", pos=(10, 10), font=mcrfpy.default_font)
Sprite
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
sprite = mcrfpy.Sprite(pos=(50, 50), texture=texture, sprite_index=0)
sprite.scale = 2.0 # single float, applies to both axes
sprite.pos = (60, 70)
# scale_x / scale_y exist for non-uniform scaling
Frame (Container)
frame = mcrfpy.Frame(pos=(10, 10), size=(200, 150))
frame.fill_color = mcrfpy.Color(64, 64, 128)
frame.outline = 2
frame.outline_color = mcrfpy.Color(255, 255, 255)
frame.children.append(caption) # Add child elements
# Alignment: add to parent first, then align
label = mcrfpy.Caption(text="Centered")
frame.children.append(label)
label.align = mcrfpy.Alignment.CENTER # Auto-position in parent
Grid (Tilemap)
grid = mcrfpy.Grid(grid_size=(20, 15), texture=texture, pos=(0, 0), size=(400, 300))
cell = grid.at(3, 4) # GridPoint at tile coordinates
cell.walkable = True
cell.transparent = True
grid.entities.append(entity) # Add entities
Tile and color visuals live on layers, not on cells:
# Construct a layer standalone, then attach it
ground = mcrfpy.TileLayer(name="ground", z_index=-1, texture=texture)
grid.add_layer(ground)
ground.fill(48) # every cell -> tile index 48
ground.set((3, 4), 12) # one cell -> tile index 12
ground.set((5, 5), -1) # -1 = no tile (transparent)
# ColorLayer for tints, fog, highlights
overlay = mcrfpy.ColorLayer(name="overlay", z_index=1)
grid.add_layer(overlay)
overlay.fill(mcrfpy.Color(0, 0, 0, 0))
# Look layers up later
grid.layers # tuple sorted by z_index
grid.layer("ground") # by name
# Or pass layers at construction
grid2 = mcrfpy.Grid(grid_size=(10, 10), texture=texture,
layers=[mcrfpy.TileLayer(name="floor", z_index=-1, texture=texture)])
Bulk edits go through layer.edit() - a context manager yielding a zero-copy
writable memoryview (TileLayer: shape (height, width), int32; ColorLayer:
(height, width, 4), uint8). The layer re-renders when the block exits:
with ground.edit() as view:
view[4, 3] = 12 # row-major: view[y, x]
# with numpy installed: np.asarray(view)[...] = 48
Camera
grid.center = (160, 120) # PIXEL coordinates
grid.center_camera((10.5, 7.5)) # tile coordinates (+0.5 = middle of tile)
grid.zoom = 2.0 # float magnification
Entity
entity = mcrfpy.Entity(grid_pos=(10, 7), texture=texture, sprite_index=84)
grid.entities.append(entity)
entity.grid_pos = (11, 7) # logical cell position (also: entity.cell_pos)
entity.labels = {"player"} # frozenset of str, for collision/targeting
# entity.draw_pos is the fractional render position - animate it for smooth movement
Collections
# Scene children
scene.children.append(element)
scene.children.remove(element)
for element in scene.children:
print(element)
# EntityCollection (for entities in grids)
entities = grid.entities
entities.append(entity)
entities.remove(entity)
Input Handling
Keyboard handlers receive Key and InputState enums, not strings:
def on_key(key, state):
if state != mcrfpy.InputState.PRESSED:
return # ignore key release
if key == mcrfpy.Key.W or key == mcrfpy.Key.UP:
move_player(0, -1)
elif key == mcrfpy.Key.ESCAPE:
mcrfpy.exit()
scene.on_key = on_key
Mouse handlers attach to drawables via on_click (not click):
def on_click(pos, button, action):
# pos: Vector, button: MouseButton, action: InputState
if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.PRESSED:
print(f"Clicked at {pos.x}, {pos.y}")
frame.on_click = on_click
# Hover callbacks receive only (pos)
frame.on_enter = lambda pos: print("entered")
frame.on_exit = lambda pos: print("left")
frame.on_move = lambda pos: None # fires every movement - keep it cheap
# Grids get cell-level callbacks in tile coordinates
grid.on_cell_click = lambda cell_pos, button, action: print(cell_pos)
grid.on_cell_enter = lambda cell_pos: None
grid.on_cell_exit = lambda cell_pos: None
Timers
def update(timer, runtime):
# timer: the Timer object
# runtime: total elapsed time in MILLISECONDS
player.update()
# Interval is in milliseconds
timer = mcrfpy.Timer("game_loop", update, 100) # fires every 100 ms
# One-shot timer
one_shot = mcrfpy.Timer("delayed", update, 500, once=True)
# Control (there is no cancel())
timer.pause()
timer.resume()
timer.stop() # remove from tick loop, callback preserved
timer.restart() # start over from the beginning
Animation
There is no mcrfpy.Animation constructor - call .animate() on the object itself:
# animate(property, target, duration_seconds, easing, ...)
frame.animate("x", 500.0, 2.0, mcrfpy.Easing.EASE_IN_OUT)
frame.animate("opacity", 0.0, 0.5, mcrfpy.Easing.EASE_OUT)
# Completion callback receives (target, property, final_value)
def on_complete(target, prop, value):
print(f"{type(target).__name__}.{prop} reached {value}")
handle = frame.animate("x", 500.0, 2.0, mcrfpy.Easing.EASE_IN_OUT,
callback=on_complete)
Turn Manager
The engine runs turn-based entity behavior natively via grid.step():
# Give an enemy a behavior
to_player = grid.get_dijkstra_map(root=(11, 7))
enemy.set_behavior(mcrfpy.Behavior.SEEK, pathfinder=to_player)
# Waypoint patrol: visit each point, waiting n turns at each
patrol.set_behavior(mcrfpy.Behavior.WAYPOINT, waypoints=[(5, 5), (10, 10)], turns=3)
# Turn order: lower goes first, 0 = skip this entity
enemy.turn_order = 2
# React to behavior events with a step callback
def on_trigger(trigger, data):
if trigger == mcrfpy.Trigger.DONE:
print("behavior finished")
elif trigger == mcrfpy.Trigger.BLOCKED:
print("path blocked")
enemy.step = on_trigger
# Advance the world
grid.step() # one round: every entity acts in turn_order
grid.step(n=3) # three rounds
Behaviors include IDLE, SEEK, FLEE, WAYPOINT, PATROL, PATH, LOOP,
NOISE4, NOISE8, SLEEP, and CUSTOM. Triggers include DONE, BLOCKED,
and TARGET.
Pathfinding & FOV
# Field of View: position tuple + radius keyword
grid.compute_fov((11, 7), radius=10)
if grid.is_in_fov(int(enemy.cell_x), int(enemy.cell_y)):
print("Enemy visible!")
# A* pathfinding (returns AStarPath or None)
path = grid.find_path((2, 2), (11, 7))
if path:
nxt = path.peek() # look without consuming
step = path.walk() # consume one step
left = path.remaining
# Dijkstra maps (cached per root; cleared with grid.clear_dijkstra_maps())
dijkstra = grid.get_dijkstra_map(root=(11, 7))
dist = dijkstra.distance((2, 2))
next_cell = dijkstra.step_from((2, 2)) # one step toward the root
full_path = dijkstra.path_from((2, 2)) # AStarPath to the root
heightmap = dijkstra.to_heightmap() # for procgen and visualization
Audio
Audio is class-based - SoundBuffer, Sound, and Music objects:
# Sound effects (short clips)
buf = mcrfpy.SoundBuffer("assets/sfx/splat1.ogg")
sound = mcrfpy.Sound(buf) # or mcrfpy.Sound("assets/sfx/splat2.ogg")
sound.volume = 80 # 0-100
sound.play()
sound.play_varied() # randomized pitch/volume for natural variation
# Music (streamed, for longer tracks)
music = mcrfpy.Music("assets/sfx/splat1.ogg") # use your own track here
music.loop = True
music.volume = 50
music.play()
Headless Mode & Testing
# Advance simulation time (seconds); timers fire at most ONCE per step call
mcrfpy.step(0.1)
mcrfpy.step(0.1) # call repeatedly for repeated timer fires
# Screenshots (synchronous in headless)
from mcrfpy import automation
automation.screenshot("game.png")
Common Patterns
Grid Movement with Collision
def move_player(dx, dy):
nx = player.cell_x + dx
ny = player.cell_y + dy
if grid.at(nx, ny).walkable:
player.grid_pos = (nx, ny)
grid.center_camera((nx + 0.5, ny + 0.5)) # tile coords, +0.5 = tile center
Scene Transitions
menu_scene = mcrfpy.Scene("menu")
game_scene = mcrfpy.Scene("game")
def start_game():
game_scene.activate()
def return_to_menu():
menu_scene.activate()
Alignment System
# Add to parent FIRST, then set alignment
frame.children.append(child)
child.align = mcrfpy.Alignment.CENTER # Centered
child.align = mcrfpy.Alignment.TOP_LEFT # Corner with margin
child.margin = 10.0 # Offset from edge (invalid for CENTER)
# Alignment values: TOP_LEFT, TOP_CENTER, TOP_RIGHT,
# CENTER_LEFT, CENTER, CENTER_RIGHT,
# BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT
Color Helper
red = mcrfpy.Color(255, 0, 0)
transparent_blue = mcrfpy.Color(0, 0, 255, 128) # RGBA
frame.fill_color = red
caption.fill_color = mcrfpy.Color(255, 255, 255)
Tips
- Enums everywhere - compare against
mcrfpy.Key,mcrfpy.InputState,mcrfpy.MouseButton, never strings - Timer intervals and runtimes are milliseconds - callback signature is
(timer, runtime_ms) - Sprite.scale is a single float - use
scale_x/scale_yfor non-uniform scaling - Animate via the object -
obj.animate(...), there is nomcrfpy.Animationconstructor - Cell visuals live on layers -
GridPointhaswalkable/transparent/entities; colors go on aColorLayer grid.centeris pixels;grid.center_camera()is tiles - don’t mix them up- Let the engine run turns -
grid.step()+ behaviors replaces hand-rolled turn loops
Common Issues
- Scene not showing: Call
scene.activate()after creating - Timer firing too rarely: Interval is milliseconds -
0.1means 0.1 ms rounds to nothing useful; use100for 100 ms - Alignment not working: Element must be in the parent’s
childrenbefore settingalign - Click handler never fires: The property is
on_click(signature(pos, button, action)), notclick - Caption font won’t change:
fontis read-only after construction - passfont=to the constructor - Pathfinding stale after digging: Call
grid.clear_dijkstra_maps()after changing walkability