TowerDefense/Projectiles/Projectile.gd
Varylios 868810ca82 refactor: projectiles logic
- add hitscan
 - add camera arrow key move
2025-09-20 19:58:50 +02:00

138 lines
3.6 KiB
GDScript

extends CharacterBody3D
class_name Projectile
enum Mode {
FOLLOW, ## Follow target
LOCATION, ## Go to target location
SPAWN_ON_TARGET, ## Spawn on target location
HITSCAN, ## Spawn on target location
}
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 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
var bodiesInRange : Array[Node3D]
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 firstTick:
firstTick = false
return
resolveContact()
if shouldQueueFree():
queue_free.call_deferred()
return
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
move_and_slide()
func shouldQueueFree() -> bool:
if maxTargets == 0 || !is_instance_valid(target) && mode == Mode.FOLLOW:
return true
elif mode != Mode.FOLLOW:
return isOnTarget()
elif target is Tower:
return not target.visible
return false
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 collidingBodies.is_empty() || not isOnTarget():
return
match type:
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) -> void:
if affectedTarget.has(body) || body is GameTile || maxTargets == 0:
return
if type == Type.DISABLING && body.has_method("disable"):
body.disable(amount)
elif body.has_method("take_damage"):
body.take_damage(amount)
affectedTarget.append(body)
maxTargets -= 1
func shoot(_target: Node3D, globalPos: Vector3) -> void:
target = _target
var transform3D : Transform3D = Transform3D()
transform3D.origin = 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)