extends Node2D
const LANE_X := [200.0, 540.0, 880.0] const LANE_Y := 1400.0 const SPAWN_Y := -100.0 const HIT_WINDOW := 110.0 const GAME_DURATION := 90.0
var score := 0 var combo := 0 var max_combo := 0 var battery := 100.0 var time_left := GAME_DURATION var active_notes: Array = [] var lane_flash := [0.0, 0.0, 0.0] var shield_active := false var double_active := false var game_over := false var note_speed := 550.0 var spawn_rate := 4.0
var score_label: Label var combo_label: Label var battery_bar: ProgressBar var time_label: Label var city_glow: ColorRect var decision_panel: Panel var lanes_visual: Array = [] var notes_layer: Node2D var end_label: Label
func _ready(): _build_scene() _update_ui()
func _build_scene(): # --- خلفية --- var bg := ColorRect.new() bg.color = Color(0.03, 0.03, 0.08) bg.size = Vector2(1080, 1920) bg.z_index = -10 add_child(bg)
# --- توهج المدينة ---
city_glow = ColorRect.new()
city_glow.color = Color(0.1, 0.6, 1.0, 0.0)
city_glow.size = Vector2(1080, 1920)
city_glow.z_index = -5
add_child(city_glow)
# --- المسارات الثلاثة ---
for i in range(3):
var circle := ColorRect.new()
circle.size = Vector2(200, 200)
circle.position = Vector2(LANE_X[i] - 100, LANE_Y - 100)
circle.color = Color(0.2, 0.8, 1.0, 0.25)
add_child(circle)
lanes_visual.append(circle)
# --- حاوية النبضات ---
notes_layer = Node2D.new()
notes_layer.name = "Notes"
add_child(notes_layer)
# --- الواجهة ---
var ui := CanvasLayer.new()
add_child(ui)
score_label = Label.new()
score_label.position = Vector2(40, 40)
score_label.add_theme_font_size_override("font_size", 52)
score_label.add_theme_color_override("font_color", Color(0.4, 0.9, 1.0))
score_label.text = "Score: 0"
ui.add_child(score_label)
combo_label = Label.new()
combo_label.position = Vector2(40, 110)
combo_label.add_theme_font_size_override("font_size", 42)
combo_label.add_theme_color_override("font_color", Color(1, 0.8, 0.2))
combo_label.text = ""
ui.add_child(combo_label)
time_label = Label.new()
time_label.position = Vector2(880, 40)
time_label.add_theme_font_size_override("font_size", 42)
time_label.add_theme_color_override("font_color", Color(1, 1, 1))
time_label.text = "90s"
ui.add_child(time_label)
battery_bar = ProgressBar.new()
battery_bar.position = Vector2(40, 1800)
battery_bar.size = Vector2(1000, 50)
battery_bar.max_value = 100
battery_bar.value = 100
ui.add_child(battery_bar)
# --- لوحة القرار ---
decision_panel = Panel.new()
decision_panel.position = Vector2(140, 700)
decision_panel.size = Vector2(800, 500)
decision_panel.visible = false
ui.add_child(decision_panel)
var title := Label.new()
title.text = "⚡ اختر!"
title.position = Vector2(0, 5)
title.size = Vector2(800, 50)
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
title.add_theme_font_size_override("font_size", 36)
decision_panel.add_child(title)
var labels := ["⚡ سرعة (x2 نقاط)", "🛡️ حماية", "💥 انفجار"]
for i in range(3):
var b := Button.new()
b.text = labels[i]
b.position = Vector2(50, 70 + i * 135)
b.size = Vector2(700, 110)
b.add_theme_font_size_override("font_size", 40)
b.pressed.connect(_on_decision.bind(i))
decision_panel.add_child(b)
# --- شاشة النهاية (مخفية) ---
end_label = Label.new()
end_label.visible = false
end_label.add_theme_font_size_override("font_size", 64)
end_label.add_theme_color_override("font_color", Color(1, 0.9, 0.3))
end_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
end_label.position = Vector2(140, 700)
end_label.size = Vector2(800, 600)
ui.add_child(end_label)
func _process(delta): if game_over: return
# المؤقت
time_left -= delta
time_label.text = str(int(max(0, time_left))) + "s"
if time_left <= 0:
_end_game()
return
# صعوبة تدريجية
var elapsed = GAME_DURATION - time_left
note_speed = 550.0 + elapsed * 6.0
spawn_rate = 4.0 + elapsed * 0.05
# إنتاج نبضات
if randf() < delta * spawn_rate:
_spawn_note()
# تحريك النبضات
var to_remove := []
for i in range(active_notes.size()):
var n = active_notes[i]
n.node.position.y += note_speed * delta
if n.node.position.y > LANE_Y + HIT_WINDOW:
to_remove.append(i)
_on_miss()
for i in range(to_remove.size() - 1, -1, -1):
_remove_note(to_remove[i])
# تلاشي المسارات
for i in range(3):
lane_flash[i] = max(0.0, lane_flash[i] - delta * 4.0)
lanes_visual[i].color = Color(0.2, 0.8, 1.0, 0.25 + lane_flash[i] * 0.6)
# تلاشي التوهج
city_glow.color.a = max(0.0, city_glow.color.a - delta * 1.5)
_update_ui()
func _spawn_note(): var lane := randi() % 3 var rect := ColorRect.new() rect.size = Vector2(160, 32) rect.position = Vector2(LANE_X[lane] - 80, SPAWN_Y) rect.color = Color(1.0, 0.3, 0.6) if lane != 1 else Color(0.3, 1.0, 0.6) notes_layer.add_child(rect) active_notes.append({"node": rect, "lane": lane})
func _remove_note(idx): if idx < 0 or idx >= active_notes.size(): return var n = active_notes[idx] if is_instance_valid(n.node): n.node.queue_free() active_notes.remove_at(idx)
func _input(event): if game_over or decision_panel.visible: return if event is InputEventKey and event.pressed and not event.echo: var lane := -1 if event.keycode == KEY_A or event.keycode == KEY_LEFT: lane = 0 elif event.keycode == KEY_S or event.keycode == KEY_DOWN: lane = 1 elif event.keycode == KEY_D or event.keycode == KEY_RIGHT: lane = 2 if lane != -1: _try_hit(lane) get_viewport().set_input_as_handled()
func _try_hit(lane): lane_flash[lane] = 1.0
var best_idx := -1
var best_dist := 99999.0
for i in range(active_notes.size()):
var n = active_notes[i]
if n.lane != lane:
continue
var dist = abs(n.node.position.y - LANE_Y)
if dist < best_dist:
best_dist = dist
best_idx = i
if best_idx != -1 and best_dist <= HIT_WINDOW:
_on_hit()
_remove_note(best_idx)
else:
_on_miss()
func _on_hit(): combo += 1 max_combo = max(max_combo, combo) var mult := 2 if double_active else 1 score += 10 * combo * mult city_glow.color.a = min(0.5, city_glow.color.a + 0.15)
if combo > 0 and combo % 10 == 0:
_show_decision()
func _on_miss(): if shield_active: shield_active = false return combo = 0 battery -= 8.0 if battery <= 0: battery = 0 _end_game()
func _show_decision(): decision_panel.visible = true set_process(false)
func _on_decision(choice): decision_panel.visible = false set_process(true)
match choice:
0:
double_active = true
await get_tree().create_timer(8.0).timeout
double_active = false
1:
shield_active = true
2:
var count = active_notes.size()
for n in active_notes:
if is_instance_valid(n.node):
n.node.queue_free()
active_notes.clear()
for _i in range(count):
_on_hit()
func _update_ui(): score_label.text = "Score: %d" % score combo_label.text = "Combo x%d" % combo if combo > 1 else "" battery_bar.value = battery
func _end_game(): if game_over: return game_over = true set_process(false)
for n in active_notes:
if is_instance_valid(n.node):
n.node.queue_free()
active_notes.clear()
end_label.text = "🎮 النهاية!\n\nScore: %d\nMax Combo: x%d\n\n[R] إعادة" % [score, max_combo]
end_label.visible = true
func _unhandled_input(event): if game_over and event is InputEventKey and event.pressed and event.keycode == KEY_R: get_tree().reload_current_scene()