diff --git a/roguelike_tutorial/part_0.py b/roguelike_tutorial/part_0.py new file mode 100644 index 0000000..eb9ed94 --- /dev/null +++ b/roguelike_tutorial/part_0.py @@ -0,0 +1,80 @@ +""" +McRogueFace Tutorial - Part 0: Introduction to Scene, Texture, and Grid + +This tutorial introduces the basic building blocks: +- Scene: A container for UI elements and game state +- Texture: Loading image assets for use in the game +- Grid: A tilemap component for rendering tile-based worlds +""" +import mcrfpy +import random + +# Create and activate a new scene +mcrfpy.createScene("tutorial") +mcrfpy.setScene("tutorial") + +# Load the texture (4x3 tiles, 64x48 pixels total, 16x16 per tile) +texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) + +# Create a grid of tiles +# Each tile is 16x16 pixels, so with 3x zoom: 16*3 = 48 pixels per tile + +grid_width, grid_height = 25, 20 # width, height in number of tiles + +# calculating the size in pixels to fit the entire grid on-screen +zoom = 2.0 +grid_size = grid_width * zoom * 16, grid_height * zoom * 16 + +# calculating the position to center the grid on the screen - assuming default 1024x768 resolution +grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 + +grid = mcrfpy.Grid( + pos=grid_position, + grid_size=(grid_width, grid_height), + texture=texture, + size=grid_size, # height and width on screen +) + +grid.zoom = zoom +grid.center = (grid_width/2.0)*16, (grid_height/2.0)*16 # center on the middle of the central tile + +# Define tile types +FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] +WALL_TILES = [3, 7, 11] + +# Fill the grid with a simple pattern +for y in range(grid_height): + for x in range(grid_width): + # Create walls around the edges + if x == 0 or x == grid_width-1 or y == 0 or y == grid_height-1: + tile_index = random.choice(WALL_TILES) + else: + # Fill interior with floor tiles + tile_index = random.choice(FLOOR_TILES) + + # Set the tile at this position + point = grid.at(x, y) + if point: + point.tilesprite = tile_index + +# Add the grid to the scene +mcrfpy.sceneUI("tutorial").append(grid) + +# Add a title caption +title = mcrfpy.Caption((320, 10), + text="McRogueFace Tutorial - Part 0", +) +title.fill_color = mcrfpy.Color(255, 255, 255, 255) +mcrfpy.sceneUI("tutorial").append(title) + +# Add instructions +instructions = mcrfpy.Caption((280, 750), + text="Scene + Texture + Grid = Tilemap!", +) +instructions.font_size=18 +instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) +mcrfpy.sceneUI("tutorial").append(instructions) + +print("Tutorial Part 0 loaded!") +print(f"Created a {grid.grid_size[0]}x{grid.grid_size[1]} grid") +print(f"Grid positioned at ({grid.x}, {grid.y})") diff --git a/roguelike_tutorial/part_1.py b/roguelike_tutorial/part_1.py new file mode 100644 index 0000000..4c19d6d --- /dev/null +++ b/roguelike_tutorial/part_1.py @@ -0,0 +1,116 @@ +""" +McRogueFace Tutorial - Part 1: Entities and Keyboard Input + +This tutorial builds on Part 0 by adding: +- Entity: A game object that can be placed in a grid +- Keyboard handling: Responding to key presses to move the entity +""" +import mcrfpy +import random + +# Create and activate a new scene +mcrfpy.createScene("tutorial") +mcrfpy.setScene("tutorial") + +# Load the texture (4x3 tiles, 64x48 pixels total, 16x16 per tile) +texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) + +# Load the hero sprite texture (32x32 sprite sheet) +hero_texture = mcrfpy.Texture("assets/custom_player.png", 16, 16) + +# Create a grid of tiles +# Each tile is 16x16 pixels, so with 3x zoom: 16*3 = 48 pixels per tile + +grid_width, grid_height = 25, 20 # width, height in number of tiles + +# calculating the size in pixels to fit the entire grid on-screen +zoom = 2.0 +grid_size = grid_width * zoom * 16, grid_height * zoom * 16 + +# calculating the position to center the grid on the screen - assuming default 1024x768 resolution +grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 + +grid = mcrfpy.Grid( + pos=grid_position, + grid_size=(grid_width, grid_height), + texture=texture, + size=grid_size, # height and width on screen +) + +grid.zoom = zoom +grid.center = (grid_width/2.0)*16, (grid_height/2.0)*16 # center on the middle of the central tile + +# Define tile types +FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] +WALL_TILES = [3, 7, 11] + +# Fill the grid with a simple pattern +for y in range(grid_height): + for x in range(grid_width): + # Create walls around the edges + if x == 0 or x == grid_width-1 or y == 0 or y == grid_height-1: + tile_index = random.choice(WALL_TILES) + else: + # Fill interior with floor tiles + tile_index = random.choice(FLOOR_TILES) + + # Set the tile at this position + point = grid.at(x, y) + if point: + point.tilesprite = tile_index + +# Add the grid to the scene +mcrfpy.sceneUI("tutorial").append(grid) + +# Create a player entity at position (4, 4) +player = mcrfpy.Entity( + (4, 4), # Entity positions are tile coordinates + texture=hero_texture, + sprite_index=0 # Use the first sprite in the texture +) + +# Add the player entity to the grid +grid.entities.append(player) + +# Define keyboard handler +def handle_keys(key, state): + """Handle keyboard input to move the player""" + if state == "start": # Only respond to key press, not release + # Get current player position in grid coordinates + px, py = player.x, player.y + + # Calculate new position based on key press + if key == "W" or key == "Up": + py -= 1 + elif key == "S" or key == "Down": + py += 1 + elif key == "A" or key == "Left": + px -= 1 + elif key == "D" or key == "Right": + px += 1 + + # Update player position (no collision checking yet) + player.x = px + player.y = py + +# Register the keyboard handler +mcrfpy.keypressScene(handle_keys) + +# Add a title caption +title = mcrfpy.Caption((320, 10), + text="McRogueFace Tutorial - Part 1", +) +title.fill_color = mcrfpy.Color(255, 255, 255, 255) +mcrfpy.sceneUI("tutorial").append(title) + +# Add instructions +instructions = mcrfpy.Caption((200, 750), + text="Use WASD or Arrow Keys to move the hero!", +) +instructions.font_size=18 +instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) +mcrfpy.sceneUI("tutorial").append(instructions) + +print("Tutorial Part 1 loaded!") +print(f"Player entity created at grid position (4, 4)") +print("Use WASD or Arrow keys to move!") diff --git a/roguelike_tutorial/part_1b.py b/roguelike_tutorial/part_1b.py new file mode 100644 index 0000000..3894fc7 --- /dev/null +++ b/roguelike_tutorial/part_1b.py @@ -0,0 +1,117 @@ +""" +McRogueFace Tutorial - Part 1: Entities and Keyboard Input + +This tutorial builds on Part 0 by adding: +- Entity: A game object that can be placed in a grid +- Keyboard handling: Responding to key presses to move the entity +""" +import mcrfpy +import random + +# Create and activate a new scene +mcrfpy.createScene("tutorial") +mcrfpy.setScene("tutorial") + +# Load the texture (4x3 tiles, 64x48 pixels total, 16x16 per tile) +texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) + +# Load the hero sprite texture (32x32 sprite sheet) +hero_texture = mcrfpy.Texture("assets/custom_player.png", 16, 16) + +# Create a grid of tiles +# Each tile is 16x16 pixels, so with 3x zoom: 16*3 = 48 pixels per tile + +grid_width, grid_height = 25, 20 # width, height in number of tiles + +# calculating the size in pixels to fit the entire grid on-screen +zoom = 2.0 +grid_size = grid_width * zoom * 16, grid_height * zoom * 16 + +# calculating the position to center the grid on the screen - assuming default 1024x768 resolution +grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 + +grid = mcrfpy.Grid( + pos=grid_position, + grid_size=(grid_width, grid_height), + texture=texture, + size=grid_size, # height and width on screen +) + +grid.zoom = 3.0 # we're not using the zoom variable! It's going to be really big! + +# Define tile types +FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] +WALL_TILES = [3, 7, 11] + +# Fill the grid with a simple pattern +for y in range(grid_height): + for x in range(grid_width): + # Create walls around the edges + if x == 0 or x == grid_width-1 or y == 0 or y == grid_height-1: + tile_index = random.choice(WALL_TILES) + else: + # Fill interior with floor tiles + tile_index = random.choice(FLOOR_TILES) + + # Set the tile at this position + point = grid.at(x, y) + if point: + point.tilesprite = tile_index + +# Add the grid to the scene +mcrfpy.sceneUI("tutorial").append(grid) + +# Create a player entity at position (4, 4) +player = mcrfpy.Entity( + (4, 4), # Entity positions are tile coordinates + texture=hero_texture, + sprite_index=0 # Use the first sprite in the texture +) + +# Add the player entity to the grid +grid.entities.append(player) +grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 # grid center is in texture/pixel coordinates + +# Define keyboard handler +def handle_keys(key, state): + """Handle keyboard input to move the player""" + if state == "start": # Only respond to key press, not release + # Get current player position in grid coordinates + px, py = player.x, player.y + + # Calculate new position based on key press + if key == "W" or key == "Up": + py -= 1 + elif key == "S" or key == "Down": + py += 1 + elif key == "A" or key == "Left": + px -= 1 + elif key == "D" or key == "Right": + px += 1 + + # Update player position (no collision checking yet) + player.x = px + player.y = py + grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 # grid center is in texture/pixel coordinates + +# Register the keyboard handler +mcrfpy.keypressScene(handle_keys) + +# Add a title caption +title = mcrfpy.Caption((320, 10), + text="McRogueFace Tutorial - Part 1", +) +title.fill_color = mcrfpy.Color(255, 255, 255, 255) +mcrfpy.sceneUI("tutorial").append(title) + +# Add instructions +instructions = mcrfpy.Caption((200, 750), + text="Use WASD or Arrow Keys to move the hero!", +) +instructions.font_size=18 +instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) +mcrfpy.sceneUI("tutorial").append(instructions) + +print("Tutorial Part 1 loaded!") +print(f"Player entity created at grid position (4, 4)") +print("Use WASD or Arrow keys to move!") diff --git a/roguelike_tutorial/part_2-naive.py b/roguelike_tutorial/part_2-naive.py new file mode 100644 index 0000000..6959a4b --- /dev/null +++ b/roguelike_tutorial/part_2-naive.py @@ -0,0 +1,149 @@ +""" +McRogueFace Tutorial - Part 2: Animated Movement + +This tutorial builds on Part 1 by adding: +- Animation system for smooth movement +- Movement that takes 0.5 seconds per tile +- Input blocking during movement animation +""" +import mcrfpy +import random + +# Create and activate a new scene +mcrfpy.createScene("tutorial") +mcrfpy.setScene("tutorial") + +# Load the texture (4x3 tiles, 64x48 pixels total, 16x16 per tile) +texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) + +# Load the hero sprite texture (32x32 sprite sheet) +hero_texture = mcrfpy.Texture("assets/custom_player.png", 16, 16) + +# Create a grid of tiles +# Each tile is 16x16 pixels, so with 3x zoom: 16*3 = 48 pixels per tile + +grid_width, grid_height = 25, 20 # width, height in number of tiles + +# calculating the size in pixels to fit the entire grid on-screen +zoom = 2.0 +grid_size = grid_width * zoom * 16, grid_height * zoom * 16 + +# calculating the position to center the grid on the screen - assuming default 1024x768 resolution +grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 + +grid = mcrfpy.Grid( + pos=grid_position, + grid_size=(grid_width, grid_height), + texture=texture, + size=grid_size, # height and width on screen +) + +grid.zoom = 3.0 # we're not using the zoom variable! It's going to be really big! + +# Define tile types +FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] +WALL_TILES = [3, 7, 11] + +# Fill the grid with a simple pattern +for y in range(grid_height): + for x in range(grid_width): + # Create walls around the edges + if x == 0 or x == grid_width-1 or y == 0 or y == grid_height-1: + tile_index = random.choice(WALL_TILES) + else: + # Fill interior with floor tiles + tile_index = random.choice(FLOOR_TILES) + + # Set the tile at this position + point = grid.at(x, y) + if point: + point.tilesprite = tile_index + +# Add the grid to the scene +mcrfpy.sceneUI("tutorial").append(grid) + +# Create a player entity at position (4, 4) +player = mcrfpy.Entity( + (4, 4), # Entity positions are tile coordinates + texture=hero_texture, + sprite_index=0 # Use the first sprite in the texture +) + +# Add the player entity to the grid +grid.entities.append(player) +grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 # grid center is in texture/pixel coordinates + +# Movement state tracking +is_moving = False +move_animations = [] # Track active animations + +# Animation completion callback +def movement_complete(runtime): + """Called when movement animation completes""" + global is_moving + is_moving = False + # Ensure grid is centered on final position + grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 + +motion_speed = 0.30 # seconds per tile +# Define keyboard handler +def handle_keys(key, state): + """Handle keyboard input to move the player""" + global is_moving, move_animations + + if state == "start" and not is_moving: # Only respond to key press when not moving + # Get current player position in grid coordinates + px, py = player.x, player.y + new_x, new_y = px, py + + # Calculate new position based on key press + if key == "W" or key == "Up": + new_y -= 1 + elif key == "S" or key == "Down": + new_y += 1 + elif key == "A" or key == "Left": + new_x -= 1 + elif key == "D" or key == "Right": + new_x += 1 + + # If position changed, start movement animation + if new_x != px or new_y != py: + is_moving = True + + # Create animations for player position + anim_x = mcrfpy.Animation("x", float(new_x), motion_speed, "easeInOutQuad") + anim_y = mcrfpy.Animation("y", float(new_y), motion_speed, "easeInOutQuad") + anim_x.start(player) + anim_y.start(player) + + # Animate grid center to follow player + center_x = mcrfpy.Animation("center_x", (new_x + 0.5) * 16, motion_speed, "linear") + center_y = mcrfpy.Animation("center_y", (new_y + 0.5) * 16, motion_speed, "linear") + center_x.start(grid) + center_y.start(grid) + + # Set a timer to mark movement as complete + mcrfpy.setTimer("move_complete", movement_complete, 500) + +# Register the keyboard handler +mcrfpy.keypressScene(handle_keys) + +# Add a title caption +title = mcrfpy.Caption((320, 10), + text="McRogueFace Tutorial - Part 2", +) +title.fill_color = mcrfpy.Color(255, 255, 255, 255) +mcrfpy.sceneUI("tutorial").append(title) + +# Add instructions +instructions = mcrfpy.Caption((150, 750), + text="Smooth movement! Each step takes 0.5 seconds.", +) +instructions.font_size=18 +instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) +mcrfpy.sceneUI("tutorial").append(instructions) + +print("Tutorial Part 2 loaded!") +print(f"Player entity created at grid position (4, 4)") +print("Movement is now animated over 0.5 seconds per tile!") +print("Use WASD or Arrow keys to move!") diff --git a/roguelike_tutorial/part_2-onemovequeued.py b/roguelike_tutorial/part_2-onemovequeued.py new file mode 100644 index 0000000..126c433 --- /dev/null +++ b/roguelike_tutorial/part_2-onemovequeued.py @@ -0,0 +1,241 @@ +""" +McRogueFace Tutorial - Part 2: Enhanced with Single Move Queue + +This tutorial builds on Part 2 by adding: +- Single queued move system for responsive input +- Debug display showing position and queue status +- Smooth continuous movement when keys are held +- Animation callbacks to prevent race conditions +""" +import mcrfpy +import random + +# Create and activate a new scene +mcrfpy.createScene("tutorial") +mcrfpy.setScene("tutorial") + +# Load the texture (4x3 tiles, 64x48 pixels total, 16x16 per tile) +texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) + +# Load the hero sprite texture (32x32 sprite sheet) +hero_texture = mcrfpy.Texture("assets/custom_player.png", 16, 16) + +# Create a grid of tiles +# Each tile is 16x16 pixels, so with 3x zoom: 16*3 = 48 pixels per tile + +grid_width, grid_height = 25, 20 # width, height in number of tiles + +# calculating the size in pixels to fit the entire grid on-screen +zoom = 2.0 +grid_size = grid_width * zoom * 16, grid_height * zoom * 16 + +# calculating the position to center the grid on the screen - assuming default 1024x768 resolution +grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 + +grid = mcrfpy.Grid( + pos=grid_position, + grid_size=(grid_width, grid_height), + texture=texture, + size=grid_size, # height and width on screen +) + +grid.zoom = 3.0 # we're not using the zoom variable! It's going to be really big! + +# Define tile types +FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] +WALL_TILES = [3, 7, 11] + +# Fill the grid with a simple pattern +for y in range(grid_height): + for x in range(grid_width): + # Create walls around the edges + if x == 0 or x == grid_width-1 or y == 0 or y == grid_height-1: + tile_index = random.choice(WALL_TILES) + else: + # Fill interior with floor tiles + tile_index = random.choice(FLOOR_TILES) + + # Set the tile at this position + point = grid.at(x, y) + if point: + point.tilesprite = tile_index + +# Add the grid to the scene +mcrfpy.sceneUI("tutorial").append(grid) + +# Create a player entity at position (4, 4) +player = mcrfpy.Entity( + (4, 4), # Entity positions are tile coordinates + texture=hero_texture, + sprite_index=0 # Use the first sprite in the texture +) + +# Add the player entity to the grid +grid.entities.append(player) +grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 # grid center is in texture/pixel coordinates + +# Movement state tracking +is_moving = False +move_queue = [] # List to store queued moves (max 1 item) +#last_position = (4, 4) # Track last position +current_destination = None # Track where we're currently moving to +current_move = None # Track current move direction + +# Store animation references +player_anim_x = None +player_anim_y = None +grid_anim_x = None +grid_anim_y = None + +# Debug display caption +debug_caption = mcrfpy.Caption((10, 40), + text="Last: (4, 4) | Queue: 0 | Dest: None", +) +debug_caption.font_size = 16 +debug_caption.fill_color = mcrfpy.Color(255, 255, 0, 255) +mcrfpy.sceneUI("tutorial").append(debug_caption) + +# Additional debug caption for movement state +move_debug_caption = mcrfpy.Caption((10, 60), + text="Moving: False | Current: None | Queued: None", +) +move_debug_caption.font_size = 16 +move_debug_caption.fill_color = mcrfpy.Color(255, 200, 0, 255) +mcrfpy.sceneUI("tutorial").append(move_debug_caption) + +def key_to_direction(key): + """Convert key to direction string""" + if key == "W" or key == "Up": + return "Up" + elif key == "S" or key == "Down": + return "Down" + elif key == "A" or key == "Left": + return "Left" + elif key == "D" or key == "Right": + return "Right" + return None + +def update_debug_display(): + """Update the debug caption with current state""" + queue_count = len(move_queue) + dest_text = f"({current_destination[0]}, {current_destination[1]})" if current_destination else "None" + debug_caption.text = f"Last: ({player.x}, {player.y}) | Queue: {queue_count} | Dest: {dest_text}" + + # Update movement state debug + current_dir = key_to_direction(current_move) if current_move else "None" + queued_dir = key_to_direction(move_queue[0]) if move_queue else "None" + move_debug_caption.text = f"Moving: {is_moving} | Current: {current_dir} | Queued: {queued_dir}" + +# Animation completion callback +def movement_complete(anim, target): + """Called when movement animation completes""" + global is_moving, move_queue, current_destination, current_move + global player_anim_x, player_anim_y + print(f"In callback for animation: {anim=} {target=}") + # Clear movement state + is_moving = False + current_move = None + current_destination = None + # Clear animation references + player_anim_x = None + player_anim_y = None + + # Update last position to where we actually are now + #last_position = (int(player.x), int(player.y)) + + # Ensure grid is centered on final position + grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 + + # Check if there's a queued move + if move_queue: + # Pop the next move from the queue + next_move = move_queue.pop(0) + print(f"Processing queued move: {next_move}") + # Process it like a fresh input + process_move(next_move) + + update_debug_display() + +motion_speed = 0.30 # seconds per tile + +def process_move(key): + """Process a move based on the key""" + global is_moving, current_move, current_destination, move_queue + global player_anim_x, player_anim_y, grid_anim_x, grid_anim_y + + # If already moving, just update the queue + if is_moving: + print(f"process_move processing {key=} as a queued move (is_moving = True)") + # Clear queue and add new move (only keep 1 queued move) + move_queue.clear() + move_queue.append(key) + update_debug_display() + return + print(f"process_move processing {key=} as a new, immediate animation (is_moving = False)") + # Calculate new position from current position + px, py = int(player.x), int(player.y) + new_x, new_y = px, py + + # Calculate new position based on key press (only one tile movement) + if key == "W" or key == "Up": + new_y -= 1 + elif key == "S" or key == "Down": + new_y += 1 + elif key == "A" or key == "Left": + new_x -= 1 + elif key == "D" or key == "Right": + new_x += 1 + + # Start the move if position changed + if new_x != px or new_y != py: + is_moving = True + current_move = key + current_destination = (new_x, new_y) + # only animate a single axis, same callback from either + if new_x != px: + player_anim_x = mcrfpy.Animation("x", float(new_x), motion_speed, "easeInOutQuad", callback=movement_complete) + player_anim_x.start(player) + elif new_y != py: + player_anim_y = mcrfpy.Animation("y", float(new_y), motion_speed, "easeInOutQuad", callback=movement_complete) + player_anim_y.start(player) + + # Animate grid center to follow player + grid_anim_x = mcrfpy.Animation("center_x", (new_x + 0.5) * 16, motion_speed, "linear") + grid_anim_y = mcrfpy.Animation("center_y", (new_y + 0.5) * 16, motion_speed, "linear") + grid_anim_x.start(grid) + grid_anim_y.start(grid) + + update_debug_display() + +# Define keyboard handler +def handle_keys(key, state): + """Handle keyboard input to move the player""" + if state == "start": + # Only process movement keys + if key in ["W", "Up", "S", "Down", "A", "Left", "D", "Right"]: + print(f"handle_keys producing actual input: {key=}") + process_move(key) + + +# Register the keyboard handler +mcrfpy.keypressScene(handle_keys) + +# Add a title caption +title = mcrfpy.Caption((320, 10), + text="McRogueFace Tutorial - Part 2 Enhanced", +) +title.fill_color = mcrfpy.Color(255, 255, 255, 255) +mcrfpy.sceneUI("tutorial").append(title) + +# Add instructions +instructions = mcrfpy.Caption((150, 750), + text="One-move queue system with animation callbacks!", +) +instructions.font_size=18 +instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) +mcrfpy.sceneUI("tutorial").append(instructions) + +print("Tutorial Part 2 Enhanced loaded!") +print(f"Player entity created at grid position (4, 4)") +print("Movement now uses animation callbacks to prevent race conditions!") +print("Use WASD or Arrow keys to move!") diff --git a/roguelike_tutorial/part_2.py b/roguelike_tutorial/part_2.py new file mode 100644 index 0000000..66a11b0 --- /dev/null +++ b/roguelike_tutorial/part_2.py @@ -0,0 +1,149 @@ +""" +McRogueFace Tutorial - Part 2: Animated Movement + +This tutorial builds on Part 1 by adding: +- Animation system for smooth movement +- Movement that takes 0.5 seconds per tile +- Input blocking during movement animation +""" +import mcrfpy +import random + +# Create and activate a new scene +mcrfpy.createScene("tutorial") +mcrfpy.setScene("tutorial") + +# Load the texture (4x3 tiles, 64x48 pixels total, 16x16 per tile) +texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) + +# Load the hero sprite texture (32x32 sprite sheet) +hero_texture = mcrfpy.Texture("assets/custom_player.png", 16, 16) + +# Create a grid of tiles +# Each tile is 16x16 pixels, so with 3x zoom: 16*3 = 48 pixels per tile + +grid_width, grid_height = 25, 20 # width, height in number of tiles + +# calculating the size in pixels to fit the entire grid on-screen +zoom = 2.0 +grid_size = grid_width * zoom * 16, grid_height * zoom * 16 + +# calculating the position to center the grid on the screen - assuming default 1024x768 resolution +grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 + +grid = mcrfpy.Grid( + pos=grid_position, + grid_size=(grid_width, grid_height), + texture=texture, + size=grid_size, # height and width on screen +) + +grid.zoom = 3.0 # we're not using the zoom variable! It's going to be really big! + +# Define tile types +FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] +WALL_TILES = [3, 7, 11] + +# Fill the grid with a simple pattern +for y in range(grid_height): + for x in range(grid_width): + # Create walls around the edges + if x == 0 or x == grid_width-1 or y == 0 or y == grid_height-1: + tile_index = random.choice(WALL_TILES) + else: + # Fill interior with floor tiles + tile_index = random.choice(FLOOR_TILES) + + # Set the tile at this position + point = grid.at(x, y) + if point: + point.tilesprite = tile_index + +# Add the grid to the scene +mcrfpy.sceneUI("tutorial").append(grid) + +# Create a player entity at position (4, 4) +player = mcrfpy.Entity( + (4, 4), # Entity positions are tile coordinates + texture=hero_texture, + sprite_index=0 # Use the first sprite in the texture +) + +# Add the player entity to the grid +grid.entities.append(player) +grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 # grid center is in texture/pixel coordinates + +# Movement state tracking +is_moving = False +move_animations = [] # Track active animations + +# Animation completion callback +def movement_complete(runtime): + """Called when movement animation completes""" + global is_moving + is_moving = False + # Ensure grid is centered on final position + grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 + +motion_speed = 0.30 # seconds per tile +# Define keyboard handler +def handle_keys(key, state): + """Handle keyboard input to move the player""" + global is_moving, move_animations + + if state == "start" and not is_moving: # Only respond to key press when not moving + # Get current player position in grid coordinates + px, py = player.x, player.y + new_x, new_y = px, py + + # Calculate new position based on key press + if key == "W" or key == "Up": + new_y -= 1 + elif key == "S" or key == "Down": + new_y += 1 + elif key == "A" or key == "Left": + new_x -= 1 + elif key == "D" or key == "Right": + new_x += 1 + + # If position changed, start movement animation + if new_x != px or new_y != py: + is_moving = True + + # Create animations for player position + anim_x = mcrfpy.Animation("x", float(new_x), motion_speed, "easeInOutQuad") + anim_y = mcrfpy.Animation("y", float(new_y), motion_speed, "easeInOutQuad") + anim_x.start(player) + anim_y.start(player) + + # Animate grid center to follow player + center_x = mcrfpy.Animation("center_x", (new_x + 0.5) * 16, motion_speed, "linear") + center_y = mcrfpy.Animation("center_y", (new_y + 0.5) * 16, motion_speed, "linear") + center_x.start(grid) + center_y.start(grid) + + # Set a timer to mark movement as complete + mcrfpy.setTimer("move_complete", movement_complete, 500) + +# Register the keyboard handler +mcrfpy.keypressScene(handle_keys) + +# Add a title caption +title = mcrfpy.Caption((320, 10), + text="McRogueFace Tutorial - Part 2", +) +title.fill_color = mcrfpy.Color(255, 255, 255, 255) +mcrfpy.sceneUI("tutorial").append(title) + +# Add instructions +instructions = mcrfpy.Caption((150, 750), + "Smooth movement! Each step takes 0.5 seconds.", +) +instructions.font_size=18 +instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) +mcrfpy.sceneUI("tutorial").append(instructions) + +print("Tutorial Part 2 loaded!") +print(f"Player entity created at grid position (4, 4)") +print("Movement is now animated over 0.5 seconds per tile!") +print("Use WASD or Arrow keys to move!") diff --git a/roguelike_tutorial/tutorial2.png b/roguelike_tutorial/tutorial2.png new file mode 100644 index 0000000..e785419 Binary files /dev/null and b/roguelike_tutorial/tutorial2.png differ diff --git a/roguelike_tutorial/tutorial_hero.png b/roguelike_tutorial/tutorial_hero.png new file mode 100644 index 0000000..c202176 Binary files /dev/null and b/roguelike_tutorial/tutorial_hero.png differ