feat: Implement core player movement, input, and player scene setup, including grid-based movement, rotation, push mechanics, and multiplayer synchronization.
This commit is contained in:
+110
-29
@@ -1,4 +1,4 @@
|
|||||||
extends Node3D
|
extends CharacterBody3D
|
||||||
|
|
||||||
# Managers
|
# Managers
|
||||||
var movement_manager
|
var movement_manager
|
||||||
@@ -133,6 +133,10 @@ var finish_locations: Array:
|
|||||||
get: return race_manager.finish_locations if race_manager else []
|
get: return race_manager.finish_locations if race_manager else []
|
||||||
|
|
||||||
var target_visual_position: Vector3 = Vector3.ZERO # For client-side smoothing
|
var target_visual_position: Vector3 = Vector3.ZERO # For client-side smoothing
|
||||||
|
var snapshot_buffer: Array = [] # Array of {time: int, pos: Vector3}
|
||||||
|
const INTERPOLATION_OFFSET: int = 150 # Increased to 150ms for better jitter buffering
|
||||||
|
var _last_received_timestamp: int = 0
|
||||||
|
var _last_movement_finish_time: int = 0
|
||||||
|
|
||||||
var spawn_point_selected = false
|
var spawn_point_selected = false
|
||||||
|
|
||||||
@@ -176,6 +180,11 @@ const AVAILABLE_CHARACTERS: Array[String] = ["Bob", "Masbro", "Gatot", "Oldpop"]
|
|||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
# Ensure name is set first (node name = authority ID for multiplayer identification)
|
# Ensure name is set first (node name = authority ID for multiplayer identification)
|
||||||
|
# 3. SET PHYSICS MODE TO FLOATING
|
||||||
|
# This prevents "grounding" friction and gravity from fighting our Tweens
|
||||||
|
motion_mode = CharacterBody3D.MOTION_MODE_FLOATING
|
||||||
|
|
||||||
|
print("[Player] initialized: ", name, " Authority: ", get_multiplayer_authority())
|
||||||
# name = str(get_multiplayer_authority()) # CRITICAL FIX: Do NOT overwrite name. Bots have authority 1 but unique names (IDs).
|
# name = str(get_multiplayer_authority()) # CRITICAL FIX: Do NOT overwrite name. Bots have authority 1 but unique names (IDs).
|
||||||
# Look up player's display name from LobbyManager
|
# Look up player's display name from LobbyManager
|
||||||
var my_id = get_multiplayer_authority()
|
var my_id = get_multiplayer_authority()
|
||||||
@@ -769,14 +778,13 @@ func apply_slow_effect(duration: float = 3.0):
|
|||||||
|
|
||||||
# Restore after duration
|
# Restore after duration
|
||||||
# Note: If they stand in the zone, this will be re-applied constantly, resetting the visual
|
# Note: If they stand in the zone, this will be re-applied constantly, resetting the visual
|
||||||
await get_tree().create_timer(duration).timeout
|
|
||||||
|
|
||||||
# Only restore if not re-applied recently (simple check: if still tinted?)
|
|
||||||
# A better way is managing a "slow_timer" but for now let's just reset if timer expires.
|
# A better way is managing a "slow_timer" but for now let's just reset if timer expires.
|
||||||
# The persistent zone logic reapplies every frame, so we want this timer to be short
|
# The persistent zone logic reapplies every frame, so we want this timer to be short
|
||||||
# OR we rely on the zone logic.
|
# OR we rely on the zone logic.
|
||||||
# The RPC call says "apply_slow_effect(0.5)", so it expires quickly.
|
# The RPC call says "apply_slow_effect(0.5)", so it expires quickly.
|
||||||
|
|
||||||
|
await get_tree().create_timer(duration).timeout
|
||||||
|
|
||||||
if movement_manager:
|
if movement_manager:
|
||||||
movement_manager.set_speed_multiplier(1.0)
|
movement_manager.set_speed_multiplier(1.0)
|
||||||
|
|
||||||
@@ -1020,18 +1028,10 @@ func _process(delta):
|
|||||||
if can_rpc():
|
if can_rpc():
|
||||||
rpc("ping_existence")
|
rpc("ping_existence")
|
||||||
else:
|
else:
|
||||||
# Client-side visual smoothing
|
# Client-side visual smoothing with snapshot interpolation
|
||||||
# Only interpolate if NOT running a movement tween, OR if the drift is large (teleport/snap)
|
# Only interpolate if NOT running a movement tween
|
||||||
if not is_player_moving:
|
if not is_player_moving:
|
||||||
var dist_sq = global_position.distance_squared_to(target_visual_position)
|
_process_remote_interpolation(delta)
|
||||||
|
|
||||||
if dist_sq > 4.0: # If distance > 2.0 units (teleport/spawn), snap immediately
|
|
||||||
global_position = target_visual_position
|
|
||||||
elif dist_sq < 0.001: # Prevent micro-jitter
|
|
||||||
global_position = target_visual_position
|
|
||||||
else:
|
|
||||||
# Interpolate towards the target position received from authority
|
|
||||||
global_position = global_position.lerp(target_visual_position, delta * 15.0)
|
|
||||||
|
|
||||||
# Delegate rotation to movement manager
|
# Delegate rotation to movement manager
|
||||||
if movement_manager:
|
if movement_manager:
|
||||||
@@ -1044,6 +1044,48 @@ func _process(delta):
|
|||||||
immunity_timer = 0
|
immunity_timer = 0
|
||||||
_apply_tint_recursive(self , Color.WHITE) # Remove immunity tint
|
_apply_tint_recursive(self , Color.WHITE) # Remove immunity tint
|
||||||
|
|
||||||
|
func _process_remote_interpolation(_delta):
|
||||||
|
if snapshot_buffer.size() < 2:
|
||||||
|
# Fallback to simple lerp if not enough snapshots
|
||||||
|
# Keep this very soft to smooth out transitions between tween and interpolation
|
||||||
|
if global_position.distance_squared_to(target_visual_position) > 0.001:
|
||||||
|
global_position = global_position.lerp(target_visual_position, _delta * 10.0)
|
||||||
|
return
|
||||||
|
|
||||||
|
var render_time = Time.get_ticks_msec() - INTERPOLATION_OFFSET
|
||||||
|
|
||||||
|
# Find the two snapshots to interpolate between
|
||||||
|
var older = snapshot_buffer[0]
|
||||||
|
var newer = null
|
||||||
|
|
||||||
|
for i in range(1, snapshot_buffer.size()):
|
||||||
|
if snapshot_buffer[i].time > render_time:
|
||||||
|
newer = snapshot_buffer[i]
|
||||||
|
older = snapshot_buffer[i-1]
|
||||||
|
break
|
||||||
|
|
||||||
|
if newer:
|
||||||
|
# Interpolate
|
||||||
|
var total_time = newer.time - older.time
|
||||||
|
var elapsed_time = render_time - older.time
|
||||||
|
var t = float(elapsed_time) / float(total_time)
|
||||||
|
|
||||||
|
var target_pos = older.pos.lerp(newer.pos, t)
|
||||||
|
|
||||||
|
# If drift is massive (teleport), snap
|
||||||
|
var dist_sq = global_position.distance_squared_to(target_pos)
|
||||||
|
if dist_sq > 16.0:
|
||||||
|
global_position = target_pos
|
||||||
|
elif dist_sq > 0.0001:
|
||||||
|
# Use a lower lerp weight for much softer tracking
|
||||||
|
global_position = global_position.lerp(target_pos, _delta * 30.0)
|
||||||
|
else:
|
||||||
|
# Extrapolate or just stay at latest if render_time is ahead of buffer
|
||||||
|
var latest = snapshot_buffer[-1]
|
||||||
|
if global_position.distance_squared_to(latest.pos) > 0.001:
|
||||||
|
global_position = global_position.lerp(latest.pos, _delta * 20.0) # Slower catchup to latest
|
||||||
|
|
||||||
|
|
||||||
@rpc("any_peer", "call_local")
|
@rpc("any_peer", "call_local")
|
||||||
func ping_existence():
|
func ping_existence():
|
||||||
# This just lets other clients know this player exists
|
# This just lets other clients know this player exists
|
||||||
@@ -1053,11 +1095,19 @@ func ping_existence():
|
|||||||
var last_sent_position: Vector3
|
var last_sent_position: Vector3
|
||||||
|
|
||||||
func _physics_process(delta):
|
func _physics_process(delta):
|
||||||
|
# Sync position periodically (Heartbeat / Smoothing)
|
||||||
if is_multiplayer_authority():
|
if is_multiplayer_authority():
|
||||||
if global_position.distance_squared_to(last_sent_position) > 0.001:
|
# OPTIMIZATION: Only send smoothing updates if we ARE NOT currently mid-tween
|
||||||
if can_rpc():
|
# The start/end of paths are already synced via start_movement_along_path.
|
||||||
rpc("remote_set_position", global_position)
|
# Sending packets during a tween just adds noise/jitter for the client.
|
||||||
last_sent_position = global_position
|
if not is_player_moving:
|
||||||
|
var current_world_pos = global_position
|
||||||
|
var last_sent_pos = get_meta("_last_sent_pos", Vector3.ZERO)
|
||||||
|
|
||||||
|
# Use a slightly larger threshold (0.05) to ignore micro-vibrations
|
||||||
|
if current_world_pos.distance_to(last_sent_pos) > 0.05:
|
||||||
|
set_meta("_last_sent_pos", current_world_pos)
|
||||||
|
rpc("remote_set_position", current_world_pos, Time.get_ticks_msec())
|
||||||
|
|
||||||
# NOTE: Finish line checking removed - game uses cycle-based goals system now
|
# NOTE: Finish line checking removed - game uses cycle-based goals system now
|
||||||
|
|
||||||
@@ -1286,17 +1336,19 @@ func simple_move_to(grid_position: Vector2i):
|
|||||||
func move_player_to_clicked_position(grid_position: Vector2i):
|
func move_player_to_clicked_position(grid_position: Vector2i):
|
||||||
movement_manager.move_to_clicked_position(grid_position)
|
movement_manager.move_to_clicked_position(grid_position)
|
||||||
|
|
||||||
@rpc("any_peer", "call_local")
|
@rpc("any_peer", "call_remote", "unreliable")
|
||||||
func start_movement_along_path(path: Array, clear_visual: bool = true):
|
func start_movement_along_path(path: Array, clear_visual: bool = true):
|
||||||
|
if is_player_moving:
|
||||||
|
return # ALREADY MOVING. Guard against redundant RPCs or interruptions.
|
||||||
|
|
||||||
print("[Player] %s starting move along path: %s" % [name, path])
|
print("[Player] %s starting move along path: %s" % [name, path])
|
||||||
|
|
||||||
# SERVER-SIDE VIOLATION CHECK (for Stop n Go)
|
# SERVER-SIDE VIOLATION CHECK (for Stop n Go)
|
||||||
if multiplayer.is_server() and LobbyManager.game_mode == "Stop n Go":
|
if multiplayer.is_server() and LobbyManager.game_mode == "Stop n Go":
|
||||||
var main = get_tree().root.get_node_or_null("Main")
|
var main = get_tree().root.get_node_or_null("Main")
|
||||||
if main:
|
if main:
|
||||||
var sng_manager = main.get_node_or_null("StopNGoManager")
|
var sng_manager = main.get_node_or_null("StopNGoManager")
|
||||||
if sng_manager and sng_manager.has_method("check_movement_violation"):
|
if sng_manager and sng_manager.has_method("check_movement_violation"):
|
||||||
# Check if the START of this movement is a violation
|
|
||||||
# We use current_position as the 'from' point.
|
|
||||||
var target_pos = Vector2i(path[0].x, path[0].y) if path.size() > 0 else current_position
|
var target_pos = Vector2i(path[0].x, path[0].y) if path.size() > 0 else current_position
|
||||||
sng_manager.check_movement_violation(name.to_int(), current_position, target_pos)
|
sng_manager.check_movement_violation(name.to_int(), current_position, target_pos)
|
||||||
|
|
||||||
@@ -1304,10 +1356,10 @@ func start_movement_along_path(path: Array, clear_visual: bool = true):
|
|||||||
if path.size() > 0:
|
if path.size() > 0:
|
||||||
target_position = Vector2i(path[-1].x, path[-1].y)
|
target_position = Vector2i(path[-1].x, path[-1].y)
|
||||||
|
|
||||||
# FORCE SNAP START: Ensure we start animating from our actual grid position
|
# RELAXED SNAP START: Only snap if the visual node is far from where it should be (> 0.5 units)
|
||||||
# This prevents "jumps" if the visual node drifted or was set via global_position vs position mismatch
|
# This prevents "twitching" when a move starts if the node was still lerping slowly to its final spot.
|
||||||
var start_world_pos = grid_to_world(current_position)
|
var start_world_pos = grid_to_world(current_position)
|
||||||
if global_position.distance_squared_to(start_world_pos) > 0.001:
|
if global_position.distance_to(start_world_pos) > 0.5:
|
||||||
global_position = start_world_pos
|
global_position = start_world_pos
|
||||||
|
|
||||||
if _movement_tween:
|
if _movement_tween:
|
||||||
@@ -1330,7 +1382,7 @@ func start_movement_along_path(path: Array, clear_visual: bool = true):
|
|||||||
is_player_moving = false
|
is_player_moving = false
|
||||||
_movement_tween = null
|
_movement_tween = null
|
||||||
target_position = Vector2i(-1, -1)
|
target_position = Vector2i(-1, -1)
|
||||||
|
_last_movement_finish_time = Time.get_ticks_msec()
|
||||||
print("[Player] %s finished move. %s -> %s" % [name, old_pos, current_position])
|
print("[Player] %s finished move. %s -> %s" % [name, old_pos, current_position])
|
||||||
|
|
||||||
if is_carrying_tekton and is_instance_valid(carried_tekton):
|
if is_carrying_tekton and is_instance_valid(carried_tekton):
|
||||||
@@ -1369,6 +1421,10 @@ func start_movement_along_path(path: Array, clear_visual: bool = true):
|
|||||||
if enhanced_gridmap:
|
if enhanced_gridmap:
|
||||||
enhanced_gridmap.clear_path_visualization()
|
enhanced_gridmap.clear_path_visualization()
|
||||||
|
|
||||||
|
# Reset interpolation buffer to prevent "jumping back" 150ms after tween ends
|
||||||
|
snapshot_buffer.clear()
|
||||||
|
target_visual_position = grid_to_world(current_position)
|
||||||
|
|
||||||
# Check for buffered input
|
# Check for buffered input
|
||||||
if movement_manager and movement_manager.has_method("_on_movement_finished"):
|
if movement_manager and movement_manager.has_method("_on_movement_finished"):
|
||||||
movement_manager._on_movement_finished()
|
movement_manager._on_movement_finished()
|
||||||
@@ -1470,9 +1526,34 @@ static func reset_race_stats():
|
|||||||
race_mgr.lap1_finishers = 0
|
race_mgr.lap1_finishers = 0
|
||||||
race_mgr.lap2_finishers = 0
|
race_mgr.lap2_finishers = 0
|
||||||
|
|
||||||
@rpc("any_peer", "call_local", "unreliable")
|
@rpc("any_peer", "call_remote", "unreliable")
|
||||||
func remote_set_position(authority_position):
|
func remote_set_position(authority_position: Vector3, timestamp: int = -1):
|
||||||
# Don't snap directly, update target for interpolation
|
if timestamp == -1:
|
||||||
|
timestamp = Time.get_ticks_msec()
|
||||||
|
|
||||||
|
# DISCARD STALE PACKETS:
|
||||||
|
# 1. If we've already seen a newer packet
|
||||||
|
if timestamp <= _last_received_timestamp:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 2. If this packet was sent before we finished our last authoritative movement step
|
||||||
|
# (Preventing "past" packets from overriding the "present" destination)
|
||||||
|
if timestamp <= _last_movement_finish_time:
|
||||||
|
return
|
||||||
|
|
||||||
|
_last_received_timestamp = timestamp
|
||||||
|
|
||||||
|
# Add to snapshot buffer
|
||||||
|
snapshot_buffer.append({"time": timestamp, "pos": authority_position})
|
||||||
|
|
||||||
|
# Ensure chronological order (in case of network jitter/reordering)
|
||||||
|
snapshot_buffer.sort_custom(func(a, b): return a.time < b.time)
|
||||||
|
|
||||||
|
# Keep buffer size manageable
|
||||||
|
if snapshot_buffer.size() > 30:
|
||||||
|
snapshot_buffer.pop_front()
|
||||||
|
|
||||||
|
# Update target visual position as fallback for simpler lerping
|
||||||
target_visual_position = authority_position
|
target_visual_position = authority_position
|
||||||
|
|
||||||
@rpc("any_peer", "call_local")
|
@rpc("any_peer", "call_local")
|
||||||
|
|||||||
@@ -18,24 +18,20 @@ func _process(delta):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Continuous movement input
|
# Continuous movement input
|
||||||
var target_position = player.current_position
|
var move_vec = Vector2i.ZERO
|
||||||
|
if Input.is_action_pressed("move_north"): move_vec.y -= 1
|
||||||
|
if Input.is_action_pressed("move_south"): move_vec.y += 1
|
||||||
|
if Input.is_action_pressed("move_east"): move_vec.x += 1
|
||||||
|
if Input.is_action_pressed("move_west"): move_vec.x -= 1
|
||||||
|
|
||||||
if Input.is_action_pressed("move_north"):
|
# Fallback for explicit diagonal actions (if mapped)
|
||||||
target_position += Vector2i(0, -1)
|
if move_vec == Vector2i.ZERO:
|
||||||
elif Input.is_action_pressed("move_northeast"):
|
if Input.is_action_pressed("move_northeast"): move_vec = Vector2i(1, -1)
|
||||||
target_position += Vector2i(1, -1)
|
elif Input.is_action_pressed("move_southeast"): move_vec = Vector2i(1, 1)
|
||||||
elif Input.is_action_pressed("move_east"):
|
elif Input.is_action_pressed("move_southwest"): move_vec = Vector2i(-1, 1)
|
||||||
target_position += Vector2i(1, 0)
|
elif Input.is_action_pressed("move_northwest"): move_vec = Vector2i(-1, -1)
|
||||||
elif Input.is_action_pressed("move_southeast"):
|
|
||||||
target_position += Vector2i(1, 1)
|
|
||||||
elif Input.is_action_pressed("move_south"):
|
|
||||||
target_position += Vector2i(0, 1)
|
|
||||||
elif Input.is_action_pressed("move_southwest"):
|
|
||||||
target_position += Vector2i(-1, 1)
|
|
||||||
elif Input.is_action_pressed("move_west"):
|
|
||||||
target_position += Vector2i(-1, 0)
|
|
||||||
elif Input.is_action_pressed("move_northwest"):
|
|
||||||
target_position += Vector2i(-1, -1)
|
|
||||||
|
|
||||||
# Action inputs (still momentary)
|
# Action inputs (still momentary)
|
||||||
if Input.is_action_just_pressed("action_grab"):
|
if Input.is_action_just_pressed("action_grab"):
|
||||||
@@ -43,7 +39,16 @@ func _process(delta):
|
|||||||
elif Input.is_action_just_pressed("action_put"):
|
elif Input.is_action_just_pressed("action_put"):
|
||||||
player.auto_put_item()
|
player.auto_put_item()
|
||||||
|
|
||||||
if target_position != player.current_position:
|
if move_vec != Vector2i.ZERO:
|
||||||
|
# Calculate target relative to intent (future position) to prevent zigzagging
|
||||||
|
var reference_pos = player.current_position
|
||||||
|
if movement_manager.is_moving:
|
||||||
|
if not movement_manager.movement_queue.is_empty():
|
||||||
|
reference_pos = movement_manager.movement_queue[-1]
|
||||||
|
elif player.target_position != Vector2i(-1, -1):
|
||||||
|
reference_pos = player.target_position
|
||||||
|
|
||||||
|
var target_position = reference_pos + move_vec
|
||||||
movement_manager.simple_move_to(target_position)
|
movement_manager.simple_move_to(target_position)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func initialize(p_player: Node3D, p_gridmap: Node):
|
|||||||
use_diagonal_movement = player.use_diagonal_movement
|
use_diagonal_movement = player.use_diagonal_movement
|
||||||
|
|
||||||
signal movement_finished
|
signal movement_finished
|
||||||
var buffered_direction: Vector2i = Vector2i.ZERO
|
var movement_queue: Array[Vector2i] = [] # Queue of target grid positions
|
||||||
var current_move_direction: Vector2i = Vector2i.ZERO
|
var current_move_direction: Vector2i = Vector2i.ZERO
|
||||||
|
|
||||||
func _process(delta):
|
func _process(delta):
|
||||||
@@ -49,10 +49,17 @@ func _can_rpc() -> bool:
|
|||||||
|
|
||||||
func simple_move_to(grid_position: Vector2i) -> bool:
|
func simple_move_to(grid_position: Vector2i) -> bool:
|
||||||
if is_moving:
|
if is_moving:
|
||||||
var direction = grid_position - player.current_position
|
# Check if we are already moving to this position or if it's already queued
|
||||||
if direction != current_move_direction:
|
var current_target = player.target_position
|
||||||
buffer_move_input(direction)
|
if movement_queue.is_empty():
|
||||||
return false
|
if current_target == grid_position:
|
||||||
|
return false
|
||||||
|
elif movement_queue[-1] == grid_position:
|
||||||
|
return false
|
||||||
|
|
||||||
|
if movement_queue.size() < 1: # Buffer at most 1 future move
|
||||||
|
movement_queue.append(grid_position)
|
||||||
|
return true
|
||||||
|
|
||||||
if not player.is_multiplayer_authority():
|
if not player.is_multiplayer_authority():
|
||||||
print("[Move] Failed: Not authority for %s (Authority: %d, My Peer: %d)" % [player.name, player.get_multiplayer_authority(), player.multiplayer.get_unique_id()])
|
print("[Move] Failed: Not authority for %s (Authority: %d, My Peer: %d)" % [player.name, player.get_multiplayer_authority(), player.multiplayer.get_unique_id()])
|
||||||
@@ -67,13 +74,15 @@ func simple_move_to(grid_position: Vector2i) -> bool:
|
|||||||
if main and main.goals_cycle_manager and not main.goals_cycle_manager.is_match_active:
|
if main and main.goals_cycle_manager and not main.goals_cycle_manager.is_match_active:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
||||||
var distance: int
|
var distance: int
|
||||||
if use_diagonal_movement:
|
if use_diagonal_movement:
|
||||||
distance = max(abs(grid_position.x - player.current_position.x), abs(grid_position.y - player.current_position.y))
|
distance = max(abs(grid_position.x - player.current_position.x), abs(grid_position.y - player.current_position.y))
|
||||||
else:
|
else:
|
||||||
distance = abs(grid_position.x - player.current_position.x) + abs(grid_position.y - player.current_position.y)
|
distance = abs(grid_position.x - player.current_position.x) + abs(grid_position.y - player.current_position.y)
|
||||||
|
|
||||||
|
if distance > movement_range:
|
||||||
|
return false
|
||||||
|
|
||||||
if not enhanced_gridmap.is_position_valid(grid_position):
|
if not enhanced_gridmap.is_position_valid(grid_position):
|
||||||
# print("[Move] Failed: Position not valid on GridMap %s" % grid_position)
|
# print("[Move] Failed: Position not valid on GridMap %s" % grid_position)
|
||||||
return false
|
return false
|
||||||
@@ -130,11 +139,13 @@ func simple_move_to(grid_position: Vector2i) -> bool:
|
|||||||
current_move_direction = grid_position - player.current_position
|
current_move_direction = grid_position - player.current_position
|
||||||
|
|
||||||
if player.is_multiplayer_authority():
|
if player.is_multiplayer_authority():
|
||||||
|
# Authority starts their own tween locally
|
||||||
var is_bot = player.is_bot or player.is_in_group("Bots")
|
var is_bot = player.is_bot or player.is_in_group("Bots")
|
||||||
|
player.start_movement_along_path(path, not is_bot)
|
||||||
|
|
||||||
|
# Authority sends RPC to others (call_remote) to start their tweens
|
||||||
if _can_rpc():
|
if _can_rpc():
|
||||||
player.rpc("start_movement_along_path", path, not is_bot)
|
player.rpc("start_movement_along_path", path, not is_bot)
|
||||||
else:
|
|
||||||
player.start_movement_along_path(path, not is_bot)
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@@ -250,14 +261,17 @@ func set_speed_multiplier(multiplier: float):
|
|||||||
# Let's check how 'start_movement_along_path' is implemented in player.gd.
|
# Let's check how 'start_movement_along_path' is implemented in player.gd.
|
||||||
|
|
||||||
|
|
||||||
func buffer_move_input(direction: Vector2i):
|
|
||||||
buffered_direction = direction
|
|
||||||
|
|
||||||
func _on_movement_finished():
|
func _on_movement_finished():
|
||||||
if buffered_direction != Vector2i.ZERO:
|
if not movement_queue.is_empty():
|
||||||
var target = player.current_position + buffered_direction
|
var next_target = movement_queue.pop_front()
|
||||||
buffered_direction = Vector2i.ZERO
|
# Use a small delay or call_deferred to avoid recursion issues,
|
||||||
simple_move_to(target)
|
# but keep it snappy by executing immediately if possible.
|
||||||
|
if not simple_move_to(next_target):
|
||||||
|
# If next move failed, clear queue and signal finished
|
||||||
|
movement_queue.clear()
|
||||||
|
current_move_direction = Vector2i.ZERO
|
||||||
|
emit_signal("movement_finished")
|
||||||
else:
|
else:
|
||||||
current_move_direction = Vector2i.ZERO
|
current_move_direction = Vector2i.ZERO
|
||||||
emit_signal("movement_finished")
|
emit_signal("movement_finished")
|
||||||
|
|||||||
Reference in New Issue
Block a user