feat: Add core manager scripts for camera context, player input, special tiles, and a powerup inventory UI.
This commit is contained in:
@@ -1,194 +1,61 @@
|
|||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
# CameraContextManager
|
# CameraContextManager - Smoothly follows player and clamps to arena edges
|
||||||
# Handles camera position based on player's row on the board.
|
|
||||||
|
|
||||||
# var screen_shake_manager: Node # Removed
|
|
||||||
var camera: Camera3D
|
var camera: Camera3D
|
||||||
var player: Node3D
|
var player: Node3D
|
||||||
|
|
||||||
# Configuration Map: Row Threshold -> Target Position
|
# Configuration
|
||||||
# We use a list of dictionaries to define ranges.
|
@export var smooth_speed: float = 5.0
|
||||||
# Setup A: Rows 0-5
|
@export var z_offset: float = 12.0
|
||||||
# Setup B: Rows 6-9
|
@export var default_y: float = 19.636
|
||||||
# Setup C: Rows 10+
|
|
||||||
var camera_setups = [
|
|
||||||
{ "max_row": 5, "position": Vector3(7.0, 19.636, 15.0) },
|
|
||||||
{ "max_row": 9, "position": Vector3(7.0, 19.636, 19.0) },
|
|
||||||
{ "max_row": 999, "position": Vector3(7.0, 19.22636, 22.5) } # Default/Bottom
|
|
||||||
]
|
|
||||||
|
|
||||||
var unique_id: int
|
# Bounds Definitions { min_x, max_x, min_z, max_z }
|
||||||
var current_row_setup: int = -1
|
var bounds_freemode = { "min_x": 3.0, "max_x": 11.0, "min_z": 15.0, "max_z": 22.5 }
|
||||||
var current_col_setup: int = -1
|
var bounds_stop_n_go = { "min_x": 3.0, "max_x": 18.5, "min_z": 15.0, "max_z": 17.5 }
|
||||||
|
var bounds_doors = { "min_x": 7.0, "max_x": 7.0, "min_z": 25.8, "max_z": 25.8 } # Static overlook
|
||||||
|
|
||||||
func initialize(p_camera: Camera3D, _p_shake_manager: Node):
|
func initialize(p_camera: Camera3D, _p_shake_manager: Node):
|
||||||
camera = p_camera
|
camera = p_camera
|
||||||
# screen_shake_manager = p_shake_manager
|
|
||||||
print("[CameraContextManager] Initialized with camera")
|
print("[CameraContextManager] Initialized with camera")
|
||||||
|
|
||||||
func set_player(p_player: Node3D):
|
func set_player(p_player: Node3D):
|
||||||
player = p_player
|
player = p_player
|
||||||
unique_id = p_player.name.to_int()
|
print("[CameraContextManager] Player set: ", player.name)
|
||||||
|
|
||||||
# Try to get movement manager (might be initializing child)
|
|
||||||
var movement_mgr = player.get("movement_manager")
|
|
||||||
if not movement_mgr and "movement_manager" in player:
|
|
||||||
movement_mgr = player.movement_manager
|
|
||||||
|
|
||||||
var has_snap_signal = player.has_signal("position_changed")
|
|
||||||
|
|
||||||
if movement_mgr and has_snap_signal:
|
|
||||||
# 1. Follow during movement finished
|
|
||||||
if not movement_mgr.movement_finished.is_connected(_on_player_moved):
|
|
||||||
movement_mgr.movement_finished.connect(_on_player_moved)
|
|
||||||
|
|
||||||
# 2. Snap on spawn/teleport (Resets setups for immediate catch-up)
|
|
||||||
if not player.position_changed.is_connected(_on_player_snapped):
|
|
||||||
player.position_changed.connect(_on_player_snapped)
|
|
||||||
|
|
||||||
print("[CameraContextManager] Successfully connected to all player signals")
|
|
||||||
else:
|
|
||||||
print("[CameraContextManager] Warning: movement_manager or signal missing. Retrying in 0.5s...")
|
|
||||||
await get_tree().create_timer(0.5).timeout
|
|
||||||
if player == p_player:
|
|
||||||
set_player(p_player)
|
|
||||||
return # Exit current (failed) initialization
|
|
||||||
|
|
||||||
_update_camera_target()
|
|
||||||
|
|
||||||
func _on_player_moved():
|
func _physics_process(delta):
|
||||||
# Normal movement finish (keeps hysteresis)
|
if not player or not camera or not is_instance_valid(player):
|
||||||
_update_camera_target()
|
|
||||||
|
|
||||||
func _on_player_snapped():
|
|
||||||
# Sharp snap (spawn/teleport) - Reset setup indices to force immediate re-eval
|
|
||||||
current_row_setup = -1
|
|
||||||
current_col_setup = -1
|
|
||||||
# print("[CameraContextManager] Player snapped, resetting setups")
|
|
||||||
_update_camera_target()
|
|
||||||
|
|
||||||
func _update_camera_target():
|
|
||||||
if not player or not camera:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
var current_row = player.current_position.y
|
var target_pos = _calculate_target_position()
|
||||||
var current_col = player.current_position.x
|
|
||||||
|
|
||||||
|
# Smoothly interpolate to target
|
||||||
|
camera.position = camera.position.lerp(target_pos, smooth_speed * delta)
|
||||||
|
|
||||||
|
func _calculate_target_position() -> Vector3:
|
||||||
|
var player_pos = player.global_position
|
||||||
|
|
||||||
|
# Initial target based on player position + offsets
|
||||||
|
var target_x = player_pos.x
|
||||||
|
var target_y = default_y
|
||||||
|
var target_z = player_pos.z + z_offset
|
||||||
|
|
||||||
|
# Apply Mode-Specific Clamping
|
||||||
var mode = LobbyManager.get_game_mode()
|
var mode = LobbyManager.get_game_mode()
|
||||||
|
var bounds = bounds_freemode # Default
|
||||||
|
|
||||||
if mode == GameMode.Mode.STOP_N_GO:
|
if mode == GameMode.Mode.STOP_N_GO:
|
||||||
_update_stop_n_go_camera(current_row, current_col)
|
bounds = bounds_stop_n_go
|
||||||
elif mode == GameMode.Mode.TEKTON_DOORS:
|
elif mode == GameMode.Mode.TEKTON_DOORS:
|
||||||
_update_tekton_doors_camera()
|
bounds = bounds_doors
|
||||||
else:
|
target_y = 32.3 # Doors uses a higher overlook
|
||||||
_update_freemode_camera(current_row, current_col)
|
|
||||||
|
|
||||||
func _update_stop_n_go_camera(current_row: int, current_col: int):
|
|
||||||
# --- STOP N GO: 4 Columns (X), 2 Rows (Y/Z) ---
|
|
||||||
# Columns: 0-5, 6-10, 11-15, 16-21
|
|
||||||
# Rows: 0-4, 5-9
|
|
||||||
|
|
||||||
# 1. Column Logic (4 Zones)
|
|
||||||
if current_col_setup == -1:
|
|
||||||
if current_col <= 5: current_col_setup = 0
|
|
||||||
elif current_col <= 10: current_col_setup = 1
|
|
||||||
elif current_col <= 15: current_col_setup = 2
|
|
||||||
else: current_col_setup = 3
|
|
||||||
|
|
||||||
match current_col_setup:
|
|
||||||
0: # Leftmost
|
|
||||||
if current_col >= 7: current_col_setup = 1
|
|
||||||
1: # Mid-Left
|
|
||||||
if current_col <= 4: current_col_setup = 0
|
|
||||||
elif current_col >= 12: current_col_setup = 2
|
|
||||||
2: # Mid-Right
|
|
||||||
if current_col <= 9: current_col_setup = 1
|
|
||||||
elif current_col >= 17: current_col_setup = 3
|
|
||||||
3: # Rightmost
|
|
||||||
if current_col <= 14: current_col_setup = 2
|
|
||||||
|
|
||||||
# 2. Row Logic (2 Zones)
|
|
||||||
if current_row_setup == -1:
|
|
||||||
if current_row <= 4: current_row_setup = 0
|
|
||||||
else: current_row_setup = 1
|
|
||||||
|
|
||||||
match current_row_setup:
|
# Clamp X and Z
|
||||||
0: # Top
|
target_x = clamp(target_x, bounds.min_x, bounds.max_x)
|
||||||
if current_row >= 6: current_row_setup = 1
|
target_z = clamp(target_z, bounds.min_z, bounds.max_z)
|
||||||
1: # Bottom
|
|
||||||
if current_row <= 3: current_row_setup = 0
|
|
||||||
|
|
||||||
# 3. Target Calculation
|
|
||||||
var target_x = 3.0 + (current_col_setup * 5.0) # approx centers for 4 zones in 22 cols
|
|
||||||
var target_y = 19.636
|
|
||||||
var target_z = 15.0 if current_row_setup == 0 else 17.5
|
|
||||||
|
|
||||||
# Adjust specific X centers for the 22-col field
|
# Special case for Setup C in Freemode (Lower Y at bottom edges)
|
||||||
match current_col_setup:
|
if mode == GameMode.Mode.FREEMODE and target_z > 21.0:
|
||||||
0: target_x = 3.0
|
target_y = 19.22636
|
||||||
1: target_x = 8.5
|
|
||||||
2: target_x = 13.5
|
|
||||||
3: target_x = 18.5
|
|
||||||
|
|
||||||
_apply_camera_tween(Vector3(target_x, target_y, target_z))
|
|
||||||
|
|
||||||
func _update_tekton_doors_camera():
|
|
||||||
# --- TEKTON DOORS: Static Overlook ---
|
|
||||||
# Grid is 14x14, center is approx (7, 7)
|
|
||||||
# User requested position: Vector3(7.0, 31.0, 25.5)
|
|
||||||
_apply_camera_tween(Vector3(7.0, 32.3, 25.8))
|
|
||||||
|
|
||||||
func _update_freemode_camera(current_row: int, current_col: int):
|
|
||||||
# --- FREEMODE: 3x3 Grid ---
|
|
||||||
# Zone thresholds 4, 9
|
|
||||||
|
|
||||||
# Row Hysteresis
|
|
||||||
if current_row_setup == -1:
|
|
||||||
if current_row <= 4: current_row_setup = 0
|
|
||||||
elif current_row <= 9: current_row_setup = 1
|
|
||||||
else: current_row_setup = 2
|
|
||||||
|
|
||||||
match current_row_setup:
|
return Vector3(target_x, target_y, target_z)
|
||||||
0: # Top
|
|
||||||
if current_row >= 6: current_row_setup = 1
|
|
||||||
1: # Mid
|
|
||||||
if current_row <= 3: current_row_setup = 0
|
|
||||||
elif current_row >= 11: current_row_setup = 2
|
|
||||||
2: # Bot
|
|
||||||
if current_row <= 8: current_row_setup = 1
|
|
||||||
|
|
||||||
# Col Hysteresis
|
|
||||||
if current_col_setup == -1:
|
|
||||||
if current_col <= 4: current_col_setup = 0
|
|
||||||
elif current_col <= 9: current_col_setup = 1
|
|
||||||
else: current_col_setup = 2
|
|
||||||
|
|
||||||
match current_col_setup:
|
|
||||||
0: # Left
|
|
||||||
if current_col >= 6: current_col_setup = 1
|
|
||||||
1: # Center
|
|
||||||
if current_col <= 3: current_col_setup = 0
|
|
||||||
elif current_col >= 11: current_col_setup = 2
|
|
||||||
2: # Right
|
|
||||||
if current_col <= 8: current_col_setup = 1
|
|
||||||
|
|
||||||
var target_z = 15.0
|
|
||||||
var target_y = 19.636
|
|
||||||
var target_x = 7.0
|
|
||||||
|
|
||||||
match current_row_setup:
|
|
||||||
0: target_z = 15.0
|
|
||||||
1: target_z = 19.0
|
|
||||||
2: target_z = 22.5; target_y = 19.22636
|
|
||||||
|
|
||||||
match current_col_setup:
|
|
||||||
0: target_x = 3.0 # Left zone center
|
|
||||||
1: target_x = 7.0 # Main center
|
|
||||||
2: target_x = 11.0 # Right zone center
|
|
||||||
|
|
||||||
_apply_camera_tween(Vector3(target_x, target_y, target_z))
|
|
||||||
|
|
||||||
func _apply_camera_tween(target_pos: Vector3):
|
|
||||||
if camera.position != target_pos:
|
|
||||||
var tween = create_tween()
|
|
||||||
tween.tween_property(camera, "position", target_pos, 0.4).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
|
||||||
|
|||||||
@@ -97,22 +97,22 @@ func handle_unhandled_input(event):
|
|||||||
KEY_KP_1, KEY_1, KEY_KP_2, KEY_2, KEY_KP_3, KEY_3, KEY_KP_4, KEY_4:
|
KEY_KP_1, KEY_1, KEY_KP_2, KEY_2, KEY_KP_3, KEY_3, KEY_KP_4, KEY_4:
|
||||||
var mode = LobbyManager.get_game_mode()
|
var mode = LobbyManager.get_game_mode()
|
||||||
var is_restricted = GameMode.is_restricted(mode)
|
var is_restricted = GameMode.is_restricted(mode)
|
||||||
match event.keycode:
|
if is_restricted:
|
||||||
KEY_KP_1, KEY_1:
|
match event.keycode:
|
||||||
player.activate_powerup(0) # FASTER_SPEED
|
KEY_KP_1, KEY_1:
|
||||||
KEY_KP_2, KEY_2:
|
player.activate_powerup(0) # FASTER_SPEED
|
||||||
if is_restricted:
|
KEY_KP_2, KEY_2:
|
||||||
player.activate_powerup(1) # AREA_FREEZE (Restricted)
|
player.activate_powerup(3) # INVISIBLE_MODE (Ghost is now 2)
|
||||||
else:
|
else:
|
||||||
player.activate_powerup(2) # BLOCK_FLOOR (Free)
|
match event.keycode:
|
||||||
KEY_KP_3, KEY_3:
|
KEY_KP_1, KEY_1:
|
||||||
if is_restricted:
|
player.activate_powerup(0) # FASTER_SPEED
|
||||||
player.activate_powerup(3) # INVISIBLE_MODE (Restricted)
|
KEY_KP_2, KEY_2:
|
||||||
else:
|
player.activate_powerup(2) # BLOCK_FLOOR
|
||||||
player.activate_powerup(1) # AREA_FREEZE (Free)
|
KEY_KP_3, KEY_3:
|
||||||
KEY_KP_4, KEY_4:
|
player.activate_powerup(1) # AREA_FREEZE
|
||||||
if not is_restricted:
|
KEY_KP_4, KEY_4:
|
||||||
player.activate_powerup(3) # INVISIBLE_MODE (Free)
|
player.activate_powerup(3) # INVISIBLE_MODE
|
||||||
# KEY_R:
|
# KEY_R:
|
||||||
# player.auto_put_item()
|
# player.auto_put_item()
|
||||||
KEY_Q:
|
KEY_Q:
|
||||||
|
|||||||
@@ -138,10 +138,17 @@ func initialize(p_player: Node3D, p_gridmap: Node):
|
|||||||
# Helper: Item ID to Effect Enum
|
# Helper: Item ID to Effect Enum
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
func get_effect_from_item(item_id: int) -> int:
|
func get_effect_from_item(item_id: int) -> int:
|
||||||
|
var mode = LobbyManager.get_game_mode()
|
||||||
|
var is_restricted = GameMode.is_restricted(mode)
|
||||||
|
|
||||||
match item_id:
|
match item_id:
|
||||||
11: return SpecialEffect.FASTER_SPEED
|
11: return SpecialEffect.FASTER_SPEED
|
||||||
12: return SpecialEffect.AREA_FREEZE
|
12:
|
||||||
13: return SpecialEffect.BLOCK_FLOOR
|
if is_restricted: return -1
|
||||||
|
return SpecialEffect.AREA_FREEZE
|
||||||
|
13:
|
||||||
|
if is_restricted: return -1
|
||||||
|
return SpecialEffect.BLOCK_FLOOR
|
||||||
14: return SpecialEffect.INVISIBLE_MODE
|
14: return SpecialEffect.INVISIBLE_MODE
|
||||||
_: return -1
|
_: return -1
|
||||||
|
|
||||||
@@ -457,7 +464,7 @@ func spawn_powerups_around(center: Vector2i, force_powerups: bool = true):
|
|||||||
var mode = LobbyManager.get_game_mode()
|
var mode = LobbyManager.get_game_mode()
|
||||||
var is_restricted = GameMode.is_restricted(mode)
|
var is_restricted = GameMode.is_restricted(mode)
|
||||||
if is_restricted:
|
if is_restricted:
|
||||||
item_id = [11, 12, 14].pick_random()
|
item_id = [11, 14].pick_random()
|
||||||
else:
|
else:
|
||||||
item_id = rng.randi_range(11, 14)
|
item_id = rng.randi_range(11, 14)
|
||||||
|
|
||||||
|
|||||||
@@ -27,16 +27,22 @@ func _ready():
|
|||||||
# 14: INVISIBLE_MODE (3) -> GhostBtn
|
# 14: INVISIBLE_MODE (3) -> GhostBtn
|
||||||
|
|
||||||
# We use 0, 1, 2, 3 to match SpecialTilesManager.SpecialEffect enum
|
# We use 0, 1, 2, 3 to match SpecialTilesManager.SpecialEffect enum
|
||||||
_setup_btn(0, container.get_node_or_null("SpeedBtn"))
|
var speed_btn = container.get_node_or_null("SpeedBtn")
|
||||||
_setup_btn(1, container.get_node_or_null("FreezeAreaBtn"))
|
var freeze_btn = container.get_node_or_null("FreezeAreaBtn")
|
||||||
var wall_btn = container.get_node_or_null("WallBtn")
|
var wall_btn = container.get_node_or_null("WallBtn")
|
||||||
_setup_btn(2, wall_btn)
|
var ghost_btn = container.get_node_or_null("GhostBtn")
|
||||||
|
|
||||||
var mode = LobbyManager.get_game_mode()
|
var mode = LobbyManager.get_game_mode()
|
||||||
var is_restricted = GameMode.is_restricted(mode)
|
var is_restricted = GameMode.is_restricted(mode)
|
||||||
if wall_btn and is_restricted:
|
|
||||||
wall_btn.visible = false # Hide Wall Power-up in restricted modes
|
_setup_btn(0, speed_btn)
|
||||||
_setup_btn(3, container.get_node_or_null("GhostBtn"))
|
_setup_btn(1, freeze_btn)
|
||||||
|
_setup_btn(2, wall_btn)
|
||||||
|
_setup_btn(3, ghost_btn)
|
||||||
|
|
||||||
|
if is_restricted:
|
||||||
|
if wall_btn: wall_btn.visible = false
|
||||||
|
if freeze_btn: freeze_btn.visible = false
|
||||||
|
|
||||||
print("[PowerUpUI] UI Initialization Complete. Mapped %d buttons." % icon_containers.size())
|
print("[PowerUpUI] UI Initialization Complete. Mapped %d buttons." % icon_containers.size())
|
||||||
|
|
||||||
@@ -108,11 +114,11 @@ func _setup_btn(effect_id: int, btn: Button):
|
|||||||
var mode = LobbyManager.get_game_mode()
|
var mode = LobbyManager.get_game_mode()
|
||||||
var is_restricted = GameMode.is_restricted(mode)
|
var is_restricted = GameMode.is_restricted(mode)
|
||||||
if is_restricted:
|
if is_restricted:
|
||||||
# Restricted Mapping: 1, 2, 3 (No Wall)
|
# Restricted Mapping: 1, 2
|
||||||
match effect_id:
|
match effect_id:
|
||||||
0: key_text = "1"
|
0: key_text = "1"
|
||||||
1: key_text = "2" # Freeze is now 2
|
3: key_text = "2" # Ghost is now 2
|
||||||
3: key_text = "3" # Ghost is now 3
|
_: key_text = ""
|
||||||
else:
|
else:
|
||||||
# Free Mode Mapping: 1, 2, 3, 4 (Original)
|
# Free Mode Mapping: 1, 2, 3, 4 (Original)
|
||||||
match effect_id:
|
match effect_id:
|
||||||
|
|||||||
Reference in New Issue
Block a user