small racing game im working on
ghostwatchers [skip ci]
bendn 2023-03-09
parent d521916 · commit e839c01
-rw-r--r--assets/cars/engine.gd10
-rw-r--r--assets/cars/engine.tscn27
-rw-r--r--assets/cars/kenney_sedan/ghost.tscn17
-rw-r--r--assets/cars/kenney_sedan/sedan.tscn17
-rw-r--r--cam.gd6
-rw-r--r--classes/ai_car.gd98
-rw-r--r--classes/car.gd7
-rw-r--r--classes/ghost.gd43
-rw-r--r--classes/resources/car_vars.gd9
-rw-r--r--classes/resources/ghost_data.gd25
m---------enginesound0
-rw-r--r--globals.gd1
-rw-r--r--project.godot5
-rw-r--r--race.gd18
-rw-r--r--scenes/ghost_watcher.gd61
-rw-r--r--scenes/ghost_watcher.tscn12
-rw-r--r--scenes/race_highlevel.gd2
-rw-r--r--scenes/track.tscn6
-rw-r--r--start.tscn4
-rw-r--r--ui/gears.gd4
-rw-r--r--ui/hud.gd2
-rw-r--r--ui/map.gd5
-rw-r--r--ui/speedometer.gd4
-rw-r--r--ui/track_button.tscn4
-rw-r--r--ui/trackbutton.gd7
-rw-r--r--ui/tracks.gd15
26 files changed, 286 insertions, 123 deletions
diff --git a/assets/cars/engine.gd b/assets/cars/engine.gd
index f64761d..911965e 100644
--- a/assets/cars/engine.gd
+++ b/assets/cars/engine.gd
@@ -1,9 +1,15 @@
extends EngineNoise
-@onready var car: Car = get_parent();
+@onready var car = get_parent().get_parent();
+@onready var player: AudioStreamPlayer3D = get_parent();
func _ready() -> void:
- set_stream($Player.get_stream_playback())
+ set_process(false)
+ for i in 4:
+ await RenderingServer.frame_post_draw # buffer underrun causes it to stop, and the cpu is busy when loading the track and rendering and stuff. https://github.com/godotengine/godot/pull/73162
+ player.play()
+ set_stream(player.get_stream_playback())
+ set_process(true)
func _process(_d: float):
set_rpm(car.engine_rpm)
diff --git a/assets/cars/engine.tscn b/assets/cars/engine.tscn
new file mode 100644
index 0000000..598833b
--- /dev/null
+++ b/assets/cars/engine.tscn
@@ -0,0 +1,27 @@
+[gd_scene load_steps=4 format=3 uid="uid://ceocarryg6o86"]
+
+[ext_resource type="Script" path="res://assets/cars/engine.gd" id="1_12n5h"]
+
+[sub_resource type="AudioStreamGenerator" id="AudioStreamGenerator_qo6ul"]
+mix_rate = 30000.0
+buffer_length = 0.2
+
+[sub_resource type="GDScript" id="GDScript_dvwcc"]
+resource_name = "engine_inbetween"
+script/source = "extends AudioStreamPlayer3D
+
+@onready var gen: EngineNoise = $generator
+
+@onready var volume: float: # not decibels
+ set(v):
+ volume = v
+ gen.set_volume(v)
+"
+
+[node name="Engine" type="AudioStreamPlayer3D"]
+stream = SubResource("AudioStreamGenerator_qo6ul")
+doppler_tracking = 2
+script = SubResource("GDScript_dvwcc")
+
+[node name="generator" type="EngineNoise" parent="."]
+script = ExtResource("1_12n5h")
diff --git a/assets/cars/kenney_sedan/ghost.tscn b/assets/cars/kenney_sedan/ghost.tscn
index 9df9deb..da6f59f 100644
--- a/assets/cars/kenney_sedan/ghost.tscn
+++ b/assets/cars/kenney_sedan/ghost.tscn
@@ -1,8 +1,9 @@
-[gd_scene load_steps=18 format=3 uid="uid://u8m366adrlqr"]
+[gd_scene load_steps=19 format=3 uid="uid://u8m366adrlqr"]
[ext_resource type="Script" path="res://classes/ghost.gd" id="2_owhnn"]
[ext_resource type="Material" uid="uid://cqm6nr0j5lo0o" path="res://assets/cars/kenney_sedan/blue_paint.tres" id="2_w22b1"]
[ext_resource type="PackedScene" uid="uid://c5kk8cn8ipuy1" path="res://scenes/skid_particles.tscn" id="4_4uffx"]
+[ext_resource type="PackedScene" uid="uid://ceocarryg6o86" path="res://assets/cars/engine.tscn" id="4_ku3bs"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_odiep"]
resource_name = "plastic"
@@ -337,10 +338,10 @@ skeleton = NodePath("")
surface_material_override/1 = ExtResource("2_w22b1")
[node name="br" type="Node3D" parent="."]
-transform = Transform3D(-1, 0, 8.74228e-08, 0, 1, 0, -8.74228e-08, 0, -1, 1.2, 0.8, 1.51)
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.2, 0.8, 1.51)
[node name="backright" type="MeshInstance3D" parent="br"]
-transform = Transform3D(-2.33967, 0, -2.0454e-07, 0, 2.33967, 0, 2.0454e-07, 0, -2.33967, 0.5, 0, 0)
+transform = Transform3D(2.33967, 0, 0, 0, 2.33967, 0, 0, 0, 2.33967, -0.5, 0, 0)
mesh = SubResource("ArrayMesh_qyvuj")
skeleton = NodePath("")
@@ -348,10 +349,10 @@ skeleton = NodePath("")
transform = Transform3D(2.33967, 0, 0, 0, 2.33967, 0, 0, 0, 2.33967, -0.4981, -0.0980995, 0.034181)
[node name="bl" type="Node3D" parent="."]
-transform = Transform3D(-1, 0, 8.74228e-08, 0, 1, 0, -8.74228e-08, 0, -1, -1.2, 0.8, 1.51)
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.2, 0.8, 1.51)
[node name="backleft" type="MeshInstance3D" parent="bl"]
-transform = Transform3D(2.33967, 2.0454e-07, 0, -2.0454e-07, 2.33967, 0, 0, 0, -2.33967, -0.5, 0, 0)
+transform = Transform3D(-2.33967, 2.0454e-07, 2.0454e-07, 2.0454e-07, 2.33967, 0, 2.0454e-07, -1.78815e-14, 2.33967, 0.5, 0, 0)
mesh = SubResource("ArrayMesh_qyvuj")
skeleton = NodePath("")
@@ -362,7 +363,7 @@ transform = Transform3D(2.33967, 0, 2.0454e-07, 0, 2.33967, 0, 2.0454e-07, 0, -2
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, -1.2, 0.8, -1.51)
[node name="frontleft" type="MeshInstance3D" parent="fl"]
-transform = Transform3D(2.33967, -1.78815e-14, -2.0454e-07, 0, 2.33967, -2.0454e-07, -2.0454e-07, -2.0454e-07, -2.33967, -0.5, 0, 0)
+transform = Transform3D(2.33967, 2.0454e-07, 0, -2.0454e-07, 2.33967, 0, 0, 0, -2.33967, -0.5, 0, 0)
mesh = SubResource("ArrayMesh_qyvuj")
skeleton = NodePath("")
@@ -373,9 +374,11 @@ transform = Transform3D(2.33967, -1.78815e-14, -2.0454e-07, 0, 2.33967, -2.0454e
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 1.2, 0.8, -1.51)
[node name="frontright" type="MeshInstance3D" parent="fr"]
-transform = Transform3D(2.33967, 0, 0, 0, 2.33967, 0, 0, 0, 2.33967, -0.5, 0, 0)
+transform = Transform3D(-2.33967, 0, -2.0454e-07, 0, 2.33967, 0, 2.0454e-07, 0, -2.33967, 0.5, 0, 0)
mesh = SubResource("ArrayMesh_qyvuj")
skeleton = NodePath("")
[node name="particles" parent="fr" instance=ExtResource("4_4uffx")]
transform = Transform3D(2.33967, 0, 0, 0, 2.33967, 0, 0, 0, 2.33967, 0.7019, 0.7019, -1.54418)
+
+[node name="Engine" parent="." instance=ExtResource("4_ku3bs")]
diff --git a/assets/cars/kenney_sedan/sedan.tscn b/assets/cars/kenney_sedan/sedan.tscn
index 0135c26..5aa64c1 100644
--- a/assets/cars/kenney_sedan/sedan.tscn
+++ b/assets/cars/kenney_sedan/sedan.tscn
@@ -1,9 +1,9 @@
-[gd_scene load_steps=24 format=3 uid="uid://c157wew5y33bo"]
+[gd_scene load_steps=23 format=3 uid="uid://c157wew5y33bo"]
[ext_resource type="Script" path="res://classes/car.gd" id="1_pdccm"]
[ext_resource type="Material" uid="uid://b6d4vebgc2y88" path="res://assets/cars/kenney_sedan/red_paint.tres" id="2_luxc5"]
-[ext_resource type="Script" path="res://assets/cars/engine.gd" id="2_rxib8"]
[ext_resource type="PackedScene" uid="uid://c5kk8cn8ipuy1" path="res://scenes/skid_particles.tscn" id="4_axi2x"]
+[ext_resource type="PackedScene" uid="uid://ceocarryg6o86" path="res://assets/cars/engine.tscn" id="4_gseqg"]
[ext_resource type="Script" path="res://assets/cars/gearbox.gd" id="11_augb2"]
[ext_resource type="AudioStream" uid="uid://di8wcnme3mqbf" path="res://assets/sounds/shifts/1.wav" id="11_rwd1j"]
[ext_resource type="AudioStream" uid="uid://d3rhrhg8srpdg" path="res://assets/sounds/checkpoint.ogg" id="19_stkh0"]
@@ -326,10 +326,6 @@ shadow_mesh = SubResource("ArrayMesh_8qmjc")
[sub_resource type="SphereShape3D" id="SphereShape3D_t532n"]
radius = 2.0
-[sub_resource type="AudioStreamGenerator" id="AudioStreamGenerator_qo6ul"]
-mix_rate = 30000.0
-buffer_length = 0.2
-
[node name="Sedan" type="VehicleBody3D"]
collision_layer = 2
collision_mask = 3
@@ -434,12 +430,7 @@ skeleton = NodePath("")
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.43328, 0)
shape = SubResource("SphereShape3D_t532n")
-[node name="Engine" type="EngineNoise" parent="."]
-script = ExtResource("2_rxib8")
-
-[node name="Player" type="AudioStreamPlayer" parent="Engine"]
-stream = SubResource("AudioStreamGenerator_qo6ul")
-autoplay = true
+[node name="Engine" parent="." instance=ExtResource("4_gseqg")]
[node name="Gearbox" type="Node3D" parent="."]
script = ExtResource("11_augb2")
@@ -452,4 +443,6 @@ volume_db = -25.0
stream = ExtResource("19_stkh0")
volume_db = -10.0
+[node name="Camera3D" type="Camera3D" parent="."]
+
[connection signal="shifted" from="." to="Gearbox" method="_on_sedan_shifted"]
diff --git a/cam.gd b/cam.gd
index dfcaedb..fb16e2f 100644
--- a/cam.gd
+++ b/cam.gd
@@ -34,10 +34,10 @@ func target() -> Vector3:
func _physics_process(delta):
- global_position = global_position.lerp(target(), delta * 20.0)
- last_lookat = last_lookat.lerp(follow_this.global_position, delta * 20.0)
+ global_position = global_position.lerp(target(), delta * 50.0)
+ last_lookat = last_lookat.lerp(follow_this.global_position, delta * 50.0)
look_at(last_lookat, Vector3(0.0, 1.0, 0.0))
-func _init(car: Car) -> void:
+func _init(car) -> void:
follow_this = car
diff --git a/classes/ai_car.gd b/classes/ai_car.gd
index 5c93fc9..d7e0361 100644
--- a/classes/ai_car.gd
+++ b/classes/ai_car.gd
@@ -1,52 +1,52 @@
extends Car
class_name AICar
-const num_rays := 32
-const look_ahead := 40.0
-const hidden_nodes := 17
-const output_nodes := 3
-
-var rays: Array[RayCast3D]
-var nn: NeuralNetwork
-
-
-func _ready():
- super()
- randomize()
- engine_force *= randf_range(0.8, 1)
- add_rays()
- # +1 for speed
- nn = NeuralNetwork.new(num_rays + 1, hidden_nodes, output_nodes)
-
-
-func add_rays():
- var n: float = 2 * PI / num_rays
- var ray_holder := $CarMesh/ContextRays
- for i in num_rays:
- var r := RayCast3D.new()
- ray_holder.add_child(r)
- r.target_position = Vector3.FORWARD * look_ahead
- r.rotation.y = (-n * i)
- rays.append(r)
-
-
-func get_distances() -> Array[float]:
- var distances: Array[float] = []
- for i in num_rays:
- var distance: float = 1.0
- if rays[i].is_colliding():
- var raycast_length: float = rays[i].target_position.y
- var collision: Vector3 = rays[i].get_collision_point()
- distance = rays[i].global_transform.origin.distance_to(collision) / raycast_length
- distances.append(distance)
- return distances
-
-func _physics_process(delta: float) -> void:
- var distances := get_distances()
- distances.append(kph())
- var outputs := nn.predict(distances) # [steer_l, throt, steer_r]
- print(outputs)
- # var steer_target = (outputs[0] - outputs[2]) * max_steering_range
- # throttle = acceleration * outputs[1]
- steer(steer_target)
- super(delta)
+# const num_rays := 32
+# const look_ahead := 40.0
+# const hidden_nodes := 17
+# const output_nodes := 3
+
+# var rays: Array[RayCast3D]
+# var nn: NeuralNetwork
+
+
+# func _ready():
+# super()
+# randomize()
+# engine_force *= randf_range(0.8, 1)
+# add_rays()
+# # +1 for speed
+# nn = NeuralNetwork.new(num_rays + 1, hidden_nodes, output_nodes)
+
+
+# func add_rays():
+# var n: float = 2 * PI / num_rays
+# var ray_holder := $CarMesh/ContextRays
+# for i in num_rays:
+# var r := RayCast3D.new()
+# ray_holder.add_child(r)
+# r.target_position = Vector3.FORWARD * look_ahead
+# r.rotation.y = (-n * i)
+# rays.append(r)
+
+
+# func get_distances() -> Array[float]:
+# var distances: Array[float] = []
+# for i in num_rays:
+# var distance: float = 1.0
+# if rays[i].is_colliding():
+# var raycast_length: float = rays[i].target_position.y
+# var collision: Vector3 = rays[i].get_collision_point()
+# distance = rays[i].global_transform.origin.distance_to(collision) / raycast_length
+# distances.append(distance)
+# return distances
+
+# func _physics_process(delta: float) -> void:
+# var distances := get_distances()
+# distances.append(kph())
+# var outputs := nn.predict(distances) # [steer_l, throt, steer_r]
+# print(outputs)
+# # var steer_target = (outputs[0] - outputs[2]) * max_steering_range
+# # throttle = acceleration * outputs[1]
+# steer(steer_target)
+# super(delta)
diff --git a/classes/car.gd b/classes/car.gd
index 2128a45..0b7d19c 100644
--- a/classes/car.gd
+++ b/classes/car.gd
@@ -66,12 +66,15 @@ func reset() -> void:
if skid is Trail3D:
skid.queue_free()
wheel.clear()
+ for p in particles:
+ p.emitting = false
skids = [[inactive], [inactive], [inactive], [inactive]] # performance and complexity hack
func _ready() -> void:
for whl in wheels:
particles.append(whl.get_node(^"particles"))
randomize()
+ reset()
func kph() -> float:
return (3 * PI * wheel_radius * wheel_rpm) / 25;
@@ -121,8 +124,8 @@ func _process(delta: float):
if can_shift:
_process_gear_inputs(delta)
steering = -steer_target
- body_mesh.rotation.z = lerp(body_mesh.rotation.z, clampf(((-steering * .2) * linear_velocity.length_squared() / 685.0) + randf_range(-0.05,0.05), -.4, .4), 10 * delta)
- engine_rpm = move_toward(engine_rpm, (wheel_rpm * engine_force * 0.0015) + 800, 800)
+ body_mesh.rotation.z = lerp(body_mesh.rotation.z, clampf(((-steering * .001) * whl_rpm()) + randf_range(-0.05,0.05), -.4, .4), 10 * delta)
+ engine_rpm = clampf(move_toward(engine_rpm, (wheel_rpm * engine_force * 0.0015), 800), 800, MAX_ENGINE_FORCE)
func limit(delta: float) -> void:
linear_damp = max((.5 * delta) * (kph() - 400), 0) if kph() > 400 else 0.0
diff --git a/classes/ghost.gd b/classes/ghost.gd
index 1b65c87..eb376f1 100644
--- a/classes/ghost.gd
+++ b/classes/ghost.gd
@@ -1,22 +1,50 @@
extends Node3D
class_name GhostCar
-@onready var wheels: Array[Node3D] = [$bl as Node3D, $br as Node3D, $fl as Node3D, $fr as Node3D]
+@onready var wheels: Array[Node3D] = [$bl as Node3D, $br as Node3D, $fr as Node3D, $fl as Node3D]
+@onready var body_mesh: MeshInstance3D = $body
+@onready var engine: AudioStreamPlayer3D = $Engine
const trail_scene = preload("res://scenes/trail.tscn")
const inactive = {active = false};
var skids: Array[Array] = [[inactive], [inactive], [inactive], [inactive]]
var particles: Array[GPUParticles3D] = []
+var v: Dictionary # [CarVars]
+var current_gear := 0
+var engine_rpm := 800.0
func _ready() -> void:
for whl in wheels:
particles.append(whl.get_node(^"particles"))
-func update(v: Dictionary) -> void:
+func clear_skids() -> void:
+ for wheel in skids:
+ if wheel:
+ for skid in wheel:
+ if skid is Trail3D:
+ skid.queue_free()
+ wheel.clear()
+ skids = [[inactive], [inactive], [inactive], [inactive]]
+
+func reset(clear_skids := true) -> void:
+ engine_rpm = 800
+ current_gear = 0
+ for p in particles:
+ p.emitting = false
+ if clear_skids:
+ clear_skids()
+
+func update(car_vars: Dictionary, delta: float) -> void:
+ v = car_vars
+ engine_rpm = maxf(800, v.engine_rpm)
+ current_gear = v.current_gear
wheels[2].rotation.y = v.steering * .75
wheels[3].rotation.y = v.steering * .75
global_rotation = v.rotation
- global_position = v.position
+ if delta > 0:
+ global_position = lerp(global_position, v.position, 10 * delta)
+ else:
+ global_position = v.position
for i in 4:
particles[i].emitting = v.wheel_skidinfo[i] < (.2 if i > 2 else .99) and v.wheel_contact[i] and v.kph > 30
@@ -26,8 +54,13 @@ func update(v: Dictionary) -> void:
if !skids[i][-1].active:
skids[i].append(trail_scene.instantiate() as Trail3D)
get_parent().add_child(skids[i][-1])
- (skids[i][-1] as Trail3D).add(v.wheel_position[i] - Vector3(0, .661, 0))
+ (skids[i][-1] as Trail3D).add(to_global(v.wheel_position[i]) - Vector3(0, .661, 0))
elif skids[i][-1].active:
skids[i][-1].active = false
- wheels[i].global_position = v.wheel_position[i]
+ wheels[i].position = v.wheel_position[i]
+
+ body_mesh.rotation.z = lerp(body_mesh.rotation.z, clampf(((-v.steering * .001) * v.wheel_rpm) + randf_range(-0.05,0.05), -.4, .4), 5 * delta)
+
+func kph() -> float:
+ return v.kph
diff --git a/classes/resources/car_vars.gd b/classes/resources/car_vars.gd
index 2854034..5d30dcb 100644
--- a/classes/resources/car_vars.gd
+++ b/classes/resources/car_vars.gd
@@ -23,12 +23,14 @@ func _init(from: Car) -> void:
engine_rpm = from.engine_rpm
position = from.global_position
rotation = from.global_rotation
- kph = from.kph()
+ wheel_rpm = from.wheel_rpm
+ current_gear = from.current_gear
for i in from.wheels.size():
var wheel := from.wheels[i]
wheel_skidinfo.append(wheel.get_skidinfo())
wheel_contact.append(wheel.is_in_contact())
- wheel_position.append(wheel.global_position)
+ wheel_position.append(wheel.position)
+ kph = from.kph()
func to_dict(): # RIP memory
return {
@@ -36,5 +38,6 @@ func to_dict(): # RIP memory
engine_force = engine_force, engine_rpm = engine_rpm,
position = position, rotation = rotation, kph = kph,
wheel_skidinfo = wheel_skidinfo, wheel_contact = wheel_contact,
- wheel_position = wheel_position,
+ wheel_position = wheel_position, current_gear = current_gear,
+ wheel_rpm = wheel_rpm
}
diff --git a/classes/resources/ghost_data.gd b/classes/resources/ghost_data.gd
index 7be0426..8ad0915 100644
--- a/classes/resources/ghost_data.gd
+++ b/classes/resources/ghost_data.gd
@@ -3,6 +3,7 @@ extends Resource
var time: float
var checkpoints: Array
+var snapped_checkpoints: Array
# i hope this is performant
var snaps: Array#[Dictionary]
var snap_count := 0
@@ -16,14 +17,18 @@ func load_snap(i: int) -> Dictionary:
return snaps[i]
func save(path: String) -> void:
- GhostData._save_file(path, {checkpoints = checkpoints, time = time, snaps = snaps, snap_count = snap_count})
+ GhostData._save_file(path, {checkpoints = checkpoints, snapped_checkpoints = snapped_checkpoints, time = time, snaps = snaps, snap_count = snap_count})
+
+func mkarr(size: int) -> Array:
+ var arr := []
+ arr.resize(size)
+ arr.fill(-1)
+ return arr
func _init(num_checkpoints := 0, laps := 0) -> void:
for i in laps:
- var arr := []
- arr.resize(num_checkpoints + 1)
- arr.fill(-1)
- checkpoints.append(arr)
+ checkpoints.append(mkarr(num_checkpoints + 1))
+ snapped_checkpoints.append(mkarr(num_checkpoints + 1))
func clear() -> void:
for lap in checkpoints:
@@ -34,11 +39,10 @@ func clear() -> void:
func collect(lap: int, cp: int, now: float) -> void:
now = snappedf(now, .001) # 3dec precision
+ checkpoints[lap][cp] = now
+ snapped_checkpoints[lap][cp] = snap_count
if lap == len(checkpoints) - 1 && cp == -1:
- checkpoints[lap][cp] = now
time = now
- else:
- checkpoints[lap][cp] = now
func has_collected(lap: int, cp: int) -> bool:
return checkpoints[lap][cp] != -1
@@ -50,19 +54,20 @@ static func from_d(d: Dictionary) -> GhostData:
var obj := GhostData.new()
obj.time = d.time
obj.checkpoints = d.checkpoints
+ obj.snapped_checkpoints = d.snapped_checkpoints
obj.snap_count = d.snap_count
obj.snaps = d.snaps
return obj
## Saves a basic dictionary to a path.
static func _save_file(path: String, data: Dictionary) -> void:
- var file := FileAccess.open_compressed(path, FileAccess.WRITE, FileAccess.COMPRESSION_FASTLZ)
+ var file := FileAccess.open_compressed(path, FileAccess.WRITE, FileAccess.COMPRESSION_ZSTD)
file.store_buffer(var_to_bytes(data))
## Loads a basic dictionary out of a file.
static func _load_file(path: String) -> Dictionary:
if FileAccess.file_exists(path):
- var file := FileAccess.open_compressed(path, FileAccess.READ, FileAccess.COMPRESSION_FASTLZ)
+ var file := FileAccess.open_compressed(path, FileAccess.READ, FileAccess.COMPRESSION_ZSTD)
var text := file.get_buffer(file.get_length())
var dict := {}
if text:
diff --git a/enginesound b/enginesound
-Subproject 98702ab77a76c2360155949540ca7834d5d4ad7
+Subproject 4c6f5674627a1a13d676cbef77abfc1ebfedbdb
diff --git a/globals.gd b/globals.gd
index 3ae8391..43e9869 100644
--- a/globals.gd
+++ b/globals.gd
@@ -1,4 +1,5 @@
extends Node
var playing: TrackResource
+var ghost: GhostData
const SAVES := "user://%s.ghost"
diff --git a/project.godot b/project.godot
index d90ddcc..eb67706 100644
--- a/project.godot
+++ b/project.godot
@@ -29,8 +29,11 @@ window/stretch/aspect="ignore"
[editor]
-movie_writer/movie_file="/home/bendn/Documents/tracer/movie.mp4.avi"
+movie_writer/mix_rate=24000
+movie_writer/mjpeg_quality=0.8
+movie_writer/movie_file="/home/bendn/Documents/tracer/movie.avi"
movie_writer/disable_vsync=true
+movie_writer/fps=15
[importer_defaults]
diff --git a/race.gd b/race.gd
index 9123c92..618c9f7 100644
--- a/race.gd
+++ b/race.gd
@@ -26,11 +26,12 @@ signal finished(time: float, prev_time: float)
signal split(time: float, prev_time: float)
signal did_reset
-func _init(t: TrackResource, _car_scene, _ghost_scene, _track_loader_scene) -> void:
+func _init(t: TrackResource, ghost_data: GhostData, _car_scene, _ghost_scene, _track_loader_scene) -> void:
car_scene = _car_scene
ghost_scene = _ghost_scene
track_loader_scene = _track_loader_scene
track_res = t
+ best_time_data = ghost_data
func mkghost() -> void:
ghost = ghost_scene.instantiate()
@@ -40,11 +41,13 @@ func mkghost() -> void:
func reset_ghost() -> void:
if best_time_data:
- ghost.global_position = best_time_data.load_snap(0).position
- ghost.global_rotation = best_time_data.load_snap(0).rotation
+ ghost.update(best_time_data.load_snap(0), -1)
+ ghost.engine.volume = .1
else:
+ ghost.engine.volume = 0
ghost.global_position = track.start_pos + Vector3(0, 2, 0) - (car.global_transform.basis.z * 2)
ghost.rotation = track.start_rot + Vector3(0, PI, -PI/2)
+ ghost.reset()
ghost.hide()
func reset_car() -> void:
@@ -54,8 +57,6 @@ func reset_car() -> void:
car.linear_velocity = Vector3.ZERO
car.angular_velocity = Vector3.ZERO
car.current_gear = 0
- for p in car.particles:
- p.emitting = false
car.reset()
func mkcar() -> void:
@@ -70,7 +71,6 @@ func _ready() -> void:
track.track = track_res
add_child(track)
data = GhostData.new(track_res.checkpoints.size(), track_res.laps)
- best_time_data = GhostData._load(Globals.SAVES % track_res.name)
mkcar()
mkghost()
connect_checkpoints()
@@ -122,7 +122,7 @@ func passed_finish() -> void:
current_lap += 1
next_lap.emit()
-func _physics_process(_delta: float) -> void:
+func _physics_process(delta: float) -> void:
data.snapshot(car)
if best_time_data:
if best_time_data.snap_count - 1 < Engine.get_physics_frames() - start_frame:
@@ -130,8 +130,9 @@ func _physics_process(_delta: float) -> void:
print("ran out of snaps, hiding ghost")
ghost.hide()
return
- ghost.update(best_time_data.load_snap(Engine.get_physics_frames() - start_frame))
+ ghost.update(best_time_data.load_snap(Engine.get_physics_frames() - start_frame), delta)
ghost.visible = (ghost.global_position.distance_squared_to(car.global_position) > 10)
+ ghost.engine.volume = lerpf(ghost.engine.volume, .7, delta) if (ghost.global_position.distance_squared_to(car.global_position) > 20) else lerpf(ghost.engine.volume, .2, delta * 3)
func collect(cp: int) -> void:
if cp != -1:
@@ -141,7 +142,6 @@ func collect(cp: int) -> void:
split.emit(timer.now(), time)
data.collect(current_lap, cp, timer.now())
-
func start() -> void:
timer.start()
start_frame = Engine.get_physics_frames()
diff --git a/scenes/ghost_watcher.gd b/scenes/ghost_watcher.gd
new file mode 100644
index 0000000..abf2d1c
--- /dev/null
+++ b/scenes/ghost_watcher.gd
@@ -0,0 +1,61 @@
+extends Node3D
+class_name GhostWatcher
+
+@export var ghost_scene: PackedScene
+@export var track_loader_scene: PackedScene
+@export var hud_scene: PackedScene
+
+signal finished
+signal next_lap
+
+var start_frame: int
+var ghost: GhostCar
+var hud: HUD
+var timer := GameTimer.new()
+var current_lap := 0
+var current_cp := 0
+
+func _ready() -> void:
+ var track_loader: TrackLoader = track_loader_scene.instantiate()
+ track_loader.track = Globals.playing
+ add_child(track_loader)
+
+ ghost = ghost_scene.instantiate()
+ add_child(ghost)
+ ghost.show()
+ ghost.update(Globals.ghost.load_snap(0), -1)
+ add_child(CarCamera.new(ghost))
+
+ add_child(timer)
+ timer.start()
+
+ hud = hud_scene.instantiate()
+ hud.assigned.emit(ghost, ghost, timer, track_loader)
+ add_child(hud)
+ next_lap.connect(hud.laps.increment)
+
+ start_frame = Engine.get_physics_frames()
+
+
+func _physics_process(delta: float) -> void:
+ var frame := Engine.get_physics_frames() - start_frame;
+
+ # splits
+ if Globals.ghost.snapped_checkpoints[current_lap][current_cp] == frame:
+ hud.splits.update(Globals.ghost.checkpoints[current_lap][current_cp], -1)
+ if len(Globals.ghost.checkpoints[current_lap]) < current_cp + 1:
+ current_cp += 1
+ elif len(Globals.ghost.checkpoints) < current_lap + 1:
+ current_lap += 1
+ current_cp = 0
+ next_lap.emit()
+ if Globals.ghost.snap_count == frame:
+ hud.splits.update(Globals.ghost.time, -1)
+ set_physics_process(false)
+ timer.elapsed_time = Globals.ghost.time
+ timer.stop()
+ finished.emit()
+ ghost.reset(false)
+ return
+
+ ghost.update(Globals.ghost.load_snap(frame), delta)
diff --git a/scenes/ghost_watcher.tscn b/scenes/ghost_watcher.tscn
new file mode 100644
index 0000000..de6079f
--- /dev/null
+++ b/scenes/ghost_watcher.tscn
@@ -0,0 +1,12 @@
+[gd_scene load_steps=5 format=3 uid="uid://demunxvqkmtwa"]
+
+[ext_resource type="Script" path="res://scenes/ghost_watcher.gd" id="1_7ws8b"]
+[ext_resource type="PackedScene" uid="uid://clw61td2wh84w" path="res://scenes/track.tscn" id="1_syr3w"]
+[ext_resource type="PackedScene" uid="uid://u8m366adrlqr" path="res://assets/cars/kenney_sedan/ghost.tscn" id="2_ooh8d"]
+[ext_resource type="PackedScene" uid="uid://vok7fdcyec68" path="res://ui/hud.tscn" id="4_255eg"]
+
+[node name="GhostWatcher" type="Node3D"]
+script = ExtResource("1_7ws8b")
+ghost_scene = ExtResource("2_ooh8d")
+track_loader_scene = ExtResource("1_syr3w")
+hud_scene = ExtResource("4_255eg")
diff --git a/scenes/race_highlevel.gd b/scenes/race_highlevel.gd
index c9128ff..7b84429 100644
--- a/scenes/race_highlevel.gd
+++ b/scenes/race_highlevel.gd
@@ -14,7 +14,7 @@ var race: Race
var huds: Array[HUD]
func _ready() -> void:
- race = Race.new(Globals.playing, car_scene, ghost_scene, track_loader_scene)
+ race = Race.new(Globals.playing, Globals.ghost, car_scene, ghost_scene, track_loader_scene)
race.did_reset.connect(count_in)
add_child(race)
add_player()
diff --git a/scenes/track.tscn b/scenes/track.tscn
index 4fc2fd6..ae9dc86 100644
--- a/scenes/track.tscn
+++ b/scenes/track.tscn
@@ -1,19 +1,15 @@
-[gd_scene load_steps=10 format=3 uid="uid://clw61td2wh84w"]
+[gd_scene load_steps=8 format=3 uid="uid://clw61td2wh84w"]
-[ext_resource type="Curve3D" uid="uid://u2f56xx8h2re" path="res://tracks/multilap_test_curve.tres" id="1_ejp6j"]
[ext_resource type="Script" path="res://scenes/track-base.gd" id="1_ke7nx"]
[ext_resource type="Material" uid="uid://be8pta62kxd2j" path="res://assets/mats/road.tres" id="2_2nntu"]
[ext_resource type="Material" uid="uid://dtpgjplswm6lr" path="res://assets/mats/rail.tres" id="2_3pcob"]
[ext_resource type="Material" uid="uid://bk4sxd2prmmom" path="res://assets/mats/support.tres" id="3_4570s"]
-[ext_resource type="Resource" uid="uid://crye0ijvmtsyb" path="res://tracks/multilap_test.tres" id="3_cdqtb"]
[ext_resource type="PackedScene" uid="uid://cd4a5y0hi58ks" path="res://scenes/floor.tscn" id="7_fidh3"]
[ext_resource type="Environment" uid="uid://biwshm46yl62v" path="res://default_env.tres" id="8_2nyv3"]
[ext_resource type="CameraAttributesPractical" uid="uid://nhsovwj5hjip" path="res://cam.tres" id="9_6ooo5"]
[node name="TrackLoader" type="Path3D"]
-curve = ExtResource("1_ejp6j")
script = ExtResource("1_ke7nx")
-track = ExtResource("3_cdqtb")
metadata/_edit_group_ = true
[node name="Road" type="CSGPolygon3D" parent="."]
diff --git a/start.tscn b/start.tscn
index 6e99a13..9330bfc 100644
--- a/start.tscn
+++ b/start.tscn
@@ -1,4 +1,4 @@
-[gd_scene load_steps=8 format=3 uid="uid://bvfqaoqjsxj73"]
+[gd_scene load_steps=9 format=3 uid="uid://bvfqaoqjsxj73"]
[ext_resource type="Theme" uid="uid://cru1d7n2ftrfm" path="res://ui/theme.tres" id="1_gm0ws"]
[ext_resource type="Script" path="res://ui/tracks.gd" id="2_po2ce"]
@@ -6,6 +6,7 @@
[ext_resource type="Resource" path="res://tracks/test.tres" id="4_3xqvr"]
[ext_resource type="PackedScene" uid="uid://dhiei0g5tr74s" path="res://scenes/race_high.tscn" id="5_m5kci"]
[ext_resource type="Resource" path="res://tracks/the fallen tramps.tres" id="5_qwie6"]
+[ext_resource type="PackedScene" uid="uid://demunxvqkmtwa" path="res://scenes/ghost_watcher.tscn" id="7_6ph7w"]
[ext_resource type="PackedScene" uid="uid://dfvtugujgcjcw" path="res://ui/track_button.tscn" id="7_pchkj"]
[node name="start" type="Control"]
@@ -43,4 +44,5 @@ columns = 5
script = ExtResource("2_po2ce")
tracks = Array[Resource("res://classes/track.gd")]([ExtResource("3_0yjp1"), ExtResource("4_3xqvr"), ExtResource("5_qwie6")])
race = ExtResource("5_m5kci")
+ghost_watch = ExtResource("7_6ph7w")
trackbutton = ExtResource("7_pchkj")
diff --git a/ui/gears.gd b/ui/gears.gd
index 12b89fd..1b778d2 100644
--- a/ui/gears.gd
+++ b/ui/gears.gd
@@ -1,6 +1,6 @@
extends RichTextLabel
-var car: Car
+var car
# assumes 6 gear + rev
const F_STRING = "[center] [b]%s[/b][/center]"
@@ -9,5 +9,5 @@ const GEARS: PackedStringArray = ["[color=#4682b4]N[/color]", "1", "2", "3", "4"
func _process(_delta: float) -> void:
text = F_STRING % GEARS[car.current_gear]
-func assigned(_car: Car) -> void:
+func assigned(_car) -> void:
car = _car
diff --git a/ui/hud.gd b/ui/hud.gd
index 708bdd6..064ac6b 100644
--- a/ui/hud.gd
+++ b/ui/hud.gd
@@ -1,7 +1,7 @@
extends Node
class_name HUD
-signal assigned(car: Car, ghost: GhostCar, timer: GameTimer, track: TrackLoader)
+signal assigned(car, ghost: GhostCar, timer: GameTimer, track: TrackLoader)
signal next_lap
@export var splits: Splits
diff --git a/ui/map.gd b/ui/map.gd
index 4dd93f5..2b48db3 100644
--- a/ui/map.gd
+++ b/ui/map.gd
@@ -64,7 +64,8 @@ func add(v: Vector2) -> float:
func flatten(v: Vector3) -> Vector2:
return Vector2(v.x, v.z)
-func assigned(car: Car, ghost: GhostCar, _timer, _track: TrackLoader) -> void:
- mkfollower(car, player_indicator, player_color)
+func assigned(car, ghost: GhostCar, _timer, _track: TrackLoader) -> void:
mkfollower(ghost, ghost_indicator, Color(1,1,1,.5), true)
+ if car != ghost:
+ mkfollower(car, player_indicator, player_color)
track = _track
diff --git a/ui/speedometer.gd b/ui/speedometer.gd
index 3e6b953..64f01f1 100644
--- a/ui/speedometer.gd
+++ b/ui/speedometer.gd
@@ -1,10 +1,10 @@
extends Label
@export var f_string = "龍 %dkm/h"
-var car: Car
+var car
func _process(_delta: float) -> void:
text = f_string % car.kph()
-func car_assigned(_car: Car) -> void:
+func car_assigned(_car) -> void:
car = _car
diff --git a/ui/track_button.tscn b/ui/track_button.tscn
index a54c48e..d67a637 100644
--- a/ui/track_button.tscn
+++ b/ui/track_button.tscn
@@ -82,8 +82,10 @@ layout_mode = 2
size_flags_horizontal = 3
[node name="watch" type="Button" parent="h2"]
+unique_name_in_owner = true
layout_mode = 2
theme_override_font_sizes/font_size = 35
text = "󰿎 "
-[connection signal="pressed" from="h2/play" to="." method="emit_signal" binds= [&"pressed"]]
+[connection signal="pressed" from="h2/play" to="." method="emit_signal" binds= [&"play"]]
+[connection signal="pressed" from="h2/watch" to="." method="emit_signal" binds= [&"watch"]]
diff --git a/ui/trackbutton.gd b/ui/trackbutton.gd
index 35f211f..c6f30a9 100644
--- a/ui/trackbutton.gd
+++ b/ui/trackbutton.gd
@@ -1,18 +1,21 @@
extends Control
+class_name TrackButton
const trackloader_scn = preload("res://scenes/track.tscn")
const thumbnail_path = "user://%s.thumb"
-signal pressed
+signal play
+signal watch
func init(t: TrackResource, g: GhostData) -> void:
%name.text = t.name
if g == null:
+ %watch.hide()
%time.text = "no time set"
else:
%time.text = GameTimer.format_precise(g.time)
var p: String = thumbnail_path % t.name
- if FileAccess.file_exists(p) and Time.get_unix_time_from_system() - FileAccess.get_modified_time(p) < 40000: # ~5days
+ if FileAccess.file_exists(p) and Time.get_unix_time_from_system() - FileAccess.get_modified_time(p) < 400000: # ~5days
print("loading thumb")
var f := FileAccess.open(p, FileAccess.READ)
var img := Image.new()
diff --git a/ui/tracks.gd b/ui/tracks.gd
index f2ea755..89463b6 100644
--- a/ui/tracks.gd
+++ b/ui/tracks.gd
@@ -2,17 +2,26 @@ extends GridContainer
@export var tracks: Array[TrackResource]
@export var race: PackedScene
+@export var ghost_watch: PackedScene
@export var trackbutton: PackedScene
func _ready() -> void:
for track in tracks:
- var button := trackbutton.instantiate()
+ var button: TrackButton = trackbutton.instantiate()
add_child(button)
var ghost := GhostData._load(Globals.SAVES % track.name)
await button.init(track, ghost)
- button.pressed.connect(track_selected.bind(track))
+ button.play.connect(play.bind(track, ghost))
+ button.watch.connect(watch.bind(track, ghost))
-func track_selected(track: TrackResource) -> void:
+func play(track: TrackResource, ghost: GhostData) -> void:
print("play %s" % track.name)
Globals.playing = track
+ Globals.ghost = ghost
get_tree().change_scene_to_packed(race)
+
+func watch(track: TrackResource, ghost: GhostData) -> void:
+ print("watch %s" % track.name)
+ Globals.playing = track
+ Globals.ghost = ghost
+ get_tree().change_scene_to_packed(ghost_watch)