Movement System
Karakter beweging en navigatie.
Overzicht
Het movement systeem gebruikt:
- Point-and-click navigatie (tap/click om te bewegen)
- A pathfinding* met walkable maps
- Perspective scaling (verder weg = kleiner)
- Walk animatie met bobbing en sway
Input Handling
Touch/Click
func _input(event: InputEvent) -> void:
# Mouse click
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
_set_movement_target(event.position)
# Touch
elif event is InputEventScreenTouch:
if event.pressed:
_set_movement_target(event.position)
Walkable Maps
Concept
Een walkable map is een PNG afbeelding waar groene pixels loopbaar zijn:
┌────────────────────────────────┐
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ ░ = Niet loopbaar
│ ░░░░████████████████░░░░░░░░░│ █ = Loopbaar (groen)
│ ░░░████████████████████░░░░░░│
│ ░░██████████████████████░░░░░│
│ ░░░████████████████████░░░░░░│
│ ░░░░░░████████████░░░░░░░░░░░│
└────────────────────────────────┘
Laden
const WALKABLE_MAP_PATH = "user://walkable_map_home.png"
var walkable_image: Image
func _load_walkable_map() -> void:
if FileAccess.file_exists(WALKABLE_MAP_PATH):
walkable_image = Image.load_from_file(WALKABLE_MAP_PATH)
func _is_walkable(pos: Vector2) -> bool:
if not walkable_image:
return true
var image_pos = _screen_to_image(pos)
var x = int(image_pos.x)
var y = int(image_pos.y)
if x < 0 or x >= walkable_image.get_width():
return false
if y < 0 or y >= walkable_image.get_height():
return false
var color = walkable_image.get_pixel(x, y)
return color.a > 0.1 # Alpha > 0 = loopbaar
Coördinaat Transformatie
func _screen_to_image(pos: Vector2) -> Vector2:
var screen_size = get_viewport().get_visible_rect().size
var image_size = Vector2(walkable_image.get_width(), walkable_image.get_height())
return Vector2(
pos.x * image_size.x / screen_size.x,
pos.y * image_size.y / screen_size.y
)
A* Pathfinding
Algoritme
func _find_path(start: Vector2, end: Vector2) -> Array[Vector2]:
var path: Array[Vector2] = []
if not walkable_image:
path.append(end)
return path
var grid_size = 16 # Stap grootte
var open_set: Array[Vector2] = [start]
var came_from: Dictionary = {}
var g_score: Dictionary = {}
var f_score: Dictionary = {}
var start_key = _vec_to_key(start)
g_score[start_key] = 0.0
f_score[start_key] = start.distance_to(end)
var iterations = 0
var max_iterations = 2000
while open_set.size() > 0 and iterations < max_iterations:
iterations += 1
# Vind node met laagste f_score
var current = _get_lowest_f(open_set, f_score)
var current_key = _vec_to_key(current)
# Doel bereikt?
if current.distance_to(end) < grid_size:
return _reconstruct_path(came_from, current, end)
open_set.erase(current)
# Check buren (8 richtingen)
for neighbor in _get_neighbors(current, grid_size):
if not _is_walkable(neighbor):
continue
var neighbor_key = _vec_to_key(neighbor)
var tentative_g = g_score.get(current_key, INF) + current.distance_to(neighbor)
if tentative_g < g_score.get(neighbor_key, INF):
came_from[neighbor_key] = current
g_score[neighbor_key] = tentative_g
f_score[neighbor_key] = tentative_g + neighbor.distance_to(end)
if neighbor not in open_set:
open_set.append(neighbor)
return path # Leeg als geen pad gevonden
func _get_neighbors(pos: Vector2, grid_size: int) -> Array[Vector2]:
return [
pos + Vector2(grid_size, 0),
pos + Vector2(-grid_size, 0),
pos + Vector2(0, grid_size),
pos + Vector2(0, -grid_size),
pos + Vector2(grid_size, grid_size),
pos + Vector2(-grid_size, -grid_size),
pos + Vector2(grid_size, -grid_size),
pos + Vector2(-grid_size, grid_size)
]
Movement Uitvoering
Langs Pad Bewegen
const MOVE_SPEED: float = 120.0
var current_path: Array[Vector2] = []
var path_index: int = 0
var is_moving: bool = false
func _handle_movement(delta: float) -> void:
if not is_moving:
return
if path_index >= current_path.size():
is_moving = false
return
var target = current_path[path_index]
var distance = character.position.distance_to(target)
# Volgende waypoint wanneer dichtbij
if distance < 8.0:
path_index += 1
if path_index >= current_path.size():
is_moving = false
return
target = current_path[path_index]
# Beweeg naar target
var direction = (target - character.position).normalized()
character.position += direction * MOVE_SPEED * delta
# Flip karakter
if direction.x > 0.1:
character.scale.x = abs(character.scale.x)
elif direction.x < -0.1:
character.scale.x = -abs(character.scale.x)
Perspective Scaling
Y-Positie naar Schaal
const MIN_Y: float = 400.0 # Bovenaan (ver weg)
const MAX_Y: float = 648.0 # Onderaan (dichtbij)
const MIN_SCALE: float = 0.018
const MAX_SCALE: float = 0.065
func _update_scale() -> void:
var t = (character.position.y - MIN_Y) / (MAX_Y - MIN_Y)
t = clamp(t, 0.0, 1.0)
var new_scale = lerp(MIN_SCALE, MAX_SCALE, t)
var flip = sign(character.scale.x) if character.scale.x != 0 else 1.0
character.scale = Vector2(new_scale * flip, new_scale)
Squash tijdens Beweging
func _update_scale() -> void:
# ... base scale berekening ...
var squash = 1.0
if is_moving and current_path.size() > 0:
var direction = (current_path[path_index] - character.position).normalized()
var horizontal_factor = abs(direction.x)
squash = lerp(1.0, 0.6, horizontal_factor)
character.scale = Vector2(new_scale * flip * squash, new_scale)
Walk Animatie
Bobbing & Sway
var walk_time: float = 0.0
var walk_strength: float = 0.0
const WALK_SPEED: float = 10.0
const BOB_AMOUNT: float = 15.0
const SWAY_AMOUNT: float = 0.03
const LEG_SWING_AMOUNT: float = 0.4
func _update_walk_animation(delta: float) -> void:
if is_moving:
walk_time += delta * WALK_SPEED
walk_strength = lerp(walk_strength, 1.0, delta * 8.0)
else:
walk_time += delta * WALK_SPEED * walk_strength
walk_strength = lerp(walk_strength, 0.0, delta * 5.0)
# Bereken animatie waarden
var bob = sin(walk_time * 2.0) * BOB_AMOUNT * walk_strength
var sway = sin(walk_time) * SWAY_AMOUNT * walk_strength
var leg_swing = sin(walk_time) * LEG_SWING_AMOUNT * walk_strength
# Pas toe op sprites
body_sprite.position.y = base_body_y + bob
body_sprite.rotation = sway
left_leg_sprite.rotation = leg_swing
right_leg_sprite.rotation = -leg_swing
Zone Detection
Exit Zones
const EXIT_ZONES = {
"door": {"center": Vector2(350, 380), "radius": 40.0},
"exit_right": {"center": Vector2(1100, 600), "radius": 60.0}
}
func _check_zones() -> void:
if is_transitioning:
return
for zone_name in EXIT_ZONES:
var zone = EXIT_ZONES[zone_name]
if character.position.distance_to(zone.center) < zone.radius:
_trigger_zone(zone_name)
break