Update Grid Interaction Patterns

John McCardle 2026-02-07 23:48:22 +00:00
commit 87921ac87c

@ -1,303 +1,303 @@
# Grid Interaction Patterns # Grid Interaction Patterns
Patterns for handling mouse and keyboard interaction with Grids, cells, and entities. These patterns build on the grid-specific event handlers. Patterns for handling mouse and keyboard interaction with Grids, cells, and entities. These patterns build on the grid-specific event handlers.
**Related Pages:** **Related Pages:**
- [[Grid-System]] - Grid architecture and layers - [[Grid-System]] - Grid architecture and layers
- [[Entity-Management]] - Entity behavior patterns - [[Entity-Management]] - Entity behavior patterns
- [[Input-and-Events]] - General event handling - [[Input-and-Events]] - General event handling
--- ---
## Grid Cell Events ## Grid Cell Events
Grids provide cell-level mouse events in addition to standard UIDrawable events: Grids provide cell-level mouse events in addition to standard UIDrawable events:
| Property | Signature | Description | | Property | Signature | Description |
|----------|-----------|-------------| |----------|-----------|-------------|
| `on_cell_click` | `(cell_pos: Vector, button: MouseButton, action: InputState) -> None` | Cell clicked | | `on_cell_click` | `(cell_pos: Vector, button: MouseButton, action: InputState) -> None` | Cell clicked |
| `on_cell_enter` | `(cell_pos: Vector) -> None` | Mouse enters cell | | `on_cell_enter` | `(cell_pos: Vector) -> None` | Mouse enters cell |
| `on_cell_exit` | `(cell_pos: Vector) -> None` | Mouse leaves cell | | `on_cell_exit` | `(cell_pos: Vector) -> None` | Mouse leaves cell |
| `hovered_cell` | `(x, y)` or `None` | Currently hovered cell (read-only) | | `hovered_cell` | `(x, y)` or `None` | Currently hovered cell (read-only) |
Cell positions arrive as `Vector` objects. Use `int(cell_pos.x)`, `int(cell_pos.y)` to get grid coordinates. Cell positions arrive as `Vector` objects. Use `int(cell_pos.x)`, `int(cell_pos.y)` to get grid coordinates.
--- ---
## Setup Template ## Setup Template
Most grid interaction patterns fit into this structure: Most grid interaction patterns fit into this structure:
```python ```python
import mcrfpy import mcrfpy
# Scene setup # Scene setup
scene = mcrfpy.Scene("game") scene = mcrfpy.Scene("game")
ui = scene.children ui = scene.children
# Load tileset texture # Load tileset texture
texture = mcrfpy.Texture("assets/sprites/tileset.png", 16, 16) texture = mcrfpy.Texture("assets/sprites/tileset.png", 16, 16)
# Create layers # Create layers
terrain = mcrfpy.TileLayer(name="terrain", z_index=-2, texture=texture) terrain = mcrfpy.TileLayer(name="terrain", z_index=-2, texture=texture)
highlight = mcrfpy.ColorLayer(name="highlight", z_index=-1) highlight = mcrfpy.ColorLayer(name="highlight", z_index=-1)
overlay = mcrfpy.ColorLayer(name="overlay", z_index=1) overlay = mcrfpy.ColorLayer(name="overlay", z_index=1)
# Create grid with layers # Create grid with layers
grid = mcrfpy.Grid( grid = mcrfpy.Grid(
grid_size=(20, 15), grid_size=(20, 15),
pos=(50, 50), pos=(50, 50),
size=(640, 480), size=(640, 480),
layers=[terrain, highlight, overlay] layers=[terrain, highlight, overlay]
) )
grid.fill_color = mcrfpy.Color(20, 20, 30) grid.fill_color = mcrfpy.Color(20, 20, 30)
ui.append(grid) ui.append(grid)
# Create player entity # Create player entity
player = mcrfpy.Entity(grid_pos=(10, 7), sprite_index=0) player = mcrfpy.Entity(grid_pos=(10, 7), sprite_index=0)
grid.entities.append(player) grid.entities.append(player)
# Wire up events (patterns below fill these in) # Wire up events (patterns below fill these in)
# grid.on_cell_click = ... # grid.on_cell_click = ...
# grid.on_cell_enter = ... # grid.on_cell_enter = ...
# grid.on_cell_exit = ... # grid.on_cell_exit = ...
mcrfpy.current_scene = scene mcrfpy.current_scene = scene
``` ```
--- ---
## Cell Hover Highlighting ## Cell Hover Highlighting
Show which cell the mouse is over using a ColorLayer. Show which cell the mouse is over using a ColorLayer.
```python ```python
current_highlight = [None] current_highlight = [None]
def on_cell_enter(cell_pos): def on_cell_enter(cell_pos):
x, y = int(cell_pos.x), int(cell_pos.y) x, y = int(cell_pos.x), int(cell_pos.y)
highlight.set((x, y), mcrfpy.Color(255, 255, 255, 40)) highlight.set((x, y), mcrfpy.Color(255, 255, 255, 40))
current_highlight[0] = (x, y) current_highlight[0] = (x, y)
def on_cell_exit(cell_pos): def on_cell_exit(cell_pos):
x, y = int(cell_pos.x), int(cell_pos.y) x, y = int(cell_pos.x), int(cell_pos.y)
highlight.set((x, y), mcrfpy.Color(0, 0, 0, 0)) highlight.set((x, y), mcrfpy.Color(0, 0, 0, 0))
current_highlight[0] = None current_highlight[0] = None
grid.on_cell_enter = on_cell_enter grid.on_cell_enter = on_cell_enter
grid.on_cell_exit = on_cell_exit grid.on_cell_exit = on_cell_exit
``` ```
--- ---
## Cell Click Actions ## Cell Click Actions
Respond to clicks on specific cells. Callbacks receive `(cell_pos, button, action)`. Respond to clicks on specific cells. Callbacks receive `(cell_pos, button, action)`.
```python ```python
def on_cell_click(cell_pos, button, action): def on_cell_click(cell_pos, button, action):
x, y = int(cell_pos.x), int(cell_pos.y) x, y = int(cell_pos.x), int(cell_pos.y)
point = grid.at(x, y) point = grid.at(x, y)
if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.PRESSED: if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.PRESSED:
if point.walkable: if point.walkable:
player.grid_x = x player.grid_x = x
player.grid_y = y player.grid_y = y
elif button == mcrfpy.MouseButton.RIGHT and action == mcrfpy.InputState.PRESSED: elif button == mcrfpy.MouseButton.RIGHT and action == mcrfpy.InputState.PRESSED:
# Inspect cell # Inspect cell
show_cell_info(x, y, point) show_cell_info(x, y, point)
elif button == mcrfpy.MouseButton.MIDDLE and action == mcrfpy.InputState.PRESSED: elif button == mcrfpy.MouseButton.MIDDLE and action == mcrfpy.InputState.PRESSED:
# Toggle walkability (level editor) # Toggle walkability (level editor)
point.walkable = not point.walkable point.walkable = not point.walkable
grid.on_cell_click = on_cell_click grid.on_cell_click = on_cell_click
``` ```
--- ---
## WASD Movement ## WASD Movement
Use `scene.on_key` with Key enums for movement. Use `scene.on_key` with Key enums for movement.
```python ```python
move_map = { move_map = {
mcrfpy.Key.W: (0, -1), mcrfpy.Key.W: (0, -1),
mcrfpy.Key.A: (-1, 0), mcrfpy.Key.A: (-1, 0),
mcrfpy.Key.S: (0, 1), mcrfpy.Key.S: (0, 1),
mcrfpy.Key.D: (1, 0), mcrfpy.Key.D: (1, 0),
} }
def handle_key(key, action): def handle_key(key, action):
if action != mcrfpy.InputState.PRESSED: if action != mcrfpy.InputState.PRESSED:
return return
if key in move_map: if key in move_map:
dx, dy = move_map[key] dx, dy = move_map[key]
new_x = int(player.grid_x + dx) new_x = int(player.grid_x + dx)
new_y = int(player.grid_y + dy) new_y = int(player.grid_y + dy)
point = player.grid.at(new_x, new_y) point = player.grid.at(new_x, new_y)
if point and point.walkable: if point and point.walkable:
player.grid_x = new_x player.grid_x = new_x
player.grid_y = new_y player.grid_y = new_y
scene.on_key = handle_key scene.on_key = handle_key
``` ```
### Smooth Animated Movement ### Smooth Animated Movement
For visual smoothness, animate the entity position: For visual smoothness, animate the entity position:
```python ```python
def handle_key(key, action): def handle_key(key, action):
if action != mcrfpy.InputState.PRESSED: if action != mcrfpy.InputState.PRESSED:
return return
if key in move_map: if key in move_map:
dx, dy = move_map[key] dx, dy = move_map[key]
new_x = int(player.grid_x + dx) new_x = int(player.grid_x + dx)
new_y = int(player.grid_y + dy) new_y = int(player.grid_y + dy)
point = player.grid.at(new_x, new_y) point = player.grid.at(new_x, new_y)
if point and point.walkable: if point and point.walkable:
player.animate("x", float(new_x), 0.15, mcrfpy.Easing.EASE_OUT_QUAD) player.animate("x", float(new_x), 0.15, mcrfpy.Easing.EASE_OUT_QUAD)
player.animate("y", float(new_y), 0.15, mcrfpy.Easing.EASE_OUT_QUAD) player.animate("y", float(new_y), 0.15, mcrfpy.Easing.EASE_OUT_QUAD)
player.grid_x = new_x player.grid_x = new_x
player.grid_y = new_y player.grid_y = new_y
scene.on_key = handle_key scene.on_key = handle_key
``` ```
--- ---
## Entity Selection ## Entity Selection
Click to select entities using a ColorLayer overlay. Click to select entities using a ColorLayer overlay.
```python ```python
selected = [None] selected = [None]
def select_entity(entity): def select_entity(entity):
# Clear previous selection # Clear previous selection
if selected[0]: if selected[0]:
ex, ey = int(selected[0].grid_x), int(selected[0].grid_y) ex, ey = int(selected[0].grid_x), int(selected[0].grid_y)
overlay.set((ex, ey), mcrfpy.Color(0, 0, 0, 0)) overlay.set((ex, ey), mcrfpy.Color(0, 0, 0, 0))
selected[0] = entity selected[0] = entity
if entity: if entity:
overlay.set((int(entity.grid_x), int(entity.grid_y)), overlay.set((int(entity.grid_x), int(entity.grid_y)),
mcrfpy.Color(255, 200, 0, 80)) mcrfpy.Color(255, 200, 0, 80))
def on_cell_click(cell_pos, button, action): def on_cell_click(cell_pos, button, action):
if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.PRESSED: if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.PRESSED:
x, y = int(cell_pos.x), int(cell_pos.y) x, y = int(cell_pos.x), int(cell_pos.y)
for entity in grid.entities: for entity in grid.entities:
if int(entity.grid_x) == x and int(entity.grid_y) == y: if int(entity.grid_x) == x and int(entity.grid_y) == y:
select_entity(entity) select_entity(entity)
return return
select_entity(None) select_entity(None)
grid.on_cell_click = on_cell_click grid.on_cell_click = on_cell_click
``` ```
--- ---
## Path Preview ## Path Preview
Show pathfinding path on hover using a ColorLayer. Show pathfinding path on hover using a ColorLayer.
```python ```python
current_path_cells = [] current_path_cells = []
def show_path_to(target_x, target_y): def show_path_to(target_x, target_y):
global current_path_cells global current_path_cells
# Clear previous path # Clear previous path
for cx, cy in current_path_cells: for cx, cy in current_path_cells:
highlight.set((cx, cy), mcrfpy.Color(0, 0, 0, 0)) highlight.set((cx, cy), mcrfpy.Color(0, 0, 0, 0))
current_path_cells = [] current_path_cells = []
# Calculate path # Calculate path
path = grid.find_path( path = grid.find_path(
(int(player.grid_x), int(player.grid_y)), (int(player.grid_x), int(player.grid_y)),
(target_x, target_y) (target_x, target_y)
) )
if not path: if not path:
return return
# Draw path cells # Draw path cells
i = 0 i = 0
while len(path) > 0: while len(path) > 0:
step = path.walk() step = path.walk()
if step: if step:
x, y = int(step.x), int(step.y) x, y = int(step.x), int(step.y)
alpha = max(30, 100 - (i * 5)) alpha = max(30, 100 - (i * 5))
highlight.set((x, y), mcrfpy.Color(100, 200, 255, alpha)) highlight.set((x, y), mcrfpy.Color(100, 200, 255, alpha))
current_path_cells.append((x, y)) current_path_cells.append((x, y))
i += 1 i += 1
def on_cell_enter(cell_pos): def on_cell_enter(cell_pos):
show_path_to(int(cell_pos.x), int(cell_pos.y)) show_path_to(int(cell_pos.x), int(cell_pos.y))
grid.on_cell_enter = on_cell_enter grid.on_cell_enter = on_cell_enter
``` ```
--- ---
## Tile Inspector Panel ## Tile Inspector Panel
Click a cell to show information in a side panel. Click a cell to show information in a side panel.
```python ```python
# Create inspector UI # Create inspector UI
inspector = mcrfpy.Frame(pos=(700, 50), size=(200, 150), inspector = mcrfpy.Frame(pos=(700, 50), size=(200, 150),
fill_color=mcrfpy.Color(30, 30, 40, 230)) fill_color=mcrfpy.Color(30, 30, 40, 230))
inspector.outline = 1 inspector.outline = 1
inspector.outline_color = mcrfpy.Color(80, 80, 100) inspector.outline_color = mcrfpy.Color(80, 80, 100)
inspector.visible = False inspector.visible = False
ui.append(inspector) ui.append(inspector)
title = mcrfpy.Caption(text="Cell Info", pos=(10, 8)) title = mcrfpy.Caption(text="Cell Info", pos=(10, 8))
title.fill_color = mcrfpy.Color(180, 180, 200) title.fill_color = mcrfpy.Color(180, 180, 200)
inspector.children.append(title) inspector.children.append(title)
info_lines = [] info_lines = []
for i in range(4): for i in range(4):
line = mcrfpy.Caption(text="", pos=(10, 30 + i * 20)) line = mcrfpy.Caption(text="", pos=(10, 30 + i * 20))
line.fill_color = mcrfpy.Color(160, 160, 180) line.fill_color = mcrfpy.Color(160, 160, 180)
inspector.children.append(line) inspector.children.append(line)
info_lines.append(line) info_lines.append(line)
def on_cell_click(cell_pos, button, action): def on_cell_click(cell_pos, button, action):
if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.PRESSED: if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.PRESSED:
x, y = int(cell_pos.x), int(cell_pos.y) x, y = int(cell_pos.x), int(cell_pos.y)
point = grid.at(x, y) point = grid.at(x, y)
title.text = f"Cell ({x}, {y})" title.text = f"Cell ({x}, {y})"
info_lines[0].text = f"Walkable: {point.walkable}" info_lines[0].text = f"Walkable: {point.walkable}"
info_lines[1].text = f"Transparent: {point.transparent}" info_lines[1].text = f"Transparent: {point.transparent}"
entities_here = [e for e in grid.entities entities_here = [e for e in grid.entities
if int(e.grid_x) == x and int(e.grid_y) == y] if int(e.grid_x) == x and int(e.grid_y) == y]
info_lines[2].text = f"Entities: {len(entities_here)}" info_lines[2].text = f"Entities: {len(entities_here)}"
if entities_here: if entities_here:
info_lines[3].text = f" {entities_here[0].name or 'unnamed'}" info_lines[3].text = f" {entities_here[0].name or 'unnamed'}"
else: else:
info_lines[3].text = "" info_lines[3].text = ""
inspector.visible = True inspector.visible = True
grid.on_cell_click = on_cell_click grid.on_cell_click = on_cell_click
``` ```
--- ---
## Related Pages ## Related Pages
- [[Entity-Management]] - Entity behavior and pathfinding - [[Entity-Management]] - Entity behavior and pathfinding
- [[Grid-System]] - Layer management and rendering - [[Grid-System]] - Layer management and rendering
- [[UI-Widget-Patterns]] - Non-grid UI patterns - [[UI-Widget-Patterns]] - Non-grid UI patterns
- [[Input-and-Events]] - Event API reference - [[Input-and-Events]] - Event API reference
--- ---
*Last updated: 2026-02-07* *Last updated: 2026-02-07*