small racing game im working on
ghostwatchers [skip ci]
| -rw-r--r-- | assets/cars/engine.gd | 10 | ||||
| -rw-r--r-- | assets/cars/engine.tscn | 27 | ||||
| -rw-r--r-- | assets/cars/kenney_sedan/ghost.tscn | 17 | ||||
| -rw-r--r-- | assets/cars/kenney_sedan/sedan.tscn | 17 | ||||
| -rw-r--r-- | cam.gd | 6 | ||||
| -rw-r--r-- | classes/ai_car.gd | 98 | ||||
| -rw-r--r-- | classes/car.gd | 7 | ||||
| -rw-r--r-- | classes/ghost.gd | 43 | ||||
| -rw-r--r-- | classes/resources/car_vars.gd | 9 | ||||
| -rw-r--r-- | classes/resources/ghost_data.gd | 25 | ||||
| m--------- | enginesound | 0 | ||||
| -rw-r--r-- | globals.gd | 1 | ||||
| -rw-r--r-- | project.godot | 5 | ||||
| -rw-r--r-- | race.gd | 18 | ||||
| -rw-r--r-- | scenes/ghost_watcher.gd | 61 | ||||
| -rw-r--r-- | scenes/ghost_watcher.tscn | 12 | ||||
| -rw-r--r-- | scenes/race_highlevel.gd | 2 | ||||
| -rw-r--r-- | scenes/track.tscn | 6 | ||||
| -rw-r--r-- | start.tscn | 4 | ||||
| -rw-r--r-- | ui/gears.gd | 4 | ||||
| -rw-r--r-- | ui/hud.gd | 2 | ||||
| -rw-r--r-- | ui/map.gd | 5 | ||||
| -rw-r--r-- | ui/speedometer.gd | 4 | ||||
| -rw-r--r-- | ui/track_button.tscn | 4 | ||||
| -rw-r--r-- | ui/trackbutton.gd | 7 | ||||
| -rw-r--r-- | ui/tracks.gd | 15 |
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"] @@ -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 @@ -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] @@ -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="."] @@ -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 @@ -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 @@ -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) |