Skip to content

Commit e404165

Browse files
authored
Show player inputs during replay playback (#32)
1 parent 3223cce commit e404165

7 files changed

Lines changed: 366 additions & 4 deletions

File tree

scripts/test-godot.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ GODOT_BIN="${GODOT_BIN:-godot}"
99
TEST_SCRIPTS=(
1010
"res://tests/unit/runtime_options_test.gd"
1111
"res://tests/unit/bootstrap_test.gd"
12+
"res://tests/replay_input_display_test.gd"
1213
"res://tests/world_record_announcement_test.gd"
1314
"res://tests/offline_playtest_smoke.gd"
1415
"res://tests/aim_menu_smoke.gd"

src/maps/map_ui.gd

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ signal return_control_to_player
1515
@export var replay_slider: HSlider
1616
@export var replay_container: Control
1717
@export var replay_v: VBoxContainer
18+
@export var replay_input_display: ReplayInputDisplay
1819
@export var game_info: Container
1920
@export var ammo_label: Label
2021
@export var leaderboard: Container
@@ -33,10 +34,31 @@ func set_frame(frame: int, total: int) -> void:
3334
replay_slider.value = frame
3435
replay_slider.max_value = total - 1
3536

37+
func set_replay_inputs(
38+
forward_input: bool,
39+
back_input: bool,
40+
left_input: bool,
41+
right_input: bool,
42+
shoot_input: bool,
43+
ads_input: bool,
44+
reload_input: bool,
45+
) -> void:
46+
replay_input_display.set_inputs(
47+
forward_input,
48+
back_input,
49+
left_input,
50+
right_input,
51+
shoot_input,
52+
ads_input,
53+
reload_input,
54+
)
55+
3656
func set_replay_visible(value: bool) -> void:
3757
replay_container.visible = value
3858
replay_v.visible = value
3959
map.recorder.controller.visible = value
60+
if not value:
61+
replay_input_display.reset()
4062

4163
func is_replay_visible() -> bool:
4264
return replay_container.visible

src/maps/map_ui.tscn

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[gd_scene load_steps=13 format=3 uid="uid://bnati2e7fo1g"]
1+
[gd_scene load_steps=14 format=3 uid="uid://bnati2e7fo1g"]
22

33
[ext_resource type="Script" uid="uid://vuacki0u3vnw" path="res://src/maps/map_ui.gd" id="1_jnrmt"]
44
[ext_resource type="Theme" uid="uid://3as6f5nxctip" path="res://src/main_theme.tres" id="2_uralg"]
@@ -10,21 +10,23 @@
1010
[ext_resource type="Texture2D" uid="uid://dqvbiit5bj0rm" path="res://src/textures/plat_medal.png" id="9_k6ak5"]
1111
[ext_resource type="Texture2D" uid="uid://doueeo1np6if1" path="res://src/textures/author_medal.png" id="10_dxk01"]
1212
[ext_resource type="FontFile" uid="uid://reim7hyo6sqy" path="res://src/fonts/OpenSans-Regular.ttf" id="11_5vm7s"]
13+
[ext_resource type="PackedScene" path="res://src/maps/replay_input_display.tscn" id="12_pfxlv"]
1314

1415
[sub_resource type="Theme" id="Theme_of3v1"]
1516
default_font_size = 15
1617

1718
[sub_resource type="Theme" id="Theme_nvgh5"]
1819
default_font = ExtResource("11_5vm7s")
1920

20-
[node name="MapUi" type="CanvasLayer" node_paths=PackedStringArray("keybind_info_label", "done_replay_btn", "tick_label", "replay_slider", "replay_container", "replay_v", "game_info", "ammo_label", "leaderboard")]
21+
[node name="MapUi" type="CanvasLayer" node_paths=PackedStringArray("keybind_info_label", "done_replay_btn", "tick_label", "replay_slider", "replay_container", "replay_v", "replay_input_display", "game_info", "ammo_label", "leaderboard")]
2122
script = ExtResource("1_jnrmt")
2223
keybind_info_label = NodePath("ReplayContainer/V/KeybindInfo")
2324
done_replay_btn = NodePath("ReplayContainer/V/Done")
2425
tick_label = NodePath("ReplayContainer/V/Label")
2526
replay_slider = NodePath("ReplayContainer/V/Slider")
2627
replay_container = NodePath("ReplayContainer")
2728
replay_v = NodePath("ReplayContainer/V")
29+
replay_input_display = NodePath("ReplayContainer/V/ReplayInputDisplay")
2830
game_info = NodePath("UiContainer/GameInfo")
2931
ammo_label = NodePath("UiContainer/BottomLeft/V/Ammo")
3032
leaderboard = NodePath("UiContainer/GameInfo/Leaderboard")
@@ -327,7 +329,7 @@ anchor_top = 1.0
327329
anchor_right = 0.5
328330
anchor_bottom = 1.0
329331
offset_left = -150.0
330-
offset_top = -147.0
332+
offset_top = -215.0
331333
offset_right = 150.0
332334
grow_horizontal = 2
333335
grow_vertical = 0
@@ -336,12 +338,15 @@ grow_vertical = 0
336338
custom_minimum_size = Vector2(300, 0)
337339
layout_mode = 2
338340
offset_right = 300.0
339-
offset_bottom = 147.0
341+
offset_bottom = 215.0
340342
grow_horizontal = 2
341343
grow_vertical = 0
342344
theme = ExtResource("2_uralg")
343345
alignment = 1
344346

347+
[node name="ReplayInputDisplay" parent="ReplayContainer/V" instance=ExtResource("12_pfxlv")]
348+
layout_mode = 2
349+
345350
[node name="Label" type="Label" parent="ReplayContainer/V"]
346351
layout_mode = 2
347352
text = "Frame: 0 / 999"

src/maps/replay_input_display.gd

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
class_name ReplayInputDisplay
2+
extends HBoxContainer
3+
4+
const ACTIVE_BG := Color(0.94, 0.93, 0.89, 1.0)
5+
const ACTIVE_BORDER := Color(0.18, 0.18, 0.18, 1.0)
6+
const ACTIVE_FONT := Color(0.10, 0.10, 0.10, 1.0)
7+
const INACTIVE_BG := Color(0.12, 0.14, 0.17, 1.0)
8+
const INACTIVE_BORDER := Color(0.30, 0.33, 0.37, 1.0)
9+
const INACTIVE_FONT := Color(0.78, 0.81, 0.84, 1.0)
10+
11+
var _input_states := {
12+
&"forward": false,
13+
&"back": false,
14+
&"left": false,
15+
&"right": false,
16+
&"shoot": false,
17+
&"ads": false,
18+
&"reload": false,
19+
}
20+
21+
var _key_panels: Dictionary = { }
22+
var _key_labels: Dictionary = { }
23+
var _active_panel_style: StyleBoxFlat
24+
var _inactive_panel_style: StyleBoxFlat
25+
26+
func _ready() -> void:
27+
_active_panel_style = _create_panel_style(ACTIVE_BG, ACTIVE_BORDER)
28+
_inactive_panel_style = _create_panel_style(INACTIVE_BG, INACTIVE_BORDER)
29+
30+
_register_decorative_panel($"Movement/TopLeftSpacer")
31+
_register_key(&"forward", $"Movement/W")
32+
_register_decorative_panel($"Movement/TopRightSpacer")
33+
_register_key(&"back", $"Movement/S")
34+
_register_key(&"left", $"Movement/A")
35+
_register_key(&"right", $"Movement/D")
36+
_register_key(&"shoot", $"Combat/Shoot")
37+
_register_key(&"ads", $"Combat/Ads")
38+
_register_key(&"reload", $"Combat/Reload")
39+
40+
reset()
41+
42+
func set_inputs(
43+
forward_input: bool,
44+
back_input: bool,
45+
left_input: bool,
46+
right_input: bool,
47+
shoot_input: bool,
48+
ads_input: bool,
49+
reload_input: bool,
50+
) -> void:
51+
_input_states[&"forward"] = forward_input
52+
_input_states[&"back"] = back_input
53+
_input_states[&"left"] = left_input
54+
_input_states[&"right"] = right_input
55+
_input_states[&"shoot"] = shoot_input
56+
_input_states[&"ads"] = ads_input
57+
_input_states[&"reload"] = reload_input
58+
_apply_visual_state()
59+
60+
func reset() -> void:
61+
set_inputs(false, false, false, false, false, false, false)
62+
63+
func is_input_active(input_name: StringName) -> bool:
64+
return _input_states.get(input_name, false)
65+
66+
func _register_key(action_name: StringName, panel: PanelContainer) -> void:
67+
var label := panel.get_node_or_null("Label") as Label
68+
_key_panels[action_name] = panel
69+
_key_labels[action_name] = label
70+
71+
func _register_decorative_panel(panel: PanelContainer) -> void:
72+
if panel != null:
73+
panel.add_theme_stylebox_override("panel", _inactive_panel_style)
74+
75+
func _apply_visual_state() -> void:
76+
for action_name in _key_panels.keys():
77+
var panel := _key_panels[action_name] as PanelContainer
78+
var label := _key_labels.get(action_name) as Label
79+
var active := _input_states.get(action_name, false)
80+
81+
if panel != null:
82+
panel.add_theme_stylebox_override("panel", _active_panel_style if active else _inactive_panel_style)
83+
if label != null:
84+
label.add_theme_color_override("font_color", ACTIVE_FONT if active else INACTIVE_FONT)
85+
86+
func _create_panel_style(bg_color: Color, border_color: Color) -> StyleBoxFlat:
87+
var style := StyleBoxFlat.new()
88+
style.bg_color = bg_color
89+
style.border_color = border_color
90+
style.border_width_left = 1
91+
style.border_width_top = 1
92+
style.border_width_right = 1
93+
style.border_width_bottom = 1
94+
style.content_margin_left = 5
95+
style.content_margin_top = 3
96+
style.content_margin_right = 5
97+
style.content_margin_bottom = 3
98+
return style

src/maps/replay_input_display.tscn

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
[gd_scene load_steps=2 format=3]
2+
3+
[ext_resource type="Script" path="res://src/maps/replay_input_display.gd" id="1_ry1dp"]
4+
5+
[node name="ReplayInputDisplay" type="HBoxContainer"]
6+
custom_minimum_size = Vector2(0, 64)
7+
layout_mode = 2
8+
theme_override_constants/separation = 12
9+
alignment = 1
10+
script = ExtResource("1_ry1dp")
11+
12+
[node name="Movement" type="GridContainer" parent="."]
13+
layout_mode = 2
14+
theme_override_constants/h_separation = 4
15+
theme_override_constants/v_separation = 4
16+
columns = 3
17+
18+
[node name="TopLeftSpacer" type="PanelContainer" parent="Movement"]
19+
custom_minimum_size = Vector2(30, 30)
20+
layout_mode = 2
21+
22+
[node name="W" type="PanelContainer" parent="Movement"]
23+
custom_minimum_size = Vector2(30, 30)
24+
layout_mode = 2
25+
26+
[node name="Label" type="Label" parent="Movement/W"]
27+
layout_mode = 2
28+
theme_override_font_sizes/font_size = 15
29+
text = "W"
30+
horizontal_alignment = 1
31+
vertical_alignment = 1
32+
33+
[node name="TopRightSpacer" type="PanelContainer" parent="Movement"]
34+
custom_minimum_size = Vector2(30, 30)
35+
layout_mode = 2
36+
37+
[node name="A" type="PanelContainer" parent="Movement"]
38+
custom_minimum_size = Vector2(30, 30)
39+
layout_mode = 2
40+
41+
[node name="Label" type="Label" parent="Movement/A"]
42+
layout_mode = 2
43+
theme_override_font_sizes/font_size = 15
44+
text = "A"
45+
horizontal_alignment = 1
46+
vertical_alignment = 1
47+
48+
[node name="S" type="PanelContainer" parent="Movement"]
49+
custom_minimum_size = Vector2(30, 30)
50+
layout_mode = 2
51+
52+
[node name="Label" type="Label" parent="Movement/S"]
53+
layout_mode = 2
54+
theme_override_font_sizes/font_size = 15
55+
text = "S"
56+
horizontal_alignment = 1
57+
vertical_alignment = 1
58+
59+
[node name="D" type="PanelContainer" parent="Movement"]
60+
custom_minimum_size = Vector2(30, 30)
61+
layout_mode = 2
62+
63+
[node name="Label" type="Label" parent="Movement/D"]
64+
layout_mode = 2
65+
theme_override_font_sizes/font_size = 15
66+
text = "D"
67+
horizontal_alignment = 1
68+
vertical_alignment = 1
69+
70+
[node name="Combat" type="HBoxContainer" parent="."]
71+
layout_mode = 2
72+
theme_override_constants/separation = 4
73+
alignment = 1
74+
75+
[node name="Shoot" type="PanelContainer" parent="Combat"]
76+
custom_minimum_size = Vector2(46, 30)
77+
layout_mode = 2
78+
79+
[node name="Label" type="Label" parent="Combat/Shoot"]
80+
layout_mode = 2
81+
theme_override_font_sizes/font_size = 15
82+
text = "LMB"
83+
horizontal_alignment = 1
84+
vertical_alignment = 1
85+
86+
[node name="Ads" type="PanelContainer" parent="Combat"]
87+
custom_minimum_size = Vector2(46, 30)
88+
layout_mode = 2
89+
90+
[node name="Label" type="Label" parent="Combat/Ads"]
91+
layout_mode = 2
92+
theme_override_font_sizes/font_size = 15
93+
text = "RMB"
94+
horizontal_alignment = 1
95+
vertical_alignment = 1
96+
97+
[node name="Reload" type="PanelContainer" parent="Combat"]
98+
custom_minimum_size = Vector2(30, 30)
99+
layout_mode = 2
100+
101+
[node name="Label" type="Label" parent="Combat/Reload"]
102+
layout_mode = 2
103+
theme_override_font_sizes/font_size = 15
104+
text = "R"
105+
horizontal_alignment = 1
106+
vertical_alignment = 1

src/recorder.gd

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,17 @@ func set_frame(value: int) -> void:
103103
else:
104104
map.map_ui.set_speed(speed)
105105
map.map_ui.set_timer(current_frame * dt)
106+
map \
107+
.map_ui \
108+
.set_replay_inputs(
109+
frame.forward_input,
110+
frame.back_input,
111+
frame.left_input,
112+
frame.right_input,
113+
frame.shoot_input,
114+
frame.ads_input,
115+
frame.reload_input,
116+
)
106117

107118
if prev_frame.weapon_index != frame.weapon_index:
108119
controller.weapon_handler.set_weapon(Global.game_manager.get_weapon_from_index(frame.weapon_index), is_ghost)

0 commit comments

Comments
 (0)