game: add weapon base and sword
This commit is contained in:
@@ -1,102 +0,0 @@
|
||||
extends Node2D
|
||||
|
||||
@export var default_attack_time: float = 0.5
|
||||
@export var player: Player = null
|
||||
|
||||
@onready var trigger_area: Area2D = $TriggerArea
|
||||
@onready var trigger_collision: CollisionShape2D = $TriggerArea/TriggerCollision
|
||||
@onready var timer: Timer = $Timer
|
||||
@onready var attack_path: Path2D = $AttackPath
|
||||
@onready var path_follow_2d: PathFollow2D = $AttackPath/PathFollow2D
|
||||
@onready var attack_area: Area2D = $AttackPath/PathFollow2D/Sprite2D/AttackArea
|
||||
|
||||
var base_damage: float = 5.0
|
||||
var current_target: Node2D = null
|
||||
var current_progress: float = 0.0
|
||||
var damaged_this_attack: Array = []
|
||||
var is_attacking: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
attack_path.visible = false
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if timer.is_stopped() and current_progress == 0.0:
|
||||
timer.start()
|
||||
if current_progress > 0.95:
|
||||
reset_attack()
|
||||
if (
|
||||
current_target
|
||||
and is_instance_valid(current_target)
|
||||
and not current_target.is_queued_for_deletion()
|
||||
):
|
||||
track_target(current_target)
|
||||
is_attacking = true
|
||||
attack_path.visible = true
|
||||
|
||||
if is_attacking:
|
||||
# Do attack animation
|
||||
current_progress += delta / default_attack_time
|
||||
current_progress = clampf(current_progress, 0.0, 1.0)
|
||||
path_follow_2d.progress_ratio = current_progress
|
||||
|
||||
|
||||
func reset_attack() -> void:
|
||||
current_target = null
|
||||
attack_path.visible = false
|
||||
current_progress = 0.0
|
||||
damaged_this_attack = []
|
||||
is_attacking = false
|
||||
position = Vector2.ZERO
|
||||
rotation = 0.0
|
||||
|
||||
|
||||
func set_target(body: Node2D):
|
||||
current_target = body
|
||||
is_attacking = true
|
||||
|
||||
|
||||
func track_target(body: Node2D):
|
||||
var mid_distance = attack_path.curve.get_baked_length() / 2
|
||||
var mid_point: Vector2 = attack_path.curve.sample_baked(mid_distance)
|
||||
var offset = body.global_position - to_global(mid_point)
|
||||
|
||||
var desired_dir = (body.global_position - to_global(mid_point)).normalized()
|
||||
var start_point_global = attack_path.to_global(attack_path.curve.sample_baked(0))
|
||||
var end_point_global = attack_path.to_global(
|
||||
attack_path.curve.sample_baked(attack_path.curve.get_baked_length())
|
||||
)
|
||||
var curve_dir = (start_point_global - end_point_global).normalized()
|
||||
var angle_diff = curve_dir.angle_to(desired_dir)
|
||||
if rotation == 0.0:
|
||||
rotation = curve_dir.angle_to(desired_dir)
|
||||
position += offset
|
||||
|
||||
|
||||
func _on_timer_timeout() -> void:
|
||||
if current_target:
|
||||
if trigger_area.has_overlapping_areas():
|
||||
if current_target not in trigger_area.get_overlapping_bodies():
|
||||
current_target = null
|
||||
return
|
||||
if trigger_area.has_overlapping_bodies():
|
||||
for body in trigger_area.get_overlapping_bodies():
|
||||
if body.is_in_group(GlobalConst.GROUP_ENEMY):
|
||||
set_target(body)
|
||||
|
||||
|
||||
func _on_attack_area_body_entered(body: Node2D) -> void:
|
||||
if not attack_path.visible:
|
||||
return
|
||||
if body in damaged_this_attack:
|
||||
return
|
||||
if body.is_in_group(GlobalConst.GROUP_ENEMY) and body.is_in_group(GlobalConst.GROUP_DAMAGEABLE):
|
||||
var crit_chance = player.player_stats.get_final("crit_chance", player.modifiers)
|
||||
var damage_dealt = base_damage
|
||||
var is_crit = randf() >= 1 - crit_chance
|
||||
if is_crit:
|
||||
damage_dealt *= player.player_stats.get_final("crit_multiplier", player.modifiers)
|
||||
|
||||
body.take_damage(damage_dealt, is_crit)
|
||||
damaged_this_attack.append(body)
|
@@ -1 +0,0 @@
|
||||
uid://db326gu8abue5
|
@@ -1,53 +0,0 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://cdojqe2m4kxx1"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://dycw7c3484dir" path="res://assets/sprites/sword.png" id="1_3fwwl"]
|
||||
[ext_resource type="Script" uid="uid://db326gu8abue5" path="res://scenes/attacks/attack_sword.gd" id="1_frsqi"]
|
||||
|
||||
[sub_resource type="Curve2D" id="Curve2D_frsqi"]
|
||||
bake_interval = 2.0
|
||||
_data = {
|
||||
"points": PackedVector2Array(0, 0, 0, 0, 0, 0, 0, 0, 25, 10, 200, 0)
|
||||
}
|
||||
point_count = 2
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_frsqi"]
|
||||
size = Vector2(13.9997, 46.999)
|
||||
|
||||
[sub_resource type="CircleShape2D" id="CircleShape2D_3fwwl"]
|
||||
radius = 267.002
|
||||
|
||||
[node name="AttackSword" type="Node2D"]
|
||||
script = ExtResource("1_frsqi")
|
||||
|
||||
[node name="AttackPath" type="Path2D" parent="."]
|
||||
curve = SubResource("Curve2D_frsqi")
|
||||
|
||||
[node name="PathFollow2D" type="PathFollow2D" parent="AttackPath"]
|
||||
loop = false
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="AttackPath/PathFollow2D"]
|
||||
position = Vector2(0.322462, -0.946582)
|
||||
rotation = 0.328329
|
||||
texture = ExtResource("1_3fwwl")
|
||||
|
||||
[node name="AttackArea" type="Area2D" parent="AttackPath/PathFollow2D/Sprite2D"]
|
||||
collision_layer = 0
|
||||
collision_mask = 2
|
||||
|
||||
[node name="AttackCollision" type="CollisionShape2D" parent="AttackPath/PathFollow2D/Sprite2D/AttackArea"]
|
||||
position = Vector2(-0.0328934, -4.50646)
|
||||
shape = SubResource("RectangleShape2D_frsqi")
|
||||
|
||||
[node name="TriggerArea" type="Area2D" parent="."]
|
||||
visible = false
|
||||
collision_layer = 0
|
||||
collision_mask = 2
|
||||
|
||||
[node name="TriggerCollision" type="CollisionShape2D" parent="TriggerArea"]
|
||||
shape = SubResource("CircleShape2D_3fwwl")
|
||||
|
||||
[node name="Timer" type="Timer" parent="."]
|
||||
one_shot = true
|
||||
|
||||
[connection signal="body_entered" from="AttackPath/PathFollow2D/Sprite2D/AttackArea" to="." method="_on_attack_area_body_entered"]
|
||||
[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]
|
@@ -55,6 +55,7 @@ func do_movement(delta: float) -> void:
|
||||
else:
|
||||
_do_nav_agent_movement()
|
||||
|
||||
|
||||
func _has_direct_path():
|
||||
target_cast.target_position = to_local(target.global_position)
|
||||
target_cast.enabled = true
|
||||
|
@@ -14,6 +14,7 @@ func _process(delta: float) -> void:
|
||||
elapsed_time += delta
|
||||
main_ui.player_ui.set_elapsed_time(elapsed_time)
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("ui_cancel"):
|
||||
main_ui.pause_ui.toggle_pause_ui()
|
||||
|
@@ -1,4 +1,4 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://bjg50n7aab3ng"]
|
||||
[gd_scene load_steps=9 format=3 uid="uid://bjg50n7aab3ng"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://brb4ssksmtq8k" path="res://scenes/main.gd" id="1_jyhfs"]
|
||||
[ext_resource type="PackedScene" uid="uid://4xha2nhf8fya" path="res://scenes/test_level.tscn" id="1_o5qli"]
|
||||
@@ -7,6 +7,7 @@
|
||||
[ext_resource type="PackedScene" uid="uid://dy73qrxcgrwg3" path="res://scenes/managers/enemy_manager.tscn" id="5_tbgi4"]
|
||||
[ext_resource type="PackedScene" uid="uid://bbev8m5g0p3a3" path="res://scenes/pickups/pickup_magnet.tscn" id="6_tefeu"]
|
||||
[ext_resource type="PackedScene" uid="uid://cr8gj1dlloamp" path="res://scenes/pickups/pickup_hp.tscn" id="7_o6xl0"]
|
||||
[ext_resource type="PackedScene" uid="uid://c2o1lpm4pimpr" path="res://scenes/managers/projectile_manager.tscn" id="8_tipki"]
|
||||
|
||||
[node name="Main" type="Node2D"]
|
||||
script = ExtResource("1_jyhfs")
|
||||
@@ -32,3 +33,5 @@ position = Vector2(1697, 414)
|
||||
|
||||
[node name="PickupHP" parent="." instance=ExtResource("7_o6xl0")]
|
||||
position = Vector2(1678, 939)
|
||||
|
||||
[node name="ProjectileManager" parent="." instance=ExtResource("8_tipki")]
|
||||
|
@@ -26,11 +26,13 @@ func _on_timer_timeout() -> void:
|
||||
new_enemy.target = target
|
||||
add_child(new_enemy)
|
||||
|
||||
|
||||
func _on_stop_spawning(val: bool):
|
||||
if val:
|
||||
timer.stop()
|
||||
elif timer.is_stopped():
|
||||
timer.start()
|
||||
|
||||
|
||||
func _on_set_spawn_rate(val: float):
|
||||
timer.wait_time = 1 / val
|
||||
|
3
scenes/managers/projectile_manager.tscn
Normal file
3
scenes/managers/projectile_manager.tscn
Normal file
@@ -0,0 +1,3 @@
|
||||
[gd_scene format=3 uid="uid://c2o1lpm4pimpr"]
|
||||
|
||||
[node name="ProjectileManager" type="Node2D"]
|
@@ -5,7 +5,6 @@ extends CharacterBody2D
|
||||
@export var main_ui: MainUI
|
||||
|
||||
@onready var sprite_2d: Sprite2D = $Sprite2D
|
||||
@onready var attack_sword: Node2D = $AttackSword
|
||||
|
||||
var player_stats: PlayerStats = PlayerStats.new()
|
||||
var modifiers: Array[PlayerStatsModifier] = []
|
||||
@@ -61,7 +60,6 @@ func die():
|
||||
dead = true
|
||||
remove_from_group("damagable")
|
||||
get_tree().call_group("enemy", "cheer")
|
||||
attack_sword.queue_free()
|
||||
GlobalConst.sig_stop_spawning.emit(true)
|
||||
sprite_2d.z_index += 10
|
||||
get_taunted()
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://5x5wimok8uw2" path="res://assets/sprites/roguelikeChar_transparent.png" id="1_3vyb7"]
|
||||
[ext_resource type="Script" uid="uid://cvqaxckx4num3" path="res://scenes/player.gd" id="1_g2els"]
|
||||
[ext_resource type="PackedScene" uid="uid://cdojqe2m4kxx1" path="res://scenes/attacks/attack_sword.tscn" id="3_qhqgy"]
|
||||
[ext_resource type="PackedScene" uid="uid://dfikvj27k01tu" path="res://scenes/weapons/weapon_sword.tscn" id="3_qhqgy"]
|
||||
|
||||
[sub_resource type="CircleShape2D" id="CircleShape2D_3vyb7"]
|
||||
radius = 8.0
|
||||
@@ -22,9 +22,6 @@ region_rect = Rect2(0, 104, 16, 14)
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("CircleShape2D_3vyb7")
|
||||
|
||||
[node name="AttackSword" parent="." node_paths=PackedStringArray("player") instance=ExtResource("3_qhqgy")]
|
||||
player = NodePath("..")
|
||||
|
||||
[node name="PickupArea" type="Area2D" parent="."]
|
||||
collision_layer = 0
|
||||
collision_mask = 24
|
||||
@@ -33,4 +30,6 @@ monitorable = false
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="PickupArea"]
|
||||
shape = SubResource("CircleShape2D_qhqgy")
|
||||
|
||||
[node name="WeaponSword" parent="." instance=ExtResource("3_qhqgy")]
|
||||
|
||||
[connection signal="area_entered" from="PickupArea" to="." method="_on_pickup_area_area_entered"]
|
||||
|
42
scenes/weapons/weapon_base.gd
Normal file
42
scenes/weapons/weapon_base.gd
Normal file
@@ -0,0 +1,42 @@
|
||||
class_name WeaponBase
|
||||
extends Node2D
|
||||
|
||||
@export var attack_cd: float
|
||||
@export var attack_damage: float
|
||||
@export var attack_aoe: float
|
||||
@export var attack_duration: float
|
||||
@export var attack_range: float
|
||||
|
||||
|
||||
func _on_attack_cd_timer_timeout() -> void:
|
||||
do_attack()
|
||||
|
||||
|
||||
func do_attack() -> void:
|
||||
push_error("%s does not implement do_attack" % self)
|
||||
|
||||
|
||||
func find_target_in_radius() -> EnemyBase:
|
||||
var space_state: PhysicsDirectSpaceState2D = get_world_2d().direct_space_state
|
||||
var shape := CircleShape2D.new()
|
||||
shape.radius = attack_range
|
||||
|
||||
var query := PhysicsShapeQueryParameters2D.new()
|
||||
query.shape = shape
|
||||
query.transform = Transform2D(0, global_position)
|
||||
query.collision_mask = 2
|
||||
query.collide_with_bodies = true
|
||||
var results := space_state.intersect_shape(query)
|
||||
if len(results) < 1:
|
||||
return null
|
||||
var closest: PhysicsBody2D = results[0]["collider"]
|
||||
for r in results:
|
||||
var c: PhysicsBody2D = r["collider"]
|
||||
if not c.is_in_group(GlobalConst.GROUP_ENEMY):
|
||||
continue
|
||||
if (
|
||||
c.global_position.distance_to(global_position)
|
||||
< closest.global_position.distance_to(global_position)
|
||||
):
|
||||
closest = c
|
||||
return closest
|
1
scenes/weapons/weapon_base.gd.uid
Normal file
1
scenes/weapons/weapon_base.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d6nwfhyethdw
|
11
scenes/weapons/weapon_base.tscn
Normal file
11
scenes/weapons/weapon_base.tscn
Normal file
@@ -0,0 +1,11 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://i8mtky2req41"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://d6nwfhyethdw" path="res://scenes/weapons/weapon_base.gd" id="1_v4xn6"]
|
||||
|
||||
[node name="WeaponBase" type="Node2D"]
|
||||
script = ExtResource("1_v4xn6")
|
||||
|
||||
[node name="AttackCDTimer" type="Timer" parent="."]
|
||||
autostart = true
|
||||
|
||||
[connection signal="timeout" from="AttackCDTimer" to="." method="_on_attack_cd_timer_timeout"]
|
42
scenes/weapons/weapon_sword.gd
Normal file
42
scenes/weapons/weapon_sword.gd
Normal file
@@ -0,0 +1,42 @@
|
||||
class_name WeaponSword
|
||||
extends WeaponBase
|
||||
|
||||
const WEAPON_SWORD_PROJECTILE = preload("res://scenes/weapons/weapon_sword_projectile.tscn")
|
||||
|
||||
@onready var targeting_range: Area2D = $TargetingRange
|
||||
@onready var targeting_range_shape: CollisionShape2D = $TargetingRange/CollisionShape2D
|
||||
|
||||
signal projectile_hit(projectile: WeaponSwordProjectile, enemy: EnemyBase)
|
||||
|
||||
var _player: Player
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
targeting_range_shape.shape.radius = attack_range
|
||||
projectile_hit.connect(_on_projectile_hit)
|
||||
_player = get_tree().get_first_node_in_group(GlobalConst.GROUP_PLAYER)
|
||||
|
||||
|
||||
func do_attack() -> void:
|
||||
var target: EnemyBase = find_target_in_radius()
|
||||
if not target:
|
||||
return
|
||||
|
||||
var projectile = WEAPON_SWORD_PROJECTILE.instantiate()
|
||||
projectile.damage = attack_damage
|
||||
projectile.target = target
|
||||
projectile.on_hit_sig = projectile_hit
|
||||
add_child(projectile)
|
||||
|
||||
|
||||
func deal_damage(enemy: EnemyBase):
|
||||
var crit_chance = _player.player_stats.get_final("crit_chance", _player.modifiers)
|
||||
var damage_dealt = attack_damage
|
||||
var is_crit = randf() >= 1 - crit_chance
|
||||
if is_crit:
|
||||
damage_dealt *= _player.player_stats.get_final("crit_multiplier", _player.modifiers)
|
||||
enemy.take_damage(damage_dealt, is_crit)
|
||||
|
||||
|
||||
func _on_projectile_hit(projectile: WeaponSwordProjectile, enemy: EnemyBase):
|
||||
deal_damage(enemy)
|
1
scenes/weapons/weapon_sword.gd.uid
Normal file
1
scenes/weapons/weapon_sword.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b072d866r4usq
|
22
scenes/weapons/weapon_sword.tscn
Normal file
22
scenes/weapons/weapon_sword.tscn
Normal file
@@ -0,0 +1,22 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://dfikvj27k01tu"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://i8mtky2req41" path="res://scenes/weapons/weapon_base.tscn" id="1_2dti5"]
|
||||
[ext_resource type="Script" uid="uid://b072d866r4usq" path="res://scenes/weapons/weapon_sword.gd" id="2_ruf80"]
|
||||
|
||||
[sub_resource type="CircleShape2D" id="CircleShape2D_ruf80"]
|
||||
|
||||
[node name="WeaponSword" instance=ExtResource("1_2dti5")]
|
||||
script = ExtResource("2_ruf80")
|
||||
attack_cd = 1.0
|
||||
attack_damage = 5.0
|
||||
attack_aoe = 1.0
|
||||
attack_duration = 1.0
|
||||
attack_range = 150.0
|
||||
|
||||
[node name="TargetingRange" type="Area2D" parent="." index="1"]
|
||||
collision_layer = 0
|
||||
collision_mask = 2
|
||||
monitorable = false
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="TargetingRange" index="0"]
|
||||
shape = SubResource("CircleShape2D_ruf80")
|
40
scenes/weapons/weapon_sword_projectile.gd
Normal file
40
scenes/weapons/weapon_sword_projectile.gd
Normal file
@@ -0,0 +1,40 @@
|
||||
class_name WeaponSwordProjectile
|
||||
extends Node2D
|
||||
|
||||
@export var speed: float = 500.0
|
||||
@export var range: float = 200.0
|
||||
@export var target: Node2D
|
||||
@export var damage: float
|
||||
@export var on_hit_sig: Signal
|
||||
|
||||
var _direction: Vector2
|
||||
var _traveled: float = 0.0
|
||||
var _already_hit: Array[PhysicsBody2D] = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_direction = global_position.direction_to(target.global_position).normalized()
|
||||
_traveled = 0.0
|
||||
rotation = _direction.angle()
|
||||
if _direction.x < 0:
|
||||
rotation += PI
|
||||
var proj_manager = get_tree().get_first_node_in_group(GlobalConst.GROUP_PROJ_MANAGER)
|
||||
if not proj_manager:
|
||||
return
|
||||
reparent(proj_manager, true)
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
var step = _direction * speed * delta
|
||||
global_position += step
|
||||
_traveled += step.length()
|
||||
|
||||
if _traveled >= range:
|
||||
queue_free()
|
||||
|
||||
|
||||
func _on_area_2d_body_entered(body: Node2D) -> void:
|
||||
if body in _already_hit:
|
||||
return
|
||||
on_hit_sig.emit(self, body)
|
||||
_already_hit.append(body)
|
1
scenes/weapons/weapon_sword_projectile.gd.uid
Normal file
1
scenes/weapons/weapon_sword_projectile.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c40iaqdubwl0p
|
24
scenes/weapons/weapon_sword_projectile.tscn
Normal file
24
scenes/weapons/weapon_sword_projectile.tscn
Normal file
@@ -0,0 +1,24 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://bv5f47x3ishmf"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c40iaqdubwl0p" path="res://scenes/weapons/weapon_sword_projectile.gd" id="1_asuu4"]
|
||||
[ext_resource type="Texture2D" uid="uid://dycw7c3484dir" path="res://assets/sprites/sword.png" id="2_pxap4"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_ygc1t"]
|
||||
size = Vector2(16, 46)
|
||||
|
||||
[node name="WeaponSwordProjectile" type="Node2D"]
|
||||
script = ExtResource("1_asuu4")
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
texture = ExtResource("2_pxap4")
|
||||
|
||||
[node name="Area2D" type="Area2D" parent="."]
|
||||
collision_layer = 0
|
||||
collision_mask = 2
|
||||
monitorable = false
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"]
|
||||
position = Vector2(0, -5)
|
||||
shape = SubResource("RectangleShape2D_ygc1t")
|
||||
|
||||
[connection signal="body_entered" from="Area2D" to="." method="_on_area_2d_body_entered"]
|
Reference in New Issue
Block a user