godot-shooter / commit
Merge branch 'main' into 'navigation'
README.md | 7 ++-- actors/Enemy.gd | 4 ++- actors/Player.gd | 100 +++++++++++++++++++++++++---------------------------- actors/Player.tscn | 86 ++++++++++++++++++++------------------------- scenes/Scores.gd | 24 +++++++++++++ scenes/Scores.tscn | 28 +++++++++++++++ scenes/World.gd | 24 +------------ scenes/World.tscn | 12 ------- utils/Utils.gd | 11 ++++-- weapons/Weapon.gd | 28 +++++++++++++++ 10 files changed, 182 insertions(+), 142 deletions(-) diff --git a/README.md b/README.md index 401373b..e21818c 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Tap on the screen or move the mouse to set the destination of the ship. The ship ## TODOs 1. Add shields -2. Add powerups -3. Add more enemies -4. Add shooting enemies +2. Add more enemies +3. Add shooting enemies +4. Add beam attack, [from here](https://www.youtube.com/watch?v=dg0CQ6NPDn8) +5. Add navigation logic so actors can move around without colliding with each other diff --git a/actors/Enemy.gd b/actors/Enemy.gd index f5c9986..d4aa41a 100644 --- a/actors/Enemy.gd +++ b/actors/Enemy.gd @@ -10,11 +10,13 @@ const Damage = preload("res://effects/Damage.tscn") const Constants = preload("res://utils/Constants.gd") export var move_speed = Constants.SPEED * 1.05 -export var turn_rate = 0.01 +export var turn_rate = 0.03 export var health = -1 export var max_health = 100 export var is_targeted = false export var is_invincible = false +export var target: Vector2 +export var follow_target = false signal enemy_killed(position) var velocity = Vector2.ZERO diff --git a/actors/Player.gd b/actors/Player.gd index 1f0ffe2..631080d 100644 --- a/actors/Player.gd +++ b/actors/Player.gd @@ -32,23 +32,26 @@ onready var pause_always_show_intro_check = $UI/PauseMenu/CenterContainer/PanelC onready var pause_show_items_in_radar_check = $UI/PauseMenu/CenterContainer/PanelContainer/VBoxContainer/TabContainer/Pref/ShowItemsInRadarCheckBox onready var health_label = $Graphics/Alive/StaticControls/HealthLabel onready var trails = $Graphics/Alive/Trails -onready var shot_timer = $ShotTimer onready var death_screen = $UI/DeathScreen onready var final_score_label = $UI/DeathScreen/CenterContainer/PanelContainer/VBoxContainer/FinalScoreLabel +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 var dead = false -var velocity: Vector2 -var can_fire = true +var velocity: Vector2 = Vector2.UP * move_speed var settings: Settings func _ready() -> void: velocity= Vector2.UP * move_speed weapon = Weapons.create_non_gun() +func create_default_weapon() -> Weapon: + return Weapons.create_basic_gun() + +func _ready() -> void: # Utils.remove_user_settings() settings = Utils.load_user_settings() settings.save_user_settings() @@ -61,7 +64,10 @@ func _process(_delta: float) -> void: if dead: return -# health_label.text = "%s / %s" % [health, max_health] + # set the rotation for static controls + static_controls.global_rotation = 0 + + # set progress bar value and color var pb: ProgressBar = $Graphics/Alive/StaticControls/ProgressBar var health_ratio = float(health) / float(max_health) var health_percentage = health_ratio * 100 @@ -72,17 +78,46 @@ func _process(_delta: float) -> void: pb.modulate = Color.yellow else: pb.modulate = Color.blue - - static_controls.global_rotation = 0 + + # add history to the trail trails.add_position(global_position) + + # set HUD info, like score and current weapon hud.set_current_weapon(weapon) hud.score = score + + # shoot at closest enemy + var enemies = get_tree().get_nodes_in_group("enemies") + for e in enemies: + var enemy: Enemy = e + enemy.is_targeted = false + + var filteredEnemies = Utils.filter(funcref(self,"filter_invalid_enemies"), enemies) + if filteredEnemies.size() > 0: + filteredEnemies.sort_custom(self, "sort_enemies_ascending_distance") + var closest_enemy: Enemy = filteredEnemies[0] + var dist = closest_enemy.global_position.distance_to(self.global_position) + if dist < self.weapon.shooting_range: + closest_enemy.is_targeted = true + self.shoot_at(closest_enemy.global_position) + player_to_target.points[0] = self.global_position + player_to_target.points[1] = closest_enemy.global_position + +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) + var result = distA < distB + return result func _physics_process(delta: float) -> void: if dead: return + + # turn toward target if is_destination_set: turn_towards(destination, delta) + + # limit movement speed var speed = velocity.length() if speed < move_speed: velocity = velocity * (1 + delta * accel_rate) @@ -90,6 +125,8 @@ func _physics_process(delta: float) -> void: if speed > move_speed: velocity = velocity.normalized() * move_speed velocity = move_and_slide(velocity) + + # rotate to match velocity global_rotation = velocity.angle() + PI/2.0 func show_intro() -> void: @@ -107,7 +144,7 @@ func update_radar(items: Array, color: Color): var item: Node = i var diff = item.global_position - global_position var dist = diff.length() - var ratio = Utils.dist_to_ratio(dist, radar_min_radius, radar_max_radius) + var ratio = Utils.dist_to_ratio(dist, radar_min_radius, radar_max_radius) * .8 + .2 if dist < radar_min_radius: ratio = 0 var angle_to = Vector2.UP.angle_to(diff) @@ -166,35 +203,11 @@ func turn_towards(dest: Vector2, _delta: float): var dir_diff = to_dir - curr_dir var change = dir_diff * turn_rate * move_speed velocity = (velocity + change).normalized() * move_speed - -func shoot(): - if !can_fire: - return - can_fire = false - shot_timer.wait_time = weapon.shot_period_seconds - shot_timer.start() - var particle = weapon.particle.instance() - owner.add_child(particle) - particle.transform = $Barrel.global_transform func shoot_at(pos: Vector2): - if !can_fire: - return - can_fire = false - if weapon.ammo != INF: - if weapon.ammo <= 0: - weapon = Weapons.create_basic_gun() - weapon.ammo -= 1 - shot_timer.wait_time = weapon.cooldown_seconds - shot_timer.start() - var particle = weapon.particle.instance() - particle.damage_amount = weapon.damage_amount - owner.add_child(particle) - particle.global_position = global_position - particle.global_rotation = global_position.angle_to_point(pos) + PI / 2.0 - -func allow_fire(): - can_fire = true + if weapon.ammo != INF && weapon.ammo <= 0: + weapon = create_default_weapon() + weapon.try_shoot(self, pos) func set_objective(objective: String) -> void: hud.objective = objective @@ -245,7 +258,6 @@ func show_message(message: String, duration_seconds: int = 2) -> void: func _on_HUD_paused() -> void: get_tree().paused = true - set_pause_menu_high_scores() $UI/PauseMenu.show() clear_destination() @@ -256,24 +268,6 @@ func _on_ResumeButton_pressed() -> void: func _on_HUD_debug_signal() -> void: emit_signal("debug") -func sort_high_scores(a, b): - return (a as Settings.HighScore).score > (b as Settings.HighScore).score - -func set_pause_menu_high_scores() -> void: - if settings.high_scores.size() > 0: - Utils.remove_all_children(scores_list) - settings.high_scores.sort_custom(self, "sort_high_scores") - var index = 0 - var now = Time.get_datetime_dict_from_system() - for hs in settings.high_scores: - index += 1 - var label = Label.new() - var date_dict = hs.date - var date_diff_secs = Utils.time_diff_secs(date_dict, now) - var date_diff = Utils.timespan_to_english(date_diff_secs) - label.text = "%s - %s points, %s" % [index, hs.score, date_diff] - scores_list.add_child(label) - func set_destination(pos: Vector2) -> void: destination = pos is_destination_set = true diff --git a/actors/Player.tscn b/actors/Player.tscn index 73d41f9..e84a2e9 100644 --- a/actors/Player.tscn +++ b/actors/Player.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=18 format=2] +[gd_scene load_steps=19 format=2] [ext_resource path="res://assets/kenney_simple-space/PNG/Default/ship_sidesA.png" type="Texture" id=1] [ext_resource path="res://effects/Explosion.tscn" type="PackedScene" id=2] @@ -6,6 +6,7 @@ [ext_resource path="res://assets/kenney_simple-space/PNG/Retina/ship_B.png" type="Texture" id=4] [ext_resource path="res://themes/CharacterTheme.tres" type="Theme" id=5] [ext_resource path="res://effects/Trails.tscn" type="PackedScene" id=6] +[ext_resource path="res://scenes/Scores.tscn" type="PackedScene" id=7] [ext_resource path="res://assets/neuropolitical rg.otf" type="DynamicFontData" id=8] [ext_resource path="res://themes/Theme.tres" type="Theme" id=9] [ext_resource path="res://scenes/HUD.tscn" type="PackedScene" id=10] @@ -100,6 +101,18 @@ percent_visible = false modulate = Color( 1, 0, 0, 1 ) gradient = SubResource( 1 ) +[node name="PlayerToTarget" type="Line2D" parent="Graphics/Alive"] +visible = false +modulate = Color( 1, 0.984314, 0, 1 ) +points = PoolVector2Array( 2085.69, 283.505, 2067.82, 274.471 ) +width = 3.0 +default_color = Color( 1, 0.976471, 0.4, 1 ) +joint_mode = 1 +begin_cap_mode = 2 +end_cap_mode = 2 +round_precision = 13 +antialiased = true + [node name="Dead" type="Node2D" parent="Graphics"] visible = false @@ -148,8 +161,9 @@ align = 1 valign = 1 [node name="FinalScoreLabel" type="Label" parent="UI/DeathScreen/CenterContainer/PanelContainer/VBoxContainer"] -margin_right = 40.0 -margin_bottom = 25.0 +margin_top = 33.0 +margin_right = 272.0 +margin_bottom = 58.0 [node name="StartNewGameButton" type="Button" parent="UI/DeathScreen/CenterContainer/PanelContainer/VBoxContainer"] margin_top = 135.0 @@ -242,7 +256,7 @@ rect_min_size = Vector2( 450, 235 ) margin_left = 14.0 margin_top = 14.0 margin_right = 436.0 -margin_bottom = 277.0 +margin_bottom = 231.0 [node name="Label" type="Label" parent="UI/PauseMenu/CenterContainer/PanelContainer/VBoxContainer"] margin_right = 422.0 @@ -254,30 +268,9 @@ align = 1 [node name="TabContainer" type="TabContainer" parent="UI/PauseMenu/CenterContainer/PanelContainer/VBoxContainer"] margin_top = 45.0 margin_right = 422.0 -margin_bottom = 218.0 - -[node name="Scores" type="VBoxContainer" parent="UI/PauseMenu/CenterContainer/PanelContainer/VBoxContainer/TabContainer"] -anchor_right = 1.0 -anchor_bottom = 1.0 -margin_left = 8.0 -margin_top = 61.0 -margin_right = -8.0 -margin_bottom = -8.0 - -[node name="Label" type="Label" parent="UI/PauseMenu/CenterContainer/PanelContainer/VBoxContainer/TabContainer/Scores"] -margin_right = 406.0 -margin_bottom = 25.0 -text = "High scores:" - -[node name="ScoresList" type="VBoxContainer" parent="UI/PauseMenu/CenterContainer/PanelContainer/VBoxContainer/TabContainer/Scores"] -margin_top = 33.0 -margin_right = 406.0 -margin_bottom = 58.0 +margin_bottom = 172.0 -[node name="Label" type="Label" parent="UI/PauseMenu/CenterContainer/PanelContainer/VBoxContainer/TabContainer/Scores/ScoresList"] -margin_right = 406.0 -margin_bottom = 25.0 -text = "No high scores yet!" +[node name="Scores" parent="UI/PauseMenu/CenterContainer/PanelContainer/VBoxContainer/TabContainer" instance=ExtResource( 7 )] [node name="Pref" type="VBoxContainer" parent="UI/PauseMenu/CenterContainer/PanelContainer/VBoxContainer/TabContainer"] visible = false @@ -323,9 +316,9 @@ focus_mode = 2 text = "Source code" [node name="ResumeButton" type="Button" parent="UI/PauseMenu/CenterContainer/PanelContainer/VBoxContainer"] -margin_top = 226.0 +margin_top = 180.0 margin_right = 422.0 -margin_bottom = 263.0 +margin_bottom = 217.0 theme = ExtResource( 5 ) text = "Resume" @@ -335,7 +328,6 @@ visible = false anchor_right = 1.0 anchor_bottom = 1.0 mouse_filter = 2 -theme = ExtResource( 9 ) [node name="CenterContainer" type="CenterContainer" parent="UI/GameFinishedMenu"] anchor_right = 1.0 @@ -350,37 +342,36 @@ margin_bottom = 414.0 [node name="VBoxContainer" type="VBoxContainer" parent="UI/GameFinishedMenu/CenterContainer/PanelContainer"] margin_left = 14.0 margin_top = 14.0 -margin_right = 182.0 -margin_bottom = 208.0 +margin_right = 220.0 +margin_bottom = 217.0 [node name="Label" type="Label" parent="UI/GameFinishedMenu/CenterContainer/PanelContainer/VBoxContainer"] -margin_right = 168.0 -margin_bottom = 118.0 +margin_right = 206.0 +margin_bottom = 80.0 theme = ExtResource( 9 ) text = "You've won!" align = 1 valign = 1 autowrap = true +[node name="Scores" parent="UI/GameFinishedMenu/CenterContainer/PanelContainer/VBoxContainer" instance=ExtResource( 7 )] +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 0.0 +margin_top = 88.0 +margin_right = 206.0 +margin_bottom = 146.0 + [node name="Button" type="Button" parent="UI/GameFinishedMenu/CenterContainer/PanelContainer/VBoxContainer"] -margin_top = 126.0 -margin_right = 168.0 -margin_bottom = 194.0 +margin_top = 154.0 +margin_right = 206.0 +margin_bottom = 203.0 +theme = ExtResource( 9 ) text = "Replay!" [node name="Camera2D" type="Camera2D" parent="."] current = true -[node name="ShotTimer" type="Timer" parent="."] -wait_time = 0.5 - -[node name="Barrel" type="Position2D" parent="."] -position = Vector2( 0, -20 ) -rotation = -3.14044 -__meta__ = { -"_gizmo_extents_": 14.2 -} - [node name="NavigationObstacle2D" type="NavigationObstacle2D" parent="."] estimate_radius = false radius = 35.0 @@ -394,4 +385,3 @@ radius = 35.0 [connection signal="pressed" from="UI/PauseMenu/CenterContainer/PanelContainer/VBoxContainer/TabContainer/Pref/ShowItemsInRadarCheckBox" to="." method="_on_ShowItemsInRadarCheckBox_pressed"] [connection signal="pressed" from="UI/PauseMenu/CenterContainer/PanelContainer/VBoxContainer/TabContainer/About/LinkButton" to="." method="_on_LinkButton_pressed"] [connection signal="pressed" from="UI/PauseMenu/CenterContainer/PanelContainer/VBoxContainer/ResumeButton" to="." method="_on_ResumeButton_pressed"] -[connection signal="timeout" from="ShotTimer" to="." method="allow_fire"] diff --git a/scenes/Scores.gd b/scenes/Scores.gd new file mode 100644 index 0000000..2173b2f --- /dev/null +++ b/scenes/Scores.gd @@ -0,0 +1,24 @@ +extends VBoxContainer + +const Utils = preload("res://utils/Utils.gd") +const Weapons = preload("res://weapons/Weapons.gd") +const PowerUp = preload("res://items/PowerUp.tscn") +const Settings = preload("res://utils/Settings.gd") +onready var scores_list = $ScoresList + +func _ready() -> void: + var settings = Utils.load_user_settings() + if settings.high_scores.size() > 0: + Utils.remove_all_children(scores_list) + settings.high_scores.sort_custom(self, "sort_high_scores") + var now = Time.get_datetime_dict_from_system() + for hs in settings.high_scores: + var label = Label.new() + var date_dict = hs.date + var date_diff_secs = Utils.time_diff_secs(date_dict, now) + var date_diff = Utils.timespan_to_english(date_diff_secs) + label.text = " %s points, %s" % [hs.score, date_diff] + scores_list.add_child(label) + +func sort_high_scores(a, b): + return (a as Settings.HighScore).score > (b as Settings.HighScore).score diff --git a/scenes/Scores.tscn b/scenes/Scores.tscn new file mode 100644 index 0000000..9df778f --- /dev/null +++ b/scenes/Scores.tscn @@ -0,0 +1,28 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://scenes/Scores.gd" type="Script" id=1] + +[node name="Scores" type="VBoxContainer"] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 8.0 +margin_top = 61.0 +margin_right = -8.0 +margin_bottom = -8.0 +script = ExtResource( 1 ) + +[node name="Label" type="Label" parent="."] +margin_right = 484.0 +margin_bottom = 25.0 +text = "High scores:" + +[node name="ScoresList" type="VBoxContainer" parent="."] +margin_top = 33.0 +margin_right = 484.0 +margin_bottom = 58.0 + +[node name="Label" type="Label" parent="ScoresList"] +margin_top = 10.0 +margin_right = 484.0 +margin_bottom = 25.0 +text = "No high scores yet!" diff --git a/scenes/World.gd b/scenes/World.gd index f8388f3..57b7734 100644 --- a/scenes/World.gd +++ b/scenes/World.gd @@ -25,7 +25,6 @@ var is_dragging = false func enemy_killed(_position: Vector2): player.score += 5 - player.show_message("Enemy killed!", 1) func health_consumed(_data): player.heal(10) player.score += 1 @@ -197,12 +196,6 @@ func _on_Destination_body_entered(body: Node) -> void: if body == player: player.clear_destination() -func sort_enemies_ascending_distance(a: Enemy,b: Enemy): - var distA = player.global_position.distance_to(a.global_position) - var distB = player.global_position.distance_to(b.global_position) - var result = distA < distB - return result - func filter_invalid_enemies(enemy: Enemy) -> bool: if enemy.dead: return false @@ -211,21 +204,6 @@ func filter_invalid_enemies(enemy: Enemy) -> bool: func _on_ShootTimer_timeout() -> void: if player.dead: return - var enemies = get_tree().get_nodes_in_group("enemies") - for e in enemies: - var enemy: Enemy = e - enemy.is_targeted = false - - var filteredEnemies = Utils.filter(funcref(self,"filter_invalid_enemies"), enemies) - if filteredEnemies.size() > 0: - filteredEnemies.sort_custom(self, "sort_enemies_ascending_distance") - var closest_enemy: Enemy = filteredEnemies[0] - var dist = closest_enemy.global_position.distance_to($Player.global_position) - if dist < player.weapon.shooting_range: - closest_enemy.is_targeted = true - player.shoot_at(closest_enemy.global_position) - $PlayerToTarget.points[0] = player.global_position - $PlayerToTarget.points[1] = closest_enemy.global_position func _on_Button_pressed() -> void: get_tree().paused = false @@ -257,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.set_target($Player.position) + enemy.turn_towards($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 aed6610..d306dd5 100644 --- a/scenes/World.tscn +++ b/scenes/World.tscn @@ -65,18 +65,6 @@ process_mode = 0 wait_time = 0.05 autostart = true -[node name="PlayerToTarget" type="Line2D" parent="."] -visible = false -modulate = Color( 1, 0.984314, 0, 1 ) -points = PoolVector2Array( 2085.69, 283.505, 2067.82, 274.471 ) -width = 3.0 -default_color = Color( 1, 0.976471, 0.4, 1 ) -joint_mode = 1 -begin_cap_mode = 2 -end_cap_mode = 2 -round_precision = 13 -antialiased = true - [node name="UpdateTimer" type="Timer" parent="."] wait_time = 0.1 autostart = true diff --git a/utils/Utils.gd b/utils/Utils.gd index 9623c56..a8bb2e3 100644 --- a/utils/Utils.gd +++ b/utils/Utils.gd @@ -34,8 +34,15 @@ static func load_user_settings(path = SettingsClass.settings_path) -> Settings: static func log(arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null, arg6 = null, arg7 = null, arg8 = null, arg9 = null): var text = "" - var now = Time.get_datetime_string_from_system(false, true) - text += now + var now_unix_time = Time.get_unix_time_from_system() + var now_dict = Time.get_datetime_dict_from_unix_time(now_unix_time) + var seconds_decimal = now_unix_time - floor(now_unix_time) + if seconds_decimal >= 1: + seconds_decimal = 0 + var seconds_decimal_text = "%.6f" % seconds_decimal + seconds_decimal_text = seconds_decimal_text.replace("0.", "") + var now_text = "%s:%s:%s.%s" % [now_dict["hour"], now_dict["minute"], now_dict["second"], seconds_decimal_text] + text += now_text text += "> " for argument in [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9]: if argument != null: diff --git a/weapons/Weapon.gd b/weapons/Weapon.gd index 359b386..c7aa55a 100644 --- a/weapons/Weapon.gd +++ b/weapons/Weapon.gd @@ -9,3 +9,31 @@ var damage_amount = 1 var ammo = INF var name = "Laser" var reload_size = 1 +var last_shoot_time: float + +const Utils = preload("res://utils/Utils.gd") + +func _init() -> void: + var now = Time.get_unix_time_from_system() + last_shoot_time = now + +func try_shoot(shooter: Node2D, target: Vector2) -> void: + var has_ammo = ammo == INF or ammo > 0 + if not has_ammo: + return + + var now = Time.get_unix_time_from_system() + var sec_since_shot = now - last_shoot_time + var can_shoot = sec_since_shot > cooldown_seconds + if not can_shoot: + return + + if ammo != INF: + ammo = ammo - 1 + + var particle_instance = particle.instance() + shooter.owner.add_child(particle_instance) + particle_instance.transform = shooter.global_transform + particle_instance.damage_amount = self.damage_amount + particle_instance.global_rotation = shooter.global_position.angle_to_point(target) + PI / 2.0 + last_shoot_time = now