Compare commits
3 commits
edaeb4d1f1
...
dfa5a48888
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfa5a48888 | ||
|
|
3eb9cdf1ec | ||
|
|
26360d0e6a |
80
game/battle/battle_actor.gd
Normal file
80
game/battle/battle_actor.gd
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
class_name BattleActor
|
||||
extends Node3D
|
||||
|
||||
#un pokemon visible dans le monde pendant le combat (un par emplacement), avec ses petites animations.
|
||||
|
||||
var slot_id : int = -1
|
||||
var pokemon : PokemonInstance
|
||||
var model : Node3D
|
||||
var home : Vector3 = Vector3.ZERO
|
||||
|
||||
var _marker : Label3D
|
||||
|
||||
func setup(p_pokemon : PokemonInstance, p_slot_id : int) -> void:
|
||||
process_mode = Node.PROCESS_MODE_ALWAYS #le monde est en pause pendant le combat, pas les acteurs
|
||||
pokemon = p_pokemon
|
||||
slot_id = p_slot_id
|
||||
model = SentPokemon.create_model(p_pokemon)
|
||||
add_child(model)
|
||||
|
||||
#marqueur "▼" au-dessus de la tete (pokemon qui agit / cible en cours de choix)
|
||||
_marker = Label3D.new()
|
||||
_marker.text = "▼"
|
||||
_marker.font_size = 96
|
||||
_marker.pixel_size = 0.01
|
||||
_marker.billboard = BaseMaterial3D.BILLBOARD_ENABLED
|
||||
_marker.no_depth_test = true
|
||||
_marker.modulate = Color(1.0, 0.85, 0.2)
|
||||
_marker.position = Vector3(0, 2.3, 0)
|
||||
_marker.visible = false
|
||||
add_child(_marker)
|
||||
|
||||
func place(position_world : Vector3, look_at_pos : Vector3) -> void:
|
||||
global_position = position_world
|
||||
home = position_world
|
||||
var flat : Vector3 = Vector3(look_at_pos.x, position_world.y, look_at_pos.z)
|
||||
if flat.distance_to(position_world) > 0.01:
|
||||
look_at(flat, Vector3.UP)
|
||||
if SentPokemon.MODEL_FACES_PLUS_Z: rotate_y(PI) #look_at oriente -Z vers la cible
|
||||
|
||||
func set_highlight(on : bool) -> void:
|
||||
if _marker: _marker.visible = on
|
||||
|
||||
#---------- animations (awaitables) ----------
|
||||
|
||||
func play_enter() -> void:
|
||||
scale = Vector3.ONE * 0.01
|
||||
var tween := create_tween()
|
||||
tween.tween_property(self, "scale", Vector3.ONE, 0.3).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
|
||||
await tween.finished
|
||||
|
||||
func play_leave() -> void:
|
||||
var tween := create_tween()
|
||||
tween.tween_property(self, "scale", Vector3.ONE * 0.01, 0.25).set_ease(Tween.EASE_IN)
|
||||
await tween.finished
|
||||
|
||||
func play_attack(toward : Vector3) -> void:
|
||||
var dir : Vector3 = toward - home
|
||||
dir.y = 0.0
|
||||
if dir.length() < 0.01: return
|
||||
var lunge : Vector3 = home + dir.normalized() * 1.2
|
||||
var tween := create_tween()
|
||||
tween.tween_property(self, "global_position", lunge, 0.12).set_ease(Tween.EASE_OUT)
|
||||
tween.tween_property(self, "global_position", home, 0.2).set_ease(Tween.EASE_IN_OUT)
|
||||
await tween.finished
|
||||
|
||||
func play_hit() -> void:
|
||||
var tween := create_tween()
|
||||
for i in 3:
|
||||
tween.tween_property(self, "global_position", home + Vector3(0.15, 0, 0), 0.04)
|
||||
tween.tween_property(self, "global_position", home + Vector3(-0.15, 0, 0), 0.04)
|
||||
tween.tween_property(self, "global_position", home, 0.04)
|
||||
await tween.finished
|
||||
|
||||
func play_faint() -> void:
|
||||
var tween := create_tween()
|
||||
tween.set_parallel(true)
|
||||
tween.tween_property(self, "global_position", home + Vector3(0, -0.6, 0), 0.5)
|
||||
tween.tween_property(self, "scale", Vector3.ONE * 0.01, 0.5)
|
||||
await tween.finished
|
||||
visible = false
|
||||
1
game/battle/battle_actor.gd.uid
Normal file
1
game/battle/battle_actor.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cftfencf611ul
|
||||
33
game/battle/battle_encounter.gd
Normal file
33
game/battle/battle_encounter.gd
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
class_name BattleEncounter
|
||||
extends Resource
|
||||
|
||||
#description d'une rencontre : qui est en face et sous quel format.
|
||||
#a assigner dans l'inspecteur d'un AimTarget (champ "Encounter").
|
||||
#exemples :
|
||||
# - 1v1 sauvage : wild_pokemon = [1 entree]
|
||||
# - 1 contre plusieurs sauvages : wild_pokemon = [3 entrees]
|
||||
# - dresseur : trainer = un TrainerData, can_flee = false
|
||||
# - combat double : player_slots = 2 et trainer_slots = 2
|
||||
# - plusieurs contre 1 (boss) : player_slots = 3 et une seule entree adverse
|
||||
|
||||
@export_group("Adversaires")
|
||||
@export var trainer : TrainerData
|
||||
@export_range(1, 3) var trainer_slots : int = 1 #pokemon que le dresseur envoie en meme temps
|
||||
@export var wild_pokemon : Array[TrainerPartyEntry] = [] #chaque entree = un adversaire (1 emplacement)
|
||||
|
||||
@export_group("Joueur")
|
||||
@export_range(1, 3) var player_slots : int = 1 #pokemon du joueur envoyes en meme temps
|
||||
|
||||
@export_group("Regles")
|
||||
@export var can_flee : bool = true #mets false pour un combat de dresseur
|
||||
@export var remove_world_actor_on_win : bool = true #fait disparaitre le pokemon/dresseur du monde apres la victoire
|
||||
|
||||
func build_participants() -> Array[BattleParticipant]:
|
||||
var result : Array[BattleParticipant] = []
|
||||
if trainer != null:
|
||||
result.append(BattleSetup.trainer_participant(trainer, trainer_slots))
|
||||
for entry in wild_pokemon:
|
||||
if entry == null: continue
|
||||
var pokemon : PokemonInstance = entry.create_instance()
|
||||
if pokemon != null: result.append(BattleSetup.wild_participant(pokemon))
|
||||
return result
|
||||
1
game/battle/battle_encounter.gd.uid
Normal file
1
game/battle/battle_encounter.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://u2t6dm1ee3se
|
||||
116
game/battle/battle_launcher.gd
Normal file
116
game/battle/battle_launcher.gd
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
class_name BattleLauncher
|
||||
extends Node
|
||||
|
||||
#lance les combats dans le monde. A ajouter UNE FOIS a ta scene de jeu (node "BattleLauncher").
|
||||
#
|
||||
#- ecoute GameData.battle_requested (lancer d'un pokemon sur un AimTarget qui a un "Encounter")
|
||||
#- start_encounter() sert aussi a un dresseur qui defie le joueur (dialogue, zone de detection...)
|
||||
#
|
||||
#pendant le combat : le monde est en pause (GameData.in_battle = true), une camera de combat prend le relais,
|
||||
#puis tout est restitue a la fin.
|
||||
|
||||
signal battle_started
|
||||
signal battle_ended(outcome : int)
|
||||
|
||||
@export var heal_team_on_defeat : bool = true #provisoire : evite de bloquer le joueur si toute son equipe est K.O.
|
||||
|
||||
var _running : bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
GameData.battle_requested.connect(_on_battle_requested)
|
||||
|
||||
func _on_battle_requested(sent : SentPokemon, target : Node) -> void:
|
||||
if _running:
|
||||
push_warning("[BattleLauncher] un combat est deja en cours")
|
||||
return
|
||||
if !(target is AimTarget):
|
||||
push_warning("[BattleLauncher] la cible n'est pas un AimTarget")
|
||||
return
|
||||
var aim_target : AimTarget = target
|
||||
if aim_target.encounter == null:
|
||||
push_warning("[BattleLauncher] cet AimTarget n'a pas d'Encounter (inspecteur : Battle > Encounter)")
|
||||
return
|
||||
await start_encounter(aim_target.encounter, aim_target.global_position, sent, aim_target.world_actor, aim_target)
|
||||
|
||||
func start_encounter(encounter : BattleEncounter, enemy_pos : Vector3, sent : SentPokemon = null, world_actor : Node3D = null, target : AimTarget = null) -> Battle.Outcome:
|
||||
#sent = pokemon lance par le joueur (il passe en premier) ; null si le joueur est defie : l'ordre de l'equipe s'applique
|
||||
if _running or encounter == null:
|
||||
push_warning("[BattleLauncher] start_encounter ignore (combat deja en cours ou Encounter vide)")
|
||||
return Battle.Outcome.NONE
|
||||
|
||||
#camera et acteurs doivent etre dans le monde que le joueur voit (SubViewport eventuel du jeu)
|
||||
var world_viewport : Viewport = get_viewport()
|
||||
var world_root : Node = world_viewport if world_viewport is SubViewport else get_tree().current_scene
|
||||
var game_camera : Camera3D = world_viewport.get_camera_3d()
|
||||
var player_pos : Vector3 = sent.global_position if (sent != null and is_instance_valid(sent)) else _find_player_position()
|
||||
|
||||
var ui := BattleUI.new()
|
||||
get_tree().root.add_child(ui) #directement sous la fenetre : toujours affiche par-dessus tout
|
||||
|
||||
var setup := BattleSetup.new()
|
||||
setup.can_flee = encounter.can_flee
|
||||
var lead : PokemonInstance = sent.pokemon if (sent != null and is_instance_valid(sent)) else null
|
||||
setup.add(BattleSetup.player_participant(GameData.player_team, encounter.player_slots, lead, PlayerBattleController.new(ui)))
|
||||
for participant in encounter.build_participants():
|
||||
setup.add(participant)
|
||||
|
||||
if !setup.is_valid():
|
||||
var details : PackedStringArray = PackedStringArray()
|
||||
for participant in setup.participants:
|
||||
details.append("%s (camp %d, %d pokemon aptes)" % [participant.name, participant.side, participant.team.get_able_pokemon().size()])
|
||||
push_warning("[BattleLauncher] combat invalide : chaque camp doit avoir un participant avec un pokemon apte. Participants : " + ", ".join(details) + ". Verifie que l'Encounter contient bien une espece (wild_pokemon / trainer) et que ton equipe a un pokemon non K.O.")
|
||||
ui.queue_free()
|
||||
return Battle.Outcome.NONE
|
||||
|
||||
_running = true
|
||||
GameData.in_battle = true
|
||||
get_tree().paused = true
|
||||
battle_started.emit()
|
||||
|
||||
#les pokemon sortis et l'adversaire du monde sont caches : les acteurs du combat les remplacent
|
||||
var hidden : Array[Node3D] = []
|
||||
for node in get_tree().get_nodes_in_group(SentPokemon.GROUP):
|
||||
if node is Node3D and node.visible:
|
||||
node.visible = false
|
||||
hidden.append(node)
|
||||
if world_actor != null and world_actor.visible:
|
||||
world_actor.visible = false
|
||||
hidden.append(world_actor)
|
||||
|
||||
var presenter := BattleWorldPresenter.new(ui)
|
||||
var battle := Battle.new(setup, presenter)
|
||||
presenter.setup(battle, player_pos, enemy_pos, world_root, game_camera)
|
||||
var outcome : Battle.Outcome = await battle.run()
|
||||
await presenter.cleanup()
|
||||
ui.queue_free()
|
||||
|
||||
#retour au monde
|
||||
for node in hidden:
|
||||
if is_instance_valid(node): node.visible = true
|
||||
for node in get_tree().get_nodes_in_group(SentPokemon.GROUP):
|
||||
if node is SentPokemon and node.pokemon != null and node.pokemon.is_fainted():
|
||||
node.recall() #un pokemon K.O. est rappele
|
||||
|
||||
if world_actor != null and is_instance_valid(world_actor):
|
||||
if outcome == Battle.Outcome.PLAYER_WON and encounter.remove_world_actor_on_win:
|
||||
world_actor.queue_free()
|
||||
|
||||
if outcome == Battle.Outcome.PLAYER_LOST and heal_team_on_defeat:
|
||||
GameData.player_team.heal_all()
|
||||
|
||||
GameData.in_battle = false
|
||||
get_tree().paused = false
|
||||
_running = false
|
||||
|
||||
if target != null and is_instance_valid(target): target.battle_finished.emit(outcome)
|
||||
GameData.battle_finished.emit(outcome, target)
|
||||
battle_ended.emit(outcome)
|
||||
return outcome
|
||||
|
||||
func _find_player_position() -> Vector3:
|
||||
#ajoute ton PlayerCharacter au groupe "player" (sinon la camera du jeu sert de repere)
|
||||
var player : Node3D = get_tree().get_first_node_in_group("player") as Node3D
|
||||
if player != null: return player.global_position
|
||||
var cam : Camera3D = get_viewport().get_camera_3d()
|
||||
return cam.global_position if cam != null else Vector3.ZERO
|
||||
1
game/battle/battle_launcher.gd.uid
Normal file
1
game/battle/battle_launcher.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bx0htbw7ygkww
|
||||
348
game/battle/battle_ui.gd
Normal file
348
game/battle/battle_ui.gd
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
class_name BattleUI
|
||||
extends CanvasLayer
|
||||
|
||||
#interface du combat : boite de texte, panneaux de PV, menus du joueur.
|
||||
#manette d'abord (navigation par focus : A valide, B revient), mais chaque element est un Button
|
||||
#cliquable a la souris ; le survol deplace le focus.
|
||||
#
|
||||
#menus : Combat / Pokemon / Fuite -> choix de la capacite -> choix de la cible (si besoin)
|
||||
|
||||
signal highlight_requested(slot_ids : Array) #pokemon a marquer d'un "▼" dans le monde
|
||||
signal _menu_chosen(index : int)
|
||||
|
||||
const ControllerIconTextureScript = preload("res://addons/controller_icons/objects/ControllerIconTexture.gd")
|
||||
|
||||
var _text_panel : PanelContainer
|
||||
var _text_label : Label
|
||||
var _menu_panel : PanelContainer
|
||||
var _menu_title : Label
|
||||
var _menu_box : VBoxContainer
|
||||
var _player_panels : VBoxContainer
|
||||
var _enemy_panels : VBoxContainer
|
||||
var _panels : Dictionary = {} #id d'emplacement -> {"panel", "bar", "fill", "hp_label"}
|
||||
|
||||
var _menu_active : bool = false
|
||||
var _menu_allow_back : bool = false
|
||||
var _focus_callback : Callable = Callable()
|
||||
var _skip_requested : bool = false
|
||||
|
||||
var _prev_mouse_mode : Input.MouseMode = Input.MOUSE_MODE_VISIBLE
|
||||
var _mouse_saved : bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
layer = 50
|
||||
process_mode = Node.PROCESS_MODE_ALWAYS #le monde est en pause pendant le combat
|
||||
_build_ui()
|
||||
|
||||
_prev_mouse_mode = Input.mouse_mode
|
||||
_mouse_saved = true
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if _mouse_saved:
|
||||
Input.mouse_mode = _prev_mouse_mode
|
||||
_mouse_saved = false
|
||||
|
||||
func _unhandled_input(event : InputEvent) -> void:
|
||||
if _menu_active:
|
||||
if _menu_allow_back and event.is_action_pressed(&"ui_cancel"):
|
||||
_menu_chosen.emit(-1)
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
#hors menu : A ou clic gauche passe les messages plus vite
|
||||
if event.is_action_pressed(&"ui_accept") or (event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT):
|
||||
_skip_requested = true
|
||||
|
||||
#---------- construction ----------
|
||||
|
||||
func _build_ui() -> void:
|
||||
var root := Control.new()
|
||||
root.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
root.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(root)
|
||||
|
||||
#PV du joueur : bas gauche, au-dessus de la boite de texte
|
||||
var player_margin := _corner(root, Control.PRESET_BOTTOM_LEFT, {"left": 32, "bottom": 170})
|
||||
_player_panels = VBoxContainer.new()
|
||||
_player_panels.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
player_margin.add_child(_player_panels)
|
||||
|
||||
#PV des adversaires : haut droite
|
||||
var enemy_margin := _corner(root, Control.PRESET_TOP_RIGHT, {"right": 32, "top": 32})
|
||||
_enemy_panels = VBoxContainer.new()
|
||||
_enemy_panels.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
enemy_margin.add_child(_enemy_panels)
|
||||
|
||||
#boite de texte : bas gauche
|
||||
var text_margin := _corner(root, Control.PRESET_BOTTOM_LEFT, {"left": 32, "bottom": 32})
|
||||
_text_panel = PanelContainer.new()
|
||||
_text_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_text_panel.hide()
|
||||
text_margin.add_child(_text_panel)
|
||||
_text_label = Label.new()
|
||||
_text_label.custom_minimum_size = Vector2(620, 80)
|
||||
_text_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_text_label.add_theme_font_size_override("font_size", 22)
|
||||
_text_panel.add_child(_text_label)
|
||||
|
||||
#menus : bas droite
|
||||
var menu_margin := _corner(root, Control.PRESET_BOTTOM_RIGHT, {"right": 32, "bottom": 32})
|
||||
_menu_panel = PanelContainer.new()
|
||||
_menu_panel.hide()
|
||||
menu_margin.add_child(_menu_panel)
|
||||
var menu_column := VBoxContainer.new()
|
||||
menu_column.add_theme_constant_override("separation", 6)
|
||||
_menu_panel.add_child(menu_column)
|
||||
_menu_title = Label.new()
|
||||
_menu_title.add_theme_font_size_override("font_size", 20)
|
||||
menu_column.add_child(_menu_title)
|
||||
_menu_box = VBoxContainer.new()
|
||||
_menu_box.add_theme_constant_override("separation", 4)
|
||||
menu_column.add_child(_menu_box)
|
||||
|
||||
func _corner(parent : Control, preset : Control.LayoutPreset, margins : Dictionary) -> MarginContainer:
|
||||
#IMPORTANT : les ancres se definissent AVANT add_child. Une fois dans l'arbre, set_anchors_preset
|
||||
#conserve la position actuelle (0, 0) en ajustant les offsets : le conteneur resterait en haut a gauche
|
||||
#et grandirait hors de l'ecran.
|
||||
var m := MarginContainer.new()
|
||||
m.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
m.set_anchors_preset(preset)
|
||||
if preset == Control.PRESET_BOTTOM_LEFT or preset == Control.PRESET_BOTTOM_RIGHT:
|
||||
m.grow_vertical = Control.GROW_DIRECTION_BEGIN
|
||||
if preset == Control.PRESET_TOP_RIGHT or preset == Control.PRESET_BOTTOM_RIGHT:
|
||||
m.grow_horizontal = Control.GROW_DIRECTION_BEGIN
|
||||
for side in margins:
|
||||
m.add_theme_constant_override("margin_" + side, margins[side])
|
||||
parent.add_child(m)
|
||||
return m
|
||||
|
||||
#---------- messages ----------
|
||||
|
||||
func show_message(text : String, duration : float = 1.1) -> void:
|
||||
_text_label.text = text
|
||||
_text_panel.show()
|
||||
await _wait(duration)
|
||||
|
||||
func hide_message() -> void:
|
||||
_text_panel.hide()
|
||||
|
||||
func _wait(duration : float) -> void:
|
||||
_skip_requested = false
|
||||
var start : int = Time.get_ticks_msec()
|
||||
while float(Time.get_ticks_msec() - start) < duration * 1000.0 and !_skip_requested:
|
||||
await get_tree().process_frame
|
||||
|
||||
#---------- panneaux de PV ----------
|
||||
|
||||
func set_slot_pokemon(slot : BattleSlot) -> void:
|
||||
remove_slot_panel(slot.id)
|
||||
var mon : BattleMon = slot.mon
|
||||
if mon == null: return
|
||||
|
||||
var panel := PanelContainer.new()
|
||||
panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
var box := VBoxContainer.new()
|
||||
panel.add_child(box)
|
||||
|
||||
var name_label := Label.new()
|
||||
name_label.text = "%s Lv.%d" % [mon.display_name(), mon.pokemon.level]
|
||||
name_label.add_theme_font_size_override("font_size", 20)
|
||||
box.add_child(name_label)
|
||||
|
||||
var bar := ProgressBar.new()
|
||||
bar.show_percentage = false
|
||||
bar.custom_minimum_size = Vector2(240, 12)
|
||||
var track := StyleBoxFlat.new()
|
||||
track.bg_color = Color(1, 1, 1, 0.25)
|
||||
track.set_corner_radius_all(6)
|
||||
var fill := StyleBoxFlat.new()
|
||||
fill.set_corner_radius_all(6)
|
||||
bar.add_theme_stylebox_override("background", track)
|
||||
bar.add_theme_stylebox_override("fill", fill)
|
||||
box.add_child(bar)
|
||||
|
||||
var hp_label : Label = null
|
||||
if slot.side == 0: #les PV chiffres ne sont montres que pour le joueur
|
||||
hp_label = Label.new()
|
||||
box.add_child(hp_label)
|
||||
|
||||
(_player_panels if slot.side == 0 else _enemy_panels).add_child(panel)
|
||||
var entry : Dictionary = {"panel": panel, "bar": bar, "fill": fill, "hp_label": hp_label}
|
||||
_panels[slot.id] = entry
|
||||
_set_hp(entry, mon.pokemon.current_hp, mon.pokemon.get_max_hp(), false)
|
||||
|
||||
func animate_hp(slot_id : int, hp : int, max_hp : int) -> void:
|
||||
var entry : Dictionary = _panels.get(slot_id, {})
|
||||
if entry.is_empty(): return
|
||||
_set_hp(entry, hp, max_hp, true)
|
||||
await get_tree().create_timer(0.55, true).timeout
|
||||
|
||||
func _set_hp(entry : Dictionary, hp : int, max_hp : int, animated : bool) -> void:
|
||||
var bar : ProgressBar = entry["bar"]
|
||||
bar.max_value = max_hp
|
||||
if animated:
|
||||
var tween := create_tween()
|
||||
tween.tween_property(bar, "value", float(hp), 0.5)
|
||||
else:
|
||||
bar.value = hp
|
||||
|
||||
var ratio : float = float(hp) / float(max_hp) if max_hp > 0 else 0.0
|
||||
var fill : StyleBoxFlat = entry["fill"]
|
||||
fill.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))
|
||||
var hp_label : Label = entry["hp_label"]
|
||||
if hp_label != null: hp_label.text = "%d/%d" % [hp, max_hp]
|
||||
|
||||
func remove_slot_panel(slot_id : int) -> void:
|
||||
var entry : Dictionary = _panels.get(slot_id, {})
|
||||
if entry.is_empty(): return
|
||||
(entry["panel"] as Node).queue_free()
|
||||
_panels.erase(slot_id)
|
||||
|
||||
func clear_panels() -> void:
|
||||
for id in _panels.keys():
|
||||
remove_slot_panel(id)
|
||||
|
||||
#---------- menus ----------
|
||||
|
||||
func ask(title : String, options : Array, allow_back : bool, focus_callback : Callable = Callable()) -> int:
|
||||
#options : Array de {"text": String, "disabled": bool}. Renvoie l'index choisi, ou -1 pour "Retour".
|
||||
_clear_menu()
|
||||
_text_panel.hide()
|
||||
_menu_title.text = title
|
||||
|
||||
for i in options.size():
|
||||
var b := Button.new()
|
||||
b.text = options[i]["text"]
|
||||
b.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
b.custom_minimum_size = Vector2(320, 44)
|
||||
var disabled : bool = options[i].get("disabled", false)
|
||||
b.disabled = disabled
|
||||
if disabled:
|
||||
b.focus_mode = Control.FOCUS_NONE
|
||||
else:
|
||||
b.pressed.connect(_on_option_pressed.bind(i))
|
||||
b.focus_entered.connect(_on_option_focused.bind(i))
|
||||
b.mouse_entered.connect(b.grab_focus) #la souris deplace le focus
|
||||
_menu_box.add_child(b)
|
||||
|
||||
if allow_back:
|
||||
var back := Button.new()
|
||||
back.text = "Retour"
|
||||
back.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
back.custom_minimum_size = Vector2(320, 44)
|
||||
back.expand_icon = true
|
||||
back.add_theme_constant_override("icon_max_width", 28)
|
||||
var icon : Texture2D = ControllerIconTextureScript.new() #icone de la touche "annuler" (suit le peripherique)
|
||||
icon.set("path", "ui_cancel")
|
||||
back.icon = icon
|
||||
back.pressed.connect(_menu_chosen.emit.bind(-1))
|
||||
back.mouse_entered.connect(back.grab_focus)
|
||||
_menu_box.add_child(back)
|
||||
|
||||
_menu_active = true
|
||||
_menu_allow_back = allow_back
|
||||
_focus_callback = focus_callback
|
||||
_menu_panel.show()
|
||||
_focus_first_enabled.call_deferred()
|
||||
|
||||
var index : int = await _menu_chosen
|
||||
|
||||
_menu_active = false
|
||||
_focus_callback = Callable()
|
||||
_menu_panel.hide()
|
||||
_clear_menu()
|
||||
return index
|
||||
|
||||
func _clear_menu() -> void:
|
||||
for child in _menu_box.get_children():
|
||||
_menu_box.remove_child(child)
|
||||
child.queue_free()
|
||||
|
||||
func _focus_first_enabled() -> void:
|
||||
for child in _menu_box.get_children():
|
||||
if child is Button and child.focus_mode == Control.FOCUS_ALL:
|
||||
child.grab_focus()
|
||||
return
|
||||
|
||||
func _on_option_pressed(index : int) -> void:
|
||||
_menu_chosen.emit(index)
|
||||
|
||||
func _on_option_focused(index : int) -> void:
|
||||
if _focus_callback.is_valid(): _focus_callback.call(index)
|
||||
|
||||
#---------- choix du joueur ----------
|
||||
|
||||
func request_action(battle : Battle, slot : BattleSlot) -> BattleAction:
|
||||
highlight_requested.emit([slot.id])
|
||||
var action : BattleAction = null
|
||||
while action == null:
|
||||
var options : Array = [
|
||||
{"text": "Combat"},
|
||||
{"text": "Pokémon"},
|
||||
{"text": "Fuite", "disabled": !battle.setup.can_flee},
|
||||
]
|
||||
var choice : int = await ask("Que doit faire %s ?" % slot.mon.display_name(), options, false)
|
||||
match choice:
|
||||
0: action = await _choose_move(battle, slot)
|
||||
1:
|
||||
var pokemon : PokemonInstance = await _choose_pokemon(battle, slot, true, "Quel Pokémon envoyer ?")
|
||||
if pokemon != null: action = BattleAction.switch_pokemon(slot, pokemon)
|
||||
2: action = BattleAction.flee(slot)
|
||||
highlight_requested.emit([slot.id])
|
||||
highlight_requested.emit([])
|
||||
return action
|
||||
|
||||
func request_replacement(battle : Battle, slot : BattleSlot) -> PokemonInstance:
|
||||
#pokemon K.O. : le joueur doit en envoyer un autre (pas de retour possible)
|
||||
highlight_requested.emit([])
|
||||
var pokemon : PokemonInstance = await _choose_pokemon(battle, slot, false, "%s est K.O. ! Qui envoyer ?" % slot.mon.display_name())
|
||||
return pokemon
|
||||
|
||||
func _choose_move(battle : Battle, slot : BattleSlot) -> BattleAction:
|
||||
var moves : Array[MoveSlot] = slot.mon.pokemon.active_moves
|
||||
if battle.get_usable_moves(slot.mon).is_empty():
|
||||
return BattleAction.use_struggle(slot) #plus aucune capacite utilisable : Lutte, sans menu
|
||||
|
||||
var options : Array = []
|
||||
for ms in moves:
|
||||
options.append({"text": "%s PP %d/%d" % [ms.move.get_display_name(), ms.current_pp, ms.max_pp], "disabled": !ms.has_pp()})
|
||||
|
||||
while true:
|
||||
var index : int = await ask("Capacité", options, true)
|
||||
if index < 0: return null
|
||||
var move_slot : MoveSlot = moves[index]
|
||||
|
||||
var target : BattleSlot = null
|
||||
if TargetResolver.needs_selection(battle, slot, move_slot.move):
|
||||
target = await _choose_target(battle, slot, move_slot.move)
|
||||
if target == null: continue #retour au choix de la capacite
|
||||
return BattleAction.use_move(slot, move_slot, target)
|
||||
return null
|
||||
|
||||
func _choose_target(battle : Battle, slot : BattleSlot, move : MoveData) -> BattleSlot:
|
||||
var targets : Array[BattleSlot] = TargetResolver.get_selectable_targets(battle, slot, move)
|
||||
var options : Array = []
|
||||
for t in targets:
|
||||
options.append({"text": "%s (%s)" % [t.mon.display_name(), "adversaire" if t.side != slot.side else "allié"]})
|
||||
|
||||
var on_focus := func(i : int) -> void: highlight_requested.emit([targets[i].id])
|
||||
var index : int = await ask("Cible", options, true, on_focus)
|
||||
highlight_requested.emit([slot.id])
|
||||
return targets[index] if index >= 0 else null
|
||||
|
||||
func _choose_pokemon(battle : Battle, slot : BattleSlot, allow_back : bool, title : String) -> PokemonInstance:
|
||||
var members : Array[PokemonInstance] = slot.participant.team.members
|
||||
var available : Array[PokemonInstance] = battle.get_replacement_options(slot)
|
||||
|
||||
var options : Array = []
|
||||
for p in members:
|
||||
var note : String = ""
|
||||
if p.is_fainted(): note = " (K.O.)"
|
||||
elif !available.has(p): note = " (au combat)"
|
||||
options.append({
|
||||
"text": "%s Lv.%d PV %d/%d%s" % [p.get_display_name(), p.level, p.current_hp, p.get_max_hp(), note],
|
||||
"disabled": !available.has(p),
|
||||
})
|
||||
|
||||
var index : int = await ask(title, options, allow_back)
|
||||
return members[index] if index >= 0 else null
|
||||
1
game/battle/battle_ui.gd.uid
Normal file
1
game/battle/battle_ui.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dekhp3tad1xga
|
||||
189
game/battle/battle_world_presenter.gd
Normal file
189
game/battle/battle_world_presenter.gd
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
class_name BattleWorldPresenter
|
||||
extends BattlePresenter
|
||||
|
||||
#met le combat en scene dans le monde 3D :
|
||||
#- place les pokemon (un BattleActor par emplacement) face a face, a l'endroit de la rencontre
|
||||
#- deplace une camera de combat qui cadre l'ensemble
|
||||
#- joue les animations et affiche les messages / PV via BattleUI
|
||||
#
|
||||
#le monde (hors combat) est en pause : tout ce qui est ici fonctionne en mode "toujours".
|
||||
|
||||
var ui : BattleUI
|
||||
var battle : Battle
|
||||
|
||||
#formation
|
||||
var side_distance : float = 7.0 #distance entre les deux camps
|
||||
var lateral_spacing : float = 2.6 #ecart entre deux pokemon d'un meme camp
|
||||
var ground_mask : int = 1
|
||||
|
||||
#camera
|
||||
var camera_back : float = 4.5 #recul derriere le camp du joueur
|
||||
var camera_height : float = 3.4
|
||||
var camera_move_time : float = 0.9
|
||||
|
||||
var _actors : Dictionary = {} #id d'emplacement -> BattleActor
|
||||
var _formation : Dictionary = {} #id d'emplacement -> position au sol
|
||||
var _center : Vector3 = Vector3.ZERO
|
||||
var _axis : Vector3 = Vector3.FORWARD #direction camp joueur -> camp adverse
|
||||
var _camera : Camera3D = null
|
||||
var _previous_camera : Camera3D = null
|
||||
var _world_root : Node = null #ou ajouter camera et acteurs : le monde que le joueur voit
|
||||
|
||||
func _init(p_ui : BattleUI) -> void:
|
||||
ui = p_ui
|
||||
ui.highlight_requested.connect(_on_highlight_requested)
|
||||
|
||||
#---------- mise en place ----------
|
||||
|
||||
func setup(p_battle : Battle, player_pos : Vector3, enemy_pos : Vector3, p_world_root : Node, p_game_camera : Camera3D) -> void:
|
||||
battle = p_battle
|
||||
_world_root = p_world_root
|
||||
_previous_camera = p_game_camera
|
||||
|
||||
_axis = enemy_pos - player_pos
|
||||
_axis.y = 0.0
|
||||
if _axis.length() < 0.1:
|
||||
#pas de direction evidente : on utilise la direction de la camera du jeu
|
||||
var cam : Camera3D = _previous_camera
|
||||
_axis = -cam.global_basis.z if cam else Vector3.FORWARD
|
||||
_axis.y = 0.0
|
||||
_axis = _axis.normalized()
|
||||
_center = (player_pos + enemy_pos) * 0.5
|
||||
|
||||
var lateral : Vector3 = _axis.cross(Vector3.UP).normalized()
|
||||
|
||||
var count_per_side : Dictionary = {0: 0, 1: 0}
|
||||
for slot in battle.slots:
|
||||
count_per_side[slot.side] += 1
|
||||
|
||||
for slot in battle.slots:
|
||||
var n : int = count_per_side[slot.side]
|
||||
var offset : float = (slot.index - (n - 1) * 0.5) * lateral_spacing
|
||||
var side_sign : float = -1.0 if slot.side == 0 else 1.0
|
||||
var pos : Vector3 = _center + _axis * side_sign * side_distance * 0.5 + lateral * offset
|
||||
_formation[slot.id] = _snap_to_ground(pos)
|
||||
|
||||
_start_camera(maxi(count_per_side[0], count_per_side[1]))
|
||||
|
||||
func _start_camera(max_slots : int) -> void:
|
||||
|
||||
var back : float = camera_back + max_slots * 0.8
|
||||
var cam_pos : Vector3 = _center - _axis * (side_distance * 0.5 + back) + Vector3.UP * (camera_height + max_slots * 0.3)
|
||||
var look_target : Vector3 = _center + Vector3.UP * 1.0
|
||||
var final_transform := Transform3D(Basis.looking_at(look_target - cam_pos, Vector3.UP), cam_pos)
|
||||
|
||||
_camera = Camera3D.new()
|
||||
_world_root.add_child(_camera)
|
||||
_camera.global_transform = _previous_camera.global_transform if _previous_camera else final_transform
|
||||
if _previous_camera: _camera.fov = _previous_camera.fov
|
||||
_camera.make_current()
|
||||
|
||||
var tween : Tween = ui.create_tween()
|
||||
tween.tween_property(_camera, "global_transform", final_transform, camera_move_time).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
||||
|
||||
func cleanup() -> void:
|
||||
#fin du combat : ecran propre, camera du jeu reprise
|
||||
ui.highlight_requested.emit([])
|
||||
ui.hide_message()
|
||||
ui.clear_panels()
|
||||
for actor in _actors.values():
|
||||
if is_instance_valid(actor): (actor as Node).queue_free()
|
||||
_actors.clear()
|
||||
|
||||
if _camera != null:
|
||||
if _previous_camera != null and is_instance_valid(_previous_camera):
|
||||
var tween : Tween = ui.create_tween()
|
||||
tween.tween_property(_camera, "global_transform", _previous_camera.global_transform, 0.6).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
||||
await tween.finished
|
||||
_previous_camera.make_current()
|
||||
_camera.queue_free()
|
||||
_camera = null
|
||||
|
||||
#---------- evenements ----------
|
||||
|
||||
func present(event : BattleEvent) -> void:
|
||||
match event.type:
|
||||
BattleEvent.Type.BATTLE_START:
|
||||
await ui.show_message(event.describe(), 1.0)
|
||||
|
||||
BattleEvent.Type.SENT_OUT:
|
||||
await _show_actor(event.source_slot)
|
||||
await ui.show_message(event.describe(), 0.9)
|
||||
|
||||
BattleEvent.Type.WITHDRAWN:
|
||||
await ui.show_message(event.describe(), 0.7)
|
||||
await _remove_actor(event.source_slot)
|
||||
|
||||
BattleEvent.Type.MOVE_USED:
|
||||
var actor : BattleActor = _actors.get(event.source_slot)
|
||||
if actor: actor.play_attack(_opponents_center(event.source_slot)) #en parallele du message
|
||||
await ui.show_message(event.describe(), 1.0)
|
||||
|
||||
BattleEvent.Type.DAMAGE:
|
||||
var actor : BattleActor = _actors.get(event.target_slot)
|
||||
if actor: actor.play_hit()
|
||||
await ui.animate_hp(event.target_slot, event.hp, event.max_hp)
|
||||
|
||||
BattleEvent.Type.FAINTED:
|
||||
var actor : BattleActor = _actors.get(event.source_slot)
|
||||
if actor: actor.play_faint()
|
||||
ui.remove_slot_panel(event.source_slot)
|
||||
await ui.show_message(event.describe(), 1.0)
|
||||
|
||||
BattleEvent.Type.TURN_START:
|
||||
ui.hide_message() #pas de texte : les menus prennent la suite
|
||||
|
||||
_:
|
||||
#MOVE_MISSED, CRITICAL, SUPER_EFFECTIVE, NOT_VERY_EFFECTIVE, NO_EFFECT, FLED, MESSAGE, BATTLE_END...
|
||||
var text : String = event.describe()
|
||||
if text != "": await ui.show_message(text, 1.0)
|
||||
|
||||
func _show_actor(slot_id : int) -> void:
|
||||
var slot : BattleSlot = battle.get_slot(slot_id)
|
||||
if slot == null or slot.mon == null: return
|
||||
|
||||
if _actors.has(slot_id):
|
||||
(_actors[slot_id] as Node).queue_free()
|
||||
_actors.erase(slot_id)
|
||||
|
||||
var actor := BattleActor.new()
|
||||
_world_root.add_child(actor)
|
||||
actor.setup(slot.mon.pokemon, slot_id)
|
||||
var facing : Vector3 = _center + _axis * (side_distance if slot.side == 0 else -side_distance)
|
||||
actor.place(_formation.get(slot_id, _center), facing)
|
||||
_actors[slot_id] = actor
|
||||
|
||||
ui.set_slot_pokemon(slot)
|
||||
await actor.play_enter()
|
||||
|
||||
func _remove_actor(slot_id : int) -> void:
|
||||
ui.remove_slot_panel(slot_id)
|
||||
var actor : BattleActor = _actors.get(slot_id)
|
||||
if actor == null: return
|
||||
await actor.play_leave()
|
||||
actor.queue_free()
|
||||
_actors.erase(slot_id)
|
||||
|
||||
func _on_highlight_requested(slot_ids : Array) -> void:
|
||||
for id in _actors:
|
||||
(_actors[id] as BattleActor).set_highlight(slot_ids.has(id))
|
||||
|
||||
#---------- utilitaires ----------
|
||||
|
||||
func _opponents_center(slot_id : int) -> Vector3:
|
||||
var slot : BattleSlot = battle.get_slot(slot_id)
|
||||
if slot == null: return _center
|
||||
var total : Vector3 = Vector3.ZERO
|
||||
var count : int = 0
|
||||
for other in battle.slots:
|
||||
if other.side != slot.side and _actors.has(other.id):
|
||||
total += (_actors[other.id] as BattleActor).home
|
||||
count += 1
|
||||
return total / count if count > 0 else _center
|
||||
|
||||
func _snap_to_ground(point : Vector3) -> Vector3:
|
||||
var world : World3D = _previous_camera.get_world_3d() if _previous_camera != null else _world_root.get_viewport().world_3d
|
||||
var space : PhysicsDirectSpaceState3D = world.direct_space_state
|
||||
var query := PhysicsRayQueryParameters3D.create(point + Vector3.UP * 3.0, point + Vector3.DOWN * 8.0, ground_mask)
|
||||
var hit : Dictionary = space.intersect_ray(query)
|
||||
return hit["position"] if !hit.is_empty() else point
|
||||
1
game/battle/battle_world_presenter.gd.uid
Normal file
1
game/battle/battle_world_presenter.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cojynjb18vw8m
|
||||
15
game/battle/player_battle_controller.gd
Normal file
15
game/battle/player_battle_controller.gd
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
class_name PlayerBattleController
|
||||
extends BattleController
|
||||
|
||||
#controleur du joueur : les choix passent par les menus de BattleUI (manette, clavier, souris)
|
||||
|
||||
var ui : BattleUI
|
||||
|
||||
func _init(p_ui : BattleUI) -> void:
|
||||
ui = p_ui
|
||||
|
||||
func choose_action(battle : Battle, slot : BattleSlot) -> BattleAction:
|
||||
return await ui.request_action(battle, slot)
|
||||
|
||||
func choose_replacement(battle : Battle, slot : BattleSlot) -> PokemonInstance:
|
||||
return await ui.request_replacement(battle, slot)
|
||||
1
game/battle/player_battle_controller.gd.uid
Normal file
1
game/battle/player_battle_controller.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://boa8hppadjl2e
|
||||
|
|
@ -2,11 +2,13 @@ extends Node
|
|||
# Autoload : GameData
|
||||
|
||||
signal selected_pokemon_changed
|
||||
signal battle_requested(sent : SentPokemon, target : Node) #emis par ThrowState : le systeme de combat s'y branchera
|
||||
signal battle_requested(sent : SentPokemon, target : Node) #emis par ThrowState : BattleLauncher s'y branche
|
||||
signal battle_finished(outcome : int, target : Node) #emis par BattleLauncher (outcome = valeur de Battle.Outcome)
|
||||
|
||||
var story_progress : int = 0 # 0 = début, 1 = après 1er badge, 2 = après 2e badge, etc.
|
||||
var player_team : PokemonTeam = PokemonTeam.new()
|
||||
var player_storage : PokemonStorage = PokemonStorage.new()
|
||||
var in_battle : bool = false #true pendant un combat (le monde est en pause)
|
||||
var selected_index : int = -1 #pokemon de l'equipe choisi pour etre envoye (HUD), -1 = aucun
|
||||
|
||||
func give_pokemon(pokemon : PokemonInstance) -> PokemonStorage.ReceiveResult:
|
||||
|
|
|
|||
|
|
@ -3,10 +3,15 @@ extends Marker3D
|
|||
class_name AimTarget
|
||||
|
||||
signal locked_changed(is_locked : bool)
|
||||
signal battle_finished(outcome : int) #emis a la fin d'un combat lance depuis cette cible (valeur de Battle.Outcome)
|
||||
|
||||
@export var enabled : bool = true
|
||||
@export var priority : float = 0.0 #plus c'est haut, plus la cible est favorisee
|
||||
|
||||
@export_group("Battle")
|
||||
@export var encounter : BattleEncounter #si defini, lancer un pokemon sur cette cible demarre ce combat
|
||||
@export var world_actor : Node3D #le pokemon/dresseur visible dans le monde : cache pendant le combat, supprime apres une victoire
|
||||
|
||||
@export_group("Indicator")
|
||||
@export var indicator : Node3D #optionnel : ton propre visuel (remplace le losange par defaut)
|
||||
@export var show_default_diamond : bool = true
|
||||
|
|
@ -16,6 +21,10 @@ signal locked_changed(is_locked : bool)
|
|||
|
||||
var is_locked : bool = false
|
||||
|
||||
#utilise par ThrowState : true si cette cible declenche un combat
|
||||
var starts_battle : bool:
|
||||
get: return encounter != null
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("aim_targets")
|
||||
|
||||
|
|
|
|||
|
|
@ -143,6 +143,9 @@ func _on_ball_landed(landing_pos : Vector3, pokemon : PokemonInstance, target :
|
|||
|
||||
if target_valid and _starts_battle(target):
|
||||
GameData.battle_requested.emit(sent, target)
|
||||
elif target_valid:
|
||||
#diagnostic : retire ce message quand tout fonctionne
|
||||
push_warning("ThrowState : la cible '%s' ne lance pas de combat (starts_battle faux : Encounter vide, ou aim_target.gd pas remplace)." % target.name)
|
||||
|
||||
#---------- nettoyage ----------
|
||||
|
||||
|
|
|
|||
|
|
@ -54,19 +54,24 @@ func _ready() -> void:
|
|||
add_to_group(GROUP)
|
||||
|
||||
func _build_model() -> void:
|
||||
var scene : PackedScene = pokemon.species.model_scene
|
||||
model = create_model(pokemon)
|
||||
add_child(model)
|
||||
|
||||
static func create_model(p : PokemonInstance) -> Node3D:
|
||||
#modele 3D de l'espece ; capsule de remplacement s'il n'y en a pas
|
||||
var result : Node3D = null
|
||||
var scene : PackedScene = p.species.model_scene
|
||||
if scene:
|
||||
model = scene.instantiate() as Node3D
|
||||
if model == null:
|
||||
#modele de remplacement si l'espece n'a pas de modele 3D
|
||||
result = scene.instantiate() as Node3D
|
||||
if result == null:
|
||||
var mesh_instance := MeshInstance3D.new()
|
||||
var capsule := CapsuleMesh.new()
|
||||
capsule.radius = 0.3
|
||||
capsule.height = 1.0
|
||||
mesh_instance.mesh = capsule
|
||||
mesh_instance.position.y = 0.5
|
||||
model = mesh_instance
|
||||
add_child(model)
|
||||
result = mesh_instance
|
||||
return result
|
||||
|
||||
func recall() -> void:
|
||||
recalled.emit()
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ func _unhandled_input(event : InputEvent) -> void:
|
|||
#---------- construction de l'interface ----------
|
||||
|
||||
func _build_ui() -> void:
|
||||
set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) #le node est deja dans l'arbre : il faut aussi remettre les offsets a zero
|
||||
|
||||
var bg := ColorRect.new()
|
||||
bg.color = Color(0, 0, 0, 0.8)
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ func _unhandled_input(event : InputEvent) -> void:
|
|||
#---------- construction de l'interface ----------
|
||||
|
||||
func _build_ui() -> void:
|
||||
set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) #le node est deja dans l'arbre : il faut aussi remettre les offsets a zero
|
||||
|
||||
var bg := ColorRect.new()
|
||||
bg.color = Color(0, 0, 0, 0.8)
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ func _unhandled_input(event : InputEvent) -> void:
|
|||
#---------- construction ----------
|
||||
|
||||
func _build_ui() -> void:
|
||||
set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) #le node est deja dans l'arbre : il faut aussi remettre les offsets a zero
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
|
||||
_root_box = MarginContainer.new()
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ func _ready() -> void:
|
|||
play_menu_music()
|
||||
fade_in_game_infos()
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
func _process(_delta: float) -> void:
|
||||
if !is_transitioning:
|
||||
Start.grab_focus()
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ func _ready() -> void:
|
|||
animation_player.play("RESET")
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if is_transitioning or sub_screen != null: return #une page ouverte gere ses propres entrees
|
||||
if is_transitioning or sub_screen != null or GameData.in_battle: return #une page ouverte gere ses propres entrees
|
||||
if event.is_action_pressed("pause"):
|
||||
if get_tree().paused:
|
||||
resume()
|
||||
|
|
|
|||
|
|
@ -6,9 +6,14 @@
|
|||
[ext_resource type="Texture2D" uid="uid://djg8y25y06h5n" path="res://assets/textures/grids/Green/texture_09.png" id="4_jcfev"]
|
||||
[ext_resource type="Texture2D" uid="uid://hu5rfl6qr2nv" path="res://assets/textures/grids/Red/texture_09.png" id="5_rkxil"]
|
||||
[ext_resource type="Script" uid="uid://ce2wj6d8xb055" path="res://game/player/aim/aim_target.gd" id="6_ajh0j"]
|
||||
[ext_resource type="Script" uid="uid://u2t6dm1ee3se" path="res://game/battle/battle_encounter.gd" id="7_g80jn"]
|
||||
[ext_resource type="PackedScene" uid="uid://di22cs2qposdm" path="res://game/ui/hud/ui.tscn" id="7_yebv1"]
|
||||
[ext_resource type="Script" uid="uid://cqsokrfi2d05y" path="res://game/pokemon/trainer_party_entry.gd" id="8_42ub6"]
|
||||
[ext_resource type="PackedScene" uid="uid://d1mcef8gl66j" path="res://game/ui/pause_menu/pause_menu.tscn" id="8_g80jn"]
|
||||
[ext_resource type="PackedScene" uid="uid://dwxpt0s7vn36k" path="res://game/player/player_character.tscn" id="9_42ub6"]
|
||||
[ext_resource type="Script" uid="uid://chc1mf57j5m4p" path="res://addons/pokeapi_importer/data/move_data.gd" id="9_qk77p"]
|
||||
[ext_resource type="Script" uid="uid://bx0htbw7ygkww" path="res://game/battle/battle_launcher.gd" id="10_rr16o"]
|
||||
[ext_resource type="Resource" uid="uid://caim7okcsrjq4" path="res://data/pokeapi/pokemon/0025_pikachu.tres" id="10_ysylv"]
|
||||
|
||||
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_tipki"]
|
||||
sky_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1)
|
||||
|
|
@ -45,6 +50,18 @@ uv1_triplanar = true
|
|||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_tipki"]
|
||||
albedo_texture = ExtResource("5_rkxil")
|
||||
|
||||
[sub_resource type="Resource" id="Resource_yebv1"]
|
||||
script = ExtResource("8_42ub6")
|
||||
species = ExtResource("10_ysylv")
|
||||
nickname = "Testkatchu"
|
||||
metadata/_custom_type_script = "uid://cqsokrfi2d05y"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_rr16o"]
|
||||
script = ExtResource("7_g80jn")
|
||||
wild_pokemon = Array[ExtResource("8_42ub6")]([SubResource("Resource_yebv1")])
|
||||
can_flee = false
|
||||
metadata/_custom_type_script = "uid://u2t6dm1ee3se"
|
||||
|
||||
[node name="world" type="Node3D" unique_id=832308324]
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=455638012]
|
||||
|
|
@ -105,14 +122,19 @@ material_override = SubResource("StandardMaterial3D_tipki")
|
|||
use_collision = true
|
||||
size = Vector3(0.5, 0.5, 0.5)
|
||||
|
||||
[node name="AimTarget" type="Marker3D" parent="map/target3" unique_id=1848628150]
|
||||
[node name="AimTarget" type="Marker3D" parent="map/target3" unique_id=1848628150 node_paths=PackedStringArray("world_actor")]
|
||||
transform = Transform3D(1, 0, 3.7252903e-09, 0, 1, 0, -3.7252903e-09, 0, 1, 0, 0.030708313, 0)
|
||||
script = ExtResource("6_ajh0j")
|
||||
encounter = SubResource("Resource_rr16o")
|
||||
world_actor = NodePath("..")
|
||||
|
||||
[node name="BattleLauncher" type="Node" parent="." unique_id=1234981764]
|
||||
script = ExtResource("10_rr16o")
|
||||
|
||||
[node name="Ui" parent="." unique_id=1881560149 instance=ExtResource("7_yebv1")]
|
||||
|
||||
[node name="PauseMenu" parent="." unique_id=871841579 instance=ExtResource("8_g80jn")]
|
||||
process_mode = 3
|
||||
|
||||
[node name="PlayerCharacter" parent="." unique_id=1830301535 instance=ExtResource("9_42ub6")]
|
||||
[node name="PlayerCharacter" parent="." unique_id=1830301535 groups=["player"] instance=ExtResource("9_42ub6")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.2155721, -0.00040316582, 0.73769236)
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ window/stretch/aspect="expand"
|
|||
|
||||
enabled=PackedStringArray("res://addons/controller_icons/plugin.cfg", "res://addons/pokeapi_importer/plugin.cfg")
|
||||
|
||||
[global_group]
|
||||
|
||||
player=""
|
||||
|
||||
[input]
|
||||
|
||||
ui_accept={
|
||||
|
|
|
|||
Loading…
Reference in a new issue