Compare commits
5
Commits
a32a223881
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
639f4b088d | ||
|
|
2138039b72 | ||
|
|
142968038c | ||
|
|
130532c4c9 | ||
|
|
54d42dade9 |
+1
-1
@@ -12,7 +12,7 @@ config_version=5
|
|||||||
|
|
||||||
config/name="alox_test_game"
|
config/name="alox_test_game"
|
||||||
run/main_scene="res://scenes/menu.tscn"
|
run/main_scene="res://scenes/menu.tscn"
|
||||||
config/features=PackedStringArray("4.6", "GL Compatibility")
|
config/features=PackedStringArray("4.7", "GL Compatibility")
|
||||||
config/icon="res://icon.svg"
|
config/icon="res://icon.svg"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|||||||
@@ -115,6 +115,12 @@ theme_override_font_sizes/font_size = 140
|
|||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
text = "—"
|
text = "—"
|
||||||
|
|
||||||
|
[node name="TargetSwatch" type="ColorRect" parent="UiLayer/Center"]
|
||||||
|
custom_minimum_size = Vector2(220, 220)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
color = Color(0.2, 0.22, 0.28, 1)
|
||||||
|
|
||||||
[node name="TimerLabel" type="Label" parent="UiLayer/Center"]
|
[node name="TimerLabel" type="Label" parent="UiLayer/Center"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_colors/font_color = Color(1, 0.88, 0.4, 1)
|
theme_override_colors/font_color = Color(1, 0.88, 0.4, 1)
|
||||||
|
|||||||
+12
-5
@@ -29,12 +29,12 @@ anchor_top = 0.5
|
|||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
anchor_bottom = 0.5
|
anchor_bottom = 0.5
|
||||||
offset_left = -180.0
|
offset_left = -180.0
|
||||||
offset_top = -280.0
|
offset_top = -320.0
|
||||||
offset_right = 180.0
|
offset_right = 180.0
|
||||||
offset_bottom = 280.0
|
offset_bottom = 320.0
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
theme_override_constants/separation = 16
|
theme_override_constants/separation = 14
|
||||||
alignment = 1
|
alignment = 1
|
||||||
|
|
||||||
[node name="Title" type="Label" parent="CenterBox"]
|
[node name="Title" type="Label" parent="CenterBox"]
|
||||||
@@ -76,12 +76,19 @@ size_flags_horizontal = 4
|
|||||||
theme_override_font_sizes/font_size = 18
|
theme_override_font_sizes/font_size = 18
|
||||||
text = "Pong"
|
text = "Pong"
|
||||||
|
|
||||||
[node name="ColorGameButton" type="Button" parent="CenterBox"]
|
[node name="DigitsGameButton" type="Button" parent="CenterBox"]
|
||||||
custom_minimum_size = Vector2(280, 48)
|
custom_minimum_size = Vector2(280, 48)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 4
|
size_flags_horizontal = 4
|
||||||
theme_override_font_sizes/font_size = 18
|
theme_override_font_sizes/font_size = 18
|
||||||
text = "Color Game"
|
text = "Zahlen-Spiel"
|
||||||
|
|
||||||
|
[node name="ColorsGameButton" type="Button" parent="CenterBox"]
|
||||||
|
custom_minimum_size = Vector2(280, 48)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "Farben-Spiel"
|
||||||
|
|
||||||
[node name="SpaceShooterButton" type="Button" parent="CenterBox"]
|
[node name="SpaceShooterButton" type="Button" parent="CenterBox"]
|
||||||
custom_minimum_size = Vector2(280, 48)
|
custom_minimum_size = Vector2(280, 48)
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
[gd_scene load_steps=6 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/orientation_gizmo.gd" id="1_gizmo"]
|
||||||
|
|
||||||
|
[sub_resource type="CylinderMesh" id="CylinderMesh_body"]
|
||||||
|
top_radius = 0.35
|
||||||
|
bottom_radius = 0.35
|
||||||
|
height = 1.1
|
||||||
|
radial_segments = 24
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="Mat_body"]
|
||||||
|
albedo_color = Color(0.42, 0.48, 0.58, 1)
|
||||||
|
roughness = 0.55
|
||||||
|
|
||||||
|
[sub_resource type="CylinderMesh" id="CylinderMesh_cap"]
|
||||||
|
top_radius = 0.38
|
||||||
|
bottom_radius = 0.38
|
||||||
|
height = 0.08
|
||||||
|
radial_segments = 24
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="Mat_top"]
|
||||||
|
albedo_color = Color(1, 0.55, 0.12, 1)
|
||||||
|
roughness = 0.4
|
||||||
|
emission_enabled = true
|
||||||
|
emission = Color(0.85, 0.4, 0.05, 1)
|
||||||
|
emission_energy_multiplier = 0.35
|
||||||
|
|
||||||
|
[node name="OrientationGizmo" type="SubViewportContainer"]
|
||||||
|
custom_minimum_size = Vector2(220, 220)
|
||||||
|
stretch = true
|
||||||
|
script = ExtResource("1_gizmo")
|
||||||
|
|
||||||
|
[node name="SubViewport" type="SubViewport" parent="."]
|
||||||
|
own_world_3d = true
|
||||||
|
transparent_bg = true
|
||||||
|
handle_input_locally = false
|
||||||
|
size = Vector2i(220, 220)
|
||||||
|
render_target_update_mode = 4
|
||||||
|
|
||||||
|
[node name="Camera3D" type="Camera3D" parent="SubViewport"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3.2)
|
||||||
|
projection = 1
|
||||||
|
size = 2.6
|
||||||
|
near = 0.05
|
||||||
|
far = 50.0
|
||||||
|
|
||||||
|
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="SubViewport"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 4)
|
||||||
|
light_energy = 1.2
|
||||||
|
shadow_enabled = false
|
||||||
|
|
||||||
|
[node name="FillLight" type="DirectionalLight3D" parent="SubViewport"]
|
||||||
|
transform = Transform3D(-0.866025, -0.353553, 0.353553, 0, 0.707107, 0.707107, -0.5, 0.612372, -0.612372, 0, 0, 0)
|
||||||
|
light_energy = 0.4
|
||||||
|
shadow_enabled = false
|
||||||
|
|
||||||
|
[node name="Pod" type="Node3D" parent="SubViewport"]
|
||||||
|
|
||||||
|
[node name="Body" type="MeshInstance3D" parent="SubViewport/Pod"]
|
||||||
|
mesh = SubResource("CylinderMesh_body")
|
||||||
|
surface_material_override/0 = SubResource("Mat_body")
|
||||||
|
|
||||||
|
[node name="TopCap" type="MeshInstance3D" parent="SubViewport/Pod"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.59, 0)
|
||||||
|
mesh = SubResource("CylinderMesh_cap")
|
||||||
|
surface_material_override/0 = SubResource("Mat_top")
|
||||||
|
|
||||||
|
[node name="TopLabel" type="Label3D" parent="SubViewport/Pod"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 0, -1, 0, 1, 0, 0, -0.66, 0)
|
||||||
|
pixel_size = 0.01
|
||||||
|
billboard = 0
|
||||||
|
text = "TOP"
|
||||||
|
font_size = 42
|
||||||
|
outline_size = 8
|
||||||
|
modulate = Color(1, 0.95, 0.85, 1)
|
||||||
|
outline_modulate = Color(0.05, 0.05, 0.08, 1)
|
||||||
+26
-101
@@ -1,7 +1,6 @@
|
|||||||
[gd_scene format=3 uid="uid://bwayhk38lpl3u"]
|
[gd_scene format=3 uid="uid://bwayhk38lpl3u"]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://scripts/space_shooter.gd" id="1_shooter"]
|
[ext_resource type="Script" path="res://scripts/space_shooter.gd" id="1_shooter"]
|
||||||
[ext_resource type="Script" path="res://scripts/calibration_overlay.gd" id="2_calib"]
|
|
||||||
|
|
||||||
[node name="SpaceShooter" type="Node2D"]
|
[node name="SpaceShooter" type="Node2D"]
|
||||||
script = ExtResource("1_shooter")
|
script = ExtResource("1_shooter")
|
||||||
@@ -40,54 +39,19 @@ theme_override_colors/font_color = Color(0.75, 0.8, 0.9, 1)
|
|||||||
theme_override_font_sizes/font_size = 14
|
theme_override_font_sizes/font_size = 14
|
||||||
text = "Starting…"
|
text = "Starting…"
|
||||||
|
|
||||||
[node name="LeftScoreLabel" type="Label" parent="UiLayer"]
|
|
||||||
offset_left = 24.0
|
|
||||||
offset_top = 52.0
|
|
||||||
offset_right = 280.0
|
|
||||||
offset_bottom = 120.0
|
|
||||||
theme_override_colors/font_color = Color(0.45, 0.75, 1, 1)
|
|
||||||
theme_override_font_sizes/font_size = 18
|
|
||||||
text = "Links · —"
|
|
||||||
|
|
||||||
[node name="RightScoreLabel" type="Label" parent="UiLayer"]
|
|
||||||
anchors_preset = 1
|
|
||||||
anchor_left = 1.0
|
|
||||||
anchor_right = 1.0
|
|
||||||
offset_left = -280.0
|
|
||||||
offset_top = 52.0
|
|
||||||
offset_right = -24.0
|
|
||||||
offset_bottom = 120.0
|
|
||||||
grow_horizontal = 0
|
|
||||||
theme_override_colors/font_color = Color(1, 0.72, 0.35, 1)
|
|
||||||
theme_override_font_sizes/font_size = 18
|
|
||||||
horizontal_alignment = 2
|
|
||||||
text = "Rechts · —"
|
|
||||||
|
|
||||||
[node name="RecalibButton" type="Button" parent="UiLayer"]
|
|
||||||
visible = false
|
|
||||||
anchors_preset = 1
|
|
||||||
anchor_left = 1.0
|
|
||||||
anchor_right = 1.0
|
|
||||||
offset_left = -240.0
|
|
||||||
offset_top = 12.0
|
|
||||||
offset_right = -130.0
|
|
||||||
offset_bottom = 44.0
|
|
||||||
grow_horizontal = 0
|
|
||||||
text = "Neu kalibrieren"
|
|
||||||
|
|
||||||
[node name="SetupPanel" type="VBoxContainer" parent="UiLayer"]
|
[node name="SetupPanel" type="VBoxContainer" parent="UiLayer"]
|
||||||
anchors_preset = 8
|
anchors_preset = 8
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
anchor_top = 0.5
|
anchor_top = 0.5
|
||||||
anchor_right = 0.5
|
anchor_right = 0.5
|
||||||
anchor_bottom = 0.5
|
anchor_bottom = 0.5
|
||||||
offset_left = -280.0
|
offset_left = -300.0
|
||||||
offset_top = -160.0
|
offset_top = -220.0
|
||||||
offset_right = 280.0
|
offset_right = 300.0
|
||||||
offset_bottom = 160.0
|
offset_bottom = 220.0
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
theme_override_constants/separation = 18
|
theme_override_constants/separation = 16
|
||||||
alignment = 1
|
alignment = 1
|
||||||
|
|
||||||
[node name="SetupTitle" type="Label" parent="UiLayer/SetupPanel"]
|
[node name="SetupTitle" type="Label" parent="UiLayer/SetupPanel"]
|
||||||
@@ -102,34 +66,36 @@ layout_mode = 2
|
|||||||
theme_override_colors/font_color = Color(0.6, 0.65, 0.75, 1)
|
theme_override_colors/font_color = Color(0.6, 0.65, 0.75, 1)
|
||||||
theme_override_font_sizes/font_size = 14
|
theme_override_font_sizes/font_size = 14
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
text = "Split-Screen · Pod-Zuweisung"
|
text = "Raster · kippen oder Lenkrad drehen"
|
||||||
|
|
||||||
[node name="ClientGrid" type="GridContainer" parent="UiLayer/SetupPanel"]
|
[node name="SlotControls" type="HBoxContainer" parent="UiLayer/SetupPanel"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 4
|
size_flags_horizontal = 4
|
||||||
theme_override_constants/h_separation = 16
|
theme_override_constants/separation = 12
|
||||||
theme_override_constants/v_separation = 12
|
alignment = 1
|
||||||
columns = 2
|
|
||||||
|
|
||||||
[node name="LeftCaption" type="Label" parent="UiLayer/SetupPanel/ClientGrid"]
|
[node name="RemoveSlotButton" type="Button" parent="UiLayer/SetupPanel/SlotControls"]
|
||||||
|
custom_minimum_size = Vector2(44, 36)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_colors/font_color = Color(0.45, 0.75, 1, 1)
|
text = "−"
|
||||||
|
|
||||||
|
[node name="SlotCountLabel" type="Label" parent="UiLayer/SetupPanel/SlotControls"]
|
||||||
|
custom_minimum_size = Vector2(110, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(0.85, 0.88, 0.95, 1)
|
||||||
theme_override_font_sizes/font_size = 16
|
theme_override_font_sizes/font_size = 16
|
||||||
text = "Links"
|
horizontal_alignment = 1
|
||||||
|
text = "2 Spieler"
|
||||||
|
|
||||||
[node name="LeftClientOption" type="OptionButton" parent="UiLayer/SetupPanel/ClientGrid"]
|
[node name="AddSlotButton" type="Button" parent="UiLayer/SetupPanel/SlotControls"]
|
||||||
custom_minimum_size = Vector2(220, 0)
|
custom_minimum_size = Vector2(44, 36)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
text = "+"
|
||||||
|
|
||||||
[node name="RightCaption" type="Label" parent="UiLayer/SetupPanel/ClientGrid"]
|
[node name="PlayerSlots" type="VBoxContainer" parent="UiLayer/SetupPanel"]
|
||||||
layout_mode = 2
|
|
||||||
theme_override_colors/font_color = Color(1, 0.72, 0.35, 1)
|
|
||||||
theme_override_font_sizes/font_size = 16
|
|
||||||
text = "Rechts"
|
|
||||||
|
|
||||||
[node name="RightClientOption" type="OptionButton" parent="UiLayer/SetupPanel/ClientGrid"]
|
|
||||||
custom_minimum_size = Vector2(220, 0)
|
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
theme_override_constants/separation = 10
|
||||||
|
|
||||||
[node name="SetupHintLabel" type="Label" parent="UiLayer/SetupPanel"]
|
[node name="SetupHintLabel" type="Label" parent="UiLayer/SetupPanel"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
@@ -145,45 +111,4 @@ layout_mode = 2
|
|||||||
size_flags_horizontal = 4
|
size_flags_horizontal = 4
|
||||||
disabled = true
|
disabled = true
|
||||||
theme_override_font_sizes/font_size = 18
|
theme_override_font_sizes/font_size = 18
|
||||||
text = "Kalibrierung starten"
|
text = "Spiel starten"
|
||||||
|
|
||||||
[node name="CalibrationOverlay" type="Control" parent="UiLayer"]
|
|
||||||
visible = false
|
|
||||||
layout_mode = 3
|
|
||||||
anchors_preset = 15
|
|
||||||
anchor_right = 1.0
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
grow_vertical = 2
|
|
||||||
mouse_filter = 0
|
|
||||||
script = ExtResource("2_calib")
|
|
||||||
|
|
||||||
[node name="HintLabel" type="Label" parent="UiLayer/CalibrationOverlay"]
|
|
||||||
layout_mode = 1
|
|
||||||
anchors_preset = 5
|
|
||||||
anchor_left = 0.5
|
|
||||||
anchor_right = 0.5
|
|
||||||
offset_left = -420.0
|
|
||||||
offset_top = 80.0
|
|
||||||
offset_right = 420.0
|
|
||||||
offset_bottom = 180.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
theme_override_colors/font_color = Color(0.9, 0.92, 0.98, 1)
|
|
||||||
theme_override_font_sizes/font_size = 20
|
|
||||||
horizontal_alignment = 1
|
|
||||||
autowrap_mode = 3
|
|
||||||
text = "Kalibrierung"
|
|
||||||
|
|
||||||
[node name="StartButton" type="Button" parent="UiLayer/CalibrationOverlay"]
|
|
||||||
layout_mode = 1
|
|
||||||
anchors_preset = 7
|
|
||||||
anchor_left = 0.5
|
|
||||||
anchor_top = 1.0
|
|
||||||
anchor_right = 0.5
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
offset_left = -140.0
|
|
||||||
offset_top = -120.0
|
|
||||||
offset_right = 140.0
|
|
||||||
offset_bottom = -72.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
text = "Kalibrierung starten"
|
|
||||||
|
|||||||
+177
-27
@@ -5,11 +5,31 @@ const MENU_SCENE := "res://scenes/menu.tscn"
|
|||||||
const STREAM_INTERVAL_MS := 16
|
const STREAM_INTERVAL_MS := 16
|
||||||
const NEXT_ROUND_DELAY_SEC := 2.5
|
const NEXT_ROUND_DELAY_SEC := 2.5
|
||||||
const DEFAULT_DIGIT_INTENSITY := 180
|
const DEFAULT_DIGIT_INTENSITY := 180
|
||||||
|
const MAX_PODS := 16
|
||||||
|
const MIN_PODS := 2
|
||||||
|
|
||||||
|
enum GameMode { DIGITS, COLORS }
|
||||||
enum Phase { IDLE, WAITING, PLAYING, WON }
|
enum Phase { IDLE, WAITING, PLAYING, WON }
|
||||||
|
|
||||||
|
## Set by the menu before opening this scene.
|
||||||
|
static var start_mode: GameMode = GameMode.DIGITS
|
||||||
|
|
||||||
|
## Max 8 high-contrast colors; duplicates across pods are intentional when N > 8.
|
||||||
|
const COLOR_PALETTE: Array[Dictionary] = [
|
||||||
|
{"name": "Rot", "color": Color(1.0, 0.12, 0.12)},
|
||||||
|
{"name": "Orange", "color": Color(1.0, 0.45, 0.05)},
|
||||||
|
{"name": "Gelb", "color": Color(1.0, 0.92, 0.1)},
|
||||||
|
{"name": "Grün", "color": Color(0.1, 0.85, 0.2)},
|
||||||
|
{"name": "Türkis", "color": Color(0.05, 0.85, 0.9)},
|
||||||
|
{"name": "Blau", "color": Color(0.15, 0.35, 1.0)},
|
||||||
|
{"name": "Lila", "color": Color(0.75, 0.2, 1.0)},
|
||||||
|
{"name": "Weiß", "color": Color(0.95, 0.95, 1.0)},
|
||||||
|
]
|
||||||
|
|
||||||
@onready var status_label: Label = $UiLayer/TopBar/StatusLabel
|
@onready var status_label: Label = $UiLayer/TopBar/StatusLabel
|
||||||
|
@onready var title_label: Label = $UiLayer/TopBar/Title
|
||||||
@onready var target_label: Label = $UiLayer/Center/TargetLabel
|
@onready var target_label: Label = $UiLayer/Center/TargetLabel
|
||||||
|
@onready var target_swatch: ColorRect = $UiLayer/Center/TargetSwatch
|
||||||
@onready var subtitle_label: Label = $UiLayer/Center/SubtitleLabel
|
@onready var subtitle_label: Label = $UiLayer/Center/SubtitleLabel
|
||||||
@onready var timer_label: Label = $UiLayer/Center/TimerLabel
|
@onready var timer_label: Label = $UiLayer/Center/TimerLabel
|
||||||
@onready var hint_label: Label = $UiLayer/HintLabel
|
@onready var hint_label: Label = $UiLayer/HintLabel
|
||||||
@@ -17,22 +37,29 @@ enum Phase { IDLE, WAITING, PLAYING, WON }
|
|||||||
@onready var intensity_slider: HSlider = $UiLayer/SettingsPanel/IntensitySlider
|
@onready var intensity_slider: HSlider = $UiLayer/SettingsPanel/IntensitySlider
|
||||||
@onready var intensity_value_label: Label = $UiLayer/SettingsPanel/IntensityValueLabel
|
@onready var intensity_value_label: Label = $UiLayer/SettingsPanel/IntensityValueLabel
|
||||||
|
|
||||||
|
var _mode: GameMode = GameMode.DIGITS
|
||||||
var _socket := WebSocketPeer.new()
|
var _socket := WebSocketPeer.new()
|
||||||
var _connected := false
|
var _connected := false
|
||||||
var _phase := Phase.IDLE
|
var _phase := Phase.IDLE
|
||||||
var _pod_ids: PackedInt32Array = PackedInt32Array()
|
var _pod_ids: PackedInt32Array = PackedInt32Array()
|
||||||
var _pod_digits: Dictionary = {}
|
## digit (int) or palette index (int), depending on mode
|
||||||
var _target_digit := -1
|
var _pod_values: Dictionary = {}
|
||||||
|
## client_id → true for pods that guessed wrong this round (LED off after red blink)
|
||||||
|
var _eliminated: Dictionary = {}
|
||||||
|
var _target_value := -1
|
||||||
var _target_cid := -1
|
var _target_cid := -1
|
||||||
var _round_start_ms := 0
|
var _round_start_ms := 0
|
||||||
var _round_token := 0
|
var _round_token := 0
|
||||||
|
const WRONG_BLINK_SEC := 0.5
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
_mode = start_mode
|
||||||
back_button.pressed.connect(_return_to_menu)
|
back_button.pressed.connect(_return_to_menu)
|
||||||
intensity_slider.value = DEFAULT_DIGIT_INTENSITY
|
intensity_slider.value = DEFAULT_DIGIT_INTENSITY
|
||||||
intensity_slider.value_changed.connect(_on_intensity_changed)
|
intensity_slider.value_changed.connect(_on_intensity_changed)
|
||||||
_on_intensity_changed(intensity_slider.value)
|
_on_intensity_changed(intensity_slider.value)
|
||||||
|
_apply_mode_chrome()
|
||||||
_set_phase(Phase.IDLE, "Verbinde…")
|
_set_phase(Phase.IDLE, "Verbinde…")
|
||||||
_connect_ws()
|
_connect_ws()
|
||||||
|
|
||||||
@@ -41,6 +68,18 @@ func _exit_tree() -> void:
|
|||||||
_cleanup_streams()
|
_cleanup_streams()
|
||||||
|
|
||||||
|
|
||||||
|
func _apply_mode_chrome() -> void:
|
||||||
|
if _mode == GameMode.DIGITS:
|
||||||
|
title_label.text = "Zahlen-Spiel"
|
||||||
|
target_label.visible = true
|
||||||
|
target_swatch.visible = false
|
||||||
|
else:
|
||||||
|
title_label.text = "Farben-Spiel"
|
||||||
|
target_label.visible = false
|
||||||
|
target_swatch.visible = true
|
||||||
|
target_swatch.color = Color(0.2, 0.22, 0.28)
|
||||||
|
|
||||||
|
|
||||||
func _return_to_menu() -> void:
|
func _return_to_menu() -> void:
|
||||||
_cleanup_streams()
|
_cleanup_streams()
|
||||||
get_tree().change_scene_to_file(MENU_SCENE)
|
get_tree().change_scene_to_file(MENU_SCENE)
|
||||||
@@ -115,7 +154,7 @@ func _on_client_list(data: Dictionary) -> void:
|
|||||||
_set_phase(Phase.IDLE, "Client-Liste fehlgeschlagen: %s" % data.get("error", "?"))
|
_set_phase(Phase.IDLE, "Client-Liste fehlgeschlagen: %s" % data.get("error", "?"))
|
||||||
return
|
return
|
||||||
|
|
||||||
_pod_ids = PackedInt32Array()
|
var all_ids := PackedInt32Array()
|
||||||
for entry in data.get("clients", []):
|
for entry in data.get("clients", []):
|
||||||
if typeof(entry) != TYPE_DICTIONARY:
|
if typeof(entry) != TYPE_DICTIONARY:
|
||||||
continue
|
continue
|
||||||
@@ -123,47 +162,126 @@ func _on_client_list(data: Dictionary) -> void:
|
|||||||
continue
|
continue
|
||||||
var cid := int(entry.get("id", 0))
|
var cid := int(entry.get("id", 0))
|
||||||
if cid > 0:
|
if cid > 0:
|
||||||
_pod_ids.append(cid)
|
all_ids.append(cid)
|
||||||
|
|
||||||
_pod_ids.sort()
|
all_ids.sort()
|
||||||
|
_pod_ids = PackedInt32Array()
|
||||||
|
for i in mini(all_ids.size(), MAX_PODS):
|
||||||
|
_pod_ids.append(all_ids[i])
|
||||||
|
|
||||||
if _pod_ids.size() < 2:
|
if _pod_ids.size() < MIN_PODS:
|
||||||
_set_phase(Phase.WAITING, "Mindestens 2 online Pods nötig (aktuell: %d)" % _pod_ids.size())
|
_set_phase(
|
||||||
target_label.text = "—"
|
Phase.WAITING,
|
||||||
|
"Mindestens %d online Pods nötig (aktuell: %d)" % [MIN_PODS, _pod_ids.size()]
|
||||||
|
)
|
||||||
|
_clear_target_display()
|
||||||
timer_label.text = ""
|
timer_label.text = ""
|
||||||
return
|
return
|
||||||
|
|
||||||
_start_round()
|
_start_round()
|
||||||
|
|
||||||
|
|
||||||
func _start_round() -> void:
|
func _clear_target_display() -> void:
|
||||||
_round_token += 1
|
target_label.text = "—"
|
||||||
_pod_digits.clear()
|
target_swatch.color = Color(0.2, 0.22, 0.28)
|
||||||
|
|
||||||
|
|
||||||
|
func _build_value_pool(count: int) -> Array:
|
||||||
var pool: Array = []
|
var pool: Array = []
|
||||||
|
if _mode == GameMode.DIGITS:
|
||||||
for d in range(10):
|
for d in range(10):
|
||||||
pool.append(d)
|
pool.append(d)
|
||||||
pool.shuffle()
|
pool.shuffle()
|
||||||
|
# With >10 pods, reuse digits but keep the first `count` slots unique
|
||||||
|
# where possible; remaining slots get shuffled leftovers with duplicates.
|
||||||
|
while pool.size() < count:
|
||||||
|
var extra: Array = []
|
||||||
|
for d in range(10):
|
||||||
|
extra.append(d)
|
||||||
|
extra.shuffle()
|
||||||
|
pool.append_array(extra)
|
||||||
|
return pool.slice(0, count)
|
||||||
|
|
||||||
|
for i in COLOR_PALETTE.size():
|
||||||
|
pool.append(i)
|
||||||
|
pool.shuffle()
|
||||||
|
while pool.size() < count:
|
||||||
|
var extra: Array = []
|
||||||
|
for i in COLOR_PALETTE.size():
|
||||||
|
extra.append(i)
|
||||||
|
extra.shuffle()
|
||||||
|
pool.append_array(extra)
|
||||||
|
return pool.slice(0, count)
|
||||||
|
|
||||||
|
|
||||||
|
func _start_round() -> void:
|
||||||
|
_round_token += 1
|
||||||
|
_pod_values.clear()
|
||||||
|
_eliminated.clear()
|
||||||
|
|
||||||
|
var pool := _build_value_pool(_pod_ids.size())
|
||||||
for i in _pod_ids.size():
|
for i in _pod_ids.size():
|
||||||
_pod_digits[_pod_ids[i]] = int(pool[i])
|
_pod_values[_pod_ids[i]] = int(pool[i])
|
||||||
|
|
||||||
|
# Ensure the chosen target value appears on exactly one pod when possible
|
||||||
|
# (digits can collide above 10 pods; re-roll until unique or give up).
|
||||||
var pick_idx := randi() % _pod_ids.size()
|
var pick_idx := randi() % _pod_ids.size()
|
||||||
_target_cid = _pod_ids[pick_idx]
|
_target_cid = _pod_ids[pick_idx]
|
||||||
_target_digit = int(_pod_digits[_target_cid])
|
_target_value = int(_pod_values[_target_cid])
|
||||||
|
if _mode == GameMode.DIGITS and _pod_ids.size() > 10:
|
||||||
|
_ensure_unique_target_digit()
|
||||||
|
|
||||||
for cid in _pod_ids:
|
for cid in _pod_ids:
|
||||||
_send_led_digit(cid, int(_pod_digits[cid]))
|
_apply_pod_display(cid, int(_pod_values[cid]))
|
||||||
|
|
||||||
_enable_input_stream()
|
_enable_input_stream()
|
||||||
_round_start_ms = Time.get_ticks_msec()
|
_round_start_ms = Time.get_ticks_msec()
|
||||||
_set_phase(Phase.PLAYING, "%d Pods bereit" % _pod_ids.size())
|
_set_phase(Phase.PLAYING, "%d Pods bereit" % _pod_ids.size())
|
||||||
target_label.text = str(_target_digit)
|
_show_target()
|
||||||
subtitle_label.text = "Drücke den Pod mit dieser Zahl"
|
|
||||||
timer_label.text = "0,00 s"
|
timer_label.text = "0,00 s"
|
||||||
hint_label.text = ""
|
hint_label.text = ""
|
||||||
|
|
||||||
|
|
||||||
|
func _ensure_unique_target_digit() -> void:
|
||||||
|
# Make sure only the target pod shows `_target_value`.
|
||||||
|
for cid in _pod_ids:
|
||||||
|
if cid == _target_cid:
|
||||||
|
continue
|
||||||
|
if int(_pod_values[cid]) != _target_value:
|
||||||
|
continue
|
||||||
|
var replacement := (_target_value + 1 + randi() % 9) % 10
|
||||||
|
_pod_values[cid] = replacement
|
||||||
|
|
||||||
|
|
||||||
|
func _show_target() -> void:
|
||||||
|
if _mode == GameMode.DIGITS:
|
||||||
|
target_label.visible = true
|
||||||
|
target_swatch.visible = false
|
||||||
|
target_label.text = str(_target_value)
|
||||||
|
target_label.add_theme_color_override("font_color", Color(0.35, 0.92, 1, 1))
|
||||||
|
subtitle_label.text = "Drücke den Pod mit dieser Zahl"
|
||||||
|
else:
|
||||||
|
target_label.visible = false
|
||||||
|
target_swatch.visible = true
|
||||||
|
var entry: Dictionary = COLOR_PALETTE[_target_value]
|
||||||
|
var c: Color = entry["color"]
|
||||||
|
target_swatch.color = c
|
||||||
|
subtitle_label.text = "Drücke den Pod mit dieser Farbe · %s" % str(entry["name"])
|
||||||
|
|
||||||
|
|
||||||
|
func _value_label(value: int) -> String:
|
||||||
|
if _mode == GameMode.DIGITS:
|
||||||
|
return str(value)
|
||||||
|
return str(COLOR_PALETTE[value]["name"])
|
||||||
|
|
||||||
|
|
||||||
|
func _apply_pod_display(cid: int, value: int) -> void:
|
||||||
|
if _mode == GameMode.DIGITS:
|
||||||
|
_send_led_digit(cid, value)
|
||||||
|
else:
|
||||||
|
_send_led_color(cid, COLOR_PALETTE[value]["color"])
|
||||||
|
|
||||||
|
|
||||||
func _enable_input_stream() -> void:
|
func _enable_input_stream() -> void:
|
||||||
for cid in _pod_ids:
|
for cid in _pod_ids:
|
||||||
_send({
|
_send({
|
||||||
@@ -186,18 +304,35 @@ func _on_input(data: Dictionary) -> void:
|
|||||||
if str(client.get("tap_kind", "none")) == "none":
|
if str(client.get("tap_kind", "none")) == "none":
|
||||||
continue
|
continue
|
||||||
var cid := int(client.get("client_id", 0))
|
var cid := int(client.get("client_id", 0))
|
||||||
if not _pod_digits.has(cid):
|
if not _pod_values.has(cid):
|
||||||
continue
|
continue
|
||||||
if cid == _target_cid:
|
if _eliminated.has(cid):
|
||||||
|
continue
|
||||||
|
if int(_pod_values[cid]) == _target_value:
|
||||||
|
_target_cid = cid
|
||||||
_on_correct_tap()
|
_on_correct_tap()
|
||||||
else:
|
else:
|
||||||
hint_label.text = "Falsch! Pod #%d hat %d — versuch es nochmal." % [cid, int(_pod_digits[cid])]
|
_eliminate_pod(cid)
|
||||||
|
|
||||||
|
|
||||||
|
func _eliminate_pod(cid: int) -> void:
|
||||||
|
if _eliminated.has(cid):
|
||||||
|
return
|
||||||
|
_eliminated[cid] = true
|
||||||
|
var wrong_value := int(_pod_values[cid])
|
||||||
|
hint_label.text = "Falsch! Pod #%d hatte %s — ausgeschieden." % [
|
||||||
|
cid, _value_label(wrong_value)
|
||||||
|
]
|
||||||
_send_led_blink(cid, Color(1.0, 0.2, 0.2))
|
_send_led_blink(cid, Color(1.0, 0.2, 0.2))
|
||||||
var wrong_digit := int(_pod_digits[cid])
|
var token := _round_token
|
||||||
get_tree().create_timer(0.5).timeout.connect(
|
get_tree().create_timer(WRONG_BLINK_SEC).timeout.connect(
|
||||||
func() -> void:
|
func() -> void:
|
||||||
if _phase == Phase.PLAYING and _pod_digits.has(cid):
|
if not is_inside_tree():
|
||||||
_send_led_digit(cid, wrong_digit),
|
return
|
||||||
|
if _round_token != token:
|
||||||
|
return
|
||||||
|
if _eliminated.has(cid):
|
||||||
|
_send_led_clear(cid),
|
||||||
CONNECT_ONE_SHOT
|
CONNECT_ONE_SHOT
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -215,7 +350,7 @@ func _on_correct_tap() -> void:
|
|||||||
func() -> void:
|
func() -> void:
|
||||||
if _round_token != token or not is_inside_tree():
|
if _round_token != token or not is_inside_tree():
|
||||||
return
|
return
|
||||||
if _pod_ids.size() >= 2:
|
if _pod_ids.size() >= MIN_PODS:
|
||||||
_start_round(),
|
_start_round(),
|
||||||
CONNECT_ONE_SHOT
|
CONNECT_ONE_SHOT
|
||||||
)
|
)
|
||||||
@@ -240,10 +375,13 @@ func _digit_intensity() -> int:
|
|||||||
|
|
||||||
func _on_intensity_changed(_value: float) -> void:
|
func _on_intensity_changed(_value: float) -> void:
|
||||||
intensity_value_label.text = str(_digit_intensity())
|
intensity_value_label.text = str(_digit_intensity())
|
||||||
if _pod_digits.is_empty():
|
if _pod_values.is_empty() or _phase != Phase.PLAYING:
|
||||||
return
|
return
|
||||||
for cid in _pod_digits:
|
for cid in _pod_values:
|
||||||
_send_led_digit(cid, int(_pod_digits[cid]))
|
if _eliminated.has(cid):
|
||||||
|
continue
|
||||||
|
_apply_pod_display(cid, int(_pod_values[cid]))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func _send_led_digit(cid: int, digit: int) -> void:
|
func _send_led_digit(cid: int, digit: int) -> void:
|
||||||
@@ -259,6 +397,18 @@ func _send_led_digit(cid: int, digit: int) -> void:
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
func _send_led_color(cid: int, color: Color) -> void:
|
||||||
|
_send({
|
||||||
|
"type": "set_led_ring",
|
||||||
|
"client_id": cid,
|
||||||
|
"mode": "color",
|
||||||
|
"r": int(color.r * 255),
|
||||||
|
"g": int(color.g * 255),
|
||||||
|
"b": int(color.b * 255),
|
||||||
|
"intensity": _digit_intensity(),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
func _send_led_clear(cid: int) -> void:
|
func _send_led_clear(cid: int) -> void:
|
||||||
_send({"type": "set_led_ring", "client_id": cid, "mode": "clear"})
|
_send({"type": "set_led_ring", "client_id": cid, "mode": "clear"})
|
||||||
|
|
||||||
|
|||||||
+23
-4
@@ -12,6 +12,7 @@ const PANEL_BORDER_FLASH := Color(1.0, 0.82, 0.15, 1.0)
|
|||||||
|
|
||||||
const LED_MODES: PackedStringArray = ["color", "clear", "progress", "digit", "blink", "find-me"]
|
const LED_MODES: PackedStringArray = ["color", "clear", "progress", "digit", "blink", "find-me"]
|
||||||
const LED_MODES_COLOR_INTENSITY: PackedStringArray = ["color", "progress", "digit", "blink"]
|
const LED_MODES_COLOR_INTENSITY: PackedStringArray = ["color", "progress", "digit", "blink"]
|
||||||
|
const ORIENTATION_GIZMO_SCENE := preload("res://scenes/orientation_gizmo.tscn")
|
||||||
|
|
||||||
@onready var status_label: Label = $UiLayer/TopBar/StatusLabel
|
@onready var status_label: Label = $UiLayer/TopBar/StatusLabel
|
||||||
@onready var log_output: TextEdit = $UiLayer/MainSplit/LogPanel/LogOutput
|
@onready var log_output: TextEdit = $UiLayer/MainSplit/LogPanel/LogOutput
|
||||||
@@ -122,6 +123,7 @@ func _stop_stream() -> void:
|
|||||||
"triple": false,
|
"triple": false,
|
||||||
})
|
})
|
||||||
ui["values_label"].text = "—"
|
ui["values_label"].text = "—"
|
||||||
|
ui["orient_gizmo"].clear()
|
||||||
|
|
||||||
_streaming = false
|
_streaming = false
|
||||||
start_stream_btn.disabled = _client_ui.is_empty()
|
start_stream_btn.disabled = _client_ui.is_empty()
|
||||||
@@ -328,14 +330,26 @@ func _populate_clients(clients: Array) -> void:
|
|||||||
_add_led_preset_btn(led_presets, "Blau", cid, available, func() -> void: _led_apply_preset(cid, "color", Color(0.2, 0.4, 1), 128))
|
_add_led_preset_btn(led_presets, "Blau", cid, available, func() -> void: _led_apply_preset(cid, "color", Color(0.2, 0.4, 1), 128))
|
||||||
_add_led_preset_btn(led_presets, "Find", cid, available, func() -> void: _led_apply_preset(cid, "find-me"))
|
_add_led_preset_btn(led_presets, "Find", cid, available, func() -> void: _led_apply_preset(cid, "find-me"))
|
||||||
|
|
||||||
|
var accel_row := HBoxContainer.new()
|
||||||
|
accel_row.add_theme_constant_override("separation", 12)
|
||||||
|
accel_row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
vbox.add_child(accel_row)
|
||||||
|
|
||||||
var values_label := Label.new()
|
var values_label := Label.new()
|
||||||
values_label.text = "—"
|
values_label.text = "—"
|
||||||
values_label.add_theme_font_size_override("font_size", 12)
|
values_label.add_theme_font_size_override("font_size", 12)
|
||||||
values_label.add_theme_color_override("font_color", Color(0.65, 0.85, 0.75))
|
values_label.add_theme_color_override("font_color", Color(0.65, 0.85, 0.75))
|
||||||
values_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
values_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
vbox.add_child(values_label)
|
values_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
values_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||||
|
accel_row.add_child(values_label)
|
||||||
|
|
||||||
|
var orient_gizmo: OrientationGizmo = ORIENTATION_GIZMO_SCENE.instantiate()
|
||||||
|
accel_row.add_child(orient_gizmo)
|
||||||
|
|
||||||
clients_list.add_child(panel)
|
clients_list.add_child(panel)
|
||||||
|
# clear() after enter_tree so @onready nodes exist; _ready also clears.
|
||||||
|
orient_gizmo.clear()
|
||||||
|
|
||||||
_client_ui[cid] = {
|
_client_ui[cid] = {
|
||||||
"available": available,
|
"available": available,
|
||||||
@@ -360,6 +374,7 @@ func _populate_clients(clients: Array) -> void:
|
|||||||
"led_digit_row": led_digit_row,
|
"led_digit_row": led_digit_row,
|
||||||
"led_blink_row": led_blink_row,
|
"led_blink_row": led_blink_row,
|
||||||
"values_label": values_label,
|
"values_label": values_label,
|
||||||
|
"orient_gizmo": orient_gizmo,
|
||||||
}
|
}
|
||||||
_update_led_fields(cid)
|
_update_led_fields(cid)
|
||||||
|
|
||||||
@@ -443,14 +458,18 @@ func _on_input(data: Dictionary) -> void:
|
|||||||
var ui: Dictionary = _client_ui[cid]
|
var ui: Dictionary = _client_ui[cid]
|
||||||
var lines: PackedStringArray = []
|
var lines: PackedStringArray = []
|
||||||
if client.get("valid", false):
|
if client.get("valid", false):
|
||||||
|
var ax := int(client.get("x", 0))
|
||||||
|
var ay := int(client.get("y", 0))
|
||||||
|
var az := int(client.get("z", 0))
|
||||||
lines.append(
|
lines.append(
|
||||||
"x=%d y=%d z=%d (age %sms)" % [
|
"x=%d y=%d z=%d (age %sms)" % [
|
||||||
int(client.get("x", 0)),
|
ax, ay, az,
|
||||||
int(client.get("y", 0)),
|
|
||||||
int(client.get("z", 0)),
|
|
||||||
str(client.get("accel_age_ms", "?")),
|
str(client.get("accel_age_ms", "?")),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
ui["orient_gizmo"].set_accel(Vector3(float(ax), float(ay), float(az)))
|
||||||
|
else:
|
||||||
|
ui["orient_gizmo"].clear()
|
||||||
|
|
||||||
var tap_kind := str(client.get("tap_kind", "none"))
|
var tap_kind := str(client.get("tap_kind", "none"))
|
||||||
if tap_kind != "none":
|
if tap_kind != "none":
|
||||||
|
|||||||
+15
-2
@@ -5,11 +5,13 @@ const CONFIG_SCENE := "res://scenes/config.tscn"
|
|||||||
const PONG_SCENE := "res://scenes/pong.tscn"
|
const PONG_SCENE := "res://scenes/pong.tscn"
|
||||||
const COLOR_GAME_SCENE := "res://scenes/color_game.tscn"
|
const COLOR_GAME_SCENE := "res://scenes/color_game.tscn"
|
||||||
const SPACE_SHOOTER_SCENE := "res://scenes/space_shooter.tscn"
|
const SPACE_SHOOTER_SCENE := "res://scenes/space_shooter.tscn"
|
||||||
|
const ColorGameScript = preload("res://scripts/color_game.gd")
|
||||||
|
|
||||||
@onready var demo_button: Button = $CenterBox/DemoButton
|
@onready var demo_button: Button = $CenterBox/DemoButton
|
||||||
@onready var config_button: Button = $CenterBox/ConfigButton
|
@onready var config_button: Button = $CenterBox/ConfigButton
|
||||||
@onready var pong_button: Button = $CenterBox/PongButton
|
@onready var pong_button: Button = $CenterBox/PongButton
|
||||||
@onready var color_game_button: Button = $CenterBox/ColorGameButton
|
@onready var digits_game_button: Button = $CenterBox/DigitsGameButton
|
||||||
|
@onready var colors_game_button: Button = $CenterBox/ColorsGameButton
|
||||||
@onready var space_shooter_button: Button = $CenterBox/SpaceShooterButton
|
@onready var space_shooter_button: Button = $CenterBox/SpaceShooterButton
|
||||||
@onready var exit_button: Button = $CenterBox/ExitButton
|
@onready var exit_button: Button = $CenterBox/ExitButton
|
||||||
|
|
||||||
@@ -18,11 +20,22 @@ func _ready() -> void:
|
|||||||
demo_button.pressed.connect(func(): _open_scene(DEMO_SCENE))
|
demo_button.pressed.connect(func(): _open_scene(DEMO_SCENE))
|
||||||
config_button.pressed.connect(func(): _open_scene(CONFIG_SCENE))
|
config_button.pressed.connect(func(): _open_scene(CONFIG_SCENE))
|
||||||
pong_button.pressed.connect(func(): _open_scene(PONG_SCENE))
|
pong_button.pressed.connect(func(): _open_scene(PONG_SCENE))
|
||||||
color_game_button.pressed.connect(func(): _open_scene(COLOR_GAME_SCENE))
|
digits_game_button.pressed.connect(_open_digits_game)
|
||||||
|
colors_game_button.pressed.connect(_open_colors_game)
|
||||||
space_shooter_button.pressed.connect(func(): _open_scene(SPACE_SHOOTER_SCENE))
|
space_shooter_button.pressed.connect(func(): _open_scene(SPACE_SHOOTER_SCENE))
|
||||||
exit_button.pressed.connect(_exit_game)
|
exit_button.pressed.connect(_exit_game)
|
||||||
|
|
||||||
|
|
||||||
|
func _open_digits_game() -> void:
|
||||||
|
ColorGameScript.start_mode = ColorGameScript.GameMode.DIGITS
|
||||||
|
_open_scene(COLOR_GAME_SCENE)
|
||||||
|
|
||||||
|
|
||||||
|
func _open_colors_game() -> void:
|
||||||
|
ColorGameScript.start_mode = ColorGameScript.GameMode.COLORS
|
||||||
|
_open_scene(COLOR_GAME_SCENE)
|
||||||
|
|
||||||
|
|
||||||
func _open_scene(path: String) -> void:
|
func _open_scene(path: String) -> void:
|
||||||
get_tree().change_scene_to_file(path)
|
get_tree().change_scene_to_file(path)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
extends SubViewportContainer
|
||||||
|
class_name OrientationGizmo
|
||||||
|
|
||||||
|
const MIN_ACCEL_LEN := 1000.0
|
||||||
|
const SMOOTH := 0.25
|
||||||
|
const VIEW_SIZE := Vector2(220, 220)
|
||||||
|
const ACTIVE_MODULATE := Color(1, 1, 1, 1)
|
||||||
|
const IDLE_MODULATE := Color(0.55, 0.58, 0.65, 0.85)
|
||||||
|
|
||||||
|
## Mesh TOP = local −Y.
|
||||||
|
## Flat accel ≈ +Z → side view, TOP at top of screen
|
||||||
|
## Facing accel ≈ −Y → TOP face toward camera
|
||||||
|
## Steer accel ≈ −X → facing + roll left (steering wheel)
|
||||||
|
var _basis_flat: Basis = Basis(Quaternion(Vector3.RIGHT, PI))
|
||||||
|
var _basis_facing: Basis = Basis(Quaternion(Vector3.RIGHT, -PI * 0.5))
|
||||||
|
|
||||||
|
@onready var _viewport: SubViewport = $SubViewport
|
||||||
|
@onready var _camera: Camera3D = $SubViewport/Camera3D
|
||||||
|
@onready var _pod: Node3D = $SubViewport/Pod
|
||||||
|
|
||||||
|
var _has_sample := false
|
||||||
|
var _target_basis := Basis.IDENTITY
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
custom_minimum_size = VIEW_SIZE
|
||||||
|
stretch = true
|
||||||
|
_viewport.own_world_3d = true
|
||||||
|
_viewport.transparent_bg = true
|
||||||
|
_viewport.size = Vector2i(VIEW_SIZE)
|
||||||
|
_viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
|
||||||
|
_camera.projection = Camera3D.PROJECTION_ORTHOGONAL
|
||||||
|
_camera.size = 2.6
|
||||||
|
_camera.near = 0.05
|
||||||
|
_camera.far = 50.0
|
||||||
|
_camera.position = Vector3(0.0, 0.0, 3.2)
|
||||||
|
_camera.look_at(Vector3.ZERO, Vector3.UP)
|
||||||
|
clear()
|
||||||
|
|
||||||
|
|
||||||
|
func set_accel(v: Vector3) -> void:
|
||||||
|
if v.length() < MIN_ACCEL_LEN:
|
||||||
|
return
|
||||||
|
_has_sample = true
|
||||||
|
modulate = ACTIVE_MODULATE
|
||||||
|
_target_basis = _basis_from_accel(v)
|
||||||
|
|
||||||
|
|
||||||
|
func clear() -> void:
|
||||||
|
_has_sample = false
|
||||||
|
_target_basis = _basis_flat
|
||||||
|
if _pod != null:
|
||||||
|
_pod.basis = _basis_flat
|
||||||
|
modulate = IDLE_MODULATE
|
||||||
|
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
if not _has_sample:
|
||||||
|
return
|
||||||
|
_pod.basis = _pod.basis.slerp(_target_basis, SMOOTH)
|
||||||
|
|
||||||
|
|
||||||
|
## Lateral tip in radians about device X.
|
||||||
|
## Positive = tip toward +X (works for flat left/right tilt and frontal wheel turn).
|
||||||
|
static func steer_roll(accel: Vector3, deadzone_rad: float = deg_to_rad(8.0)) -> float:
|
||||||
|
if accel.length() < MIN_ACCEL_LEN:
|
||||||
|
return 0.0
|
||||||
|
var n := accel.normalized()
|
||||||
|
var angle := atan2(n.x, sqrt(n.y * n.y + n.z * n.z))
|
||||||
|
if absf(angle) <= deadzone_rad:
|
||||||
|
return 0.0
|
||||||
|
return signf(angle) * (absf(angle) - deadzone_rad)
|
||||||
|
|
||||||
|
|
||||||
|
func _basis_from_accel(accel: Vector3) -> Basis:
|
||||||
|
var n := accel.normalized()
|
||||||
|
# 0 = liegend (+Z), 1 = aufgerichtet / gedreht (wenig +Z)
|
||||||
|
var face_t := clampf(1.0 - maxf(n.z, 0.0), 0.0, 1.0)
|
||||||
|
var b := _basis_flat.slerp(_basis_facing, face_t)
|
||||||
|
var lateral := atan2(n.x, sqrt(n.y * n.y + n.z * n.z))
|
||||||
|
return Basis(Quaternion(Vector3.BACK, -lateral)) * b
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://djoqip111hnbd
|
||||||
+532
-311
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,18 @@ static func named(name: String) -> AtlasTexture:
|
|||||||
return region(211, 941, 99, 75)
|
return region(211, 941, 99, 75)
|
||||||
"playerShip1_orange":
|
"playerShip1_orange":
|
||||||
return region(247, 84, 99, 75)
|
return region(247, 84, 99, 75)
|
||||||
|
"playerShip1_green":
|
||||||
|
return region(237, 377, 99, 75)
|
||||||
|
"playerShip1_red":
|
||||||
|
return region(224, 832, 99, 75)
|
||||||
|
"playerShip2_blue":
|
||||||
|
return region(112, 791, 112, 75)
|
||||||
|
"playerShip2_orange":
|
||||||
|
return region(112, 716, 112, 75)
|
||||||
|
"playerShip2_green":
|
||||||
|
return region(112, 866, 112, 75)
|
||||||
|
"playerShip2_red":
|
||||||
|
return region(0, 941, 112, 75)
|
||||||
"laserBlue01":
|
"laserBlue01":
|
||||||
return region(856, 421, 9, 54)
|
return region(856, 421, 9, 54)
|
||||||
"laserRed01":
|
"laserRed01":
|
||||||
|
|||||||
Reference in New Issue
Block a user