1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
# Hurry Curry! - a game about cooking
# Copyright (C) 2025 Hurry Curry! contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, version 3 of the License only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
extends Node
@export var record: bool = false
@export var replay: bool = false
@export_file var recording_path: String = ""
@onready var camera: Camera3D = $".."
var time = 0.
var keyframes = [] # Array<[float, Transform3D]>
func _ready() -> void:
if record and replay: return push_error("concurrent ecord and replay is not supported")
if replay: load_recording()
func _exit_tree() -> void:
if record: save_recording()
func load_recording():
print("Loading replay...")
var file := FileAccess.open(recording_path, FileAccess.READ)
var text := file.get_as_text()
keyframes.clear()
for line in text.split("\n"):
if line == "": continue
var toks = line.split(",")
var time2 = float(toks[0])
var transform = parse_transform(toks[1])
var fov = float(toks[2])
keyframes.push_back([time2, transform, fov])
print("Done")
func save_recording():
print("Saving replay...")
var file := FileAccess.open(recording_path, FileAccess.WRITE)
var out = ""
for k in keyframes:
out += "%f,%s,%f\n" % [k[0], serialize_transform(k[1]), k[2]]
file.store_string(out)
print("Done")
func serialize_transform(t: Transform3D) -> String:
return "%f|%f|%f|%f|%f|%f|%f|%f|%f|%f|%f|%f" % [
t.basis.x.x, t.basis.y.x, t.basis.z.x, t.origin.x,
t.basis.x.y, t.basis.y.y, t.basis.z.y, t.origin.y,
t.basis.x.z, t.basis.y.z, t.basis.z.z, t.origin.z,
]
func parse_transform(s: String) -> Transform3D:
var c := s.split("|")
var t := Transform3D.IDENTITY
t.basis.x.x = float(c[0])
t.basis.y.x = float(c[1])
t.basis.z.x = float(c[2])
t.origin.x = float(c[3])
t.basis.x.y = float(c[4])
t.basis.y.y = float(c[5])
t.basis.z.y = float(c[6])
t.origin.y = float(c[7])
t.basis.x.z = float(c[8])
t.basis.y.z = float(c[9])
t.basis.z.z = float(c[10])
t.origin.z = float(c[11])
return t
func _process(dt: float):
if Global.game_paused: return
time += dt
if record: keyframes.push_back([time, camera.global_transform, camera.fov])
if replay:
var index = keyframes.bsearch_custom([time], func (a, b): return a[0] < b[0])
if index >= keyframes.size(): return
var from = keyframes[max(0, index - 1)]
var to = keyframes[index]
var t = (time - from[0]) / (to[0] - from[0])
camera.global_transform = from[1].interpolate_with(to[1], t)
camera.fov = from[2] * (1. - t) + to[2] * t
|