chore: improve UX + tower hitbox

- add tower hitbox
 - change selector icons
 - Cube selectable on map
This commit is contained in:
Varylios 2025-09-13 16:35:20 +02:00
parent d4c62243d8
commit ef08992673
11 changed files with 119 additions and 46 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://7v37noapccnc"
path="res://.godot/imported/IconsFlat-32.png-cbb60368a49eb8516061cb2084e62d5b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Assets/Icones/IconsFlat-32.png"
dest_files=["res://.godot/imported/IconsFlat-32.png-cbb60368a49eb8516061cb2084e62d5b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View file

@ -2,8 +2,8 @@
extends Node extends Node
@warning_ignore_start("unused_signal") @warning_ignore_start("unused_signal")
## [b]Emitter[/b] : [param gui.gd], [br] ## [b]Emitter[/b] : [param gui.gd], [TowerManager][br]
## [b]Subscriber[/b] : [TowerManager] ## [b]Subscriber[/b] : [param InfoPanel.gd], [GuiButton], [param gui.gd]
signal cube_selected signal cube_selected

View file

@ -80,3 +80,18 @@ static func showConfirmPopup(
confirmPopup.label.text = text confirmPopup.label.text = text
confirmPopup.confirmed.connect(confirmCallback) confirmPopup.confirmed.connect(confirmCallback)
confirmPopup.canceled.connect(cancelCallback) confirmPopup.canceled.connect(cancelCallback)
static func getTopOfHitBox(body : CollisionObject3D) -> float:
if body is GameTile:
return .2
if body.has_node("CollisionShape3D"):
var shape : Shape3D = body.shape_owner_get_shape(0, 0)
var transform : Transform3D = body.shape_owner_get_transform(0)
if shape is CapsuleShape3D:
return shape.height + shape.radius + transform.origin.y
if shape is SphereShape3D:
return shape.radius + transform.origin.y
return 0

View file

@ -3,7 +3,6 @@
[ext_resource type="Script" uid="uid://qqid42jkpkmv" path="res://Levels/Scripts/WorldManager.gd" id="1_tk0a6"] [ext_resource type="Script" uid="uid://qqid42jkpkmv" path="res://Levels/Scripts/WorldManager.gd" id="1_tk0a6"]
[ext_resource type="Script" uid="uid://caf3yamufmhd4" path="res://Towers/TowerManager.gd" id="2_7pixp"] [ext_resource type="Script" uid="uid://caf3yamufmhd4" path="res://Towers/TowerManager.gd" id="2_7pixp"]
[ext_resource type="Script" uid="uid://wdyg06i1eb6b" path="res://Levels/Scripts/Camera.gd" id="2_c1rgm"] [ext_resource type="Script" uid="uid://wdyg06i1eb6b" path="res://Levels/Scripts/Camera.gd" id="2_c1rgm"]
[ext_resource type="Texture2D" uid="uid://o83munu8dibp" path="res://Assets/Icones/kenney_game_icons_vector.svg" id="3_6dp1o"]
[ext_resource type="PackedScene" uid="uid://p6a6rb7sgeqd" path="res://UI/gui.tscn" id="6_ebgat"] [ext_resource type="PackedScene" uid="uid://p6a6rb7sgeqd" path="res://UI/gui.tscn" id="6_ebgat"]
[ext_resource type="AudioStream" uid="uid://bdcq7jxg08sih" path="res://Assets/Audio/SFX/Voiceover/final_round.ogg" id="6_ul70d"] [ext_resource type="AudioStream" uid="uid://bdcq7jxg08sih" path="res://Assets/Audio/SFX/Voiceover/final_round.ogg" id="6_ul70d"]
[ext_resource type="AudioStream" uid="uid://c3x3krwcm4bbu" path="res://Assets/Audio/SFX/Voiceover/prepare_yourself.ogg" id="7_6yqi7"] [ext_resource type="AudioStream" uid="uid://c3x3krwcm4bbu" path="res://Assets/Audio/SFX/Voiceover/prepare_yourself.ogg" id="7_6yqi7"]
@ -15,6 +14,9 @@ _limits = [-70.0, -20.0, 2.0, 10.0]
_data = [Vector2(2, -20), 0.0, -12.5, 0, 1, Vector2(6, -70), 0.0, 0.0, 0, 0, Vector2(10, -70), 0.0, 0.0, 1, 0] _data = [Vector2(2, -20), 0.0, -12.5, 0, 1, Vector2(6, -70), 0.0, 0.0, 0, 0, Vector2(10, -70), 0.0, 0.0, 1, 0]
point_count = 3 point_count = 3
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_jh6jd"]
load_path = "res://.godot/imported/IconsFlat-32.png-cbb60368a49eb8516061cb2084e62d5b.ctex"
[sub_resource type="Animation" id="Animation_oyb16"] [sub_resource type="Animation" id="Animation_oyb16"]
length = 0.001 length = 0.001
tracks/0/type = "value" tracks/0/type = "value"
@ -78,20 +80,18 @@ curve = SubResource("Curve_c1rgm")
[node name="TowerManager" type="Node3D" parent="."] [node name="TowerManager" type="Node3D" parent="."]
process_mode = 3 process_mode = 3
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.73017, 0.728414, 0)
script = ExtResource("2_7pixp") script = ExtResource("2_7pixp")
[node name="Sprite3DSelection" type="Sprite3D" parent="TowerManager"] [node name="Sprite3DSelection" type="Sprite3D" parent="TowerManager"]
modulate = Color(0.966071, 0.695469, 0, 1) modulate = Color(0.966071, 0.695469, 0, 1)
pixel_size = 0.03 pixel_size = 0.1
axis = 1 axis = 1
billboard = 2
alpha_cut = 1 alpha_cut = 1
texture = ExtResource("3_6dp1o") texture = SubResource("CompressedTexture2D_jh6jd")
hframes = 15 hframes = 10
vframes = 7 vframes = 10
frame = 44 frame = 5
region_rect = Rect2(703, 96, 21, 30) region_rect = Rect2(1925, 1925, 0, 0)
[node name="AnimationPlayer" type="AnimationPlayer" parent="TowerManager"] [node name="AnimationPlayer" type="AnimationPlayer" parent="TowerManager"]
libraries = { libraries = {

View file

@ -27,15 +27,27 @@ func _process(_delta: float) -> void:
var tower : Tower var tower : Tower
if collider is GameTile: if collider is GameTile:
tower = usedLocations.get(collider.global_position.round()) tower = usedLocations.get(collider.global_position.round())
selection_icon.frame = 5
selection_icon.axis = Vector3.Axis.AXIS_Y
selection_icon.pixel_size = .03
else:
selection_icon.frame = 68
selection_icon.pixel_size = .02 if collider is Tower else .01
selection_icon.axis = Vector3.Axis.AXIS_Z
if collider is Tower:
tower = collider
if Input.is_action_just_pressed("build"): if Input.is_action_just_pressed("build"):
if not collider is GameTile || tower == selected_tower: if tower == selected_tower && selected_tower:
return return
if isTileFree(collider): if isTileAndFree(collider):
placeTower() placeTower()
elif tower: elif tower:
selectTower(tower.type) selectTower(tower.type)
elif collider is TheCube:
selected_tower = null
EventBus.cube_selected.emit()
if Input.is_action_just_pressed("rest"): if Input.is_action_just_pressed("rest"):
if tower: if tower:
@ -45,7 +57,7 @@ func _process(_delta: float) -> void:
if Input.is_action_just_pressed("test"): if Input.is_action_just_pressed("test"):
if tower: if tower:
tower.disable(3) tower.disable(2)
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
@ -53,7 +65,7 @@ func _input(event: InputEvent) -> void:
handleTowerShortCuts(event) handleTowerShortCuts(event)
func handle_player_controls() -> Node3D: func handle_player_controls() -> CollisionObject3D:
#If the player has the mouse on the GUI, player can't place tower #If the player has the mouse on the GUI, player can't place tower
if is_on_gui: if is_on_gui:
return return
@ -71,15 +83,16 @@ func handle_player_controls() -> Node3D:
visible = false visible = false
return null return null
var collider : Node3D = ray_result.get("collider") var collider : CollisionObject3D = ray_result.get("collider")
visible = true visible = true
selection_icon.visible = true selection_icon.visible = true
global_position = collider.global_position + Vector3(0.0, 0.21, 0.0) global_position = collider.global_position
global_position.y += Helper.getTopOfHitBox(collider) + .01
if selected_tower && selected_tower.state == Tower.STATE.BLUEPRINT: if selected_tower && selected_tower.state == Tower.STATE.BLUEPRINT:
selected_tower.sprite.modulate = "ff4545c8" # If the tower can't be placed he is red 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 selection_icon.visible = false # If we are placing a tower, hide the selector model
if collider is GameTile && isTileFree(collider): if isTileAndFree(collider):
selected_tower.sprite.modulate = "61ff45c8" # If the tower can be placed he is green selected_tower.sprite.modulate = "61ff45c8" # If the tower can be placed he is green
return collider return collider
@ -100,9 +113,10 @@ func placeTower() -> void:
moveTower(selected_tower, global_position) moveTower(selected_tower, global_position)
func isTileFree(tile: GameTile) -> bool: func isTileAndFree(collider: CollisionObject3D) -> bool:
return not usedLocations.has(tile.global_position.round()) \ return collider is GameTile && collider.type == GameTile.TYPE.TOWER \
&& tile.type == GameTile.TYPE.TOWER && not usedLocations.has(collider.global_position.round())
## Set [param toPosition] with [Vector3.INF] to make the tower rest ## Set [param toPosition] with [Vector3.INF] to make the tower rest

View file

@ -1,10 +1,14 @@
[gd_scene load_steps=8 format=3 uid="uid://trg7ag3dqr2l"] [gd_scene load_steps=9 format=3 uid="uid://trg7ag3dqr2l"]
[ext_resource type="Script" uid="uid://8kpvuurr5h5n" path="res://Towers/Tower.gd" id="1_egfuc"] [ext_resource type="Script" uid="uid://8kpvuurr5h5n" path="res://Towers/Tower.gd" id="1_egfuc"]
[ext_resource type="Texture2D" uid="uid://bn6ikwol6x8r0" path="res://Assets/Characters/Male1.png" id="2_egfuc"] [ext_resource type="Texture2D" uid="uid://bn6ikwol6x8r0" path="res://Assets/Characters/Male1.png" id="2_egfuc"]
[ext_resource type="Texture2D" uid="uid://uptdcefxlv4c" path="res://Assets/Icones/ppdf_bio_image_placeholder_2.png" id="2_mnaic"] [ext_resource type="Texture2D" uid="uid://uptdcefxlv4c" path="res://Assets/Icones/ppdf_bio_image_placeholder_2.png" id="2_mnaic"]
[ext_resource type="Script" uid="uid://blnmjxmusrsa7" path="res://UI/GameStyleBoxFlat.gd" id="8_5dr1v"] [ext_resource type="Script" uid="uid://blnmjxmusrsa7" path="res://UI/GameStyleBoxFlat.gd" id="8_5dr1v"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_ynmsb"]
radius = 0.15
height = 0.5
[sub_resource type="ViewportTexture" id="ViewportTexture_jv31o"] [sub_resource type="ViewportTexture" id="ViewportTexture_jv31o"]
viewport_path = NodePath("EnergyBar3D/SubViewport") viewport_path = NodePath("EnergyBar3D/SubViewport")
@ -28,6 +32,8 @@ icone = ExtResource("2_mnaic")
bio = "Aime se promener dans l'herbe et manger des framboises. Sa petite bouille la rend trop mignonne." bio = "Aime se promener dans l'herbe et manger des framboises. Sa petite bouille la rend trop mignonne."
[node name="CollisionShape3D" type="CollisionShape3D" parent="."] [node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0)
shape = SubResource("CapsuleShape3D_ynmsb")
[node name="Range" type="Area3D" parent="."] [node name="Range" type="Area3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.4, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.4, 0)

View file

@ -5,18 +5,17 @@ const BUTTON_QTY = 4
const guiButton : PackedScene = preload("res://UI/gui_button.tscn") const guiButton : PackedScene = preload("res://UI/gui_button.tscn")
@onready var buttonContainer = %ButtonContainer @onready var buttonContainer : GridContainer = %ButtonContainer
@onready var cubeIntegrity = %CubeIntegrity @onready var cubeIntegrity : ProgressBar = %CubeIntegrity
@onready var cubeBtn = %CubeBtn @onready var cubeBtn : GuiButton = %CubeBtn
var waveCooldown : float
func _ready() -> void: func _ready() -> void:
EventBus.team_in_rest_changed.connect(func(count): %LabelTowerInCube.text = "Zzz : %d" % count) EventBus.team_in_rest_changed.connect(func(count): %LabelTowerInCube.text = "Zzz : %d" % count)
EventBus.team_in_action_changed.connect(func(count): %LabelTowerOnTerrain.text = " In action : %d" % count) EventBus.team_in_action_changed.connect(func(count): %LabelTowerOnTerrain.text = " In action : %d" % count)
EventBus.wave_has_change.connect(onWaveChange) EventBus.wave_has_change.connect(onWaveChange)
EventBus.tower_selected.connect(func(_type): EventBus.cube_selected.connect(cubeBtn.set_pressed_no_signal.bind(true))
cubeBtn.set_pressed_no_signal(cubeBtn.button_pressed && _type == Tower.TYPE.NONE)) EventBus.tower_selected.connect(func(_type): cubeBtn.set_pressed_no_signal(false))
Game.allowed_tower_has_change.connect(addTowerButtonNodes) Game.allowed_tower_has_change.connect(addTowerButtonNodes)
Game.cube_integrity_changed.connect(func(): cubeIntegrity.value = Game.health) Game.cube_integrity_changed.connect(func(): cubeIntegrity.value = Game.health)
@ -25,7 +24,6 @@ func _ready() -> void:
Game.money_changed.connect(func(): %LabelMoney.text = "%d" % Game.money) Game.money_changed.connect(func(): %LabelMoney.text = "%d" % Game.money)
%NextWaveBtn.pressed.connect(EventBus.lauch_next_wave.emit) %NextWaveBtn.pressed.connect(EventBus.lauch_next_wave.emit)
$WaveCooldown/Timer.timeout.connect(updateWaveCooldownLabel)
%QuitLevelBtn.pressed.connect(Helper.showConfirmPopup.bind("Quit level ?", self, Game.quitLevel)) %QuitLevelBtn.pressed.connect(Helper.showConfirmPopup.bind("Quit level ?", self, Game.quitLevel))
%QuitGameBtn.pressed.connect(Helper.showConfirmPopup.bind("Quit game ?", self, Game.quitGame)) %QuitGameBtn.pressed.connect(Helper.showConfirmPopup.bind("Quit game ?", self, Game.quitGame))
cubeBtn.toggled.connect(onCubeBtnPressed) cubeBtn.toggled.connect(onCubeBtnPressed)
@ -47,22 +45,23 @@ func onCubeBtnPressed(state : bool) -> void:
EventBus.cube_selected.emit() EventBus.cube_selected.emit()
func onWaveChange(waveNumber : int, timeRemaining : float) -> void: func onWaveChange(waveNumber : int, timeRemaining : float) -> void:
%WaveNumber.text = "Wave N°%d" % waveNumber %WaveNumber.text = "Wave N°%d" % waveNumber
waveCooldown = timeRemaining updateWaveCooldownLabel(timeRemaining)
$WaveCooldown/Timer.start()
func updateWaveCooldownLabel() -> void: func updateWaveCooldownLabel(waveCooldown : float) -> void:
waveCooldown -= $WaveCooldown/Timer.wait_time const tickDuration : float = .1
$WaveCooldown.visible = true
while waveCooldown > 0:
if waveCooldown > 5: if waveCooldown > 5:
$WaveCooldown.text = "\n\nNext wave : %3.0f" % waveCooldown $WaveCooldown.text = "\n\nNext wave : %3.0f" % waveCooldown
elif waveCooldown < 0:
$WaveCooldown/Timer.stop()
$WaveCooldown.text = ""
else: else:
$WaveCooldown.text = "\n\nNext wave : %3.1f" % waveCooldown $WaveCooldown.text = "\n\nNext wave : %3.1f" % waveCooldown
waveCooldown -= tickDuration
await get_tree().create_timer(tickDuration).timeout
$WaveCooldown.visible = false
func addTowerButtonNodes() -> void: func addTowerButtonNodes() -> void:

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=15 format=3 uid="uid://p6a6rb7sgeqd"] [gd_scene load_steps=16 format=3 uid="uid://p6a6rb7sgeqd"]
[ext_resource type="Script" uid="uid://bhylcok1l6eke" path="res://UI/gui.gd" id="2_sac4j"] [ext_resource type="Script" uid="uid://bhylcok1l6eke" path="res://UI/gui.gd" id="2_sac4j"]
[ext_resource type="Script" uid="uid://blnmjxmusrsa7" path="res://UI/GameStyleBoxFlat.gd" id="4_h4fn5"] [ext_resource type="Script" uid="uid://blnmjxmusrsa7" path="res://UI/GameStyleBoxFlat.gd" id="4_h4fn5"]
@ -27,6 +27,12 @@ script = ExtResource("4_h4fn5")
color = 4 color = 4
metadata/_custom_type_script = "uid://blnmjxmusrsa7" metadata/_custom_type_script = "uid://blnmjxmusrsa7"
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_parkk"]
bg_color = Color(0.721569, 0.600196, 0.355686, 1)
script = ExtResource("4_h4fn5")
color = 2
metadata/_custom_type_script = "uid://blnmjxmusrsa7"
[node name="GUI" type="Control"] [node name="GUI" type="Control"]
process_mode = 3 process_mode = 3
layout_mode = 3 layout_mode = 3
@ -51,9 +57,6 @@ grow_horizontal = 2
theme_override_colors/font_color = Color(0.2, 0.2, 0.2, 1) theme_override_colors/font_color = Color(0.2, 0.2, 0.2, 1)
theme_override_font_sizes/font_size = 40 theme_override_font_sizes/font_size = 40
[node name="Timer" type="Timer" parent="WaveCooldown"]
wait_time = 0.1
[node name="TowerButtonPanel" type="HBoxContainer" parent="."] [node name="TowerButtonPanel" type="HBoxContainer" parent="."]
layout_mode = 1 layout_mode = 1
anchors_preset = 3 anchors_preset = 3
@ -155,6 +158,7 @@ columns = 4
[node name="CubeBtn" parent="TowerButtonPanel/ControlPanelBase/MarginContainer/ButtonContainer" instance=ExtResource("7_parkk")] [node name="CubeBtn" parent="TowerButtonPanel/ControlPanelBase/MarginContainer/ButtonContainer" instance=ExtResource("7_parkk")]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
theme_override_styles/pressed = SubResource("StyleBoxFlat_parkk")
toggle_mode = true toggle_mode = true
texture = ExtResource("7_fffne") texture = ExtResource("7_fffne")

View file

@ -23,6 +23,7 @@ func towerLinked(value) -> void:
tower.state_changed.connect(updateDisabled) tower.state_changed.connect(updateDisabled)
Game.money_changed.connect(updateDisabled) Game.money_changed.connect(updateDisabled)
EventBus.tower_selected.connect(func(_type): set_pressed_no_signal(_type == tower.type)) EventBus.tower_selected.connect(func(_type): set_pressed_no_signal(_type == tower.type))
EventBus.cube_selected.connect(func(): set_pressed_no_signal(false))
toggled.connect(func(state): EventBus.tower_selected.emit(tower.type if state else Tower.TYPE.NONE)) toggled.connect(func(state): EventBus.tower_selected.emit(tower.type if state else Tower.TYPE.NONE))
tooltip_text = tower.name tooltip_text = tower.name
texture = tower.icone texture = tower.icone

View file

@ -55,5 +55,5 @@ func getNextValue(oldValue, baseValue, scaleType : SCALE_TYPE):
match scaleType: match scaleType:
SCALE_TYPE.LINEAR: return oldValue + baseValue SCALE_TYPE.LINEAR: return oldValue + baseValue
_: _:
push_warning("Upgrade scale type not defined !") push_error("Upgrade scale type not defined !")
return oldValue return oldValue