fuzzyworld

godot-shooter / asteroids / files / actors / Enemy.gd

Branch asteroids · default branch · all branches

Enemy.gd

6.3 KB · history · blame

1extends KinematicBody2D
2class_name Enemy
3
4onready var damage_points = $DamagePoints
5onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D
6onready var viewcone_polygon2d: Polygon2D = $ViewconePolygon2D
7const Utils = preload("res://utils/Utils.gd")
8const Explosion = preload("res://effects/Explosion.tscn")
9const Smoke = preload("res://effects/Smoke.tscn")
10const Damage = preload("res://effects/Damage.tscn")
11const Constants = preload("res://utils/Constants.gd")
12const Weapons = preload("res://weapons/Weapons.gd")
13
14export var is_invincible = false
15export var move_speed = Constants.SPEED * 1.05
16export var turn_rate = 0.03
17export var health = -1
18export var max_health = 100
19export var is_targeted = false
20export var target: Vector2
21export var follow_target = false
22signal enemy_killed(position)
23
24var player: Node2D
25var weapon: Weapon
26var velocity = Vector2.ZERO
27var dead = false
28var health_ratio
29var health_percentage
30var sees_player: bool
31
32func _ready() -> void:
33 weapon = Weapons.create_basic_enemy_gun()
34 navigation_agent.max_speed = move_speed
35 var _err = navigation_agent.connect("path_changed", self, "path_changed")
36 _err = navigation_agent.connect("velocity_computed", self, "velocity_computed")
37 _err = navigation_agent.connect("target_reached", self, "target_reached")
38 var players = get_tree().get_nodes_in_group("player")
39 self.player = players[0]
40
41 $AnimationPlayer.play("fade_in")
42 if health < 0 or health > max_health:
43 health = max_health
44 else:
45 var hurt_points = max_health - health
46 health = max_health
47 for i in hurt_points:
48 hurt()
49
50func path_changed() -> void:
51 pass
52
53func velocity_computed(new_vel: Vector2) -> void:
54 velocity = move_and_slide(new_vel)
55 global_rotation = velocity.angle() + PI/2.0
56 pass
57
58func target_reached() -> void:
59 Utils.log("enemy", self, "reached player", player, "at", self.position)
60 player.hurt(25)
61 var explosion = Explosion.instance()
62 explosion.transform = transform
63 get_parent().add_child(explosion)
64 kill()
65 pass
66
67func _process(_delta: float) -> void:
68 var is_player_in_visibility_cone = Geometry.is_point_in_polygon(player.global_position, viewcone_polygon2d.polygon)
69 if is_player_in_visibility_cone:
70 var space_state = get_world_2d().direct_space_state
71 var intersections = space_state.intersect_ray(global_position, player.global_position, [self])
72 if intersections.size() == 1:
73 var collider = intersections[0]['collider']
74 sees_player = collider == player
75
76 if sees_player:
77 set_target(player.global_position)
78
79 var pb: ProgressBar = $Graphics/Alive/Container/ProgressBar
80 health_ratio = float(health) / float(max_health)
81 health_percentage = health_ratio * 100
82 pb.value = health_percentage
83 if health_percentage < 20:
84 pb.modulate = Color.red
85 elif health_percentage < 60:
86 pb.modulate = Color.yellow
87 else:
88 pb.modulate = Color.blue
89 $Graphics/Alive/Container.global_rotation = 0
90 $Graphics/Alive/Sprite/Target.visible = is_targeted
91 $Graphics/Alive/Sprite/Target.global_rotation = 0
92 $Graphics/Alive/Sprite/Trails.add_position(global_position)
93
94 var dist_to_player = global_position.distance_to(player.global_position)
95# Utils.log("dist to player", dist_to_player)
96 if dist_to_player < 25:
97 target_reached()
98
99 var should_shoot = dist_to_player < weapon.shooting_range
100 if should_shoot:
101 var space_state = get_world_2d().direct_space_state
102 var intersections = space_state.intersect_ray(global_position, player.global_position, [self])
103 var hits_player = intersections and intersections.collider == player
104 if hits_player:
105 weapon.try_shoot(self, player.global_position)
106
107func _physics_process(delta: float) -> void:
108 if dead:
109 return
110
111 # navigate to the next position
112 var current_pos = global_position
113 var next_pos = navigation_agent.get_next_location()
114 var diff: Vector2 = next_pos - current_pos
115 var dir = diff.normalized()
116 var desired_vel = dir * navigation_agent.max_speed
117 var steering = (desired_vel - velocity) * delta * 4
118 var new_vel = velocity + steering
119 velocity = new_vel
120
121 if navigation_agent.avoidance_enabled:
122 navigation_agent.set_velocity(velocity)
123 else:
124 velocity_computed(velocity)
125
126func set_target(pos: Vector2):
127# var to_dir = (pos - global_position).normalized()
128# var curr_dir = velocity.normalized()
129# var dir_diff = to_dir - curr_dir
130# var change = dir_diff * turn_rate * move_speed
131# velocity = (velocity + change).normalized() * move_speed
132# var nav_map = navigation_agent.get_navigation_map()
133# Utils.log("setting target location", pos, "map", nav_map)
134 navigation_agent.set_target_location(pos)
135
136func _ensure_smoke_exists() -> void:
137 var max_smoke_count = $DamagePoints.get_child_count()
138 var existing_smoke_count = $DamageObjects.get_child_count()
139 var expected_smoke_count = ceil(health_ratio * max_smoke_count)
140 var smoke_to_add_count = expected_smoke_count - existing_smoke_count
141 for i in smoke_to_add_count:
142 var smoke = Smoke.instance()
143 var smoke_pos = get_damage_point_on_body()
144 smoke.position = smoke_pos
145 $DamageObjects.add_child(smoke)
146
147func hurt(damage: int = 1):
148 if dead || is_invincible:
149 return
150 health -= damage
151 add_damage_label(damage)
152# add_child(smoke)
153 _ensure_smoke_exists()
154 if health <= 0:
155 kill()
156
157func kill():
158 if dead || is_invincible:
159 return
160 dead = true
161 $Graphics/Alive.hide()
162 $CollisionPolygon2D.set_deferred("disabled", true)
163 var explosion = Explosion.instance()
164 explosion.connect("explosion_done", self, "_on_death_explosion_finished")
165 $Graphics.add_child(explosion)
166 z_index = -1
167 emit_signal("enemy_killed", global_position)
168
169func add_damage_label(damage_amount: int = 5) -> void:
170 var damage_label: Damage = Damage.instance()
171 damage_label.value = -damage_amount
172 damage_label.position = self.position
173 get_tree().root.add_child(damage_label)
174
175func teleport(dest: Vector2) -> void:
176 $Graphics/Alive/Sprite/Trails.clear()
177 position = dest
178
179func _on_death_explosion_finished():
180 $AnimationPlayer.play("fade_out")
181
182func get_damage_point_on_body() -> Vector2:
183 var child_count = damage_points.get_child_count()
184 var random_index = randi() % child_count
185 var random_child: Position2D = damage_points.get_child(random_index)
186 return random_child.position
187
188func remove_thyself():
189 queue_free()
190
191func _on_Area2D_body_entered(body: Node) -> void:
192 var is_player = body == player
193 if is_player:
194 player.hurt(2)
195 var explosion = Explosion.instance()
196 explosion.transform = transform
197 get_parent().add_child(explosion)
198 kill()