Update Grid Interaction Patterns
parent
f44973d9fd
commit
87921ac87c
1 changed files with 302 additions and 302 deletions
|
|
@ -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*
|
||||||
Loading…
Add table
Add a link
Reference in a new issue