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:
2026-06-06 18:56:55 +02:00
co-authored by Cursor
parent 70e66f4d60
commit a32a223881
635 changed files with 15367 additions and 52 deletions
+82 -33
View File
@@ -3,18 +3,33 @@ extends RefCounted
enum Axis { X, Y, Z }
const MIN_SAMPLES := 25
const TAIL_FRACTION := 0.55
var axis: Axis = Axis.X
var sign: float = 1.0
var center: float = 0.0
var suggested_threshold: float = 3000.0
var direction: Vector3 = Vector3.RIGHT
var neutral: Vector3 = Vector3.ZERO
var uses_vector: bool = false
var _samples_neutral: Array[Vector3] = []
var _samples_right: Array[Vector3] = []
var _samples_left: Array[Vector3] = []
func reset() -> void:
_samples_neutral.clear()
_samples_right.clear()
_samples_left.clear()
uses_vector = false
direction = Vector3.RIGHT
neutral = Vector3.ZERO
func record_neutral(accel: Vector3i) -> void:
_samples_neutral.append(Vector3(accel))
func record_right(accel: Vector3i) -> void:
@@ -26,56 +41,74 @@ func record_left(accel: Vector3i) -> void:
func is_ready() -> bool:
return _samples_right.size() >= 20 and _samples_left.size() >= 20
return (
_samples_neutral.size() >= MIN_SAMPLES
and _samples_right.size() >= MIN_SAMPLES
and _samples_left.size() >= MIN_SAMPLES
)
func analyze() -> bool:
if not is_ready():
return false
var mean_r := _mean(_samples_right)
var mean_l := _mean(_samples_left)
var diff := mean_r - mean_l
var mean_n := _tail_mean(_samples_neutral)
var mean_r := _tail_mean(_samples_right)
var mean_l := _tail_mean(_samples_left)
var delta := mean_r - mean_l
var diffs := [absf(diff.x), absf(diff.y), absf(diff.z)]
axis = Axis.X
var best: float = diffs[0]
if diffs[1] > best:
best = diffs[1]
axis = Axis.Y
if diffs[2] > best:
best = diffs[2]
axis = Axis.Z
if delta.length_squared() < 150.0 * 150.0:
return false
var delta := _component(diff, axis)
sign = 1.0 if delta >= 0.0 else -1.0
direction = delta.normalized()
neutral = mean_n
uses_vector = true
sign = 1.0
_set_axis_from_direction()
var all_samples: Array[Vector3] = []
all_samples.append_array(_samples_right)
all_samples.append_array(_samples_left)
var projections: Array[float] = []
for sample in all_samples:
projections.append(_component(sample, axis) * sign)
projections.sort()
center = projections[projections.size() / 2]
var neutral_proj: Array[float] = []
for sample in _samples_neutral:
neutral_proj.append(_project_vec(sample))
neutral_proj.sort()
center = neutral_proj[neutral_proj.size() / 2]
var deviations: Array[float] = []
for value in projections:
for value in neutral_proj:
deviations.append(absf(value - center))
deviations.sort()
var median_idx := deviations.size() / 2
var spread := deviations[median_idx] if not deviations.is_empty() else 500.0
suggested_threshold = clampf(spread * 1.35, 200.0, 12000.0)
var spread := deviations[deviations.size() / 2] if not deviations.is_empty() else 500.0
suggested_threshold = clampf(spread * 2.2 + 180.0, 250.0, 8000.0)
var right_proj := _project_vec(mean_r) - center
var left_proj := _project_vec(mean_l) - center
var separation := absf(right_proj - left_proj)
if separation < suggested_threshold * 2.0:
suggested_threshold = clampf(separation * 0.18, 250.0, 8000.0)
return true
func project(accel: Vector3i) -> float:
if uses_vector:
return _project_vec(Vector3(accel))
return _component(Vector3(accel), axis) * sign
func duplicate() -> AccelCalibration:
var copy := AccelCalibration.new()
copy.axis = axis
copy.sign = sign
copy.center = center
copy.suggested_threshold = suggested_threshold
copy.direction = direction
copy.neutral = neutral
copy.uses_vector = uses_vector
return copy
func axis_name() -> String:
if uses_vector:
return "3D-Schwank (%+.1f,%+.1f,%+.1f)" % [direction.x, direction.y, direction.z]
match axis:
Axis.X:
return "x"
@@ -86,6 +119,26 @@ func axis_name() -> String:
return "?"
func _project_vec(v: Vector3) -> float:
return (v - neutral).dot(direction)
func _tail_mean(samples: Array[Vector3]) -> Vector3:
var start_idx := int(samples.size() * TAIL_FRACTION)
return _mean(samples.slice(start_idx))
func _set_axis_from_direction() -> void:
var abs_dir := direction.abs()
axis = Axis.X
var best := abs_dir.x
if abs_dir.y > best:
best = abs_dir.y
axis = Axis.Y
if abs_dir.z > best:
axis = Axis.Z
func _mean(samples: Array[Vector3]) -> Vector3:
if samples.is_empty():
return Vector3.ZERO
@@ -95,10 +148,6 @@ func _mean(samples: Array[Vector3]) -> Vector3:
return sum / float(samples.size())
func _mean_scalar(samples: Array[Vector3], ax: Axis) -> float:
return _component(_mean(samples), ax)
func _component(v: Vector3, ax: Axis) -> float:
match ax:
Axis.X: