refactor: add state machine on tower + disable option

This commit is contained in:
Varylios 2025-09-09 23:54:59 +02:00
parent dd7ec4229f
commit 4bd9a76d64
8 changed files with 105 additions and 62 deletions

View file

@ -85,6 +85,7 @@ viewport_path = NodePath("EnergyBar3D/SubViewport")
tower_name = "Pierre"
type = 1
icone = ExtResource("2_lcjqw")
price = 100
projectileRessource = SubResource("Resource_r52mr")
towerRange = SubResource("SphereShape3D_c55ds")
action_cooldown = 1.0

View file

@ -3,6 +3,10 @@ extends StaticBody3D
class_name Tower
enum STATE { BLUEPRINT, REST, ACTION, EXHAUSTED, DISABLED }
signal state_changed
signal energy_changed
signal changed
@ -17,26 +21,25 @@ var projectileScene : PackedScene = preload("res://Towers/Projectiles/projectile
@export_group("Base data")
@export var icone : Texture2D
@export var bio : String
@export var price : int = 100
@export var price : int
@export_group("Attack")
@export var projectileRessource : ProjectileResource
@export var towerRange : Shape3D
@export var action_cooldown : float = 1.5:
@export var action_cooldown : float = 0.3 :
set(value):
action_cooldown = clamp(value, 0.3, 999)
@export_group("Energy")
@export var max_energy : float = 100.0 :
@export var max_energy : float :
set(value):
var diff : int = value - max_energy
var diff : float = value - max_energy
max_energy = value
if not Engine.is_editor_hint() && is_node_ready():
energy += diff
energyBar.max_value = max_energy
changed.emit()
@export var energy_regen : float = 10.0
@export var energy_cost : float = 50.0
@export var energy_regen : float
@export var energy_cost : float
@export_group("Button")
@export var buttonTooltip : String
@ -47,18 +50,22 @@ var projectileScene : PackedScene = preload("res://Towers/Projectiles/projectile
@onready var energyRecoveryCooldown : Timer = $EnergyRecoveryCooldown
var state : STATE = STATE.BLUEPRINT :
set(value):
state = value
state_changed.emit()
var energy : float :
set(value):
energyBar.value = value
energy = clampf(value, 0.0, max_energy)
is_exhausted = energy < energy_cost
energy = clampf(value, 0, max_energy)
energyBar.value = energy
energy_changed.emit()
if not energy:
state = STATE.EXHAUSTED
var availableTargets : Array[Enemy]
var is_exhausted : bool = false
var is_rest : bool :
get(): return not energyRecoveryCooldown.is_stopped()
var builded : bool = false
var selectable : bool :
get():
return state != STATE.DISABLED && (state != STATE.BLUEPRINT || Game.money >= price)
@export_category("Upgrades")
@ -68,9 +75,9 @@ var builded : bool = false
func _ready() -> void:
energy = max_energy
# WARNING : Prevent .tscn file to be modified by the load of the scene in editor
if not Engine.is_editor_hint():
energy = max_energy
energyRecoveryCooldown.timeout.connect(func(): energy += energy_regen + Game.energy_boost)
collision_layer = 0
collision_mask = 0
@ -79,10 +86,24 @@ func _ready() -> void:
func _process(_delta: float) -> void:
if visible && $AttackCooldown.is_stopped() && builded:
if state == STATE.ACTION && $AttackCooldown.is_stopped():
shoot()
func changeState(newState : STATE) -> void:
if [STATE.BLUEPRINT, STATE.DISABLED].has(state):
return
match newState:
STATE.ACTION when state == STATE.REST: in_action()
STATE.ACTION when not energy: newState = STATE.EXHAUSTED
STATE.ACTION: pass
STATE.REST: resting()
_: return # NOTE Prevent changing of state
state = newState
func shoot() -> void:
var target : Enemy = choose_target()
if not target:
@ -99,20 +120,16 @@ func resting() -> void:
visible = false
collision_layer = 0
collision_mask = 0
if builded:
toggleConnection(false)
energyRecoveryCooldown.start()
toggleConnection(false)
energyRecoveryCooldown.start()
func in_action() -> void:
visible = true
if builded:
toggleConnection(true)
collision_layer = 0b100
collision_mask = 0b100
energyRecoveryCooldown.stop()
collision_layer = 0b100
collision_mask = 0b100
toggleConnection(true)
energyRecoveryCooldown.stop()
func choose_target() -> Enemy:
@ -125,13 +142,14 @@ func choose_target() -> Enemy:
func build() -> bool:
if builded || not Game.spendMoney(price):
if state != STATE.BLUEPRINT || not Game.spendMoney(price):
return false
sprite.modulate = "ffffffff"
$EnergyBar3D.visible = true
builded = true
$PriceTag.visible = false
state = STATE.ACTION
in_action()
changed.emit()
return true
@ -155,3 +173,10 @@ func toggleConnection(activate : bool) -> void:
$Range.body_entered.disconnect(onBodyEntered)
$Range.body_exited.disconnect(onBodyExited)
availableTargets.clear()
func disable(duration : float) -> void:
state = STATE.DISABLED
resting()
await get_tree().create_timer(duration).timeout
state = STATE.REST

View file

@ -18,6 +18,7 @@ func _ready() -> void:
#$AnimationPlayer.play("arrow_bobbing")
EventBus.mouse_entered_gui.connect(onMouseEnteredGui)
EventBus.mouse_exited_gui.connect(onMouseExitedGui)
Game.allowed_tower_has_change.connect(connectTowerSignals)
func _process(_delta: float) -> void:
@ -42,6 +43,10 @@ func _process(_delta: float) -> void:
else:
EventBus.tower_selected.emit(Tower.TYPE.NONE)
if Input.is_action_just_pressed("test"):
if tower:
tower.disable(3)
func _input(event: InputEvent) -> void:
if event is InputEventKey:
@ -71,7 +76,7 @@ func handle_player_controls() -> Node3D:
selection_icon.visible = true
global_position = collider.global_position + Vector3(0.0, 0.21, 0.0)
if selected_tower && not selected_tower.builded:
if selected_tower && selected_tower.state == Tower.STATE.BLUEPRINT:
selected_tower.sprite.modulate = "ff4545c8" # If the tower can't be placed he is red
selection_icon.visible = false # If we are placing a tower, hide the selector model
if collider is GameTile && isTileFree(collider):
@ -84,7 +89,7 @@ func placeTower() -> void:
if not selected_tower:
return
if not selected_tower.builded:
if selected_tower.state == Tower.STATE.BLUEPRINT:
if not selected_tower.build():
return
buildedTower += 1
@ -105,11 +110,11 @@ func moveTower(tower: Tower, toPosition: Vector3) -> void:
usedLocations.erase(tower.global_position.round())
if toPosition == Vector3.INF:
tower.resting()
tower.changeState(Tower.STATE.REST)
else:
usedLocations.set(toPosition.round(), tower)
tower.global_position = toPosition
tower.in_action()
tower.changeState(Tower.STATE.ACTION)
var inAction : int = usedLocations.size()
EventBus.team_in_action_changed.emit(inAction)
@ -118,22 +123,22 @@ func moveTower(tower: Tower, toPosition: Vector3) -> void:
func onTowerSelect(towerType: Tower.TYPE):
# Hide current not builded tower
if selected_tower && not selected_tower.builded:
if selected_tower && selected_tower.state == Tower.STATE.BLUEPRINT:
selected_tower.visible = false
if selected_tower && selected_tower.type == towerType || towerType == Tower.TYPE.NONE:
if selected_tower && not selected_tower.builded:
if selected_tower && selected_tower.state == Tower.STATE.BLUEPRINT:
remove_child(selected_tower)
selected_tower = null
else:
selected_tower = Game.towers.get(towerType)
if not selected_tower.builded:
if selected_tower.state == Tower.STATE.BLUEPRINT:
selected_tower.visible = true
add_child(selected_tower)
func selectTower(towerType: Tower.TYPE, force : bool = false) -> void:
if towerType && (force || not selected_tower || selected_tower.builded):
if towerType && (force || not selected_tower || selected_tower.state):
EventBus.tower_selected.emit(towerType)
@ -162,3 +167,14 @@ func onMouseEnteredGui() -> void:
func onMouseExitedGui() -> void:
is_on_gui = false
func connectTowerSignals() -> void:
for tower : Tower in Game.towers.values():
tower.state_changed.connect(onTowerStateChange.bind(tower))
func onTowerStateChange(tower : Tower) -> void:
if tower.state == Tower.STATE.DISABLED && tower == selected_tower:
EventBus.tower_selected.emit(Tower.TYPE.NONE)
moveTower(tower, Vector3.INF)

View file

@ -86,7 +86,3 @@ metadata/_custom_type_script = "uid://blnmjxmusrsa7"
[node name="PriceTag" type="Label3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
billboard = 2
[connection signal="body_entered" from="Range" to="." method="_on_range_body_entered"]
[connection signal="body_exited" from="Range" to="." method="_on_range_body_exited"]
[connection signal="timeout" from="AttackCooldown" to="." method="_on_cooldown_timeout"]

View file

@ -12,7 +12,7 @@ func _ready() -> void:
func onCubeSelected() -> void:
visible = true
$PanelContainer2.visible = false
$InfoContainer.visible = false
createTowerUpgradeButtons(Game.upgrades)
@ -26,7 +26,7 @@ func onTowerSelected(towerType : Tower.TYPE) -> void:
visible = false
return
$PanelContainer2.visible = true
$InfoContainer.visible = true
tower = Game.towers.get(towerType)
tower.energy_changed.connect(onEnergyChange)
tower.changed.connect(onTowerChange)
@ -48,12 +48,11 @@ func onTowerChange() -> void:
# TODO Check for better UI to display it
#%TowerBio.text = tower.bio
if tower.builded && not %UpgradeContainer.visible:
createTowerUpgradeButtons(tower.upgrades)
createTowerUpgradeButtons(tower.upgrades)
func createTowerUpgradeButtons(upgrades : Array) -> void:
if tower && not tower.builded:
if tower && tower.state == Tower.STATE.BLUEPRINT:
%UpgradeContainer.visible = false
return

View file

@ -185,30 +185,30 @@ grow_vertical = 0
theme_override_constants/separation = 0
script = ExtResource("9_3lugd")
[node name="PanelContainer2" type="PanelContainer" parent="InfoPanel"]
[node name="InfoContainer" type="PanelContainer" parent="InfoPanel"]
custom_minimum_size = Vector2(200, 0)
layout_mode = 2
mouse_filter = 1
theme_override_styles/panel = SubResource("StyleBoxFlat_h4fn5")
[node name="MarginContainer" type="MarginContainer" parent="InfoPanel/PanelContainer2"]
[node name="MarginContainer" type="MarginContainer" parent="InfoPanel/InfoContainer"]
layout_mode = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="VBoxContainer" type="VBoxContainer" parent="InfoPanel/PanelContainer2/MarginContainer"]
[node name="VBoxContainer" type="VBoxContainer" parent="InfoPanel/InfoContainer/MarginContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="InfoPanel/PanelContainer2/MarginContainer/VBoxContainer"]
[node name="HBoxContainer" type="HBoxContainer" parent="InfoPanel/InfoContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 20
[node name="VBoxContainer" type="VBoxContainer" parent="InfoPanel/PanelContainer2/MarginContainer/VBoxContainer/HBoxContainer"]
[node name="VBoxContainer" type="VBoxContainer" parent="InfoPanel/InfoContainer/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
[node name="TowerName" type="Label" parent="InfoPanel/PanelContainer2/MarginContainer/VBoxContainer/HBoxContainer/VBoxContainer"]
[node name="TowerName" type="Label" parent="InfoPanel/InfoContainer/MarginContainer/VBoxContainer/HBoxContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 1
@ -219,7 +219,7 @@ text = "Name"
horizontal_alignment = 2
vertical_alignment = 2
[node name="TowerEnergy" type="Label" parent="InfoPanel/PanelContainer2/MarginContainer/VBoxContainer/HBoxContainer/VBoxContainer"]
[node name="TowerEnergy" type="Label" parent="InfoPanel/InfoContainer/MarginContainer/VBoxContainer/HBoxContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Le nombre de héros en action sur le terrain !"
@ -228,14 +228,14 @@ theme_override_colors/font_color = Color(0.2, 0.2, 0.2, 1)
text = "0/0"
horizontal_alignment = 2
[node name="TowerIcon" type="TextureRect" parent="InfoPanel/PanelContainer2/MarginContainer/VBoxContainer/HBoxContainer"]
[node name="TowerIcon" type="TextureRect" parent="InfoPanel/InfoContainer/MarginContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
theme = ExtResource("5_wpcnu")
texture = ExtResource("10_parkk")
expand_mode = 3
[node name="TowerDamage" type="Label" parent="InfoPanel/PanelContainer2/MarginContainer/VBoxContainer"]
[node name="TowerDamage" type="Label" parent="InfoPanel/InfoContainer/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Le nombre de héros disponible dans le cube !"
@ -244,7 +244,7 @@ theme_override_colors/font_color = Color(0.2, 0.2, 0.2, 1)
text = "0"
horizontal_alignment = 2
[node name="TowerCooldown" type="Label" parent="InfoPanel/PanelContainer2/MarginContainer/VBoxContainer"]
[node name="TowerCooldown" type="Label" parent="InfoPanel/InfoContainer/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Le nombre de héros en action sur le terrain !"
@ -253,7 +253,7 @@ theme_override_colors/font_color = Color(0.2, 0.2, 0.2, 1)
text = "0"
horizontal_alignment = 2
[node name="TowerBio" type="Label" parent="InfoPanel/PanelContainer2/MarginContainer/VBoxContainer"]
[node name="TowerBio" type="Label" parent="InfoPanel/InfoContainer/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 2

View file

@ -20,7 +20,8 @@ func towerLinked(value) -> void:
add_theme_stylebox_override("pressed", GameStyleBoxFlat.createWithColor(GameColor.COLOR.SELECTED))
tower.changed.connect(onTowerChanged)
tower.energy_changed.connect(func(): $EnergyBar.value = tower.energy)
Game.money_changed.connect(onMoneyChanged)
tower.state_changed.connect(updateDisabled)
Game.money_changed.connect(updateDisabled)
EventBus.tower_selected.connect(func(_type): set_pressed_no_signal(_type == tower.type))
toggled.connect(func(state): EventBus.tower_selected.emit(tower.type if state else Tower.TYPE.NONE))
tooltip_text = tower.name
@ -29,11 +30,11 @@ func towerLinked(value) -> void:
func onTowerChanged() -> void :
disabled = not tower.builded && Game.money < tower.price
disabled = not tower.selectable
$EnergyBar.max_value = tower.max_energy
$EnergyBar.visible = tower.builded
$LeFond.visible = not tower.builded
$EnergyBar.visible = tower.selectable
$LeFond.visible = tower.state == Tower.STATE.BLUEPRINT
func onMoneyChanged() -> void:
disabled = not tower.builded && Game.money < tower.price
func updateDisabled() -> void:
disabled = not tower.selectable

View file

@ -101,6 +101,11 @@ pause_game={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":80,"key_label":0,"unicode":112,"location":0,"echo":false,"script":null)
]
}
test={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":3,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
[layer_names]