Files
autgame/games/juggleboard/scripts/game_controller.gd
T

275 lines
6.9 KiB
GDScript

class_name JuggleBoardGame
extends Node2D
## Controller del gameplay JuggleBoard. Menu, HUD e audio sono moduli separati.
signal return_requested
const BOARD_SCRIPT := preload("res://games/juggleboard/scripts/components/board.gd")
const BALL_SCRIPT := preload("res://games/juggleboard/scripts/components/ball.gd")
const HUD_SCRIPT := preload("res://games/juggleboard/scripts/ui/game_hud.gd")
const AUDIO_SCRIPT := preload("res://games/juggleboard/scripts/audio/game_audio.gd")
const OPTIONS_SCENE := preload("res://games/juggleboard/scenes/options_menu.tscn")
const LOCK_SCENE := preload("res://games/juggleboard/scenes/lock_menu.tscn")
const END_SCENE := preload("res://games/juggleboard/scenes/victory_overlay.tscn")
var balls: Dictionary = {}
var target_color := ""
var score := 0
var combo := 0.0
var lives := 4
var elapsed := 0.0
var window_timer := 0.0
var window_time := Settings.start_time
var next_milestone := 100
var end_shown := false
var hud: JuggleBoardHud
var audio: JuggleBoardAudio
var options_menu: CanvasLayer
var lock_menu: CanvasLayer
var end_overlay: CanvasLayer
func _ready() -> void:
add_child(BOARD_SCRIPT.new())
_build_balls()
_build_services()
_new_game()
audio.start_music()
func _process(delta: float) -> void:
elapsed += delta
if target_color == "" or Settings.game_mode == 1:
return
if combo > 0.0:
combo = maxf(0.0, combo - _combo_decay() * delta)
window_timer += delta
if window_timer >= window_time:
_on_timeout()
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
_toggle_options()
return
var position := Vector2.INF
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
position = event.position
elif event is InputEventScreenTouch and event.pressed:
position = event.position
if position != Vector2.INF:
_handle_click(position)
func _build_balls() -> void:
var names := JuggleBoardConfig.COLORS.keys()
var width_scale := JuggleBoardConfig.horizontal_scale(get_viewport().get_visible_rect().size.x)
for index in range(names.size()):
var color_name: String = names[index]
var lane_y := JuggleBoardConfig.LANE_TOP + index * JuggleBoardConfig.LANE_GAP
# Il fondale si espande in orizzontale: alloggiamenti e biglie seguono
# la stessa scala X, mentre dimensione e coordinate Y restano invariate.
var home := Vector2(JuggleBoardConfig.HOME_X * width_scale, lane_y)
var left := Vector2(JuggleBoardConfig.LEFT_X * width_scale, lane_y)
var ball: Area2D = BALL_SCRIPT.new()
add_child(ball)
ball.setup(color_name, JuggleBoardConfig.COLORS[color_name], home, left, JuggleBoardConfig.BALL_RADIUS)
ball.returned.connect(_on_ball_returned)
balls[color_name] = ball
func _build_services() -> void:
hud = HUD_SCRIPT.new()
hud.options_requested.connect(_request_options)
hud.back_requested.connect(func(): return_requested.emit())
add_child(hud)
audio = AUDIO_SCRIPT.new()
add_child(audio)
options_menu = OPTIONS_SCENE.instantiate()
add_child(options_menu)
lock_menu = LOCK_SCENE.instantiate()
lock_menu.unlocked.connect(_open_options)
add_child(lock_menu)
end_overlay = END_SCENE.instantiate()
end_overlay.reset_requested.connect(_reset_after_end)
add_child(end_overlay)
func _request_options() -> void:
lock_menu.show_menu()
func _open_options() -> void:
options_menu.show_menu()
func _toggle_options() -> void:
if options_menu.visible:
options_menu.hide_menu()
else:
_request_options()
func _new_game() -> void:
score = 0
combo = 0.0
elapsed = 0.0
window_timer = 0.0
window_time = Settings.start_time
next_milestone = 100
lives = 4
for ball in balls.values():
ball.reset()
_pick_target()
hud.reset(score, combo, lives, Settings.game_mode == 2)
func _handle_click(position: Vector2) -> void:
var clicked: Area2D
var best := JuggleBoardConfig.HIT_RADIUS
for ball in balls.values():
if not ball.is_home():
continue
var distance := position.distance_to(ball.position)
if distance < best:
best = distance
clicked = ball
if clicked == null:
return
if clicked.color_name == target_color:
_on_correct_ball(clicked)
else:
_on_wrong_ball()
func _on_correct_ball(ball: Area2D) -> void:
combo = minf(float(Settings.max_combo), combo + 1.0)
score += _points_for_combo()
audio.play_ball(ball.color_name)
var current_time := _current_time()
ball.launch(current_time)
window_time = current_time
window_timer = 0.0
_pick_target()
hud.set_score(score, combo)
_check_milestone()
_check_victory()
func _on_wrong_ball() -> void:
audio.play(audio.error)
_apply_combo_penalty()
hud.set_score(score, combo)
if Settings.game_mode == 2:
_lose_life()
func _on_timeout() -> void:
audio.play(audio.timeout)
_apply_combo_penalty()
window_timer = 0.0
_pick_target()
hud.set_score(score, combo)
if Settings.game_mode == 2:
_lose_life()
func _apply_combo_penalty() -> void:
if Settings.gentle_errors:
combo = maxf(0.0, combo - 1.0)
else:
combo = 0.0
func _on_ball_returned() -> void:
if target_color == "":
_pick_target()
func _current_time() -> float:
return lerpf(Settings.start_time, Settings.min_time, _difficulty())
func _difficulty() -> float:
var factor := clampf(score / Settings.score_ramp, 0.0, 1.0)
return factor * factor * (3.0 - 2.0 * factor)
func _combo_decay() -> float:
var combo_penalty := maxf(0.0, combo - Settings.combo_decay_threshold) * Settings.combo_decay_rate
return combo_penalty + _difficulty() * Settings.difficulty_decay_rate
func _points_for_combo() -> int:
return int(combo) * Settings.points_per_combo
func _pick_target() -> void:
var homes := _home_names()
if homes.is_empty():
target_color = ""
hud.set_target(Color.WHITE, false)
return
var pool := homes.duplicate()
if target_color in pool and pool.size() > 1:
pool.erase(target_color)
target_color = pool[randi() % pool.size()]
hud.set_target(JuggleBoardConfig.COLORS[target_color])
func _home_names() -> Array:
var result := []
for color_name in balls:
if balls[color_name].is_home():
result.append(color_name)
return result
func _check_victory() -> void:
if Settings.game_mode != 2 and not end_shown and score >= Settings.target_score:
_show_end(true)
func _lose_life() -> void:
if end_shown:
return
lives -= 1
audio.play(audio.lose_heart)
hud.hearts.set_lives(maxi(lives, 0))
if lives <= 0:
_show_end(false)
func _show_end(victory: bool) -> void:
if end_shown:
return
end_shown = true
audio.stop_music()
audio.play(audio.victory if victory else audio.defeat)
get_tree().paused = true
if victory:
end_overlay.show_victory(score)
else:
end_overlay.show_game_over(score)
func _reset_after_end() -> void:
get_tree().paused = false
end_shown = false
end_overlay.hide_victory()
audio.start_music()
_new_game()
func _check_milestone() -> void:
if score < next_milestone:
return
var celebrated := next_milestone
while next_milestone <= score:
celebrated = next_milestone
next_milestone *= 10
audio.play(audio.milestone)
hud.celebrate(celebrated)