refactor: projectiles logic

- add hitscan
 - add camera arrow key move
This commit is contained in:
Varylios 2025-09-20 19:58:50 +02:00
parent 579fc26423
commit 868810ca82
11 changed files with 137 additions and 124 deletions

View file

@ -29,7 +29,7 @@ signal team_in_rest_changed(count : int)
## [b]Emitter[/b] : [Projectile][br]
## [b]Subscriber[/b] : [code]null[/code]
signal projectile_shooted(projectile: Projectile, startPosition: Vector3)
signal projectile_shooted(projectile: Projectile, transform: Transform3D)
## [b]Emitter[/b] : [WorldManager][br]

View file

@ -22,12 +22,12 @@ func _process(_delta: float) -> void:
var mousePosition : Vector2 = get_viewport().get_mouse_position()
if mousePosition.x < SCREEN_MARGIN:
position.x += -SCREEN_MOVEMENT_SPEED
if mousePosition.y < SCREEN_MARGIN:
position.z += -SCREEN_MOVEMENT_SPEED
if mousePosition.x > windowSize.x - SCREEN_MARGIN:
position.x -= SCREEN_MOVEMENT_SPEED
elif mousePosition.x > windowSize.x - SCREEN_MARGIN:
position.x += SCREEN_MOVEMENT_SPEED
if mousePosition.y > windowSize.y - SCREEN_MARGIN:
if mousePosition.y < SCREEN_MARGIN:
position.z -= SCREEN_MOVEMENT_SPEED
elif mousePosition.y > windowSize.y - SCREEN_MARGIN:
position.z += SCREEN_MOVEMENT_SPEED
@ -38,9 +38,13 @@ func _notification(what: int) -> void:
func _input(event: InputEvent) -> void:
if not event is InputEventMouseButton:
return
if event is InputEventMouseButton:
onEventMouseButton(event)
elif event is InputEventKey:
onEventKey(event)
func onEventMouseButton(event: InputEventMouseButton) -> void:
var newPosition : float = position.y
match event.button_index:
MOUSE_BUTTON_WHEEL_UP: newPosition -= .2
@ -50,3 +54,11 @@ func _input(event: InputEvent) -> void:
rotation.x = deg_to_rad(curve.sample(newPosition))
position.y = newPosition
func onEventKey(event: InputEventKey) -> void:
match event.keycode:
KEY_LEFT: position.x -= SCREEN_MOVEMENT_SPEED * 4
KEY_RIGHT: position.x += SCREEN_MOVEMENT_SPEED * 4
KEY_UP: position.z -= SCREEN_MOVEMENT_SPEED * 4
KEY_DOWN: position.z += SCREEN_MOVEMENT_SPEED * 4

View file

@ -118,6 +118,6 @@ func addMap(mapScene : PackedScene) -> void:
state = STATE.SPAWN if level.auto_start else STATE.IDLE
func onProjectileShooted(projectile: Projectile, startPosition: Vector3) -> void:
func onProjectileShooted(projectile: Projectile, _transform: Transform3D) -> void:
add_child(projectile)
projectile.global_position = startPosition
projectile.transform = _transform

View file

@ -2,27 +2,35 @@ extends CharacterBody3D
class_name Projectile
enum MODE {
FOLLOW, ## Follow Entity
LOCATION, ## Go to entity location
HITSCAN, ## DANGER NOT implemented yet
enum Mode {
FOLLOW, ## Follow target
LOCATION, ## Go to target location
SPAWN_ON_TARGET, ## Spawn on target location
HITSCAN, ## Spawn on target location
}
enum TYPE { ## Types of projectiles
BASIC, ## One target
AOE, ## Multiple targets[br]work with [member damageArea]
PIERCING, ## Piercing through enemies[br]work with [member maxTarets] and [member damageArea]
BOUNCING, ## Bouncing over enemies[br]work with [member maxTarets] and [member damageArea]
DISABLING, ## Disable ally tower for [member damage] duration [br]Usable on [Boss] projectiles
enum Duration {
ONE_HIT, ## Make damage on hit
DAMAGE_OVER_TIME, ## Make damage every tick for specified duration[br]work with [member maxTargets] for number of tick
}
enum Type { ## Types of projectiles
BASIC, ## One defined target
AOE, ## Multiple targets
BOUNCING, ## Bouncing over enemies[br]work with [member maxTargets]
DISABLING, ## Disable ally tower for [member amount] duration [br]Usable on [Boss] projectiles
}
@export var type : TYPE = TYPE.BASIC
@export var mode : MODE = MODE.FOLLOW
@export var mode : Mode = Mode.FOLLOW
@export var duration : Duration = Duration.ONE_HIT
@export var type : Type = Type.BASIC
@export var speed : int
## Usefull when [enum Type] is not [constant BASIC][br]
## [code]-1[/code] for no maximum
## Used as time when [enum Duration] is [constant HITSCAN]
@export var maxTargets : int = 1
var amount : float
var target : PhysicsBody3D
var vectorTarget : Vector3
@ -31,93 +39,75 @@ var collidingBodies : Array[Node3D]
var affectedTarget : Array[Node3D]
func _ready() -> void:
$HitBox.body_entered.connect(collidingBodies.append)
#$HitBox.body_entered.connect(test)
$HitBox.body_exited.connect(collidingBodies.erase)
$EffectArea.body_entered.connect(bodiesInRange.append)
$EffectArea.body_exited.connect(bodiesInRange.erase)
func test(body):
print(body)
var firstTick : bool = true
func _physics_process(_delta: float) -> void:
if mode == MODE.LOCATION && vectorTarget.distance_squared_to(global_position) < .4:
resolveContact()
maxTargets = 0 # Ensure queue free in next if
if firstTick:
firstTick = false
return
resolveContact()
if shouldQueueFree():
return queue_free()
queue_free.call_deferred()
return
if not collidingBodies.is_empty() && collidingBodies.has(target):
return onBodyCollideWithProjectile(target)
var globalPos : Vector3 = vectorTarget if vectorTarget else target.global_position
if target:
if mode == Mode.FOLLOW:
var globalPos : Vector3 = target.global_position
globalPos.y += Helper.getHitBoxLocation(target, Helper.POSITION.CENTER)
look_at(globalPos)
velocity = global_position.direction_to(globalPos) * speed
look_at(globalPos)
velocity = global_position.direction_to(globalPos) * speed
move_and_slide()
func shouldQueueFree() -> bool:
if maxTargets < 1:
if maxTargets == 0 || !is_instance_valid(target) && mode == Mode.FOLLOW:
return true
if !is_instance_valid(target):
return mode == MODE.FOLLOW
elif mode != Mode.FOLLOW:
return isOnTarget()
elif target is Tower:
return not target.visible
return false
func onBodyEnteredDamageArea(body: Node3D) -> void:
if type != TYPE.BASIC && targetable(body):
addBodyInRange(body)
if type == TYPE.PIERCING:
resolveContact()
func onBodyCollideWithProjectile(body: Node3D) -> void:
if not collidingBodies.has(body):
collidingBodies.push_back(body)
if mode != MODE.LOCATION && (body == target || type == TYPE.PIERCING) && targetable(body):
addBodyInRange(body, true)
resolveContact()
func addBodyInRange(body: Node3D, pushFront: bool = false) -> void:
var idx : int = bodiesInRange.find(body)
if idx == -1:
if pushFront:
bodiesInRange.push_front(body)
else:
bodiesInRange.push_back(body)
elif pushFront && idx != 0:
bodiesInRange.remove_at(idx)
bodiesInRange.push_front(body)
func targetable(body: Node3D) -> bool:
return not affectedTarget.has(body)
func isOnTarget() -> bool:
match mode:
Mode.LOCATION, Mode.SPAWN_ON_TARGET: return vectorTarget.distance_squared_to(global_position) < .4
Mode.FOLLOW: return collidingBodies.has(target)
Mode.HITSCAN: return true
_: return false
func resolveContact() -> void:
if bodiesInRange.is_empty():
if collidingBodies.is_empty() || not isOnTarget():
return
resolveEffect(bodiesInRange[0])
match type:
TYPE.AOE:
for bodyInRange in bodiesInRange:
if is_instance_valid(bodyInRange):
resolveEffect(bodyInRange, false)
TYPE.BOUNCING:
target = null if bodiesInRange.is_empty() else bodiesInRange[0]
Type.AOE:
collidingBodies.map(resolveEffect)
_ when collidingBodies.has(target):
resolveEffect(target)
if type == Type.BOUNCING:
target = null if bodiesInRange.is_empty() else bodiesInRange[0]
func resolveEffect(body : Node3D, erase : bool = true) -> void:
if erase:
bodiesInRange.erase(body)
if affectedTarget.has(body) || body is GameTile:
func resolveEffect(body : Node3D) -> void:
if affectedTarget.has(body) || body is GameTile || maxTargets == 0:
return
if type == TYPE.DISABLING && body.has_method("disable"):
if type == Type.DISABLING && body.has_method("disable"):
body.disable(amount)
elif body.has_method("take_damage"):
body.take_damage(amount)
@ -126,23 +116,23 @@ func resolveEffect(body : Node3D, erase : bool = true) -> void:
maxTargets -= 1
func removeTarget(body: Node3D) -> void:
bodiesInRange.erase(body)
func removeCollidingBody(body: Node3D) -> void:
collidingBodies.erase(body)
func shoot(_target: Node3D, globalPos: Vector3) -> void:
target = _target
match mode:
Projectile.MODE.HITSCAN:
globalPos = target.global_position
globalPos.y += Helper.getHitBoxLocation(target, Helper.POSITION.CENTER)
Projectile.MODE.LOCATION:
vectorTarget = target.global_position
vectorTarget.y += Helper.getHitBoxLocation(target, Helper.POSITION.CENTER)
var transform3D : Transform3D = Transform3D()
transform3D.origin = globalPos
EventBus.projectile_shooted.emit(self, globalPos)
var targetPosition : Vector3 = target.global_position
targetPosition.y += Helper.getHitBoxLocation(target, Helper.POSITION.CENTER)
transform3D = transform3D.looking_at(targetPosition)
match mode:
Mode.SPAWN_ON_TARGET:
transform3D.origin = targetPosition
transform3D = transform3D.looking_at(globalPos)
vectorTarget = targetPosition
Mode.LOCATION:
vectorTarget = targetPosition
velocity = transform3D.origin.direction_to(vectorTarget) * speed
print(transform3D)
EventBus.projectile_shooted.emit(self, transform3D)

View file

@ -2,20 +2,20 @@
[ext_resource type="PackedScene" uid="uid://oykrff3g74eo" path="res://Projectiles/projectile.tscn" id="1_4tmpc"]
[sub_resource type="SphereShape3D" id="SphereShape3D_k24mn"]
[sub_resource type="SphereShape3D" id="SphereShape3D_2ioqj"]
radius = 0.3
[node name="Projectile" instance=ExtResource("1_4tmpc")]
type = 1
mode = 1
type = 1
speed = 3
damage = 6
maxTargets = -1
[node name="HitBox" parent="." index="2"]
collision_mask = 2
[node name="ProjectileSize" parent="HitBox" index="0"]
shape = SubResource("SphereShape3D_2ioqj")
[node name="DamageArea" parent="." index="3"]
collision_mask = 2
[node name="Node" type="CollisionShape3D" parent="DamageArea" index="0"]
shape = SubResource("SphereShape3D_k24mn")

View file

@ -4,9 +4,8 @@
[ext_resource type="Texture2D" uid="uid://b7jiyk3w5tl02" path="res://Assets/Icones/Spritesheet_Cakes_WITH_OUTLINE.png" id="2_ckawd"]
[node name="Projectile" instance=ExtResource("1_do0ca")]
type = 4
type = 3
speed = 2
damage = 3
[node name="Sprite3D" parent="." index="1"]
transform = Transform3D(1.3, 0, 0, 0, 1.3, 0, 0, 0, 1.3, 0, 0, 0)

View file

@ -4,7 +4,6 @@
[node name="Projectile" instance=ExtResource("1_d01p1")]
speed = 15
damage = 2
[node name="HitBox" parent="." index="2"]
collision_mask = 2

View file

@ -6,15 +6,12 @@
radius = 1.5
[node name="Projectile" instance=ExtResource("1_suva6")]
type = 3
type = 2
speed = 5
maxTargets = 3
[node name="HitBox" parent="." index="2"]
collision_mask = 2
[node name="DamageArea" parent="." index="3"]
collision_mask = 2
[node name="BoucingRange" type="CollisionShape3D" parent="DamageArea" index="0"]
[node name="DamageArea#BoucingRange" type="CollisionShape3D" parent="." index="0"]
shape = SubResource("SphereShape3D_k24mn")
[node name="HitBox" parent="." index="3"]
collision_mask = 2

View file

@ -0,0 +1,19 @@
[gd_scene load_steps=3 format=3 uid="uid://baixm8pfsdo3t"]
[ext_resource type="PackedScene" uid="uid://oykrff3g74eo" path="res://Projectiles/projectile.tscn" id="1_a1h27"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_a1h27"]
radius = 0.1
height = 10.0
[node name="Projectile" instance=ExtResource("1_a1h27")]
mode = 3
type = 1
maxTargets = 5
[node name="HitBox" parent="." index="2"]
collision_mask = 2
[node name="ProjectileSize" parent="HitBox" index="0"]
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, -1, 0, 1, -4.371139e-08, 0, 0, -5)
shape = SubResource("CapsuleShape3D_a1h27")

View file

@ -30,11 +30,6 @@ collision_mask = 0
shape = SubResource("SphereShape3D_dsts2")
debug_color = Color(0.926858, 0.237749, 0.335021, 0.42)
[node name="DamageArea" type="Area3D" parent="."]
[node name="EffectArea" type="Area3D" parent="."]
collision_layer = 0
collision_mask = 0
[connection signal="body_entered" from="HitBox" to="." method="onBodyCollideWithProjectile" flags=3]
[connection signal="body_exited" from="HitBox" to="." method="removeCollidingBody"]
[connection signal="body_entered" from="DamageArea" to="." method="onBodyEnteredDamageArea"]
[connection signal="body_exited" from="DamageArea" to="." method="removeTarget"]

View file

@ -1,7 +1,8 @@
[gd_scene load_steps=5 format=3 uid="uid://b1pg1hgysx3am"]
[gd_scene load_steps=6 format=3 uid="uid://b1pg1hgysx3am"]
[ext_resource type="PackedScene" uid="uid://trg7ag3dqr2l" path="res://Towers/tower.tscn" id="1_laam8"]
[ext_resource type="Texture2D" uid="uid://boxdrq4nrq7hv" path="res://Assets/Icones/flamingo.svg" id="2_sciv6"]
[ext_resource type="PackedScene" uid="uid://baixm8pfsdo3t" path="res://Projectiles/Scenes/projectile-maxence.tscn" id="3_7fox5"]
[sub_resource type="SphereShape3D" id="SphereShape3D_pw4mj"]
radius = 10.0
@ -16,6 +17,7 @@ icone = ExtResource("2_sciv6")
bio = ""
price = 200
damage = 10
projectileScene = ExtResource("3_7fox5")
towerRange = SubResource("SphereShape3D_pw4mj")
action_cooldown = 3.0
max_energy = 50.0