Refactor Godot architecture into AppShell, modular menus and game domains
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
class_name JuggleBoardGame
|
||||
extends Node2D
|
||||
## Controller del gameplay JuggleBoard. Menu, HUD e audio sono moduli separati.
|
||||
|
||||
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()
|
||||
for index in range(names.size()):
|
||||
var color_name: String = names[index]
|
||||
var home := Vector2(JuggleBoardConfig.HOME_X, JuggleBoardConfig.LANE_TOP + index * JuggleBoardConfig.LANE_GAP)
|
||||
var left := Vector2(JuggleBoardConfig.LEFT_X, JuggleBoardConfig.LANE_TOP + index * JuggleBoardConfig.LANE_GAP)
|
||||
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)
|
||||
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)
|
||||
Reference in New Issue
Block a user