fuzzyworld

godot-shooter / commit

Merge branch 'navigation' into 'main'

9b5866ba48d7124209ac8cd8f03dea2dacf4c60a · Pavel Safronov · 2023-12-01 06:55:46 +0000 · parents f295bf46cd2d afaeaa95ecc1

Merge branch 'navigation' into 'main'

Starting to implement navigation logic using the NavigationAgend2D.

See merge request PavelSafronov/godot-shooter!3
 actors/Enemy.gd                | 76 +++++++++++++++++++++++++++---------------
 actors/Enemy.tscn              | 21 ++++++------
 actors/Player.gd               | 17 ++++++----
 actors/Player.tscn             |  4 +++
 items/ConstantItemGenerator.gd |  4 ++-
 items/LevelItemGenerator.gd    |  6 ++--
 project.godot                  |  2 ++
 scenes/HUD.gd                  |  9 +++--
 scenes/World.gd                | 69 ++++++++++++++++++++++++++++----------
 scenes/World.tscn              | 11 +++++-
 weapons/Weapons.gd             | 13 +++++++-
 11 files changed, 164 insertions(+), 68 deletions(-)

diff --git a/actors/Enemy.gd b/actors/Enemy.gd
index 8dfed65..d4aa41a 100644
--- a/actors/Enemy.gd
+++ b/actors/Enemy.gd
@@ -1,8 +1,8 @@
 extends KinematicBody2D
 class_name Enemy
 
-onready var ray_cast_2d: RayCast2D = $RayCast2D
 onready var damage_points = $DamagePoints
+onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D
 const Utils = preload("res://utils/Utils.gd")
 const Explosion = preload("res://effects/Explosion.tscn")
 const Smoke = preload("res://effects/Smoke.tscn")
@@ -25,6 +25,11 @@ var health_ratio
 var health_percentage
 
 func _ready() -> void:
+	navigation_agent.max_speed = move_speed
+	var _err = navigation_agent.connect("path_changed", self, "path_changed")
+	_err = navigation_agent.connect("velocity_computed", self, "velocity_computed")
+	_err = navigation_agent.connect("target_reached", self, "target_reached")
+	
 	$AnimationPlayer.play("fade_in")
 	if health < 0 or health > max_health:
 		health = max_health
@@ -34,6 +39,24 @@ func _ready() -> void:
 		for i in hurt_points:
 			hurt()
 
+func path_changed() -> void:
+	pass
+
+func velocity_computed(new_vel: Vector2) -> void:
+	velocity = move_and_slide(new_vel)
+	global_rotation = velocity.angle() + PI/2.0
+	pass
+
+func target_reached() -> void:
+	var players = get_tree().get_nodes_in_group("player")
+	var player = players[0]
+	player.hurt(25)
+	var explosion = Explosion.instance()
+	explosion.transform = transform
+	get_parent().add_child(explosion)
+	kill()
+	pass
+
 func _process(_delta: float) -> void:
 	var pb: ProgressBar = $Graphics/Alive/Container/ProgressBar
 	health_ratio = float(health) / float(max_health)
@@ -50,35 +73,34 @@ func _process(_delta: float) -> void:
 	$Graphics/Alive/Sprite/Target.global_rotation = 0
 	$Graphics/Alive/Sprite/Trails.add_position(global_position)
 
-func _physics_process(_delta: float) -> void:
+func _physics_process(delta: float) -> void:
 	if dead:
 		return
-		
-	velocity = move_and_slide(velocity)
-	global_rotation = velocity.angle() + PI/2.0
 	
-	if self.follow_target:
-		var to_dir = (target - global_position).normalized()
-		var curr_dir = velocity.normalized()
-		var dir_diff = to_dir - curr_dir
-		var change = dir_diff * turn_rate * move_speed
-		velocity = (velocity + change).normalized() * move_speed
-
-	var ray_length = ray_cast_2d.cast_to.length()
-	if ray_cast_2d.is_colliding():
-		var players = get_tree().get_nodes_in_group("player")
-		var player = players[0]
-		var dist_to_player = global_position.distance_to(player.global_position)
-		if ray_cast_2d.get_collider() == player and dist_to_player < (ray_length + 5):
-			player.hurt(25)
-			var explosion = Explosion.instance()
-			explosion.transform = transform
-			get_parent().add_child(explosion)
-			kill()
-
-func turn_towards(pos: Vector2):
-	self.target = pos
-	self.follow_target = true
+	# navigate to the next position
+	var current_pos = global_position
+	var next_pos = navigation_agent.get_next_location()
+	var diff: Vector2 = next_pos - current_pos
+	var dir = diff.normalized()
+	var desired_vel = dir * navigation_agent.max_speed
+	var steering = (desired_vel - velocity) * delta * 4
+	var new_vel = velocity + steering
+	velocity = new_vel
+	
+	if navigation_agent.avoidance_enabled:
+		navigation_agent.set_velocity(velocity)
+	else:
+		velocity_computed(velocity)
+
+func set_target(pos: Vector2):
+#	var to_dir = (pos - global_position).normalized()
+#	var curr_dir = velocity.normalized()
+#	var dir_diff = to_dir - curr_dir
+#	var change = dir_diff * turn_rate * move_speed
+#	velocity = (velocity + change).normalized() * move_speed
+#	var nav_map = navigation_agent.get_navigation_map()
+#	Utils.log("setting target location", pos, "map", nav_map)
+	navigation_agent.set_target_location(pos)
 
 func _ensure_smoke_exists() -> void:
 	var max_smoke_count = $DamagePoints.get_child_count()
diff --git a/actors/Enemy.tscn b/actors/Enemy.tscn
index 6246ee2..affedab 100644
--- a/actors/Enemy.tscn
+++ b/actors/Enemy.tscn
@@ -89,7 +89,7 @@ tracks/2/keys = {
 [node name="Enemy" type="KinematicBody2D"]
 z_index = 10
 collision_layer = 2
-collision_mask = 9
+collision_mask = 11
 script = ExtResource( 3 )
 
 [node name="Graphics" type="Node2D" parent="."]
@@ -101,12 +101,10 @@ modulate = Color( 1, 0.470588, 0, 1 )
 texture = ExtResource( 4 )
 
 [node name="Target" type="Sprite" parent="Graphics/Alive/Sprite"]
-visible = false
-modulate = Color( 1, 0, 0, 1 )
+modulate = Color( 1, 0, 0, 0.643137 )
 texture = ExtResource( 5 )
 
 [node name="Trails" parent="Graphics/Alive/Sprite" instance=ExtResource( 2 )]
-visible = false
 gradient = SubResource( 4 )
 
 [node name="Container" type="Node2D" parent="Graphics/Alive"]
@@ -139,13 +137,16 @@ position = Vector2( 0, -13 )
 [node name="Position2D2" type="Position2D" parent="DamagePoints"]
 position = Vector2( 16, 16 )
 
-[node name="RayCast2D" type="RayCast2D" parent="."]
-visible = false
-enabled = true
-cast_to = Vector2( 0, -50 )
-
 [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="."]
-visible = false
 polygon = PoolVector2Array( 0, -23, -7, 5, -12, 9, -19, 9, -23, 14, -23, 19, -19, 23, -14, 23, -9, 19, -9, 14, -10, 12, -6, 9, 0, 15, 6, 9, 10, 12, 9, 14, 9, 19, 13, 23, 18, 23, 23, 18, 23, 14, 19, 10, 13, 10, 7, 5 )
 
 [node name="DamageObjects" type="Node2D" parent="."]
+
+[node name="NavigationAgent2D" type="NavigationAgent2D" parent="."]
+path_desired_distance = 35.0
+target_desired_distance = 35.0
+navigation_layers = 3
+avoidance_enabled = true
+radius = 35.0
+neighbor_dist = 100.0
+max_speed = 50.0
diff --git a/actors/Player.gd b/actors/Player.gd
index edb8921..b3aa23b 100644
--- a/actors/Player.gd
+++ b/actors/Player.gd
@@ -5,11 +5,11 @@ const Damage = preload("res://effects/Damage.tscn")
 const Weapons = preload("res://weapons/Weapons.gd")
 const Constants = preload("res://utils/Constants.gd")
 
+export var is_invincible = true
 export var score = 0
 export var move_speed = Constants.SPEED
 export var health = 50
 export var max_health = 100
-export var is_invincible = false
 export var turn_rate = 0.03
 export var accel_rate = 1
 export var radar_min_radius = 350
@@ -39,20 +39,18 @@ onready var player_to_target = $Graphics/Alive/PlayerToTarget
 const Utils = preload("res://utils/Utils.gd")
 const PopupControl = preload("res://utils/Popup.tscn")
 
-var weapon: Weapon = create_default_weapon()
+var weapon: Weapon
 var dead = false
 var velocity: Vector2 = Vector2.UP * move_speed
 var settings: Settings
 
-func filter_invalid_enemies(enemy: Enemy) -> bool:
-	if enemy.dead:
-		return false
-	return true
-
 func create_default_weapon() -> Weapon:
 	return Weapons.create_basic_gun()
 
 func _ready() -> void:
+	velocity= Vector2.UP * move_speed
+	weapon = Weapons.create_non_gun()
+
 #	Utils.remove_user_settings()
 	settings = Utils.load_user_settings()
 	settings.save_user_settings()
@@ -104,6 +102,11 @@ func _process(_delta: float) -> void:
 			player_to_target.points[0] = self.global_position
 			player_to_target.points[1] = closest_enemy.global_position
 
+func filter_invalid_enemies(enemy: Enemy) -> bool:
+	if enemy.dead:
+		return false
+	return true
+
 func sort_enemies_ascending_distance(a: Enemy,b: Enemy):
 	var distA = self.global_position.distance_to(a.global_position)
 	var distB = self.global_position.distance_to(b.global_position)
diff --git a/actors/Player.tscn b/actors/Player.tscn
index 0652a01..e84a2e9 100644
--- a/actors/Player.tscn
+++ b/actors/Player.tscn
@@ -372,6 +372,10 @@ text = "Replay!"
 [node name="Camera2D" type="Camera2D" parent="."]
 current = true
 
+[node name="NavigationObstacle2D" type="NavigationObstacle2D" parent="."]
+estimate_radius = false
+radius = 35.0
+
 [connection signal="debug_signal" from="UI/HUD" to="." method="_on_HUD_debug_signal"]
 [connection signal="paused" from="UI/HUD" to="." method="_on_HUD_paused"]
 [connection signal="position_set" from="UI/HUD" to="." method="_on_HUD_position_set"]
diff --git a/items/ConstantItemGenerator.gd b/items/ConstantItemGenerator.gd
index 24790a6..09bd10a 100644
--- a/items/ConstantItemGenerator.gd
+++ b/items/ConstantItemGenerator.gd
@@ -14,6 +14,7 @@ func _init(creator, instance, group, sim_count = 1) -> void:
 	group_name = group
 	simultaneous_count = sim_count
 
+# runs the generator, returns is_done, a boolean
 func run(node: Node) -> bool:
 	var items = node.get_tree().get_nodes_in_group(group_name)
 	var should_add = items.size() < simultaneous_count
@@ -22,4 +23,5 @@ func run(node: Node) -> bool:
 		item.add_to_group(group_name)
 		node.add_child(item)
 		generated_count += 1
-	return false
+	var is_done = not should_add
+	return is_done
diff --git a/items/LevelItemGenerator.gd b/items/LevelItemGenerator.gd
index f0b8782..94639de 100644
--- a/items/LevelItemGenerator.gd
+++ b/items/LevelItemGenerator.gd
@@ -16,6 +16,7 @@ func _init(count, creator, instance, group, sim_count = 1) -> void:
 	instance_object = instance
 	simultaneous_count = sim_count
 
+# runs the generator, returns is_done, a boolean
 func run(node: Node) -> bool:
 	var items = node.get_tree().get_nodes_in_group(group_name)
 	var generated_enough = generated_count == item_count
@@ -23,11 +24,12 @@ func run(node: Node) -> bool:
 	if finished:
 		return true
 	if generated_enough:
-		return false
+		return true
 	var should_add = items.size() < simultaneous_count
 	if should_add:
 		var item: Node = funcref(instance_object, creator_name).call_func()
 		item.add_to_group(group_name)
 		node.add_child(item)
 		generated_count += 1
-	return false
+	var is_done = not should_add
+	return is_done
diff --git a/project.godot b/project.godot
index 23b7065..a27c543 100644
--- a/project.godot
+++ b/project.godot
@@ -132,6 +132,8 @@ debug_action={
 2d_physics/layer_2="enemies"
 2d_physics/layer_3="power_ups"
 2d_physics/layer_4="bullets"
+2d_navigation/layer_1="player"
+2d_navigation/layer_2="enemies"
 
 [network]
 
diff --git a/scenes/HUD.gd b/scenes/HUD.gd
index f4cb76f..108cc55 100644
--- a/scenes/HUD.gd
+++ b/scenes/HUD.gd
@@ -86,5 +86,10 @@ func _on_PauseButton_pressed() -> void:
 	emit_signal("paused")
 
 func set_current_weapon(weapon: Weapon) -> void:
-	weapon_label.text = "%s:" % weapon.name
-	ammo_label.text = "%s" % weapon.ammo
+	var is_non_weapon = weapon.name == ""
+	if is_non_weapon:
+		weapon_label.text = ""
+		ammo_label.text = ""
+	else:
+		weapon_label.text = "%s:" % weapon.name
+		ammo_label.text = "%s" % weapon.ammo
diff --git a/scenes/World.gd b/scenes/World.gd
index 6b10aac..6c837a2 100644
--- a/scenes/World.gd
+++ b/scenes/World.gd
@@ -9,9 +9,10 @@ const PowerUp = preload("res://items/PowerUp.tscn")
 const Settings = preload("res://utils/Settings.gd")
 onready var player: Player = $Player
 onready var update_timer = $UpdateTimer
+onready var navigation_polygon_instance: NavigationPolygonInstance = $NavigationPolygonInstance
 
 var generators = [
-	ConstantItemGenerator.new("create_enemy", self, "enemies", 4),
+	ConstantItemGenerator.new("create_enemy", self, "enemies", 5),
 	ConstantItemGenerator.new("create_health", self, "health", 10),
 	ConstantItemGenerator.new("create_weapon", self, "weapon", 10),
 	ConstantItemGenerator.new("create_bomb", self, "bombs", 10),
@@ -47,7 +48,7 @@ func bomb_consumed(data):
 
 func create_enemy() -> Enemy:
 	var enemy = Enemy.instance()
-	enemy.position = get_new_environment_position()
+	enemy.position = get_new_environment_position(1)
 	enemy.connect("enemy_killed", self, "enemy_killed")
 	return enemy
 func create_health() -> PowerUp:
@@ -83,7 +84,7 @@ func create_weapon() -> PowerUp:
 		weapon.label = "ROC"
 		weapon.color = Color.red
 	elif r < .9:
-		weapon.data = Weapons.create_gattling_gun()
+		weapon.data = Weapons.create_smg_gun()
 		weapon.label = "SMG"
 		weapon.color = Color.orange
 	else:
@@ -120,12 +121,49 @@ func get_new_environment_position(dist_ratio: float = 1.0) -> Vector2:
 			break
 	return potential_pos
 
+func _ready() -> void:
+	run_generators()
+#	# position first two enemies
+#	var enemies = get_tree().get_nodes_in_group("enemies")
+#	var enemy1: Enemy = enemies[0]
+#	enemy1.position = Vector2(-300, 200)
+#	var enemy2: Enemy = enemies[1]
+#	enemy2.position = Vector2(300, 200)
+	pass
+
+func run_generators() -> void:
+	for g in generators:
+		var generator = g
+		while true:
+			var is_done = generator.run($Environment)
+			if is_done:
+				break
+	pass
+
 func _input(_event: InputEvent) -> void:
 	if player.dead:
 		return
 
 func _process(_delta: float) -> void:
+	# move navigation polygon around the player, navigation won't work outside of this polygon
+	var nav_size = Vector2(item_replacement_distance * 2 * 5, item_replacement_distance * 2 * 5)
+	var nav_pos = player.global_position - (nav_size * 0.5)
+	var vertices = PoolVector2Array([
+		nav_pos,
+		nav_pos + Vector2(nav_size.x, 0),
+		nav_pos + nav_size,
+		nav_pos + Vector2(0, nav_size.y)
+	])
+	var polygon = NavigationPolygon.new()
+	polygon.set_vertices(vertices)
+	var indices = PoolIntArray([0, 1, 2, 3])
+	polygon.add_polygon(indices)
+	navigation_polygon_instance.navpoly = polygon
+
+	# set the objective text
 	player.set_objective("%s / %s" % [completed_objectives, max_objectives])
+	
+	# if all objectives are complete, show the game finished menu
 	var all_done = completed_objectives == max_objectives
 	if all_done:
 		get_tree().paused = true
@@ -137,25 +175,23 @@ func _process(_delta: float) -> void:
 		$Player/UI/GameFinishedMenu.show()
 		return
 
-	for g in generators:
-		var generator = g
-		var _is_done = generator.run($Environment)
-	
+	run_generators()
+
+	# position the destination
 	$Destination.visible = player.is_destination_set
 	$Destination.global_position = player.destination
 
-	player.clear_radar()
-	
+	# clear radar and populate with all items
+	var enemies = get_tree().get_nodes_in_group("enemies")
+	var filteredEnemies = Utils.filter(funcref(self,"filter_invalid_enemies"), enemies)
+	player.clear_radar()	
+	player.update_radar(get_tree().get_nodes_in_group("gold"), Color.gold)
+	player.update_radar(filteredEnemies, Color.red)
 	if player.settings.show_items:
 		player.update_radar(get_tree().get_nodes_in_group("health"), Color.gray)
 		player.update_radar(get_tree().get_nodes_in_group("weapons"), Color.white)
 		player.update_radar(get_tree().get_nodes_in_group("bombs"), Color.purple)
 
-	player.update_radar(get_tree().get_nodes_in_group("gold"), Color.gold)
-	var enemies = get_tree().get_nodes_in_group("enemies")
-	var filteredEnemies = Utils.filter(funcref(self,"filter_invalid_enemies"), enemies)
-	player.update_radar(filteredEnemies, Color.red)
-
 func _on_Destination_body_entered(body: Node) -> void:
 	if body == player:
 		player.clear_destination()
@@ -186,8 +222,7 @@ func _on_Player_debug() -> void:
 #	player.kill()
 #	completed_objectives = max_objectives
 #	player.debug()
-#	weapon_consumed(Weapons.create_rocket_gun())
-#	weapon_consumed(Weapons.create_gattling_gun())
+	weapon_consumed(Weapons.create_smg_gun())
 	pass # Replace with function body.
 
 func _on_Player_destination_set() -> void:
@@ -200,7 +235,7 @@ func _on_UpdateTimer_timeout() -> void:
 	var filteredEnemies = Utils.filter(funcref(self,"filter_invalid_enemies"), enemies)
 	for e in filteredEnemies:
 		var enemy: Enemy = e
-		enemy.turn_towards($Player.position)
+		enemy.set_target($Player.position)
 		var diff = enemy.global_position - player.global_position
 		var dist = diff.length()
 		var is_too_far = dist > item_replacement_distance
diff --git a/scenes/World.tscn b/scenes/World.tscn
index abee8e7..d306dd5 100644
--- a/scenes/World.tscn
+++ b/scenes/World.tscn
@@ -1,4 +1,4 @@
-[gd_scene load_steps=6 format=2]
+[gd_scene load_steps=7 format=2]
 
 [ext_resource path="res://assets/kenney_simple-space/PNG/Default/icon_crossLarge.png" type="Texture" id=1]
 [ext_resource path="res://actors/Player.tscn" type="PackedScene" id=2]
@@ -8,6 +8,11 @@
 [sub_resource type="CircleShape2D" id=1]
 radius = 29.0172
 
+[sub_resource type="NavigationPolygon" id=2]
+vertices = PoolVector2Array( -400, -400, 400, -400, 400, 400, -400, 400 )
+polygons = [ PoolIntArray( 0, 1, 2, 3 ) ]
+outlines = [ PoolVector2Array( -400, -400, 400, -400, 400, 400, -400, 400 ) ]
+
 [node name="World" type="Node2D"]
 script = ExtResource( 4 )
 
@@ -64,6 +69,10 @@ autostart = true
 wait_time = 0.1
 autostart = true
 
+[node name="NavigationPolygonInstance" type="NavigationPolygonInstance" parent="."]
+navpoly = SubResource( 2 )
+navigation_layers = 2
+
 [connection signal="body_entered" from="Destination" to="." method="_on_Destination_body_entered"]
 [connection signal="debug" from="Player" to="." method="_on_Player_debug"]
 [connection signal="destination_set" from="Player" to="." method="_on_Player_destination_set"]
diff --git a/weapons/Weapons.gd b/weapons/Weapons.gd
index d191331..fc4981f 100644
--- a/weapons/Weapons.gd
+++ b/weapons/Weapons.gd
@@ -3,6 +3,17 @@ tool
 
 const Constants = preload("res://utils/Constants.gd")
 
+static func create_non_gun() -> Weapon:
+	var weapon = Weapon.new()
+	weapon.shooting_range = 0
+	weapon.particle_lifetime_secs = 0
+	weapon.cooldown_seconds = 0
+	weapon.damage_amount = 10
+	weapon.name = ""
+	weapon.ammo = 0
+	weapon.reload_size = 0
+	return weapon
+
 static func create_basic_gun() -> Weapon:
 	var weapon = Weapon.new()
 	weapon.particle = preload("res://weapons/Bullet.tscn")
@@ -15,7 +26,7 @@ static func create_basic_gun() -> Weapon:
 	weapon.reload_size = 1
 	return weapon
 
-static func create_gattling_gun() -> Weapon:
+static func create_smg_gun() -> Weapon:
 	var weapon = Weapon.new()
 	weapon.particle = preload("res://weapons/Bullet.tscn")
 	weapon.shooting_range = 400