feat: Implement enemy spawning and movement logic in the level manager
This commit is contained in:
@@ -3,6 +3,8 @@
|
|||||||
# Responsible for generating, painting, and initializing the level, as well as providing pathfinding and coordinate helpers.
|
# Responsible for generating, painting, and initializing the level, as well as providing pathfinding and coordinate helpers.
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
|
const ENEMY_SCENE_PATH: String = "res://scenes/actors/enemy.tscn"
|
||||||
|
|
||||||
# The source of truth for the current level's layout (MapData resource).
|
# The source of truth for the current level's layout (MapData resource).
|
||||||
var _data: MapData
|
var _data: MapData
|
||||||
# The TileMapLayer node where the map is visually rendered.
|
# The TileMapLayer node where the map is visually rendered.
|
||||||
@@ -18,8 +20,8 @@ var _pathfinding: PathfindingHandler = PathfindingHandler.new()
|
|||||||
# 1. Generates the map data.
|
# 1. Generates the map data.
|
||||||
# 2. Paints the map visually.
|
# 2. Paints the map visually.
|
||||||
# 3. Builds the navigation graph for AI/pathfinding.
|
# 3. Builds the navigation graph for AI/pathfinding.
|
||||||
# 4. Spawns the player at the correct location.
|
# 4. Spawns the player at the correct location.
|
||||||
func initialize(settings: MapSettings, layer: TileMapLayer, player: CharacterBody2D) -> void:
|
func initialize(settings: MapSettings, layer: TileMapLayer, player: Player) -> void:
|
||||||
_layer = layer
|
_layer = layer
|
||||||
|
|
||||||
# 1. Generate the abstract map data (rooms, corridors, etc.).
|
# 1. Generate the abstract map data (rooms, corridors, etc.).
|
||||||
@@ -38,6 +40,19 @@ func initialize(settings: MapSettings, layer: TileMapLayer, player: CharacterBod
|
|||||||
# Uses the TileMapLayer to convert grid coordinates to world pixel positions.
|
# Uses the TileMapLayer to convert grid coordinates to world pixel positions.
|
||||||
player.position = _layer.map_to_local(_data.spawn_point)
|
player.position = _layer.map_to_local(_data.spawn_point)
|
||||||
|
|
||||||
|
# 5. Spawn one enemy per room, skipping the player's spawn room (index 0).
|
||||||
|
var enemy_scene := load(ENEMY_SCENE_PATH) as PackedScene
|
||||||
|
var world_node := player.get_parent()
|
||||||
|
for i in range(1, _data.rooms.size()):
|
||||||
|
var enemy_instance = enemy_scene.instantiate()
|
||||||
|
var enemy = enemy_instance as Enemy
|
||||||
|
if enemy == null:
|
||||||
|
push_error("Failed to cast enemy instance to Enemy at room index %d" % i)
|
||||||
|
continue
|
||||||
|
enemy._player = player
|
||||||
|
world_node.add_child(enemy)
|
||||||
|
enemy.position = _layer.map_to_local(_data.rooms[i].get_center())
|
||||||
|
|
||||||
# Public API for other systems (like AI) to query the navigation grid.
|
# Public API for other systems (like AI) to query the navigation grid.
|
||||||
# Encapsulates the PathfindingHandler so callers don't need to know about it directly.
|
# Encapsulates the PathfindingHandler so callers don't need to know about it directly.
|
||||||
func find_path(from: Vector2i, to: Vector2i) -> Array[Vector2i]:
|
func find_path(from: Vector2i, to: Vector2i) -> Array[Vector2i]:
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
[gd_scene format=3 uid="uid://hwbs3q16spqx"]
|
[gd_scene format=3 uid="uid://hwbs3q16spqx"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://dh5hx2lospkug" path="res://scripts/actors/character.gd" id="1_4i4j6"]
|
[ext_resource type="Script" uid="uid://lm82h1fj2mbp" path="res://scripts/actors/enemy.gd" id="1_qkj82"]
|
||||||
[ext_resource type="Texture2D" uid="uid://b7s06o6gm42rn" path="res://assets/sprites/icon.svg" id="2_qkj82"]
|
[ext_resource type="Texture2D" uid="uid://b7s06o6gm42rn" path="res://assets/sprites/icon.svg" id="2_qkj82"]
|
||||||
|
|
||||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_35asm"]
|
[sub_resource type="RectangleShape2D" id="RectangleShape2D_35asm"]
|
||||||
size = Vector2(32, 32)
|
size = Vector2(32, 32)
|
||||||
|
|
||||||
[node name="Enemy" type="CharacterBody2D" unique_id=1059490557]
|
[node name="Enemy" type="CharacterBody2D" unique_id=1059490557]
|
||||||
script = ExtResource("1_4i4j6")
|
script = ExtResource("1_qkj82")
|
||||||
metadata/_custom_type_script = "uid://dh5hx2lospkug"
|
metadata/_custom_type_script = "uid://dh5hx2lospkug"
|
||||||
|
|
||||||
[node name="Sprite2D" type="Sprite2D" parent="." unique_id=569576189]
|
[node name="Sprite2D" type="Sprite2D" parent="." unique_id=569576189]
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
## enemy.gd
|
||||||
|
# Enemy character script. Each turn, chases the player one tile using pathfinding.
|
||||||
|
class_name Enemy
|
||||||
|
extends Character
|
||||||
|
|
||||||
|
# Movement speed in pixels per second.
|
||||||
|
const SPEED: float = 200.0
|
||||||
|
# Distance threshold for snapping to a tile (prevents jitter).
|
||||||
|
const ARRIVAL_THRESHOLD: float = 4.0
|
||||||
|
|
||||||
|
# Reference to the player — set by LevelManager after spawning.
|
||||||
|
var _player: Player
|
||||||
|
# True while animating toward _move_target.
|
||||||
|
var _is_moving: bool = false
|
||||||
|
# World position of the tile this enemy is stepping toward.
|
||||||
|
var _move_target: Vector2 = Vector2.ZERO
|
||||||
|
|
||||||
|
|
||||||
|
# Called by TurnManager. Pathfinds to the player and steps one tile closer.
|
||||||
|
func take_turn() -> void:
|
||||||
|
var my_tile := LevelManager.world_to_tile(position)
|
||||||
|
var player_tile := LevelManager.world_to_tile(_player.position)
|
||||||
|
var path := LevelManager.find_path(my_tile, player_tile)
|
||||||
|
# path[0] is own tile; path[-1] is the player's tile (don't step there).
|
||||||
|
if path.size() < 2:
|
||||||
|
turn_finished.emit()
|
||||||
|
return
|
||||||
|
# Stop one tile away from the player — don't overlap.
|
||||||
|
if path.size() == 2:
|
||||||
|
turn_finished.emit()
|
||||||
|
return
|
||||||
|
_move_target = LevelManager.tile_to_world(path[1])
|
||||||
|
_is_moving = true
|
||||||
|
|
||||||
|
|
||||||
|
# Glides toward _move_target each frame. Emits turn_finished on arrival.
|
||||||
|
func _physics_process(_delta: float) -> void:
|
||||||
|
if not _is_moving:
|
||||||
|
velocity = Vector2.ZERO
|
||||||
|
move_and_slide()
|
||||||
|
return
|
||||||
|
|
||||||
|
var to_target: Vector2 = _move_target - position
|
||||||
|
|
||||||
|
if to_target.length() < ARRIVAL_THRESHOLD:
|
||||||
|
position = _move_target
|
||||||
|
velocity = Vector2.ZERO
|
||||||
|
move_and_slide()
|
||||||
|
_is_moving = false
|
||||||
|
turn_finished.emit()
|
||||||
|
else:
|
||||||
|
velocity = to_target.normalized() * SPEED
|
||||||
|
move_and_slide()
|
||||||
+2
-3
@@ -2,12 +2,11 @@
|
|||||||
# Main world node script. Sets up the map, player, and level on scene ready.
|
# Main world node script. Sets up the map, player, and level on scene ready.
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
# Reference to the TileMapLayer node for rendering the map.
|
|
||||||
|
|
||||||
# Reference to the TileMapLayer node for rendering the map.
|
|
||||||
var map_layer: TileMapLayer
|
|
||||||
# Path to the Player scene to instance.
|
# Path to the Player scene to instance.
|
||||||
const PLAYER_SCENE_PATH: String = "res://scenes/actors/player.tscn"
|
const PLAYER_SCENE_PATH: String = "res://scenes/actors/player.tscn"
|
||||||
|
# Reference to the TileMapLayer node for rendering the map.
|
||||||
|
var map_layer: TileMapLayer
|
||||||
# Reference to the Player node.
|
# Reference to the Player node.
|
||||||
var player: Player
|
var player: Player
|
||||||
# Map generation settings (size, room count, etc.).
|
# Map generation settings (size, room count, etc.).
|
||||||
|
|||||||
Reference in New Issue
Block a user