godot-shooter / asteroids / files / actors / Enemy.gd
Branch asteroids · default branch · all branches
Enemy.gd
| 1 | extends KinematicBody2D |
| 2 | class_name Enemy |
| 3 | |
| 4 | onready var damage_points = $DamagePoints |
| 5 | onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D |
| 6 | onready var viewcone_polygon2d: Polygon2D = $ViewconePolygon2D |
| 7 | const Utils = preload("res://utils/Utils.gd") |
| 8 | const Explosion = preload("res://effects/Explosion.tscn") |
| 9 | const Smoke = preload("res://effects/Smoke.tscn") |
| 10 | const Damage = preload("res://effects/Damage.tscn") |
| 11 | const Constants = preload("res://utils/Constants.gd") |
| 12 | const Weapons = preload("res://weapons/Weapons.gd") |
| 13 | |
| 14 | export var is_invincible = false |
| 15 | export var move_speed = Constants.SPEED * 1.05 |
| 16 | export var turn_rate = 0.03 |
| 17 | export var health = -1 |
| 18 | export var max_health = 100 |
| 19 | export var is_targeted = false |
| 20 | export var target: Vector2 |
| 21 | export var follow_target = false |
| 22 | signal enemy_killed(position) |
| 23 | |
| 24 | var player: Node2D |
| 25 | var weapon: Weapon |
| 26 | var velocity = Vector2.ZERO |
| 27 | var dead = false |
| 28 | var health_ratio |
| 29 | var health_percentage |
| 30 | var sees_player: bool |
| 31 | |
| 32 | func _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 | |
| 50 | func path_changed() -> void: |
| 51 | pass |
| 52 | |
| 53 | func velocity_computed(new_vel: Vector2) -> void: |
| 54 | velocity = move_and_slide(new_vel) |
| 55 | global_rotation = velocity.angle() + PI/2.0 |
| 56 | pass |
| 57 | |
| 58 | func 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 | |
| 67 | func _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 | |
| 107 | func _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 | |
| 126 | func 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 | |
| 136 | func _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 | |
| 147 | func 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 | |
| 157 | func 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 | |
| 169 | func 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 | |
| 175 | func teleport(dest: Vector2) -> void: |
| 176 | $Graphics/Alive/Sprite/Trails.clear() |
| 177 | position = dest |
| 178 | |
| 179 | func _on_death_explosion_finished(): |
| 180 | $AnimationPlayer.play("fade_out") |
| 181 | |
| 182 | func 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 | |
| 188 | func remove_thyself(): |
| 189 | queue_free() |
| 190 | |
| 191 | func _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() |