From bd5a83c5efefdeafeab05a8bdd4862d6d0b612f6 Mon Sep 17 00:00:00 2001 From: Laurent Bovet Date: Sun, 8 Aug 2021 23:46:08 +0200 Subject: [PATCH] feat: artificial intelligence wip --- Bush.tscn | 2 ++ Field.gd | 11 +++++-- PathCalculation.gd | 17 +++++++---- Robot.gd | 71 ++++++++++++++++++++++++++++++---------------- Tank.gd | 17 ++++++----- Tank.tscn | 5 ++++ 6 files changed, 82 insertions(+), 41 deletions(-) diff --git a/Bush.tscn b/Bush.tscn index 73839c4..530cd3c 100644 --- a/Bush.tscn +++ b/Bush.tscn @@ -77,6 +77,8 @@ space_override = 3 gravity_vec = Vector2( 0, 0 ) linear_damp = 12.0 angular_damp = 29.858 +collision_layer = 1024 +collision_mask = 1024 [node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] shape = SubResource( 6 ) diff --git a/Field.gd b/Field.gd index e513882..0dc2cad 100644 --- a/Field.gd +++ b/Field.gd @@ -12,6 +12,8 @@ func _ready(): $Tank1.setOthers([$Tank2, $Tank3]) $Tank2.setOthers([$Tank1, $Tank3]) $Tank3.setOthers([$Tank2, $Tank1]) + $Tank1.robot = true + $Tank2.robot = true var winner = null @@ -87,6 +89,9 @@ func _on_Airfcrafts_timeout(): spawnAircraft() func _on_PathCalculation_paths_updated(paths): - $Tank1.updatePaths(paths) - $Tank2.updatePaths(paths) - $Tank3.updatePaths(paths) + if $Tank1 != null: + $Tank1.updatePaths(paths) + if $Tank2 != null: + $Tank2.updatePaths(paths) + if $Tank3 != null: + $Tank3.updatePaths(paths) diff --git a/PathCalculation.gd b/PathCalculation.gd index bfddd86..2bf6559 100644 --- a/PathCalculation.gd +++ b/PathCalculation.gd @@ -1,5 +1,7 @@ extends Node2D +const DEBUG_PATHS = false + var rocks var triangles var ready = false @@ -19,17 +21,19 @@ func _on_Game_ready(): func _draw(): if not ready: return - for rock in rocks: - draw_circle(to_local(rock.global_position), 4, Color(1,1,1,0.5)) - for c in getCorners(): - draw_circle(to_local(c), 6, Color(1,0,0,0.5)) + if DEBUG_PATHS: + for rock in rocks: + draw_circle(to_local(rock.global_position), 4, Color(1,1,1,0.5)) + for c in getCorners(): + draw_circle(to_local(c), 6, Color(1,0,0,0.5)) var threshold = 80 paths = [] var blacklist = [] for t in triangles: var path = [] for i in range(0,3): - draw_polyline([t[i], t[(i+1) % 3]], Color(0,1,0,0.1)) + if DEBUG_PATHS: + draw_polyline([t[i], t[(i+1) % 3]], Color(0,1,0,0.1)) var weight = (t[i] - t[(i+1) % 3]).length() var center = t[i] + (t[(i+1) % 3] - t[i])/2 if (center - t[(i+2)%3]).length() < threshold / 2: @@ -60,7 +64,8 @@ func _draw(): for path in paths: if len(path) == 1: path.remove(0) - draw_polyline(path, Color(0,0,1)) + if DEBUG_PATHS: + draw_polyline(path, Color(0,0,1)) emit_signal("paths_updated", paths) func insideMargin(point: Vector2, margin): diff --git a/Robot.gd b/Robot.gd index 49f2c2f..805909c 100644 --- a/Robot.gd +++ b/Robot.gd @@ -4,49 +4,70 @@ var paths var currentDestination = null const DIR_DISTANCE=300 const DISPERSE_DISTANCE=75 +var disperse = false var dispersing = 0 var currentPaths = [] var shooting = false +var updating = 0 +var started = false func updatePaths(paths): self.paths = paths func _ready(): + $RayCast2D.add_exception(get_parent()) $RayCast2D.collide_with_areas = true + $FireLine.add_exception(get_parent()) + $FireLine.collide_with_areas = true $RayCast2D.enabled = true - $RayCast2D.add_exception(get_parent()) func _physics_process(delta): - if get_parent().robot and paths != null: - computeDestination() - var disperse = false - var disperseTarget = Vector2(0,-DISPERSE_DISTANCE).rotated(global_rotation) + var start = true + if not started: for other in get_parent().others: - $RayCast2D.cast_to = to_local(other.global_position) - if $RayCast2D.get_collider() == other: - currentDestination = other.global_position - shooting = true - else: - shooting = false - if other != null and other.robot and \ - (other.global_position + Vector2(0,-DISPERSE_DISTANCE).rotated(other.global_rotation)) \ - .distance_to(global_position + disperseTarget) < DISPERSE_DISTANCE * 1.5: - disperse = true - if disperse or dispersing > 0: - dispersing += delta - if dispersing > 2: - dispersing = 0 + if weakref(other).get_ref() and not other.robot: + start = start and not other.virgin + started = start + if get_parent().robot and started and get_parent().life > 0 and paths != null: + if updating == 0: + computeDestination() + disperse = false + var disperseTarget = Vector2(0,-DISPERSE_DISTANCE).rotated(global_rotation) + for other in get_parent().others: + if !weakref(other).get_ref(): + continue + if not other.robot: + $RayCast2D.cast_to = to_local(other.global_position) + $RayCast2D.force_raycast_update() + if $RayCast2D.get_collider() == other and other.life > 0: + currentDestination = other.global_position + shooting = true + else: + shooting = false + if other.robot and other.life > 0 and \ + (other.global_position + Vector2(0,-DISPERSE_DISTANCE).rotated(other.global_rotation)) \ + .distance_to(global_position + disperseTarget) < DISPERSE_DISTANCE * 1.5: + disperse = true + if disperse or dispersing > 0: + dispersing += delta + if dispersing > 2: + dispersing = 0 var dir = Vector2(0,-DIR_DISTANCE).rotated(global_rotation) var angle = (currentDestination - global_position).angle_to(dir) - if shooting: - $RayCast2D.cast_to = Vector2(-1000,0) - if $RayCast2D.is_colliding() and $RayCast2D.get_collider().is_in_group("tank"): + if $FireLine.is_colliding() and updating == 0: + var other = $FireLine.get_collider() + if other.is_in_group("tank") and not other.robot and other.life > 0: get_parent().fire() + shooting = true - if abs(angle) > PI / 60 or disperse: - get_parent().rotation_impulse(delta, (angle < 0) or (dispersing > 0 and not shooting)) + if abs(angle) > PI / 32 or disperse or shooting: + get_parent().rotation_impulse(delta, (angle < 0) or (dispersing > 0)) else: get_parent().throttle(delta) + updating += delta + if updating > 0.5: + updating = 0 + func computeDestination(): var threshold = 20 @@ -76,7 +97,7 @@ func computeDestination(): closest = Vector2(100000,100000) for path in paths: for point in path: - if point.distance_to(direction) < closest.distance_to(direction): + if point.distance_to(pos) < closest.distance_to(pos): closest = point destination = closest diff --git a/Tank.gd b/Tank.gd index ef2da5c..364e393 100644 --- a/Tank.gd +++ b/Tank.gd @@ -38,7 +38,7 @@ var virgin = true var springed = false var bigShell = 0 var smallShells = 0 -var robot = true +var robot = false var others = [] func _ready(): @@ -75,11 +75,11 @@ func _physics_process(delta): virgin = false if rightPressed: - rotation_impulse(delta, false, amplitude) + rotation_impulse(delta, true, amplitude) return if leftPressed: - rotation_impulse(delta, true, amplitude) + rotation_impulse(delta, false, amplitude) return if buttonPressed: @@ -204,16 +204,19 @@ func addSpring(): func setOthers(tanks): others = tanks -func die(): +func die(emit=true): emit_signal("dead") $Life.hide() $Sprite.modulate = Color(0.2,0.2,0.2) $Explosion.restart() - if points > 0: + if points > 0 and emit: var box = Box.instance() box.shells = points / 3 box.global_position = global_position get_parent().add_child(box) + box.collision_layer = 128 + box.collision_mask = 128 + box.apply_central_impulse(-transform.y * 300) if virgin: queue_free() @@ -231,11 +234,11 @@ func _on_InitTimer_timeout(): func _on_TankButton_pressed(): if virgin: - die() + die(false) func _on_MouseButton_pressed(): if virgin: - die() + die(false) func updatePaths(paths): $Robot.updatePaths(paths) diff --git a/Tank.tscn b/Tank.tscn index acde022..bd83457 100644 --- a/Tank.tscn +++ b/Tank.tscn @@ -167,11 +167,16 @@ __meta__ = { script = ExtResource( 9 ) [node name="Destination" type="Polygon2D" parent="Robot"] +visible = false polygon = PoolVector2Array( 6.15118, 6.07089, 5.35748, -6.62831, -4.76219, -6.82674, -4.36534, 6.07089 ) [node name="RayCast2D" type="RayCast2D" parent="Robot"] cast_to = Vector2( 0, 400 ) +[node name="FireLine" type="RayCast2D" parent="Robot"] +enabled = true +cast_to = Vector2( 0, -1000 ) + [connection signal="timeout" from="Muzzle/MuzzleFire/Timer" to="Muzzle/MuzzleFire/Timer" method="_on_Timer_timeout"] [connection signal="timeout" from="Muzzle/MuzzleFire/Timer" to="." method="_on_Timer_timeout"] [connection signal="timeout" from="LoadTimer" to="." method="_on_LoadTimer_timeout"]