Add virtua hand lighting up floor buttons!

- hand sweeps down the button panel at game start
- button panel now spawns its floor buttons procedurally via button_panel.gd
- elevator_panel waits for the new intro_finished signal before starting floor 1
- removes the stray ShaftStrip from hud.tscn
- cleaned up timing but henry will probably want to tune further
This commit is contained in:
Jennie Robinson Faber 2026-05-16 19:31:39 +01:00
parent 046ae6f8a0
commit fae69c4816
13 changed files with 201 additions and 82 deletions

18
scenes/button_panel.gd Normal file
View file

@ -0,0 +1,18 @@
extends Node3D
const BUTTON_SCENE = preload("res://scenes/floor_button.tscn")
const BUTTON_COUNT := 10
const Y_TOP := 8.49
const Y_BOTTOM := -4.36
@onready var _container: Node3D = $FloorButtons
func _ready() -> void:
var spacing = (Y_TOP - Y_BOTTOM) / float(BUTTON_COUNT - 1)
for i in range(BUTTON_COUNT):
var btn = BUTTON_SCENE.instantiate()
btn.name = "%02d" % i
btn.position = Vector3(0, Y_BOTTOM + i * spacing, 0)
_container.add_child(btn)
var sprite: Sprite3D = btn.get_node("ButtonSprite")
sprite.frame = i

View file

@ -0,0 +1 @@
uid://cp2n258uuuou3

View file

@ -1,75 +1,9 @@
[gd_scene format=3 uid="uid://cnjn0vhg1phav"]
[ext_resource type="PackedScene" uid="uid://bw1kbbl3n83e8" path="res://scenes/floor_button.tscn" id="1_lff67"]
[ext_resource type="Script" path="res://scenes/button_panel.gd" id="2_bpscr"]
[node name="ButtonPanel" type="Node3D" unique_id=1270714626]
script = ExtResource("2_bpscr")
[node name="FloorButtons" type="Node3D" parent="." unique_id=1608506954]
[node name="00" parent="." unique_id=1628684586 instance=ExtResource("1_lff67")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -4.360477, 0)
[node name="01" parent="." unique_id=1324476691 instance=ExtResource("1_lff67")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.8598118, 0)
[node name="ButtonSprite" parent="01" index="0" unique_id=1628684586]
frame = 1
[node name="02" parent="." unique_id=1143590285 instance=ExtResource("1_lff67")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.3875825, 0)
[node name="ButtonSprite" parent="02" index="0" unique_id=1628684586]
frame = 2
[node name="03" parent="." unique_id=1467372561 instance=ExtResource("1_lff67")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.03961301, 0)
[node name="ButtonSprite" parent="03" index="0" unique_id=1628684586]
frame = 3
[node name="04" parent="." unique_id=652433944 instance=ExtResource("1_lff67")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.3832786, 0)
[node name="ButtonSprite" parent="04" index="0" unique_id=1628684586]
frame = 4
[node name="05" parent="." unique_id=470439905 instance=ExtResource("1_lff67")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.842876, 0)
[node name="ButtonSprite" parent="05" index="0" unique_id=1628684586]
frame = 5
[node name="06" parent="." unique_id=1293107230 instance=ExtResource("1_lff67")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4.1931896, 0)
[node name="ButtonSprite" parent="06" index="0" unique_id=1628684586]
frame = 6
[node name="07" parent="." unique_id=1309697731 instance=ExtResource("1_lff67")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5.632162, 0)
[node name="ButtonSprite" parent="07" index="0" unique_id=1628684586]
frame = 7
[node name="08" parent="." unique_id=447931949 instance=ExtResource("1_lff67")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 7.0681796, 0)
[node name="ButtonSprite" parent="08" index="0" unique_id=1628684586]
frame = 8
[node name="09" parent="." unique_id=1200513083 instance=ExtResource("1_lff67")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 8.490524, 0)
[node name="ButtonSprite" parent="09" index="0" unique_id=1628684586]
frame = 9
[editable path="00"]
[editable path="01"]
[editable path="02"]
[editable path="03"]
[editable path="04"]
[editable path="05"]
[editable path="06"]
[editable path="07"]
[editable path="08"]
[editable path="09"]

View file

@ -41,7 +41,7 @@ func _ready():
_screen.pulse_blocked_perfect.connect(_on_pulse_blocked_perfect)
_screen.doors_closing.connect(func(): _on_doors_closing(true))
EventBus.game_started.connect(_start_floor)
EventBus.game_started.connect(_on_game_started)
EventBus.game_lost.connect(func(_reason): $SfxRobotIUnderstand.play())
EventBus.survivor_squeaked_in.connect(_on_survivor_squeaked_in)
EventBus.robot_close_warning.connect(_on_robot_close_warning)
@ -59,6 +59,10 @@ func _unhandled_input(event):
EventBus.debug_starting_floor = EventBus.STARTING_FLOOR
get_tree().reload_current_scene()
func _on_game_started():
await EventBus.intro_finished
_start_floor()
func _reset_floor_state():
doors_closing_flag = false
people_in_elevator = 0

View file

@ -18,7 +18,7 @@
[ext_resource type="AudioStream" uid="uid://b3d6rntbosvc6" path="res://audio/Ambience.wav" id="12_amb"]
[ext_resource type="PackedScene" path="res://scenes/pause_menu.tscn" id="12_pause"]
[ext_resource type="AudioStream" uid="uid://cnfmytsyo8biq" path="res://audio/RoboZacSpeaker.wav" id="13_muzak"]
[ext_resource type="Script" path="res://scenes/muzak.gd" id="14_muzak_s"]
[ext_resource type="Script" uid="uid://deyjxii7yykjv" path="res://scenes/muzak.gd" id="14_muzak_s"]
[node name="Game" type="Node3D" unique_id=1456297160]
script = ExtResource("1_lbhrr")

View file

@ -1,7 +1,6 @@
[gd_scene load_steps=3 format=3 uid="uid://cbvi51vvpt7mu"]
[gd_scene load_steps=2 format=3 uid="uid://cbvi51vvpt7mu"]
[ext_resource type="Script" uid="uid://fpaw3u5yjtbk" path="res://scenes/hud.gd" id="1_64ctp"]
[ext_resource type="PackedScene" uid="uid://dshaftstrip01" path="res://scenes/shaft_strip.tscn" id="2_shaft"]
[node name="HUD" type="MarginContainer" unique_id=769131693]
offset_right = 40.0
@ -31,13 +30,3 @@ text = "FLOOR: 0"
[node name="ScoreLabel" type="Label" parent="StatsColumn" unique_id=1595653166]
layout_mode = 2
theme_override_font_sizes/font_size = 24
[node name="ShaftStrip" parent="." instance=ExtResource("2_shaft")]
top_level = true
anchors_preset = 9
anchor_bottom = 1.0
offset_left = 12.0
offset_top = 12.0
offset_right = 60.0
offset_bottom = -12.0
grow_vertical = 2

114
scenes/left_hand.gd Normal file
View file

@ -0,0 +1,114 @@
extends Sprite3D
const INTRO_DURATION := 1.5
const ENTRY_OFFSET := 0.5
const EXIT_OFFSET := 0.5
const FORWARD_OFFSET := 0.05
const FINGER_OFFSET_X := 0.0
@onready var _button_panel: Node3D = get_parent().get_node("ButtonPanel")
var _buttons: Array = []
var _intro_playing := false
var _sprite_half_h := 0.0
func _ready() -> void:
visible = false
_sprite_half_h = texture.get_height() * pixel_size * global_transform.basis.get_scale().y * 0.5
EventBus.game_started.connect(_play_intro)
EventBus.floor_changed.connect(_on_floor_changed)
func _ensure_buttons() -> void:
if not _buttons.is_empty():
return
_buttons = _collect_buttons()
if not _buttons.is_empty():
_set_all_lit(false)
func _collect_buttons() -> Array:
var arr: Array = []
var container = _button_panel.get_node_or_null("FloorButtons")
if container == null or container.get_child_count() == 0:
container = _button_panel
for i in range(10):
var button_name := "%02d" % i
var btn = container.get_node_or_null(button_name)
if btn != null:
arr.append(btn)
return arr
func _play_intro() -> void:
if _intro_playing:
return
_intro_playing = true
_ensure_buttons()
if _buttons.size() < 10:
_intro_playing = false
EventBus.intro_finished.emit()
return
_set_all_lit(false)
# animate the hand's global_position.y from start to end over a set duration
# each button gets a sibling callback scheduled in *parallel*!
# what fraction of the way from start to end is this button's Y?
# to figure out when to fire callback on set_delay() to stagge.3.r
# this way it is easy to change the duration if we want
var top_button: Node3D = _buttons[9]
var bottom_button: Node3D = _buttons[0]
var camera = get_viewport().get_camera_3d()
var camera_pos = camera.global_position if camera else Vector3.ZERO
var button_z = top_button.global_position.z
var hand_z = button_z - FORWARD_OFFSET
var z_ratio = (hand_z - camera_pos.z) / (button_z - camera_pos.z)
var hand_x = camera_pos.x + (top_button.global_position.x - camera_pos.x) * z_ratio + FINGER_OFFSET_X
var finger_start_y_world = top_button.global_position.y + ENTRY_OFFSET
var finger_end_y_world = bottom_button.global_position.y - EXIT_OFFSET
var finger_start_y = camera_pos.y + (finger_start_y_world - camera_pos.y) * z_ratio
var finger_end_y = camera_pos.y + (finger_end_y_world - camera_pos.y) * z_ratio
var sprite_start_y = finger_start_y - _sprite_half_h
var sprite_end_y = finger_end_y - _sprite_half_h
global_position = Vector3(hand_x, sprite_start_y, hand_z)
visible = true
var tween := create_tween().set_parallel(true)
tween.tween_property(self, "global_position:y", sprite_end_y, INTRO_DURATION)
for i in range(_buttons.size()):
var button_y_world = _buttons[i].global_position.y
# weird thing: button_y is projected onto the hand's Z-plane to account for perspective
# to keep the fingertip visually aligned with each button at the exact right moment
var button_y_proj = camera_pos.y + (button_y_world - camera_pos.y) * z_ratio
var t_fraction = (finger_start_y - button_y_proj) / (finger_start_y - finger_end_y)
t_fraction = clamp(t_fraction, 0.0, 1.0)
tween.tween_callback(_light_button.bind(i)).set_delay(t_fraction * INTRO_DURATION)
await tween.finished
visible = false
_intro_playing = false
EventBus.intro_finished.emit()
func _light_button(index: int) -> void:
var sprite: Sprite3D = _buttons[index].get_node("ButtonSprite")
sprite.frame = index + 10
func _set_all_lit(lit: bool) -> void:
for i in range(_buttons.size()):
var sprite: Sprite3D = _buttons[i].get_node("ButtonSprite")
sprite.frame = i + (10 if lit else 0)
func _on_floor_changed(floor_num: int) -> void:
if floor_num >= EventBus.STARTING_FLOOR:
return
_ensure_buttons()
if floor_num < 0 or floor_num >= _buttons.size():
return
var sprite: Sprite3D = _buttons[floor_num].get_node("ButtonSprite")
sprite.frame = floor_num

1
scenes/left_hand.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://cqi3wsvqno12v

11
scenes/left_hand.tscn Normal file
View file

@ -0,0 +1,11 @@
[gd_scene format=3 uid="uid://cl3hb1nm4xy8t"]
[ext_resource type="Texture2D" path="res://images/virtua_hand_left.png" id="1_lhand"]
[ext_resource type="Script" path="res://scenes/left_hand.gd" id="2_lhscr"]
[node name="LeftHand" type="Sprite3D"]
billboard = 1
texture_filter = 0
alpha_cut = 1
texture = ExtResource("1_lhand")
script = ExtResource("2_lhscr")

View file

@ -7,6 +7,7 @@
[ext_resource type="PackedScene" uid="uid://cwwexawpj46hk" path="res://scenes/elevator_button.tscn" id="5_qfnet"]
[ext_resource type="Script" uid="uid://c6v2lhrkeup5i" path="res://scenes/world.gd" id="5_world"]
[ext_resource type="PackedScene" uid="uid://cnjn0vhg1phav" path="res://scenes/button_panel.tscn" id="7_i7141"]
[ext_resource type="PackedScene" uid="uid://cl3hb1nm4xy8t" path="res://scenes/left_hand.tscn" id="8_lefth"]
[sub_resource type="BoxShape3D" id="BoxShape3D_k0juu"]
size = Vector3(3.8, 6.6328125, 0.8612671)
@ -65,6 +66,10 @@ shape = SubResource("BoxShape3D_k0juu")
[node name="ButtonPanel" parent="ElevatorSafeZone" unique_id=1270714626 instance=ExtResource("7_i7141")]
transform = Transform3D(0.3, 0, 0, 0, 0.3, 0, 0, 0, 0.3, 3.4193654, 2.2598982, -0.39259052)
[node name="LeftHand" parent="ElevatorSafeZone" instance=ExtResource("8_lefth")]
transform = Transform3D(3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0)
visible = false
[node name="SurvivorSpawn" type="Marker3D" parent="." unique_id=1095768768]
unique_name_in_owner = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.9181392, 13.711893)