small racing game im working on
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
extends Node3D
class_name Race

# in order of initialization
var car_scene: PackedScene
var track_loader_scene: PackedScene
var ghost_scene: PackedScene
var track_res: TrackResource
var track: TrackLoader
var data: Array[GhostData]
var best_time_data: GhostData
var cars: Array[Car]
var cars_needed: PackedInt32Array = []
var finished_cars := 0
@onready var mp := cars_needed.size() > 1
var ghost: GhostCar
var start_frame: int

var current_lap: PackedInt32Array = []
var playing := false
var timers: Array[GameTimer]
var distances: PackedFloat32Array = []

signal next_lap
signal created_car(car: Car, n: int)
signal created_ghost(ghost: GhostCar)
signal finished(time: float, prev_time: float, n: int)
signal all_finished
signal split(time: float, prev_time: float, n: int)
signal did_reset

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()
	add_child(ghost)
	reset_ghost()
	created_ghost.emit(ghost)

func reset_ghost() -> void:
	if best_time_data:
		ghost.update(best_time_data.load_snap(0), -1)
		ghost.engine.volume = .2
	else:
		ghost.engine.volume = 0
		ghost.global_position = track.start_transf.origin + Vector3(0, 1, -2)
		ghost.rotation = track.start_transf.basis.get_euler()# + Vector3(0, PI, -PI/2)
	ghost.reset()
	ghost.hide()

func reset_car(i: int) -> void:
	await get_tree().physics_frame
	cars[i].rotation = track.start_transf.basis.get_euler()# + Vector3(0, PI, -PI/2)
	cars[i].global_position = track.start_transf.origin + Vector3(0, 1, -2)
	cars[i].linear_velocity = Vector3.ZERO
	cars[i].angular_velocity = Vector3.ZERO
	cars[i].current_gear = 0
	cars[i].reset()

func mkcar(i: int, dev: int) -> void:
	cars[i] = HumanCar.attach(car_scene, dev)
	add_child(cars[i])
	reset_car(i)
	created_car.emit(cars[i], i)
	if !mp:
		cars[i].collision_mask = 2 ^ 1

func _ready() -> void:
	set_physics_process(false)
	track = track_loader_scene.instantiate()
	track.track = track_res
	add_child(track)
	data.resize(cars_needed.size())
	cars.resize(cars_needed.size())
	current_lap.resize(cars_needed.size())
	current_lap.fill(0)
	timers.resize(cars_needed.size())
	if mp:
		distances.resize(cars_needed.size())
		distances.fill(0)
	for i in cars_needed.size():
		data[i] = GhostData.new(track.checkpoints.size(), track_res.laps)
		timers[i] = GameTimer.new()
		add_child(timers[i])
		mkcar(i, cars_needed[i])
	mkghost()
	connect_checkpoints()

func _input(event: InputEvent) -> void:
	if event.is_action("reset") and playing:
		reset()

func reset() -> void:
	playing = false
	finished_cars = 0
	if best_time_data:
		if ghost:
			reset_ghost()
	distances.fill(0)
	for car in cars.size():
		await reset_car(car)
		current_lap[car] = 0
	set_physics_process(false)
	for d in data:
		d.clear()
	for timer in timers:
		timer.reset()
	did_reset.emit()

func fcar(c: Car) -> int:
	return cars.find(c)

func connect_checkpoints() -> void:
	for i in len(track.checkpoints):
		track.checkpoints[i].collected.connect(func(c: Car): passed_cp(i, fcar(c)))
	track.finish.collected.connect(func(c: Car): passed_finish(fcar(c)))

func passed_cp(cp: int, car: int) -> void: if playing and !data[car].has_collected(current_lap[car], cp): collect(cp, car)

func passed_finish(car: int) -> void:
	if !playing: return
	for i in len(data[car].checkpoints[current_lap[car]]) - 1:
		if data[car].checkpoints[current_lap[car]][i] < 0:
			return
	collect(-1, car)
	if track_res.laps - 1 == current_lap[car]:
		timers[car].stop()
		cars[car].reset()
		finished_cars += 1
		finished.emit(data[car].time, best_time_data.time if best_time_data else -1.0, car)
		if finished_cars > cars.size() - 1: # all finished
			var best = {time = INF}
			for datapoint in data:
				if best.time > datapoint.time:
					best = datapoint
			if not best_time_data or best.time < best_time_data.time:
				best.save(Globals.SAVES % track_res.name)
				best_time_data = best
			all_finished.emit()
			playing = false
	else:
		current_lap[car] += 1
		next_lap.emit()

func _physics_process(delta: float) -> void:
	if mp:
		for i in cars_needed.size():
			if distances[i] < 0:
				continue
			var flag := true
			for j in cars_needed.size():
				if j == i: continue
				var dist := cars[i].global_position.distance_squared_to(cars[j].global_position)
				if dist > 50:
					continue
				flag = false
				break # gdscript cant label loops
			if flag:
				distances[i] = distances[i] + delta
				if distances[i] > 1.5: # when apart for longer than 1.5 seconds, enable collisions
					distances[i] = -1
					cars[i].collision_mask = 1 ^ 2 # world & players
	for i in data.size():
		data[i].snapshot(cars[i])
	if best_time_data:
		if best_time_data.snap_count - 1 < Engine.get_physics_frames() - start_frame:
			if ghost.visible:
				ghost.hide()
				ghost.engine.volume = 0
			return
		ghost.update(best_time_data.load_snap(Engine.get_physics_frames() - start_frame), delta)
		ghost.visible = (ghost.global_position.distance_squared_to(cars[0].global_position) > 10)
		ghost.engine.volume = lerpf(ghost.engine.volume, .7, delta * 3) if (ghost.global_position.distance_squared_to(cars[0].global_position) > 20) else lerpf(ghost.engine.volume, .2, delta * 3)

func collect(cp: int, car: int) -> void:
	if cp != -1:
		cars[car].checkpoint_sound.play()
	var time := best_time_data.get_time(current_lap[car], cp) if best_time_data else -1.0
	time = best_time_data.time if (not track_res.laps or track_res.laps == current_lap[car] + 1) and cp == -1 and time != -1.0 else time
	split.emit(snappedf(timers[car].now(), .001), time, car)
	data[car].collect(current_lap[car], cp, snappedf(timers[car].now(), .001))

func start() -> void:
	for timer in timers:
		timer.start()
	start_frame = Engine.get_physics_frames()
	playing = true
	set_physics_process(true)
	for car in cars:
		car.start()