Skip to content

Commit

Permalink
Optimize tower targeting logic
Browse files Browse the repository at this point in the history
Call Utils.get_units_in_range() less frequently
Old: it was called on every Tower.update(), so 30 times per second
New: it gets called only before tower attacks, so ~1 time per second
Add validity check to get_current_target() since target list is now
\ updated less frequently and Autocast may request a target while
\ target list contains an invalid unit
This change improves performance for bonus waves
  • Loading branch information
Kvel2D committed Oct 4, 2024
1 parent 94db90a commit 8bd7d64
Showing 1 changed file with 38 additions and 28 deletions.
66 changes: 38 additions & 28 deletions src/towers/tower.gd
Original file line number Diff line number Diff line change
Expand Up @@ -224,26 +224,6 @@ func _ready():
func update(delta: float):
super.update(delta)

# NOTE: need to calculate creeps_in_range here one time
# and reuse it because get_units_in_range is an expensive
# f-n.
#
# NOTE: need to extend attack range by "tower radius".
# This is how it works in the original game.
var attack_range: float = get_range() + Constants.RANGE_CHECK_BONUS_FOR_TOWERS
var creeps_in_range: Array = Utils.get_units_in_range(self, _attack_target_type, get_position_wc3_2d(), attack_range)

# NOTE: need to consider tower to be in combat if there
# are *any* creeps in attack range, even if the tower is
# not able to attack for whatever reason. This is so that
# towers use their offensive abilities and items as
# expected.
_is_in_combat = !creeps_in_range.is_empty()

var attack_enabled: bool = get_attack_enabled()
if !attack_enabled:
return

if is_stunned():
return

Expand All @@ -266,11 +246,22 @@ func update(delta: float):
_current_attack_cooldown -= delta

if _current_attack_cooldown <= 0.0:
var attack_success: bool = _try_to_attack(creeps_in_range)
# NOTE: need to update target list even if tower is
# not attacking, to have targets for offensive
# abilities and items.
_update_target_list()

var attack_enabled: bool = get_attack_enabled()

var should_reset_attack_cooldown: bool
if attack_enabled:
should_reset_attack_cooldown = _try_to_attack()
else:
should_reset_attack_cooldown = true

# NOTE: important to add, not set! So that if game is
# lagging, all of the attacks fire instead of skipping.
if attack_success:
if should_reset_attack_cooldown:
_current_attack_cooldown += attack_speed


Expand Down Expand Up @@ -466,9 +457,7 @@ func _make_projectile(from_pos_base: Vector3, target: Unit) -> Projectile:
return projectile


func _try_to_attack(creeps_in_range: Array) -> bool:
_update_target_list(creeps_in_range)

func _try_to_attack() -> bool:
# NOTE: have to do this weird stuff instead of just
# iterating over target list because attacks can modify
# the target list by killing creeps (and not just the
Expand Down Expand Up @@ -611,8 +600,22 @@ func _attack_target(target: Unit, target_is_first: bool) -> Unit:
return target


func _update_target_list(creeps_in_range_arg: Array):
var creeps_in_range: Array = creeps_in_range_arg.duplicate()
func _update_target_list():
# NOTE: need to extend attack range by "tower radius".
# This is how it works in the original game.
var attack_range: float = get_range() + Constants.RANGE_CHECK_BONUS_FOR_TOWERS

# NOTE: get_units_in_range() is one of the most expensive
# calls in the gameplay logic. Call it here once right
# before attacking and no more.
var creeps_in_range: Array = Utils.get_units_in_range(self, _attack_target_type, get_position_wc3_2d(), attack_range)

# NOTE: need to consider tower to be in combat if there
# are *any* creeps in attack range, even if the tower is
# not able to attack for whatever reason. This is so that
# towers use their offensive abilities and items as
# expected.
_is_in_combat = !creeps_in_range.is_empty()

# Remove targets that have become invalid. Targets can
# become invalid by moving out of range, becoming
Expand Down Expand Up @@ -1028,7 +1031,14 @@ func get_ability_info_list() -> Array[AbilityInfo]:

func get_current_target() -> Unit:
if !_target_list.is_empty():
return _target_list.front()
var target: Unit = _target_list.front()

# NOTE: need to check validity here because target may
# be in list but become invalid because it just died
if Utils.unit_is_valid(target):
return target
else:
return null
else:
return null

Expand Down

0 comments on commit 8bd7d64

Please sign in to comment.