godot-shooter / files / actors / Player.gd
Player.gd
| 1 | extends KinematicBody2D |
| 2 | class_name Player |
| 3 | |
| 4 | const Damage = preload("res://effects/Damage.tscn") |
| 5 | const Weapons = preload("res://weapons/Weapons.gd") |
| 6 | const Constants = preload("res://utils/Constants.gd") |
| 7 | |
| 8 | export var is_invincible = false |
| 9 | export var score = 0 |
| 10 | export var move_speed = Constants.SPEED |
| 11 | export var health = 50 |
| 12 | export var max_health = 100 |
| 13 | export var turn_rate = 0.03 |
| 14 | export var accel_rate = 1 |
| 15 | export var radar_min_radius = 350 |
| 16 | export var radar_max_radius = 2000 |
| 17 | export var destination: Vector2 |
| 18 | export var is_destination_set: bool = false |
| 19 | signal player_died |
| 20 | signal destination_set |
| 21 | signal debug |
| 22 | |
| 23 | onready var hud = $UI/HUD |
| 24 | onready var camera: Camera2D = $Camera2D |
| 25 | onready var static_controls = $Graphics/Alive/StaticControls |
| 26 | onready var radar_sprite = $Graphics/Alive/StaticControls/RadarSprite |
| 27 | onready var radar_container = $Graphics/Alive/StaticControls/Radar |
| 28 | onready var pause_menu = $UI/PauseMenu |
| 29 | onready var scores_container = $UI/PauseMenu/CenterContainer/PanelContainer/VBoxContainer/TabContainer/Scores |
| 30 | onready var scores_list = $UI/PauseMenu/CenterContainer/PanelContainer/VBoxContainer/TabContainer/Scores/ScoresList |
| 31 | onready var pause_always_show_intro_check = $UI/PauseMenu/CenterContainer/PanelContainer/VBoxContainer/TabContainer/Pref/AlwaysShowIntroCheckBox |
| 32 | onready var pause_show_items_in_radar_check = $UI/PauseMenu/CenterContainer/PanelContainer/VBoxContainer/TabContainer/Pref/ShowItemsInRadarCheckBox |
| 33 | onready var intro_menu = $UI/IntroMenu |
| 34 | onready var intro_always_show_intro_check = $UI/IntroMenu/CenterContainer/PanelContainer/VBoxContainer/AlwaysShowIntroCheckBox |
| 35 | onready var health_label = $Graphics/Alive/StaticControls/HealthLabel |
| 36 | onready var trails = $Graphics/Alive/Trails |
| 37 | onready var death_screen = $UI/DeathScreen |
| 38 | onready var final_score_label = $UI/DeathScreen/CenterContainer/PanelContainer/VBoxContainer/FinalScoreLabel |
| 39 | onready var player_to_target = $Graphics/Alive/PlayerToTarget |
| 40 | |
| 41 | const Utils = preload("res://utils/Utils.gd") |
| 42 | const PopupControl = preload("res://utils/Popup.tscn") |
| 43 | |
| 44 | var weapon: Weapon |
| 45 | var dead = false |
| 46 | var velocity: Vector2 = Vector2.UP * move_speed |
| 47 | var settings: Settings |
| 48 | const zoom_step = 1.01 |
| 49 | |
| 50 | func create_default_weapon() -> Weapon: |
| 51 | return Weapons.create_basic_gun() |
| 52 | |
| 53 | func _ready() -> void: |
| 54 | velocity= Vector2.UP * move_speed |
| 55 | weapon = create_default_weapon() |
| 56 | |
| 57 | # Utils.remove_user_settings() |
| 58 | settings = Utils.load_user_settings() |
| 59 | settings.save_user_settings() |
| 60 | pause_always_show_intro_check.pressed = settings.show_intro |
| 61 | pause_show_items_in_radar_check.pressed = settings.show_items |
| 62 | if settings.show_intro: |
| 63 | show_intro() |
| 64 | |
| 65 | func _process(_delta: float) -> void: |
| 66 | if dead: |
| 67 | return |
| 68 | |
| 69 | # handle up/down actions |
| 70 | if Input.is_action_pressed("up"): |
| 71 | camera.zoom /= zoom_step |
| 72 | if Input.is_action_pressed("down"): |
| 73 | camera.zoom *= zoom_step |
| 74 | |
| 75 | # set the rotation for static controls |
| 76 | static_controls.global_rotation = 0 |
| 77 | |
| 78 | # set progress bar value and color |
| 79 | var pb: ProgressBar = $Graphics/Alive/StaticControls/ProgressBar |
| 80 | var health_ratio = float(health) / float(max_health) |
| 81 | var 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 | |
| 90 | # add history to the trail |
| 91 | trails.add_position(global_position) |
| 92 | |
| 93 | # set HUD info, like score and current weapon |
| 94 | hud.set_current_weapon(weapon) |
| 95 | hud.score = score |
| 96 | |
| 97 | # shoot at closest enemy |
| 98 | var enemies = get_tree().get_nodes_in_group("enemies") |
| 99 | for e in enemies: |
| 100 | var enemy: Enemy = e |
| 101 | enemy.is_targeted = false |
| 102 | |
| 103 | var filteredEnemies = Utils.filter(funcref(self,"filter_invalid_enemies"), enemies) |
| 104 | if filteredEnemies.size() > 0: |
| 105 | filteredEnemies.sort_custom(self, "sort_enemies_ascending_distance") |
| 106 | var closest_enemy: Enemy = filteredEnemies[0] |
| 107 | var dist = closest_enemy.global_position.distance_to(self.global_position) |
| 108 | if dist < self.weapon.shooting_range: |
| 109 | closest_enemy.is_targeted = true |
| 110 | self.shoot_at(closest_enemy.global_position) |
| 111 | player_to_target.points[0] = self.global_position |
| 112 | player_to_target.points[1] = closest_enemy.global_position |
| 113 | |
| 114 | func filter_invalid_enemies(enemy: Enemy) -> bool: |
| 115 | if enemy.dead: |
| 116 | return false |
| 117 | return true |
| 118 | |
| 119 | func sort_enemies_ascending_distance(a: Enemy,b: Enemy): |
| 120 | var distA = self.global_position.distance_to(a.global_position) |
| 121 | var distB = self.global_position.distance_to(b.global_position) |
| 122 | var result = distA < distB |
| 123 | return result |
| 124 | |
| 125 | func _physics_process(delta: float) -> void: |
| 126 | if dead: |
| 127 | return |
| 128 | |
| 129 | # turn toward target |
| 130 | if is_destination_set: |
| 131 | turn_towards(destination, delta) |
| 132 | |
| 133 | # limit movement speed |
| 134 | var speed = velocity.length() |
| 135 | if speed < move_speed: |
| 136 | velocity = velocity * (1 + delta * accel_rate) |
| 137 | speed = velocity.length() |
| 138 | if speed > move_speed: |
| 139 | velocity = velocity.normalized() * move_speed |
| 140 | velocity = move_and_slide(velocity) |
| 141 | |
| 142 | # rotate to match velocity |
| 143 | global_rotation = velocity.angle() + PI/2.0 |
| 144 | |
| 145 | func show_intro() -> void: |
| 146 | get_tree().paused = true |
| 147 | intro_menu.visible = true |
| 148 | |
| 149 | func clear_radar(): |
| 150 | radar_sprite.modulate = Color(1,1,1,0) |
| 151 | var children = radar_container.get_children() |
| 152 | for child in children: |
| 153 | radar_container.remove_child(child) |
| 154 | |
| 155 | func update_radar(items: Array, color: Color): |
| 156 | for i in items: |
| 157 | var item: Node = i |
| 158 | var diff = item.global_position - global_position |
| 159 | var dist = diff.length() |
| 160 | var ratio = Utils.dist_to_ratio(dist, radar_min_radius, radar_max_radius) * .8 + .2 |
| 161 | if dist < radar_min_radius: |
| 162 | ratio = 0 |
| 163 | var angle_to = Vector2.UP.angle_to(diff) |
| 164 | var sprite: Sprite = radar_sprite.duplicate() |
| 165 | sprite.scale = camera.zoom / 4 |
| 166 | sprite.rotate(angle_to) |
| 167 | sprite.modulate = Color(color.r,color.g,color.b,ratio * .5) |
| 168 | radar_container.add_child(sprite) |
| 169 | |
| 170 | func restart(): |
| 171 | var _error = get_tree().reload_current_scene() |
| 172 | |
| 173 | func kill(): |
| 174 | if dead or is_invincible: |
| 175 | return |
| 176 | dead = true |
| 177 | emit_signal("player_died") |
| 178 | add_high_score(score) |
| 179 | $Graphics/Dead.show() |
| 180 | $Graphics/Alive.hide() |
| 181 | final_score_label.text = "Final score: %s" % score |
| 182 | death_screen.show() |
| 183 | z_index = -1 |
| 184 | get_tree().paused = true |
| 185 | |
| 186 | func heal(amount: int = 1): |
| 187 | if dead: |
| 188 | return |
| 189 | health += amount |
| 190 | add_damage_label(-amount) |
| 191 | if health > max_health: |
| 192 | health = max_health |
| 193 | |
| 194 | func speed_up(delta: int = 10): |
| 195 | if dead: |
| 196 | return |
| 197 | move_speed += delta |
| 198 | |
| 199 | func hurt(damage_amount: int = 1): |
| 200 | if dead or is_invincible: |
| 201 | return |
| 202 | else: |
| 203 | health -= damage_amount |
| 204 | add_damage_label(damage_amount) |
| 205 | if health <= 0: |
| 206 | kill() |
| 207 | |
| 208 | func add_damage_label(damage_amount: int = 5) -> void: |
| 209 | var damage_label: Damage = Damage.instance() |
| 210 | damage_label.value = -damage_amount |
| 211 | damage_label.position = self.position |
| 212 | get_tree().root.add_child(damage_label) |
| 213 | |
| 214 | func turn_towards(dest: Vector2, _delta: float): |
| 215 | var to_dir = (dest - global_position).normalized() |
| 216 | var curr_dir = velocity.normalized() |
| 217 | var dir_diff = to_dir - curr_dir |
| 218 | var change = dir_diff * turn_rate * move_speed |
| 219 | velocity = (velocity + change).normalized() * move_speed |
| 220 | |
| 221 | func shoot_at(pos: Vector2): |
| 222 | if weapon.ammo != INF && weapon.ammo <= 0: |
| 223 | weapon = create_default_weapon() |
| 224 | weapon.try_shoot(self, pos) |
| 225 | |
| 226 | func set_objective(objective: String) -> void: |
| 227 | hud.objective = objective |
| 228 | |
| 229 | func _on_StartButton_pressed() -> void: |
| 230 | get_tree().paused = false |
| 231 | intro_menu.visible = false |
| 232 | settings.show_intro = intro_always_show_intro_check.pressed |
| 233 | settings.save_user_settings() |
| 234 | |
| 235 | func set_intro_dialog(enabled: bool) -> void: |
| 236 | settings.show_intro = enabled |
| 237 | settings.save_user_settings() |
| 238 | |
| 239 | func _on_PauseAlwaysShowIntroCheckBox_pressed() -> void: |
| 240 | var show_intro = pause_always_show_intro_check.pressed |
| 241 | set_intro_dialog(show_intro) |
| 242 | |
| 243 | func _on_ShowItemsInRadarCheckBox_pressed() -> void: |
| 244 | var show_items = pause_show_items_in_radar_check.pressed |
| 245 | settings.show_items = show_items |
| 246 | settings.save_user_settings() |
| 247 | |
| 248 | func sort_high_scores(a, b): |
| 249 | return (a as Settings.HighScore).score > (b as Settings.HighScore).score |
| 250 | |
| 251 | func add_high_score(new_score: int) -> void: |
| 252 | var hs = Settings.HighScore.new(new_score, Time.get_datetime_dict_from_system()) |
| 253 | var hss = settings.high_scores |
| 254 | hss.push_back(hs) |
| 255 | hss.sort_custom(self, "sort_high_scores") |
| 256 | while hss.size() > 10: |
| 257 | hss.pop_back() |
| 258 | settings.high_scores = hss |
| 259 | settings.save_user_settings() |
| 260 | |
| 261 | func _on_LinkButton_pressed() -> void: |
| 262 | get_tree().paused = true |
| 263 | var _err = OS.shell_open("https://code.fuzzyworld.net/godot-shooter/") |
| 264 | get_tree().paused = true |
| 265 | |
| 266 | func _on_HUD_position_set(event_pos: Vector2) -> void: |
| 267 | var width = ProjectSettings.get("display/window/size/width") |
| 268 | var height = ProjectSettings.get("display/window/size/height") |
| 269 | var mid = Vector2(width / 2.0, height / 2.0) |
| 270 | var pos = camera_scale(event_pos) + self.position - camera_scale(mid) |
| 271 | set_destination(pos) |
| 272 | emit_signal("destination_set") |
| 273 | |
| 274 | func camera_scale(pos: Vector2) -> Vector2: |
| 275 | var scaled_pos = Vector2(pos.x * camera.zoom.x, pos.y * camera.zoom.y) |
| 276 | return scaled_pos |
| 277 | |
| 278 | func show_message(message: String, duration_seconds: int = 2) -> void: |
| 279 | hud.show_message(message, duration_seconds) |
| 280 | |
| 281 | func _on_HUD_paused() -> void: |
| 282 | get_tree().paused = true |
| 283 | pause_menu.show() |
| 284 | clear_destination() |
| 285 | |
| 286 | func _on_ResumeButton_pressed() -> void: |
| 287 | get_tree().paused = false |
| 288 | pause_menu.hide() |
| 289 | |
| 290 | func _on_HUD_debug_signal() -> void: |
| 291 | emit_signal("debug") |
| 292 | |
| 293 | func set_destination(pos: Vector2) -> void: |
| 294 | destination = pos |
| 295 | is_destination_set = true |
| 296 | emit_signal("destination_set") |
| 297 | func clear_destination() -> void: |
| 298 | is_destination_set = false |
| 299 | emit_signal("destination_set") |
| 300 | |
| 301 | func _on_StartNewGameButton_pressed() -> void: |
| 302 | get_tree().paused = false |
| 303 | restart() |
| 304 | |
| 305 | |
| 306 | func _on_Button_pressed() -> void: |
| 307 | var _err = get_tree().reload_current_scene() |
| 308 | get_tree().paused = false |