-
Notifications
You must be signed in to change notification settings - Fork 7
/
car_main.gd
129 lines (112 loc) · 5.38 KB
/
car_main.gd
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
extends Node2D
"""This demo shows how to evolve arcade-style cars to successfully complete a track.
This is accomplished by assigning fitness based on how many degrees around the track
a car has driven, and regularly starting a new generation where the fittest individuals
are more prevalent.
New generations are started based on a timer (generation_step), because a lot of
cars end up just loitering around the track, and I haven't implemented a method
to detect this yet. This may cause successful agents to be stopped prematurely however.
"""
# 1.0 = one second. time gets reset every time_step, then all agents get updated
var time = 0
# total_time gets reset every time a new generation is started
var total_time = 0
# every time_step the cars network takes sensory information and decides how to act
var time_step = 0.2
# every generation_step a new generation is made. this gets increased over time.
var generation_step = 15
# path to the car scene that will be controlled by the AI
var agent_body_path = "res://demos/cars/car/Car.tscn"
# initialize the main node that handles the genetic algorithm with 11 inputs, 4 outputs
# the path to the car scene, enable the NEAT_Gui, and use the car_params parameters, which
# are saved under user://param_configs
var ga = GeneticAlgorithm.new(11, 4, agent_body_path, true, "car_params")
# chosen track. Tracks are numbered, however the car_splash refers to them by difficulty
var curr_track_num: int
# end the demo when the first car reaches this. TAU (360 degrees) = complete one track
var fitness_threshold = TAU + 1
# a splash screen on how to continue after reaching fitness threshold
onready var DemoCompletedSplash = preload("res://demos/demo_loader/DemoCompletedSplash.tscn")
# while the splashscreen is open, do not continue the genetic algorithm
var paused = true
# when the first car reaches the halfway checkpoint, the generation time gets increased
var first_car_reached_checkpoint = false
func load_track(track_num: int) -> void:
"""Loads the selected track, adds the GeneticAlgorithm node as a child and places
the agent_bodies at the starting position of the track.
"""
curr_track_num = track_num
# load the selected track
var track_path = "res://demos/cars/tracks/track_%s/Track_%s.tscn" % [track_num, track_num]
add_child(load(track_path).instance())
# connect a signal to increase the generation_step once the first car reaches HalfLap
$Track/HalfLap.connect("body_entered", self, "increase_time")
# IMPORTANT add the ga node as a child
add_child(ga)
# get the bodies (agent_body_path instances) generated by the ga, and place them on the track
place_bodies(ga.get_curr_bodies())
# Center the camera on the starting position
$ZoomPanCam.position = $Track/Start.position
paused = false
func _physics_process(delta) -> void:
"""Car agents update their networks every time_step seconds, and then drive
according to the networks output. If generation_step time has passed, start a
new generation.
"""
if not paused:
# update time since last update
time += delta; total_time += delta
# if enough time has passed for the next time_step, update all agents
if time > time_step:
ga.next_timestep()
time = 0
# check if enough time has passed to start a new generation
if total_time > generation_step or ga.all_agents_dead:
# check if the best agent exceeded the fitness threshold
ga.evaluate_generation()
if ga.curr_best.fitness > fitness_threshold:
end_car_demo()
else:
# go to the next gen
ga.next_generation()
place_bodies(ga.get_curr_bodies())
# every x gens, increase the generation_step
if ga.curr_generation % 2 == 0:
generation_step += 6
print("increased step to " + str(generation_step))
total_time = 0
func place_bodies(bodies: Array) -> void:
"""Adds the bodies scenes generated by the ga to the tree, and removes the old ones.
"""
# remove the bodies from the last generation
for last_gen_body in $Track/Start.get_children():
last_gen_body.queue_free()
# add the new bodies to the track
for body in bodies:
$Track/Start.add_child(body)
func increase_time(_body) -> void:
"""Dependant on the chosen track, this method increases the generation_step
such that capable generations are not terminated prematurely.
"""
if not first_car_reached_checkpoint:
match curr_track_num:
1:
generation_step = 100
2:
generation_step = 70
3:
generation_step = 60
first_car_reached_checkpoint = true
func end_car_demo() -> void:
"""Open the DemoCompletedSplash.
"""
paused = true
var demo_completed_splash = DemoCompletedSplash.instance()
demo_completed_splash.initialize(ga, fitness_threshold)
demo_completed_splash.connect("set_new_threshold", self, "continue_ga")
add_child(demo_completed_splash)
func continue_ga(new_threshold) -> void:
"""Continue the evolution until new_threshold is reached.
"""
fitness_threshold = new_threshold
paused = false