Add split-screen Space Shooter with pod steering and boost.
Two-player mode with client assignment, 3D vector calibration for cylindrical pods, lives, tap/double-tap boost with low-intensity LED cooldown feedback, and Kenney sprite assets. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,10 +2,12 @@ extends Control
|
||||
|
||||
signal calibration_finished(calibration: AccelCalibration)
|
||||
|
||||
enum Phase { WAITING, RIGHT, LEFT, DONE }
|
||||
enum Phase { WAITING, NEUTRAL, RIGHT, LEFT, DONE }
|
||||
|
||||
const TRAVEL_PX := 140.0
|
||||
const PHASE_DURATION := 4.5
|
||||
const NEUTRAL_DURATION := 3.5
|
||||
const SWAY_DURATION := 5.0
|
||||
const SAMPLE_START_FRACTION := 0.5
|
||||
const CIRCLE_RADIUS := 44.0
|
||||
const NEEDLE_LEN := 58.0
|
||||
|
||||
@@ -15,6 +17,8 @@ var _guide_offset := Vector2.ZERO
|
||||
var _instruction := ""
|
||||
var _calibration := AccelCalibration.new()
|
||||
var _connection_notified := false
|
||||
var _context_label := ""
|
||||
var _last_accel := Vector3i.ZERO
|
||||
|
||||
@onready var _hint_label: Label = $HintLabel
|
||||
@onready var _start_button: Button = $StartButton
|
||||
@@ -27,12 +31,19 @@ func _ready() -> void:
|
||||
_show_waiting()
|
||||
|
||||
|
||||
func set_context_label(text: String) -> void:
|
||||
_context_label = text
|
||||
if _phase == Phase.WAITING:
|
||||
_show_waiting()
|
||||
|
||||
|
||||
func start() -> void:
|
||||
_calibration.reset()
|
||||
_connection_notified = false
|
||||
_phase = Phase.WAITING
|
||||
_phase_time = 0.0
|
||||
_guide_offset = Vector2.ZERO
|
||||
_last_accel = Vector3i.ZERO
|
||||
visible = true
|
||||
_start_button.visible = true
|
||||
_show_waiting()
|
||||
@@ -44,27 +55,37 @@ func is_active() -> bool:
|
||||
|
||||
|
||||
func feed_accel(accel: Vector3i) -> void:
|
||||
_last_accel = accel
|
||||
if not _should_record():
|
||||
return
|
||||
match _phase:
|
||||
Phase.NEUTRAL:
|
||||
_calibration.record_neutral(accel)
|
||||
Phase.RIGHT:
|
||||
_calibration.record_right(accel)
|
||||
Phase.LEFT:
|
||||
_calibration.record_left(accel)
|
||||
|
||||
|
||||
func get_live_projection() -> float:
|
||||
if _calibration.uses_vector:
|
||||
return _calibration.project(_last_accel) - _calibration.center
|
||||
return 0.0
|
||||
|
||||
|
||||
func notify_connected() -> void:
|
||||
if _phase != Phase.WAITING or _connection_notified:
|
||||
return
|
||||
_hint_label.text = "Verbunden – tippe „Kalibrierung starten“ oder warte…"
|
||||
# Automatisch starten sobald verbunden
|
||||
begin_calibration()
|
||||
|
||||
|
||||
func begin_calibration() -> void:
|
||||
if _phase == Phase.RIGHT or _phase == Phase.LEFT:
|
||||
if _phase == Phase.NEUTRAL or _phase == Phase.RIGHT or _phase == Phase.LEFT:
|
||||
return
|
||||
_connection_notified = true
|
||||
_start_button.visible = false
|
||||
_begin_phase(Phase.RIGHT)
|
||||
_begin_phase(Phase.NEUTRAL)
|
||||
|
||||
|
||||
func _on_start_pressed() -> void:
|
||||
@@ -76,34 +97,73 @@ func _process(delta: float) -> void:
|
||||
return
|
||||
|
||||
_phase_time += delta
|
||||
var t := clampf(_phase_time / PHASE_DURATION, 0.0, 1.0)
|
||||
var duration := _phase_duration()
|
||||
var t := clampf(_phase_time / duration, 0.0, 1.0)
|
||||
var ease := t * t * (3.0 - 2.0 * t)
|
||||
|
||||
match _phase:
|
||||
Phase.NEUTRAL:
|
||||
_guide_offset = Vector2.ZERO
|
||||
_instruction = _phase_instruction(
|
||||
"Halte den Zylinder senkrecht und ruhig\n(mittlere Position – nicht schwanken)"
|
||||
)
|
||||
if _phase_time >= duration:
|
||||
_begin_phase(Phase.RIGHT)
|
||||
Phase.RIGHT:
|
||||
_guide_offset.x = TRAVEL_PX * ease
|
||||
_instruction = "Neige das Gerät nach rechts – folge dem Kreis →"
|
||||
if _phase_time >= PHASE_DURATION:
|
||||
_instruction = _phase_instruction(
|
||||
"Schwank den Pod langsam nach rechts ↷\nWie ein Pendel – Spitze kippt nach rechts"
|
||||
)
|
||||
if _phase_time >= duration:
|
||||
_begin_phase(Phase.LEFT)
|
||||
Phase.LEFT:
|
||||
_guide_offset.x = -TRAVEL_PX * ease
|
||||
_instruction = "Neige das Gerät nach links – folge dem Kreis ←"
|
||||
if _phase_time >= PHASE_DURATION:
|
||||
_instruction = _phase_instruction(
|
||||
"Schwank den Pod langsam nach links ↶\nWie ein Pendel – Spitze kippt nach links"
|
||||
)
|
||||
if _phase_time >= duration:
|
||||
_finish()
|
||||
|
||||
_hint_label.text = _instruction
|
||||
var record_hint := ""
|
||||
if _phase != Phase.NEUTRAL and _phase_time < duration * SAMPLE_START_FRACTION:
|
||||
record_hint = "\n\n… halte gleich die Endposition"
|
||||
elif _phase != Phase.WAITING and _phase != Phase.DONE:
|
||||
record_hint = "\n\n✓ Aufnahme läuft"
|
||||
|
||||
_hint_label.text = _instruction + record_hint
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _phase_duration() -> float:
|
||||
match _phase:
|
||||
Phase.NEUTRAL:
|
||||
return NEUTRAL_DURATION
|
||||
return SWAY_DURATION
|
||||
|
||||
|
||||
func _should_record() -> bool:
|
||||
if _phase == Phase.WAITING or _phase == Phase.DONE:
|
||||
return false
|
||||
return _phase_time >= _phase_duration() * SAMPLE_START_FRACTION
|
||||
|
||||
|
||||
func _begin_phase(phase: Phase) -> void:
|
||||
_phase = phase
|
||||
_phase_time = 0.0
|
||||
_guide_offset = Vector2.ZERO
|
||||
match phase:
|
||||
Phase.NEUTRAL:
|
||||
_instruction = _phase_instruction(
|
||||
"Halte den Zylinder senkrecht und ruhig\n(mittlere Position – nicht schwanken)"
|
||||
)
|
||||
Phase.RIGHT:
|
||||
_instruction = "Neige das Gerät nach rechts – folge dem Kreis →"
|
||||
_instruction = _phase_instruction(
|
||||
"Schwank den Pod langsam nach rechts ↷\nWie ein Pendel – Spitze kippt nach rechts"
|
||||
)
|
||||
Phase.LEFT:
|
||||
_instruction = "Neige das Gerät nach links – folge dem Kreis ←"
|
||||
_instruction = _phase_instruction(
|
||||
"Schwank den Pod langsam nach links ↶\nWie ein Pendel – Spitze kippt nach links"
|
||||
)
|
||||
_hint_label.text = _instruction
|
||||
queue_redraw()
|
||||
|
||||
@@ -112,17 +172,27 @@ func _finish() -> void:
|
||||
_phase = Phase.DONE
|
||||
visible = false
|
||||
if _calibration.analyze():
|
||||
calibration_finished.emit(_calibration)
|
||||
calibration_finished.emit(_calibration.duplicate())
|
||||
else:
|
||||
_hint_label.text = "Kalibrierung fehlgeschlagen – zu wenig Daten"
|
||||
_hint_label.text = (
|
||||
"Kalibrierung fehlgeschlagen.\n"
|
||||
+ "Schwank weiter links/rechts oder halte die Neutralposition länger still."
|
||||
)
|
||||
visible = true
|
||||
_start_button.visible = true
|
||||
_show_waiting()
|
||||
|
||||
|
||||
func _phase_instruction(base: String) -> String:
|
||||
if _context_label == "":
|
||||
return base
|
||||
return "%s\n\n%s" % [_context_label, base]
|
||||
|
||||
|
||||
func _show_waiting() -> void:
|
||||
_instruction = "Warte auf Verbindung zu localhost:8081…"
|
||||
_hint_label.text = _instruction + "\n(Oder „Kalibrierung starten“ ohne Verbindung)"
|
||||
var prefix := "%s\n\n" % _context_label if _context_label != "" else ""
|
||||
_hint_label.text = prefix + _instruction + "\n(Oder „Kalibrierung starten“ ohne Verbindung)"
|
||||
|
||||
|
||||
func _screen_center() -> Vector2:
|
||||
@@ -134,6 +204,14 @@ func _draw() -> void:
|
||||
return
|
||||
|
||||
var pos := _screen_center() + _guide_offset
|
||||
var center := _screen_center()
|
||||
|
||||
if _phase == Phase.NEUTRAL:
|
||||
var pulse := 0.85 + 0.15 * sin(_phase_time * 4.0)
|
||||
draw_circle(center, CIRCLE_RADIUS * pulse, Color(0.35, 0.85, 0.55, 0.18))
|
||||
draw_arc(center, CIRCLE_RADIUS * pulse, 0.0, TAU, 64, Color(0.45, 0.95, 0.65), 2.5)
|
||||
draw_line(center + Vector2(-18, 0), center + Vector2(18, 0), Color(1, 1, 1, 0.35), 2.0)
|
||||
return
|
||||
|
||||
draw_circle(pos, CIRCLE_RADIUS, Color(0.25, 0.72, 0.95, 0.2))
|
||||
draw_arc(pos, CIRCLE_RADIUS, 0.0, TAU, 64, Color(0.35, 0.82, 1.0), 2.5)
|
||||
@@ -142,7 +220,6 @@ func _draw() -> void:
|
||||
draw_line(pos, needle_tip, Color(1.0, 1.0, 1.0, 0.95), 3.0)
|
||||
draw_circle(needle_tip, 5.0, Color(1.0, 1.0, 1.0))
|
||||
|
||||
var center := _screen_center()
|
||||
if _phase == Phase.RIGHT:
|
||||
draw_line(center, center + Vector2(TRAVEL_PX, 0.0), Color(1.0, 1.0, 1.0, 0.12), 1.0)
|
||||
elif _phase == Phase.LEFT:
|
||||
|
||||
Reference in New Issue
Block a user