Update Procedural-Generation wiki: modernize API usage (Scene/Timer/Entity/Grid constructors, remove cell.color, use positional grid.at, add visual layers section)
parent
120b4370ed
commit
991b6d95dd
1 changed files with 527 additions and 630 deletions
|
|
@ -14,11 +14,6 @@ McRogueFace supports procedural content generation through its Grid and Entity s
|
|||
- `src/scripts/cos_tiles.py` - Wave Function Collapse tile placement
|
||||
- `src/UIGrid.cpp` - Grid manipulation API
|
||||
|
||||
**Related Issues:**
|
||||
- [#123](../issues/123) - Subgrid system for large worlds
|
||||
- [#67](../issues/67) - Grid stitching for seamless chunks
|
||||
- [#55](../issues/55) - Agent simulation demo
|
||||
|
||||
---
|
||||
|
||||
## Binary Space Partitioning (BSP)
|
||||
|
|
@ -43,7 +38,6 @@ class BinaryRoomNode:
|
|||
|
||||
def split(self, min_size=8):
|
||||
"""Split this room into two sub-rooms"""
|
||||
# Choose split direction based on aspect ratio
|
||||
if self.w > self.h:
|
||||
direction = "vertical"
|
||||
elif self.h > self.w:
|
||||
|
|
@ -51,7 +45,6 @@ class BinaryRoomNode:
|
|||
else:
|
||||
direction = random.choice(["vertical", "horizontal"])
|
||||
|
||||
# Calculate split position (30-70%)
|
||||
split_ratio = random.uniform(0.3, 0.7)
|
||||
|
||||
if direction == "vertical" and self.w >= min_size * 2:
|
||||
|
|
@ -67,7 +60,7 @@ class BinaryRoomNode:
|
|||
self.w, self.h - split_y)
|
||||
return True
|
||||
|
||||
return False # Can't split further
|
||||
return False
|
||||
|
||||
def get_leaves(self):
|
||||
"""Get all leaf rooms (no children)"""
|
||||
|
|
@ -80,12 +73,12 @@ class BinaryRoomNode:
|
|||
leaves.extend(self.right.get_leaves())
|
||||
return leaves
|
||||
|
||||
# Generate dungeon layout
|
||||
|
||||
def generate_bsp_dungeon(grid, num_splits=4):
|
||||
"""Generate dungeon layout using BSP"""
|
||||
w, h = grid.grid_size
|
||||
root = BinaryRoomNode(0, 0, w, h)
|
||||
|
||||
# Split recursively
|
||||
nodes = [root]
|
||||
for _ in range(num_splits):
|
||||
new_nodes = []
|
||||
|
|
@ -96,12 +89,10 @@ def generate_bsp_dungeon(grid, num_splits=4):
|
|||
new_nodes.append(node)
|
||||
nodes = new_nodes
|
||||
|
||||
# Get leaf rooms
|
||||
rooms = root.get_leaves()
|
||||
|
||||
# Carve rooms into grid
|
||||
for room in rooms:
|
||||
# Add margin
|
||||
rx = room.x + 2
|
||||
ry = room.y + 2
|
||||
rw = room.w - 4
|
||||
|
|
@ -109,10 +100,10 @@ def generate_bsp_dungeon(grid, num_splits=4):
|
|||
|
||||
for x in range(rx, rx + rw):
|
||||
for y in range(ry, ry + rh):
|
||||
cell = grid.at((x, y))
|
||||
cell = grid.at(x, y)
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.tilesprite = 0 # Floor tile
|
||||
cell.color = (128, 128, 128, 255)
|
||||
|
||||
return rooms
|
||||
```
|
||||
|
|
@ -126,30 +117,28 @@ After generating rooms, create corridors:
|
|||
```python
|
||||
def carve_corridor(grid, x1, y1, x2, y2):
|
||||
"""L-shaped corridor between two points"""
|
||||
# Horizontal first
|
||||
for x in range(min(x1, x2), max(x1, x2) + 1):
|
||||
cell = grid.at((x, y1))
|
||||
cell = grid.at(x, y1)
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.tilesprite = 0
|
||||
|
||||
# Then vertical
|
||||
for y in range(min(y1, y2), max(y1, y2) + 1):
|
||||
cell = grid.at((x2, y))
|
||||
cell = grid.at(x2, y)
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.tilesprite = 0
|
||||
|
||||
|
||||
def connect_rooms(grid, rooms):
|
||||
"""Connect all rooms with corridors"""
|
||||
for i in range(len(rooms) - 1):
|
||||
r1 = rooms[i]
|
||||
r2 = rooms[i + 1]
|
||||
|
||||
# Room centers
|
||||
x1 = r1.x + r1.w // 2
|
||||
y1 = r1.y + r1.h // 2
|
||||
x2 = r2.x + r2.w // 2
|
||||
y2 = r2.y + r2.h // 2
|
||||
|
||||
carve_corridor(grid, x1, y1, x2, y2)
|
||||
```
|
||||
|
||||
|
|
@ -173,69 +162,35 @@ class TileRule:
|
|||
"west": west or []
|
||||
}
|
||||
|
||||
def can_place(self, grid, x, y):
|
||||
"""Check if tile can be placed at (x, y)"""
|
||||
# Check each neighbor
|
||||
if y > 0: # North
|
||||
north_tile = grid.at((x, y - 1)).tilesprite
|
||||
if north_tile not in self.neighbors["north"]:
|
||||
return False
|
||||
|
||||
if y < grid.grid_size[1] - 1: # South
|
||||
south_tile = grid.at((x, y + 1)).tilesprite
|
||||
if south_tile not in self.neighbors["south"]:
|
||||
return False
|
||||
|
||||
# ... check east/west similarly
|
||||
return True
|
||||
|
||||
# Define tile rules (example: simple dungeon)
|
||||
FLOOR = 0
|
||||
WALL = 1
|
||||
RULES = [
|
||||
TileRule(FLOOR, north=[FLOOR, WALL], south=[FLOOR, WALL],
|
||||
east=[FLOOR, WALL], west=[FLOOR, WALL]),
|
||||
TileRule(WALL, north=[WALL], south=[WALL],
|
||||
east=[WALL], west=[WALL])
|
||||
]
|
||||
|
||||
def wfc_generate(grid):
|
||||
"""Generate tiles using Wave Function Collapse"""
|
||||
w, h = grid.grid_size
|
||||
|
||||
# Track cells with multiple possibilities
|
||||
possibilities = {}
|
||||
for x in range(w):
|
||||
for y in range(h):
|
||||
possibilities[(x, y)] = [0, 1] # Can be floor or wall
|
||||
possibilities[(x, y)] = [0, 1]
|
||||
|
||||
# Iteratively collapse
|
||||
while possibilities:
|
||||
# Find cell with minimum entropy (fewest possibilities)
|
||||
min_cell = min(possibilities.keys(),
|
||||
key=lambda c: len(possibilities[c]))
|
||||
x, y = min_cell
|
||||
|
||||
# Choose random tile from possibilities
|
||||
tile = random.choice(possibilities[min_cell])
|
||||
grid.at((x, y)).tilesprite = tile
|
||||
grid.at(x, y).tilesprite = tile
|
||||
del possibilities[min_cell]
|
||||
|
||||
# Update neighbor possibilities based on constraints
|
||||
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
||||
nx, ny = x + dx, y + dy
|
||||
if (nx, ny) in possibilities:
|
||||
# Filter based on placed tile's constraints
|
||||
# (simplified - real WFC is more complex)
|
||||
possibilities[(nx, ny)] = [t for t in possibilities[(nx, ny)]
|
||||
if can_coexist(tile, t)]
|
||||
possibilities[(nx, ny)] = [
|
||||
t for t in possibilities[(nx, ny)]
|
||||
if can_coexist(tile, t)
|
||||
]
|
||||
```
|
||||
|
||||
See `src/scripts/cos_tiles.py` for the complete WFC implementation with:
|
||||
- 9-cell constraint matching
|
||||
- Special cardinal direction rules
|
||||
- Weighted tile selection
|
||||
- Multi-pass convergence
|
||||
See `src/scripts/cos_tiles.py` for the complete WFC implementation with 9-cell constraint matching and weighted tile selection.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -255,19 +210,21 @@ def generate_cave(grid, fill_probability=0.45, iterations=5):
|
|||
# Initialize with random noise
|
||||
for x in range(w):
|
||||
for y in range(h):
|
||||
cell = grid.at(x, y)
|
||||
if random.random() < fill_probability:
|
||||
grid.at((x, y)).walkable = False
|
||||
grid.at((x, y)).tilesprite = 1 # Wall
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.tilesprite = 1 # Wall
|
||||
else:
|
||||
grid.at((x, y)).walkable = True
|
||||
grid.at((x, y)).tilesprite = 0 # Floor
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.tilesprite = 0 # Floor
|
||||
|
||||
# Iterate cellular automata rules
|
||||
for _ in range(iterations):
|
||||
next_state = []
|
||||
for x in range(w):
|
||||
for y in range(h):
|
||||
# Count wall neighbors
|
||||
wall_count = 0
|
||||
for dx in [-1, 0, 1]:
|
||||
for dy in [-1, 0, 1]:
|
||||
|
|
@ -275,21 +232,21 @@ def generate_cave(grid, fill_probability=0.45, iterations=5):
|
|||
continue
|
||||
nx, ny = x + dx, y + dy
|
||||
if nx < 0 or nx >= w or ny < 0 or ny >= h:
|
||||
wall_count += 1 # Out of bounds = wall
|
||||
elif not grid.at((nx, ny)).walkable:
|
||||
wall_count += 1
|
||||
elif not grid.at(nx, ny).walkable:
|
||||
wall_count += 1
|
||||
|
||||
# Apply rule: become wall if 5+ neighbors are walls
|
||||
is_wall = wall_count >= 5
|
||||
next_state.append((x, y, is_wall))
|
||||
|
||||
# Apply next state
|
||||
for x, y, is_wall in next_state:
|
||||
grid.at((x, y)).walkable = not is_wall
|
||||
grid.at((x, y)).tilesprite = 1 if is_wall else 0
|
||||
cell = grid.at(x, y)
|
||||
cell.walkable = not is_wall
|
||||
cell.transparent = not is_wall
|
||||
cell.tilesprite = 1 if is_wall else 0
|
||||
```
|
||||
|
||||
### Smoothing and Refinement
|
||||
### Region Cleanup
|
||||
|
||||
```python
|
||||
def remove_small_regions(grid, min_size=10):
|
||||
|
|
@ -298,7 +255,6 @@ def remove_small_regions(grid, min_size=10):
|
|||
visited = set()
|
||||
|
||||
def flood_fill(x, y):
|
||||
"""Returns size of connected region"""
|
||||
stack = [(x, y)]
|
||||
region = []
|
||||
while stack:
|
||||
|
|
@ -307,79 +263,60 @@ def remove_small_regions(grid, min_size=10):
|
|||
continue
|
||||
if cx < 0 or cx >= w or cy < 0 or cy >= h:
|
||||
continue
|
||||
if not grid.at((cx, cy)).walkable:
|
||||
if not grid.at(cx, cy).walkable:
|
||||
continue
|
||||
|
||||
visited.add((cx, cy))
|
||||
region.append((cx, cy))
|
||||
|
||||
# Add neighbors
|
||||
stack.extend([(cx-1, cy), (cx+1, cy), (cx, cy-1), (cx, cy+1)])
|
||||
|
||||
return region
|
||||
|
||||
# Find all regions
|
||||
regions = []
|
||||
for x in range(w):
|
||||
for y in range(h):
|
||||
if (x, y) not in visited and grid.at((x, y)).walkable:
|
||||
if (x, y) not in visited and grid.at(x, y).walkable:
|
||||
region = flood_fill(x, y)
|
||||
regions.append(region)
|
||||
|
||||
# Keep only largest region, fill others
|
||||
if regions:
|
||||
largest = max(regions, key=len)
|
||||
for region in regions:
|
||||
if len(region) < min_size:
|
||||
if region is not largest and len(region) < min_size:
|
||||
for x, y in region:
|
||||
grid.at((x, y)).walkable = False
|
||||
grid.at((x, y)).tilesprite = 1
|
||||
cell = grid.at(x, y)
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.tilesprite = 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Noise-Based Terrain
|
||||
## Visual Layers for Generated Content
|
||||
|
||||
### Perlin/Simplex Noise
|
||||
|
||||
Generate heightmaps for terrain:
|
||||
GridPoint properties (`walkable`, `transparent`, `tilesprite`) handle world state. For visual styling, use layers:
|
||||
|
||||
```python
|
||||
# Requires noise library: pip install noise
|
||||
import noise
|
||||
# Create grid with explicit layers
|
||||
grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600), layers=[])
|
||||
|
||||
def generate_terrain(grid, scale=0.1, octaves=4):
|
||||
"""Generate terrain using Perlin noise"""
|
||||
w, h = grid.grid_size
|
||||
# Background colors layer
|
||||
bg = mcrfpy.ColorLayer(name="background", z_index=-2)
|
||||
grid.add_layer(bg)
|
||||
bg.fill(mcrfpy.Color(20, 20, 30, 255)) # Dark blue
|
||||
|
||||
for x in range(w):
|
||||
for y in range(h):
|
||||
# Sample noise
|
||||
value = noise.pnoise2(x * scale, y * scale,
|
||||
octaves=octaves,
|
||||
persistence=0.5,
|
||||
lacunarity=2.0,
|
||||
repeatx=w,
|
||||
repeaty=h,
|
||||
base=0)
|
||||
# Tile sprites layer (requires a texture)
|
||||
# tiles = mcrfpy.TileLayer(name="terrain", z_index=-1, texture=tileset)
|
||||
# grid.add_layer(tiles)
|
||||
|
||||
# Map to tile types (-1 to 1 -> 0 to N)
|
||||
if value < -0.3:
|
||||
tile = 2 # Water
|
||||
walkable = False
|
||||
elif value < 0.0:
|
||||
tile = 0 # Grass
|
||||
walkable = True
|
||||
elif value < 0.3:
|
||||
tile = 1 # Forest
|
||||
walkable = True
|
||||
else:
|
||||
tile = 3 # Mountain
|
||||
walkable = False
|
||||
# Fog overlay (above entities)
|
||||
fog = mcrfpy.ColorLayer(name="fog", z_index=1)
|
||||
grid.add_layer(fog)
|
||||
fog.fill(mcrfpy.Color(0, 0, 0, 255)) # Start fully hidden
|
||||
|
||||
cell = grid.at((x, y))
|
||||
cell.tilesprite = tile
|
||||
cell.walkable = walkable
|
||||
# After generating rooms, color the background
|
||||
for room in rooms:
|
||||
for x in range(room.x + 2, room.x + room.w - 2):
|
||||
for y in range(room.y + 2, room.y + room.h - 2):
|
||||
bg.set((x, y), mcrfpy.Color(128, 128, 128, 255))
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -398,30 +335,28 @@ def place_entities(grid, rooms, entity_density=0.05):
|
|||
num_entities = int(room_area * entity_density)
|
||||
|
||||
for _ in range(num_entities):
|
||||
# Random walkable position in room
|
||||
x = random.randint(room.x, room.x + room.w - 1)
|
||||
y = random.randint(room.y, room.y + room.h - 1)
|
||||
x = random.randint(room.x + 2, room.x + room.w - 3)
|
||||
y = random.randint(room.y + 2, room.y + room.h - 3)
|
||||
|
||||
if grid.at((x, y)).walkable:
|
||||
if grid.at(x, y).walkable:
|
||||
enemy = mcrfpy.Entity(
|
||||
grid_pos=(x, y),
|
||||
sprite_index=random.randint(10, 15)
|
||||
)
|
||||
grid.entities.append(enemy)
|
||||
|
||||
|
||||
def place_items(grid, rooms, num_items=10):
|
||||
"""Place items in random locations"""
|
||||
placed = 0
|
||||
attempts = 0
|
||||
max_attempts = num_items * 10
|
||||
|
||||
while placed < num_items and attempts < max_attempts:
|
||||
# Random room
|
||||
while placed < num_items and attempts < num_items * 10:
|
||||
room = random.choice(rooms)
|
||||
x = random.randint(room.x, room.x + room.w - 1)
|
||||
y = random.randint(room.y, room.y + room.h - 1)
|
||||
x = random.randint(room.x + 2, room.x + room.w - 3)
|
||||
y = random.randint(room.y + 2, room.y + room.h - 3)
|
||||
|
||||
if grid.at((x, y)).walkable:
|
||||
if grid.at(x, y).walkable:
|
||||
item = mcrfpy.Entity(
|
||||
grid_pos=(x, y),
|
||||
sprite_index=random.randint(20, 25)
|
||||
|
|
@ -443,114 +378,76 @@ For worlds larger than memory allows:
|
|||
```python
|
||||
class ChunkManager:
|
||||
"""Generate chunks on-demand"""
|
||||
def __init__(self, chunk_size=256):
|
||||
def __init__(self, chunk_size=64):
|
||||
self.chunk_size = chunk_size
|
||||
self.loaded_chunks = {} # (cx, cy) -> Grid
|
||||
self.loaded_chunks = {}
|
||||
|
||||
def get_chunk(self, chunk_x, chunk_y):
|
||||
"""Load or generate chunk"""
|
||||
key = (chunk_x, chunk_y)
|
||||
if key not in self.loaded_chunks:
|
||||
self.loaded_chunks[key] = self._generate_chunk(chunk_x, chunk_y)
|
||||
return self.loaded_chunks[key]
|
||||
|
||||
def _generate_chunk(self, cx, cy):
|
||||
"""Generate a single chunk"""
|
||||
grid = mcrfpy.Grid(
|
||||
grid_size=(self.chunk_size, self.chunk_size),
|
||||
pos=(cx * self.chunk_size * 16, cy * self.chunk_size * 16),
|
||||
size=(self.chunk_size * 16, self.chunk_size * 16)
|
||||
)
|
||||
|
||||
# Use chunk coordinates as seed for deterministic generation
|
||||
seed = hash((cx, cy))
|
||||
random.seed(seed)
|
||||
|
||||
# Generate terrain for this chunk
|
||||
generate_terrain(grid, scale=0.1)
|
||||
generate_cave(grid)
|
||||
|
||||
return grid
|
||||
|
||||
def unload_distant_chunks(self, player_chunk, radius=2):
|
||||
"""Unload chunks far from player"""
|
||||
px, py = player_chunk
|
||||
to_unload = []
|
||||
|
||||
for (cx, cy), chunk in self.loaded_chunks.items():
|
||||
if abs(cx - px) > radius or abs(cy - py) > radius:
|
||||
to_unload.append((cx, cy))
|
||||
|
||||
to_unload = [
|
||||
key for key in self.loaded_chunks
|
||||
if abs(key[0] - px) > radius or abs(key[1] - py) > radius
|
||||
]
|
||||
for key in to_unload:
|
||||
del self.loaded_chunks[key]
|
||||
```
|
||||
|
||||
**Issue #123** and **Issue #67** track official subgrid and chunk stitching support.
|
||||
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
## Incremental Generation with Timers
|
||||
|
||||
### Batch Grid Updates
|
||||
|
||||
Set many cells efficiently:
|
||||
Generate during scene transitions to avoid frame drops:
|
||||
|
||||
```python
|
||||
# SLOW: Individual cell updates
|
||||
for x in range(100):
|
||||
for y in range(100):
|
||||
cell = grid.at((x, y))
|
||||
cell.tilesprite = compute_tile(x, y)
|
||||
generation_step = [0]
|
||||
rooms = [None]
|
||||
|
||||
# FASTER: Minimize Python/C++ crossings
|
||||
tiles = []
|
||||
for x in range(100):
|
||||
for y in range(100):
|
||||
tiles.append(compute_tile(x, y))
|
||||
def start_generation(grid):
|
||||
"""Begin incremental procedural generation"""
|
||||
scene = mcrfpy.Scene("loading")
|
||||
mcrfpy.current_scene = scene
|
||||
|
||||
# Then set in batch (when batch API available - see [[Performance-and-Profiling]])
|
||||
for i, (x, y) in enumerate(coords):
|
||||
grid.at((x, y)).tilesprite = tiles[i]
|
||||
```
|
||||
|
||||
### Generation Timing
|
||||
|
||||
Generate during scene transitions or in background:
|
||||
|
||||
```python
|
||||
def start_generation():
|
||||
"""Begin procedural generation"""
|
||||
mcrfpy.createScene("loading")
|
||||
mcrfpy.setScene("loading")
|
||||
|
||||
# Start generation timer
|
||||
mcrfpy.setTimer("generate", generate_world, 10)
|
||||
|
||||
def generate_world(ms):
|
||||
"""Generate incrementally"""
|
||||
global generation_step
|
||||
|
||||
if generation_step == 0:
|
||||
rooms = generate_bsp_dungeon(grid)
|
||||
elif generation_step == 1:
|
||||
connect_rooms(grid, rooms)
|
||||
elif generation_step == 2:
|
||||
wfc_generate_tiles(grid)
|
||||
elif generation_step == 3:
|
||||
place_entities(grid)
|
||||
def generate_world(timer, runtime):
|
||||
if generation_step[0] == 0:
|
||||
rooms[0] = generate_bsp_dungeon(grid)
|
||||
elif generation_step[0] == 1:
|
||||
connect_rooms(grid, rooms[0])
|
||||
elif generation_step[0] == 2:
|
||||
place_entities(grid, rooms[0])
|
||||
else:
|
||||
# Done!
|
||||
mcrfpy.setScene("game")
|
||||
mcrfpy.delTimer("generate")
|
||||
timer.stop()
|
||||
game_scene = mcrfpy.Scene("game")
|
||||
game_scene.children.append(grid)
|
||||
mcrfpy.current_scene = game_scene
|
||||
return
|
||||
|
||||
generation_step += 1
|
||||
generation_step[0] += 1
|
||||
|
||||
mcrfpy.Timer("gen_step", generate_world, 10)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples and Demos
|
||||
|
||||
### Complete Dungeon Generator
|
||||
## Complete Dungeon Generator
|
||||
|
||||
```python
|
||||
import mcrfpy
|
||||
|
|
@ -558,7 +455,6 @@ import random
|
|||
|
||||
def create_procedural_dungeon():
|
||||
"""Complete dungeon generation pipeline"""
|
||||
# Create grid
|
||||
grid = mcrfpy.Grid(grid_size=(100, 100), pos=(0, 0), size=(1024, 768))
|
||||
|
||||
# 1. Generate layout with BSP
|
||||
|
|
@ -567,13 +463,7 @@ def create_procedural_dungeon():
|
|||
# 2. Connect rooms
|
||||
connect_rooms(grid, rooms)
|
||||
|
||||
# 3. Tile placement with WFC
|
||||
wfc_generate_tiles(grid)
|
||||
|
||||
# 4. Smooth with cellular automata
|
||||
smooth_edges(grid)
|
||||
|
||||
# 5. Place player in first room
|
||||
# 3. Place player in first room
|
||||
first_room = rooms[0]
|
||||
player = mcrfpy.Entity(
|
||||
grid_pos=(first_room.x + first_room.w // 2,
|
||||
|
|
@ -582,10 +472,10 @@ def create_procedural_dungeon():
|
|||
)
|
||||
grid.entities.append(player)
|
||||
|
||||
# 6. Place enemies
|
||||
# 4. Place enemies
|
||||
place_entities(grid, rooms[1:], entity_density=0.05)
|
||||
|
||||
# 7. Place exit in last room
|
||||
# 5. Place exit in last room
|
||||
last_room = rooms[-1]
|
||||
exit_entity = mcrfpy.Entity(
|
||||
grid_pos=(last_room.x + last_room.w // 2,
|
||||
|
|
@ -596,31 +486,38 @@ def create_procedural_dungeon():
|
|||
|
||||
return grid, player
|
||||
|
||||
|
||||
# Use it
|
||||
mcrfpy.createScene("game")
|
||||
mcrfpy.setScene("game")
|
||||
game_scene = mcrfpy.Scene("game")
|
||||
grid, player = create_procedural_dungeon()
|
||||
ui = mcrfpy.sceneUI("game")
|
||||
ui.append(grid)
|
||||
game_scene.children.append(grid)
|
||||
mcrfpy.current_scene = game_scene
|
||||
```
|
||||
|
||||
See `src/scripts/cos_level.py` and `src/scripts/cos_tiles.py` for production examples from "Crypt of Sokoban."
|
||||
See `src/scripts/cos_level.py` and `src/scripts/cos_tiles.py` for production examples.
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
See [`docs/api_reference_dynamic.html`](../src/branch/master/docs/api_reference_dynamic.html) for Grid and Entity APIs.
|
||||
**Grid Construction:**
|
||||
- `mcrfpy.Grid(grid_size=(w, h), pos=(x, y), size=(pw, ph))` - Create grid
|
||||
|
||||
**Key Grid Methods:**
|
||||
- `grid.at((x, y))` - Get cell for modification
|
||||
- `grid.grid_size` - Get dimensions
|
||||
- `grid.entities` - Entity collection
|
||||
**Cell Properties (GridPoint):**
|
||||
- `cell.walkable` - Pathfinding flag (bool)
|
||||
- `cell.transparent` - FOV flag (bool)
|
||||
- `cell.tilesprite` - Tile sprite index (int, legacy)
|
||||
|
||||
**Key Cell Properties:**
|
||||
- `cell.tilesprite` - Tile index
|
||||
- `cell.walkable` - Pathfinding flag
|
||||
- `cell.color` - RGBA tint
|
||||
**Visual Layers:**
|
||||
- `mcrfpy.ColorLayer(name="...", z_index=N)` - Color overlay layer
|
||||
- `mcrfpy.TileLayer(name="...", z_index=N, texture=tex)` - Tile sprite layer
|
||||
- `grid.add_layer(layer)` - Attach layer to grid
|
||||
- `layer.set((x, y), value)` - Set cell value
|
||||
- `layer.fill(value)` - Fill all cells
|
||||
|
||||
**Entity Placement:**
|
||||
- `mcrfpy.Entity(grid_pos=(x, y), sprite_index=N)` - Create entity
|
||||
- `grid.entities.append(entity)` - Add to grid
|
||||
|
||||
---
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue