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