392 lines
13 KiB
Python
392 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Path & Vision Sizzle Reel
|
|
=========================
|
|
|
|
A choreographed demo showing:
|
|
- Smooth entity movement along paths
|
|
- Camera following with grid center animation
|
|
- Field of view updates as entities move
|
|
- Dramatic perspective transitions with zoom effects
|
|
"""
|
|
|
|
import mcrfpy
|
|
import sys
|
|
|
|
# Colors
|
|
WALL_COLOR = mcrfpy.Color(40, 30, 30)
|
|
FLOOR_COLOR = mcrfpy.Color(80, 80, 100)
|
|
PATH_COLOR = mcrfpy.Color(120, 120, 180)
|
|
DARK_FLOOR = mcrfpy.Color(40, 40, 50)
|
|
|
|
# Global state
|
|
grid = None
|
|
player = None
|
|
enemy = None
|
|
sequence_step = 0
|
|
player_path = []
|
|
enemy_path = []
|
|
player_path_index = 0
|
|
enemy_path_index = 0
|
|
|
|
def create_scene():
|
|
"""Create the demo environment"""
|
|
global grid, player, enemy
|
|
|
|
mcrfpy.createScene("path_vision_demo")
|
|
|
|
# Create larger grid for more dramatic movement
|
|
grid = mcrfpy.Grid(grid_x=40, grid_y=25)
|
|
grid.fill_color = mcrfpy.Color(20, 20, 30)
|
|
|
|
# Map layout - interconnected rooms with corridors
|
|
map_layout = [
|
|
"########################################", # 0
|
|
"#......##########......################", # 1
|
|
"#......##########......################", # 2
|
|
"#......##########......################", # 3
|
|
"#......#.........#.....################", # 4
|
|
"#......#.........#.....################", # 5
|
|
"####.###.........####.#################", # 6
|
|
"####.....................##############", # 7
|
|
"####.....................##############", # 8
|
|
"####.###.........####.#################", # 9
|
|
"#......#.........#.....################", # 10
|
|
"#......#.........#.....################", # 11
|
|
"#......#.........#.....################", # 12
|
|
"#......###.....###.....################", # 13
|
|
"#......###.....###.....################", # 14
|
|
"#......###.....###.....#########......#", # 15
|
|
"#......###.....###.....#########......#", # 16
|
|
"#......###.....###.....#########......#", # 17
|
|
"#####.############.#############......#", # 18
|
|
"#####...........................#.....#", # 19
|
|
"#####...........................#.....#", # 20
|
|
"#####.############.#############......#", # 21
|
|
"#......###########.##########.........#", # 22
|
|
"#......###########.##########.........#", # 23
|
|
"########################################", # 24
|
|
]
|
|
|
|
# Build the map
|
|
for y, row in enumerate(map_layout):
|
|
for x, char in enumerate(row):
|
|
cell = grid.at(x, y)
|
|
if char == '#':
|
|
cell.walkable = False
|
|
cell.transparent = False
|
|
cell.color = WALL_COLOR
|
|
else:
|
|
cell.walkable = True
|
|
cell.transparent = True
|
|
cell.color = FLOOR_COLOR
|
|
|
|
# Create player in top-left room
|
|
player = mcrfpy.Entity(3, 3, grid=grid)
|
|
player.sprite_index = 64 # @
|
|
|
|
# Create enemy in bottom-right area
|
|
enemy = mcrfpy.Entity(35, 20, grid=grid)
|
|
enemy.sprite_index = 69 # E
|
|
|
|
# Initial visibility
|
|
player.update_visibility()
|
|
enemy.update_visibility()
|
|
|
|
# Set initial perspective to player
|
|
grid.perspective = 0
|
|
|
|
def setup_paths():
|
|
"""Define the paths for entities"""
|
|
global player_path, enemy_path
|
|
|
|
# Player path: Top-left room → corridor → middle room
|
|
player_waypoints = [
|
|
(3, 3), # Start
|
|
(3, 8), # Move down
|
|
(7, 8), # Enter corridor
|
|
(16, 8), # Through corridor
|
|
(16, 12), # Enter middle room
|
|
(12, 12), # Move in room
|
|
(12, 16), # Move down
|
|
(16, 16), # Move right
|
|
(16, 19), # Exit room
|
|
(25, 19), # Move right
|
|
(30, 19), # Continue
|
|
(35, 19), # Near enemy start
|
|
]
|
|
|
|
# Enemy path: Bottom-right → around → approach player area
|
|
enemy_waypoints = [
|
|
(35, 20), # Start
|
|
(30, 20), # Move left
|
|
(25, 20), # Continue
|
|
(20, 20), # Continue
|
|
(16, 20), # Corridor junction
|
|
(16, 16), # Move up (might see player)
|
|
(16, 12), # Continue up
|
|
(16, 8), # Top corridor
|
|
(10, 8), # Move left
|
|
(7, 8), # Continue
|
|
(3, 8), # Player's area
|
|
(3, 12), # Move down
|
|
]
|
|
|
|
# Calculate full paths using pathfinding
|
|
player_path = []
|
|
for i in range(len(player_waypoints) - 1):
|
|
x1, y1 = player_waypoints[i]
|
|
x2, y2 = player_waypoints[i + 1]
|
|
|
|
# Use grid's A* pathfinding
|
|
segment = grid.compute_astar_path(x1, y1, x2, y2)
|
|
if segment:
|
|
# Add segment (avoiding duplicates)
|
|
if not player_path or segment[0] != player_path[-1]:
|
|
player_path.extend(segment)
|
|
else:
|
|
player_path.extend(segment[1:])
|
|
|
|
enemy_path = []
|
|
for i in range(len(enemy_waypoints) - 1):
|
|
x1, y1 = enemy_waypoints[i]
|
|
x2, y2 = enemy_waypoints[i + 1]
|
|
|
|
segment = grid.compute_astar_path(x1, y1, x2, y2)
|
|
if segment:
|
|
if not enemy_path or segment[0] != enemy_path[-1]:
|
|
enemy_path.extend(segment)
|
|
else:
|
|
enemy_path.extend(segment[1:])
|
|
|
|
print(f"Player path: {len(player_path)} steps")
|
|
print(f"Enemy path: {len(enemy_path)} steps")
|
|
|
|
def setup_ui():
|
|
"""Create UI elements"""
|
|
ui = mcrfpy.sceneUI("path_vision_demo")
|
|
ui.append(grid)
|
|
|
|
# Position and size grid
|
|
grid.position = (50, 80)
|
|
grid.size = (700, 500) # Adjust based on zoom
|
|
|
|
# Title
|
|
title = mcrfpy.Caption("Path & Vision Sizzle Reel", 300, 20)
|
|
title.fill_color = mcrfpy.Color(255, 255, 255)
|
|
ui.append(title)
|
|
|
|
# Status
|
|
global status_text, perspective_text
|
|
status_text = mcrfpy.Caption("Starting demo...", 50, 50)
|
|
status_text.fill_color = mcrfpy.Color(200, 200, 200)
|
|
ui.append(status_text)
|
|
|
|
perspective_text = mcrfpy.Caption("Perspective: Player", 550, 50)
|
|
perspective_text.fill_color = mcrfpy.Color(100, 255, 100)
|
|
ui.append(perspective_text)
|
|
|
|
# Controls
|
|
controls = mcrfpy.Caption("Space: Pause/Resume | R: Restart | Q: Quit", 250, 600)
|
|
controls.fill_color = mcrfpy.Color(150, 150, 150)
|
|
ui.append(controls)
|
|
|
|
# Animation control
|
|
paused = False
|
|
move_timer = 0
|
|
zoom_transition = False
|
|
|
|
def move_entity_smooth(entity, target_x, target_y, duration=0.3):
|
|
"""Smoothly animate entity to position"""
|
|
# Create position animation
|
|
anim_x = mcrfpy.Animation("x", float(target_x), duration, "easeInOut")
|
|
anim_y = mcrfpy.Animation("y", float(target_y), duration, "easeInOut")
|
|
|
|
anim_x.start(entity)
|
|
anim_y.start(entity)
|
|
|
|
def update_camera_smooth(center_x, center_y, duration=0.3):
|
|
"""Smoothly move camera center"""
|
|
# Convert grid coords to pixel coords (assuming 16x16 tiles)
|
|
pixel_x = center_x * 16
|
|
pixel_y = center_y * 16
|
|
|
|
anim = mcrfpy.Animation("center", (pixel_x, pixel_y), duration, "easeOut")
|
|
anim.start(grid)
|
|
|
|
def start_perspective_transition():
|
|
"""Begin the dramatic perspective shift"""
|
|
global zoom_transition, sequence_step
|
|
zoom_transition = True
|
|
sequence_step = 100 # Special sequence number
|
|
|
|
status_text.text = "Perspective shift: Zooming out..."
|
|
|
|
# Zoom out with elastic easing
|
|
zoom_out = mcrfpy.Animation("zoom", 0.5, 2.0, "easeInExpo")
|
|
zoom_out.start(grid)
|
|
|
|
# Schedule the perspective switch
|
|
mcrfpy.setTimer("switch_perspective", switch_perspective, 2100)
|
|
|
|
def switch_perspective(dt):
|
|
"""Switch perspective at the peak of zoom"""
|
|
global sequence_step
|
|
|
|
# Switch to enemy perspective
|
|
grid.perspective = 1
|
|
perspective_text.text = "Perspective: Enemy"
|
|
perspective_text.fill_color = mcrfpy.Color(255, 100, 100)
|
|
|
|
status_text.text = "Perspective shift: Following enemy..."
|
|
|
|
# Update camera to enemy position
|
|
update_camera_smooth(enemy.x, enemy.y, 0.1)
|
|
|
|
# Zoom back in
|
|
zoom_in = mcrfpy.Animation("zoom", 1.2, 2.0, "easeOutExpo")
|
|
zoom_in.start(grid)
|
|
|
|
# Resume sequence
|
|
mcrfpy.setTimer("resume_enemy", resume_enemy_sequence, 2100)
|
|
|
|
# Cancel this timer
|
|
mcrfpy.delTimer("switch_perspective")
|
|
|
|
def resume_enemy_sequence(dt):
|
|
"""Resume following enemy after perspective shift"""
|
|
global sequence_step, zoom_transition
|
|
zoom_transition = False
|
|
sequence_step = 101 # Continue with enemy movement
|
|
mcrfpy.delTimer("resume_enemy")
|
|
|
|
def sequence_tick(dt):
|
|
"""Main sequence controller"""
|
|
global sequence_step, player_path_index, enemy_path_index, move_timer
|
|
|
|
if paused or zoom_transition:
|
|
return
|
|
|
|
move_timer += dt
|
|
if move_timer < 400: # Move every 400ms
|
|
return
|
|
move_timer = 0
|
|
|
|
if sequence_step < 50:
|
|
# Phase 1: Follow player movement
|
|
if player_path_index < len(player_path):
|
|
x, y = player_path[player_path_index]
|
|
move_entity_smooth(player, x, y)
|
|
player.update_visibility()
|
|
|
|
# Camera follows player
|
|
if grid.perspective == 0:
|
|
update_camera_smooth(player.x, player.y)
|
|
|
|
player_path_index += 1
|
|
status_text.text = f"Player moving... Step {player_path_index}/{len(player_path)}"
|
|
|
|
# Start enemy movement after player has moved a bit
|
|
if player_path_index == 10:
|
|
sequence_step = 1 # Enable enemy movement
|
|
else:
|
|
# Player reached destination, start perspective transition
|
|
start_perspective_transition()
|
|
|
|
if sequence_step >= 1 and sequence_step < 50:
|
|
# Phase 2: Enemy movement (concurrent with player)
|
|
if enemy_path_index < len(enemy_path):
|
|
x, y = enemy_path[enemy_path_index]
|
|
move_entity_smooth(enemy, x, y)
|
|
enemy.update_visibility()
|
|
|
|
# Check if enemy is visible to player
|
|
if grid.perspective == 0:
|
|
enemy_cell_idx = int(enemy.y) * grid.grid_x + int(enemy.x)
|
|
if enemy_cell_idx < len(player.gridstate) and player.gridstate[enemy_cell_idx].visible:
|
|
status_text.text = "Enemy spotted!"
|
|
|
|
enemy_path_index += 1
|
|
|
|
elif sequence_step == 101:
|
|
# Phase 3: Continue following enemy after perspective shift
|
|
if enemy_path_index < len(enemy_path):
|
|
x, y = enemy_path[enemy_path_index]
|
|
move_entity_smooth(enemy, x, y)
|
|
enemy.update_visibility()
|
|
|
|
# Camera follows enemy
|
|
update_camera_smooth(enemy.x, enemy.y)
|
|
|
|
enemy_path_index += 1
|
|
status_text.text = f"Following enemy... Step {enemy_path_index}/{len(enemy_path)}"
|
|
else:
|
|
status_text.text = "Demo complete! Press R to restart"
|
|
sequence_step = 200 # Done
|
|
|
|
def handle_keys(key, state):
|
|
"""Handle keyboard input"""
|
|
global paused, sequence_step, player_path_index, enemy_path_index, move_timer
|
|
key = key.lower()
|
|
if state != "start":
|
|
return
|
|
|
|
if key == "q":
|
|
print("Exiting sizzle reel...")
|
|
sys.exit(0)
|
|
elif key == "space":
|
|
paused = not paused
|
|
status_text.text = "PAUSED" if paused else "Running..."
|
|
elif key == "r":
|
|
# Reset everything
|
|
player.x, player.y = 3, 3
|
|
enemy.x, enemy.y = 35, 20
|
|
player.update_visibility()
|
|
enemy.update_visibility()
|
|
grid.perspective = 0
|
|
perspective_text.text = "Perspective: Player"
|
|
perspective_text.fill_color = mcrfpy.Color(100, 255, 100)
|
|
sequence_step = 0
|
|
player_path_index = 0
|
|
enemy_path_index = 0
|
|
move_timer = 0
|
|
update_camera_smooth(player.x, player.y, 0.5)
|
|
|
|
# Reset zoom
|
|
zoom_reset = mcrfpy.Animation("zoom", 1.2, 0.5, "easeOut")
|
|
zoom_reset.start(grid)
|
|
|
|
status_text.text = "Demo restarted!"
|
|
|
|
# Initialize everything
|
|
print("Path & Vision Sizzle Reel")
|
|
print("=========================")
|
|
print("Demonstrating:")
|
|
print("- Smooth entity movement along calculated paths")
|
|
print("- Camera following with animated grid centering")
|
|
print("- Field of view updates as entities move")
|
|
print("- Dramatic perspective transitions with zoom effects")
|
|
print()
|
|
|
|
create_scene()
|
|
setup_paths()
|
|
setup_ui()
|
|
|
|
# Set scene and input
|
|
mcrfpy.setScene("path_vision_demo")
|
|
mcrfpy.keypressScene(handle_keys)
|
|
|
|
# Initial camera setup
|
|
grid.zoom = 1.2
|
|
update_camera_smooth(player.x, player.y, 0.1)
|
|
|
|
# Start the sequence
|
|
mcrfpy.setTimer("sequence", sequence_tick, 50) # Tick every 50ms
|
|
|
|
print("Demo started!")
|
|
print("- Player (@) will navigate through rooms")
|
|
print("- Enemy (E) will move on a different path")
|
|
print("- Watch for the dramatic perspective shift!")
|
|
print()
|
|
print("Controls: Space=Pause, R=Restart, Q=Quit")
|