dev #23
271
game/storage/team_screen.gd
Normal file
271
game/storage/team_screen.gd
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
class_name TeamScreen
|
||||
extends Control
|
||||
|
||||
#page "Equipe" : liste des 6 emplacements a gauche, fiche detaillee du pokemon a droite.
|
||||
#manette d'abord (navigation par focus), clavier et souris fonctionnent aussi.
|
||||
#
|
||||
#usage :
|
||||
# var screen := TeamScreen.new()
|
||||
# canvas_layer.add_child(screen)
|
||||
# screen.setup(GameData.player_team)
|
||||
# screen.closed.connect(screen.queue_free)
|
||||
|
||||
signal closed
|
||||
|
||||
const ControllerIconTextureScript = preload("res://addons/controller_icons/objects/ControllerIconTexture.gd")
|
||||
|
||||
const STAT_LINES : Array = [
|
||||
[PokemonNature.Stat.ATTACK, "Attaque"],
|
||||
[PokemonNature.Stat.DEFENSE, "Défense"],
|
||||
[PokemonNature.Stat.SP_ATTACK, "Att. Spé."],
|
||||
[PokemonNature.Stat.SP_DEFENSE, "Déf. Spé."],
|
||||
[PokemonNature.Stat.SPEED, "Vitesse"],
|
||||
]
|
||||
|
||||
var _team : PokemonTeam
|
||||
var _slot_buttons : Array[Button] = []
|
||||
var _hp_bars : Array[ProgressBar] = []
|
||||
var _hp_fills : Array[StyleBoxFlat] = []
|
||||
|
||||
var _info_sprite : TextureRect
|
||||
var _info_label : Label
|
||||
var _empty_label : Label
|
||||
|
||||
var _prev_mouse_mode : Input.MouseMode = Input.MOUSE_MODE_VISIBLE
|
||||
var _mouse_saved : bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
process_mode = Node.PROCESS_MODE_ALWAYS #fonctionne meme si le jeu est en pause
|
||||
_build_ui()
|
||||
|
||||
_prev_mouse_mode = Input.mouse_mode
|
||||
_mouse_saved = true
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
|
||||
func _exit_tree() -> void:
|
||||
_restore_mouse()
|
||||
|
||||
func setup(team : PokemonTeam) -> void:
|
||||
if !is_node_ready(): await ready
|
||||
_team = team
|
||||
_team.team_changed.connect(_refresh)
|
||||
_refresh()
|
||||
_focus_first_member.call_deferred()
|
||||
|
||||
func _unhandled_input(event : InputEvent) -> void:
|
||||
if !visible: return
|
||||
if event.is_action_pressed(&"ui_cancel"):
|
||||
_close()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
#---------- construction de l'interface ----------
|
||||
|
||||
func _build_ui() -> void:
|
||||
set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
|
||||
var bg := ColorRect.new()
|
||||
bg.color = Color(0, 0, 0, 0.8)
|
||||
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
add_child(bg)
|
||||
|
||||
var margin := MarginContainer.new()
|
||||
margin.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
for side in ["left", "top", "right", "bottom"]:
|
||||
margin.add_theme_constant_override("margin_" + side, 40)
|
||||
add_child(margin)
|
||||
|
||||
var root := VBoxContainer.new()
|
||||
root.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
root.add_theme_constant_override("separation", 12)
|
||||
margin.add_child(root)
|
||||
|
||||
var title := Label.new()
|
||||
title.text = "Équipe"
|
||||
title.add_theme_font_size_override("font_size", 28)
|
||||
root.add_child(title)
|
||||
|
||||
var columns := HBoxContainer.new()
|
||||
columns.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
columns.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
columns.add_theme_constant_override("separation", 24)
|
||||
root.add_child(columns)
|
||||
|
||||
#liste des emplacements
|
||||
var list_col := VBoxContainer.new()
|
||||
list_col.add_theme_constant_override("separation", 6)
|
||||
columns.add_child(list_col)
|
||||
for i in PokemonTeam.MAX_SIZE:
|
||||
_add_slot(list_col, i)
|
||||
|
||||
#fiche detaillee
|
||||
var info_panel := PanelContainer.new()
|
||||
info_panel.custom_minimum_size = Vector2(520, 0) #un label avec retour a la ligne n'a pas de largeur minimale : on la garantit ici
|
||||
info_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
columns.add_child(info_panel)
|
||||
var info_margin := MarginContainer.new()
|
||||
for side in ["left", "top", "right", "bottom"]:
|
||||
info_margin.add_theme_constant_override("margin_" + side, 16)
|
||||
info_panel.add_child(info_margin)
|
||||
var info_box := HBoxContainer.new()
|
||||
info_box.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
info_box.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
info_box.add_theme_constant_override("separation", 20)
|
||||
info_margin.add_child(info_box)
|
||||
|
||||
_info_sprite = TextureRect.new()
|
||||
_info_sprite.custom_minimum_size = Vector2(160, 160)
|
||||
_info_sprite.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
_info_sprite.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
_info_sprite.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
|
||||
info_box.add_child(_info_sprite)
|
||||
|
||||
_info_label = Label.new()
|
||||
_info_label.custom_minimum_size = Vector2(300, 0) #evite que le texte s'ecrase a une lettre par ligne
|
||||
_info_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_info_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
info_box.add_child(_info_label)
|
||||
|
||||
_empty_label = Label.new()
|
||||
_empty_label.text = "Aucun Pokémon dans l'équipe pour le moment."
|
||||
_empty_label.hide()
|
||||
root.add_child(_empty_label)
|
||||
|
||||
#aide des touches
|
||||
var hints := HBoxContainer.new()
|
||||
root.add_child(hints)
|
||||
_make_hint(hints, &"ui_cancel", "Retour", _close)
|
||||
|
||||
func _add_slot(parent : Control, index : int) -> void:
|
||||
var b := Button.new()
|
||||
b.custom_minimum_size = Vector2(380, 88)
|
||||
b.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
b.expand_icon = true
|
||||
b.clip_text = true
|
||||
b.add_theme_constant_override("icon_max_width", 64)
|
||||
b.pressed.connect(_update_info.bind(index))
|
||||
b.focus_entered.connect(_update_info.bind(index))
|
||||
b.mouse_entered.connect(_on_slot_hovered.bind(index))
|
||||
parent.add_child(b)
|
||||
_slot_buttons.append(b)
|
||||
|
||||
#barre de PV en bas du bouton
|
||||
var bar := ProgressBar.new()
|
||||
bar.show_percentage = false
|
||||
bar.custom_minimum_size = Vector2(0, 8)
|
||||
bar.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
bar.anchor_left = 0.0
|
||||
bar.anchor_right = 1.0
|
||||
bar.anchor_top = 1.0
|
||||
bar.anchor_bottom = 1.0
|
||||
bar.offset_left = 8.0
|
||||
bar.offset_right = -8.0
|
||||
bar.offset_top = -14.0
|
||||
bar.offset_bottom = -6.0
|
||||
var fill := StyleBoxFlat.new()
|
||||
fill.bg_color = Color(0.3, 0.8, 0.35)
|
||||
bar.add_theme_stylebox_override("fill", fill)
|
||||
b.add_child(bar)
|
||||
_hp_bars.append(bar)
|
||||
_hp_fills.append(fill)
|
||||
|
||||
func _make_hint(parent : Control, action : StringName, text : String, callback : Callable) -> Button:
|
||||
#icone qui s'adapte au peripherique (addon controller_icons) + texte ; cliquable a la souris
|
||||
var b := Button.new()
|
||||
b.flat = true
|
||||
b.focus_mode = Control.FOCUS_NONE
|
||||
b.text = text
|
||||
var tex : Texture2D = ControllerIconTextureScript.new()
|
||||
tex.set("path", String(action))
|
||||
b.icon = tex
|
||||
b.expand_icon = true
|
||||
b.add_theme_constant_override("icon_max_width", 32)
|
||||
b.pressed.connect(callback)
|
||||
parent.add_child(b)
|
||||
return b
|
||||
|
||||
#---------- affichage ----------
|
||||
|
||||
func _refresh() -> void:
|
||||
if _team == null: return
|
||||
|
||||
for i in _slot_buttons.size():
|
||||
var b : Button = _slot_buttons[i]
|
||||
var bar : ProgressBar = _hp_bars[i]
|
||||
var p : PokemonInstance = _team.get_pokemon(i)
|
||||
|
||||
if p == null:
|
||||
b.icon = null
|
||||
b.text = "—"
|
||||
b.modulate = Color(1, 1, 1, 0.4)
|
||||
b.focus_mode = Control.FOCUS_NONE
|
||||
bar.hide()
|
||||
continue
|
||||
|
||||
var max_hp : int = p.get_max_hp()
|
||||
b.focus_mode = Control.FOCUS_ALL
|
||||
b.modulate = Color(1.0, 0.6, 0.6) if p.is_fainted() else Color.WHITE
|
||||
b.icon = p.species.get_menu_sprite(false)
|
||||
b.text = "%s Niv.%d\nPV %d/%d%s" % [p.get_display_name(), p.level, p.current_hp, max_hp, " (K.O.)" if p.is_fainted() else ""]
|
||||
|
||||
bar.show()
|
||||
bar.max_value = max_hp
|
||||
bar.value = p.current_hp
|
||||
var ratio : float = float(p.current_hp) / float(max_hp)
|
||||
_hp_fills[i].bg_color = Color(0.3, 0.8, 0.35) if ratio > 0.5 else (Color(0.95, 0.7, 0.2) if ratio > 0.2 else Color(0.9, 0.25, 0.25))
|
||||
|
||||
_empty_label.visible = _team.is_empty()
|
||||
|
||||
#garde la fiche a jour pour l'emplacement qui a le focus
|
||||
var focused : int = _focused_index()
|
||||
if focused != -1: _update_info(focused)
|
||||
elif _team.is_empty(): _update_info(-1)
|
||||
|
||||
func _update_info(index : int) -> void:
|
||||
var p : PokemonInstance = _team.get_pokemon(index) if _team and index >= 0 else null
|
||||
if p == null:
|
||||
_info_sprite.texture = null
|
||||
_info_label.text = ""
|
||||
return
|
||||
|
||||
_info_sprite.texture = p.species.get_menu_sprite(false)
|
||||
var lines : Array[String] = []
|
||||
lines.append("%s Niv.%d" % [p.get_display_name(), p.level])
|
||||
lines.append("Nature : %s" % String(PokemonNature.Nature.keys()[p.nature]).capitalize())
|
||||
if p.can_be_mounted(): lines.append("Peut être monté")
|
||||
lines.append("")
|
||||
lines.append("PV : %d / %d" % [p.current_hp, p.get_max_hp()])
|
||||
for line in STAT_LINES:
|
||||
lines.append("%s : %d" % [line[1], p.get_stat(line[0])])
|
||||
lines.append("")
|
||||
lines.append("Capacités")
|
||||
for slot in p.active_moves:
|
||||
lines.append(" %s PP %d/%d" % [slot.move.get_display_name(), slot.current_pp, slot.max_pp])
|
||||
_info_label.text = "\n".join(lines)
|
||||
|
||||
#---------- focus ----------
|
||||
|
||||
func _focused_index() -> int:
|
||||
var focus_owner : Control = get_viewport().gui_get_focus_owner()
|
||||
return _slot_buttons.find(focus_owner) if focus_owner is Button else -1
|
||||
|
||||
func _focus_first_member() -> void:
|
||||
for b in _slot_buttons:
|
||||
if b.focus_mode == Control.FOCUS_ALL:
|
||||
b.grab_focus()
|
||||
return
|
||||
|
||||
func _on_slot_hovered(index : int) -> void:
|
||||
if _slot_buttons[index].focus_mode == Control.FOCUS_ALL:
|
||||
_slot_buttons[index].grab_focus()
|
||||
|
||||
#---------- fermeture ----------
|
||||
|
||||
func _close() -> void:
|
||||
_restore_mouse()
|
||||
hide()
|
||||
closed.emit()
|
||||
|
||||
func _restore_mouse() -> void:
|
||||
if !_mouse_saved: return
|
||||
Input.mouse_mode = _prev_mouse_mode
|
||||
_mouse_saved = false
|
||||
1
game/storage/team_screen.gd.uid
Normal file
1
game/storage/team_screen.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bkncbi260q01o
|
||||
|
|
@ -5,6 +5,7 @@ extends CanvasLayer
|
|||
@export var click_sound : AudioStream
|
||||
|
||||
@onready var resume_button: Button = $PanelContainer/VBoxContainer/Resume
|
||||
@onready var team_button: Button = $PanelContainer/VBoxContainer/Team
|
||||
@onready var storage_button: Button = $PanelContainer/VBoxContainer/Storage
|
||||
@onready var save_button: Button = $PanelContainer/VBoxContainer/Save
|
||||
@onready var load_button: Button = $PanelContainer/VBoxContainer/Load
|
||||
|
|
@ -12,7 +13,8 @@ extends CanvasLayer
|
|||
@onready var animation_player: AnimationPlayer = $AnimationPlayer
|
||||
|
||||
var is_transitioning : bool = false
|
||||
var pc_screen : PCScreen = null
|
||||
var sub_screen : Control = null #page ouverte depuis le menu (equipe, PC...)
|
||||
var sub_screen_origin : Button = null #bouton qui a ouvert la page, pour lui rendre le focus
|
||||
var save_feedback_active : bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -21,7 +23,7 @@ func _ready() -> void:
|
|||
animation_player.play("RESET")
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if is_transitioning or pc_screen != null: return #le PC gere ses propres entrees
|
||||
if is_transitioning or sub_screen != null: return #une page ouverte gere ses propres entrees
|
||||
if event.is_action_pressed("pause"):
|
||||
if get_tree().paused:
|
||||
resume()
|
||||
|
|
@ -56,22 +58,37 @@ func resume() -> void:
|
|||
func _on_resume_pressed() -> void:
|
||||
resume()
|
||||
|
||||
func _on_storage_pressed() -> void:
|
||||
if is_transitioning or pc_screen != null: return
|
||||
UiAudio.play(click_sound)
|
||||
|
||||
pc_screen = PCScreen.new()
|
||||
add_child(pc_screen) #ajoute en dernier : s'affiche au-dessus du menu
|
||||
pc_screen.setup(GameData.player_team, GameData.player_storage)
|
||||
pc_screen.closed.connect(_on_pc_closed)
|
||||
menu_panel.hide() #le flou de fond reste visible derriere le PC
|
||||
#---------- pages (equipe, PC) ----------
|
||||
|
||||
func _on_pc_closed() -> void:
|
||||
if pc_screen:
|
||||
pc_screen.queue_free()
|
||||
pc_screen = null
|
||||
func _open_sub_screen(screen : Control, origin : Button) -> void:
|
||||
sub_screen = screen
|
||||
sub_screen_origin = origin
|
||||
add_child(screen) #ajoute en dernier : s'affiche au-dessus du menu
|
||||
screen.connect("closed", _on_sub_screen_closed)
|
||||
menu_panel.hide() #le flou de fond reste visible derriere la page
|
||||
|
||||
func _on_sub_screen_closed() -> void:
|
||||
if sub_screen:
|
||||
sub_screen.queue_free()
|
||||
sub_screen = null
|
||||
menu_panel.show()
|
||||
storage_button.grab_focus()
|
||||
if sub_screen_origin: sub_screen_origin.grab_focus()
|
||||
|
||||
func _on_team_pressed() -> void:
|
||||
if is_transitioning or sub_screen != null: return
|
||||
UiAudio.play(click_sound)
|
||||
var screen := TeamScreen.new()
|
||||
_open_sub_screen(screen, team_button)
|
||||
screen.setup(GameData.player_team)
|
||||
|
||||
func _on_storage_pressed() -> void:
|
||||
if is_transitioning or sub_screen != null: return
|
||||
UiAudio.play(click_sound)
|
||||
var screen := PCScreen.new()
|
||||
_open_sub_screen(screen, storage_button)
|
||||
screen.setup(GameData.player_team, GameData.player_storage)
|
||||
|
||||
#---------- sauvegarde ----------
|
||||
|
||||
func _on_save_pressed() -> void:
|
||||
UiAudio.play(click_sound)
|
||||
|
|
|
|||
|
|
@ -162,12 +162,18 @@ alignment = 1
|
|||
|
||||
[node name="Resume" type="Button" parent="PanelContainer/VBoxContainer" unique_id=1186461560]
|
||||
layout_mode = 2
|
||||
focus_neighbor_bottom = NodePath("../Storage")
|
||||
focus_neighbor_bottom = NodePath("../Team")
|
||||
text = "Resume"
|
||||
|
||||
[node name="Team" type="Button" parent="PanelContainer/VBoxContainer" unique_id=1587203946]
|
||||
layout_mode = 2
|
||||
focus_neighbor_top = NodePath("../Resume")
|
||||
focus_neighbor_bottom = NodePath("../Storage")
|
||||
text = "Pokémon Team"
|
||||
|
||||
[node name="Storage" type="Button" parent="PanelContainer/VBoxContainer" unique_id=1204857311]
|
||||
layout_mode = 2
|
||||
focus_neighbor_top = NodePath("../Resume")
|
||||
focus_neighbor_top = NodePath("../Team")
|
||||
focus_neighbor_bottom = NodePath("../Save")
|
||||
text = "Pokémon Storage"
|
||||
|
||||
|
|
@ -198,6 +204,8 @@ text = "Quit"
|
|||
libraries/ = SubResource("AnimationLibrary_2nc7s")
|
||||
|
||||
[connection signal="pressed" from="PanelContainer/VBoxContainer/Resume" to="." method="_on_resume_pressed"]
|
||||
[connection signal="pressed" from="PanelContainer/VBoxContainer/Team" to="." method="_on_team_pressed"]
|
||||
[connection signal="pressed" from="PanelContainer/VBoxContainer/Team" to="." method="_on_team_pressed"]
|
||||
[connection signal="pressed" from="PanelContainer/VBoxContainer/Storage" to="." method="_on_storage_pressed"]
|
||||
[connection signal="pressed" from="PanelContainer/VBoxContainer/Save" to="." method="_on_save_pressed"]
|
||||
[connection signal="pressed" from="PanelContainer/VBoxContainer/Load" to="." method="_on_load_pressed"]
|
||||
|
|
|
|||
Loading…
Reference in a new issue