Compare commits
3 commits
349987f290
...
4275a88a49
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4275a88a49 | ||
|
|
31c3e232f2 | ||
|
|
250976d170 |
|
|
@ -1,10 +0,0 @@
|
||||||
[gd_resource type="Resource" script_class="MountProfile" format=3 uid="uid://did0vw42e8fex"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://dsgbmr2jqjata" path="res://scripts/mount/mount_profile.gd" id="1_aoce8"]
|
|
||||||
[ext_resource type="Resource" uid="uid://hogb1068ywfe" path="res://data/pokeapi/pokemon/0006_charizard.tres" id="2_onvr1"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("1_aoce8")
|
|
||||||
species = ExtResource("2_onvr1")
|
|
||||||
surfaces = 5
|
|
||||||
metadata/_custom_type_script = "uid://dsgbmr2jqjata"
|
|
||||||
|
|
@ -86,12 +86,11 @@ func start_encounter(encounter : BattleEncounter, enemy_pos : Vector3, sent : Se
|
||||||
await presenter.cleanup()
|
await presenter.cleanup()
|
||||||
ui.queue_free()
|
ui.queue_free()
|
||||||
|
|
||||||
#retour au monde
|
#retour au monde : tous les pokemon sortis rentrent dans leur balle ; l'adversaire du monde reapparait
|
||||||
for node in hidden:
|
|
||||||
if is_instance_valid(node): node.visible = true
|
|
||||||
for node in get_tree().get_nodes_in_group(SentPokemon.GROUP):
|
for node in get_tree().get_nodes_in_group(SentPokemon.GROUP):
|
||||||
if node is SentPokemon and node.pokemon != null and node.pokemon.is_fainted():
|
if node is SentPokemon: node.recall()
|
||||||
node.recall() #un pokemon K.O. est rappele
|
for node in hidden:
|
||||||
|
if is_instance_valid(node) and !(node is SentPokemon): node.visible = true
|
||||||
|
|
||||||
if world_actor != null and is_instance_valid(world_actor):
|
if world_actor != null and is_instance_valid(world_actor):
|
||||||
if outcome == Battle.Outcome.PLAYER_WON and encounter.remove_world_actor_on_win:
|
if outcome == Battle.Outcome.PLAYER_WON and encounter.remove_world_actor_on_win:
|
||||||
|
|
|
||||||
25
game/mount/mount_detector.gd
Normal file
25
game/mount/mount_detector.gd
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
class_name MountDetector
|
||||||
|
|
||||||
|
#trouve le pokemon montable le plus proche du joueur. Pas de node ni d'etat : MountRider l'appelle avec ses reglages
|
||||||
|
#(mount_range, max_height_diff), donc ils restent modifiables dans l'inspecteur, y compris en cours de partie.
|
||||||
|
|
||||||
|
static func find_candidate(player : PlayerCharacter, mount_range : float, max_height_diff : float) -> SentPokemon:
|
||||||
|
var best : SentPokemon = null
|
||||||
|
var best_dist : float = mount_range
|
||||||
|
for node in player.get_tree().get_nodes_in_group(SentPokemon.GROUP):
|
||||||
|
var s := node as SentPokemon
|
||||||
|
if s == null or s.is_queued_for_deletion() or s.rider != null: continue
|
||||||
|
if !is_rideable_here(s): continue
|
||||||
|
var diff : Vector3 = s.global_position - player.global_position
|
||||||
|
if absf(diff.y) > max_height_diff: continue
|
||||||
|
var dist : float = Vector2(diff.x, diff.z).length()
|
||||||
|
if dist <= best_dist:
|
||||||
|
best_dist = dist
|
||||||
|
best = s
|
||||||
|
return best
|
||||||
|
|
||||||
|
static func is_rideable_here(s : SentPokemon) -> bool:
|
||||||
|
var p : MountProfile = MountRegistry.get_profile(s.pokemon.species)
|
||||||
|
if p == null: return false
|
||||||
|
#sur terre : une monture terrestre, ou volante (qui pourra decoller). Une monture aquatique seule reste a quai.
|
||||||
|
return p.can_use_surface(MountProfile.Surface.GROUND) or p.can_use_surface(MountProfile.Surface.AIR)
|
||||||
1
game/mount/mount_detector.gd.uid
Normal file
1
game/mount/mount_detector.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dwfv5631jam2c
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
class_name MountRider
|
class_name MountRider
|
||||||
extends Node
|
extends PlayerComponent
|
||||||
|
|
||||||
#composant a ajouter en enfant de PlayerCharacter (a cote de InputManagementComponent / RecallInput).
|
#composant a ajouter en enfant de PlayerCharacter (a cote de InputManagementComponent / RecallInput).
|
||||||
#gere la monte d'un SentPokemon dont l'espece a un MountProfile (voir MountRegistry) :
|
#gere la monte d'un SentPokemon dont l'espece a un MountProfile (voir MountRegistry) :
|
||||||
# - s'approcher + appuyer sur mount_action (A) pour monter, dismount_action pour descendre
|
# - s'approcher + appuyer sur mount_action (A) pour monter, dismount_action pour descendre
|
||||||
# - le joueur garde ses etats Idle / Walk / Run / Jump / Aim / Throw
|
# - le joueur garde ses etats Idle / Walk / Run / Jump / Aim / Throw
|
||||||
# - Dodge et Crouch sont refuses par la StateMachine tant qu'on est monte
|
# - Dodge est refuse par la StateMachine et le crouch est desactive (player.can_crouch) tant qu'on est monte
|
||||||
# - vitesse, accel et saut sont remplaces par ceux du MountProfile
|
# - vitesse, accel et saut sont remplaces par ceux du MountProfile
|
||||||
|
#
|
||||||
|
#le travail est reparti en trois classes :
|
||||||
|
# - MountRider (ici) : cycle monter / descendre, etats bloques, hints, signaux
|
||||||
|
# - MountDetector : trouve le pokemon montable le plus proche
|
||||||
|
# - MountSeat : la monture suit le joueur, le joueur est assis a la selle, la camera monte
|
||||||
|
#l'appui qui monte ou descend est consomme (InputManagementComponent.consume) : il ne declenche pas en plus un saut, une roulade ou un crouch.
|
||||||
|
|
||||||
signal mounted(sent : SentPokemon)
|
signal mounted(sent : SentPokemon)
|
||||||
signal dismounted(sent : SentPokemon)
|
signal dismounted(sent : SentPokemon)
|
||||||
|
|
@ -16,7 +22,7 @@ signal candidate_changed(sent : SentPokemon) #pokemon montable a portee (null =
|
||||||
#@export var dismount_action : StringName = &"player_dismount" #Y (manette) / F (clavier) : cree automatiquement s'il n'existe pas
|
#@export var dismount_action : StringName = &"player_dismount" #Y (manette) / F (clavier) : cree automatiquement s'il n'existe pas
|
||||||
@export var mount_range : float = 2.5 #distance horizontale max entre le joueur et le pokemon
|
@export var mount_range : float = 2.5 #distance horizontale max entre le joueur et le pokemon
|
||||||
@export var max_height_diff : float = 2.0
|
@export var max_height_diff : float = 2.0
|
||||||
@export var blocked_states : PackedStringArray = ["DodgeState", "CrouchState"] #etats refuses pendant la monte
|
@export var blocked_states : PackedStringArray = [States.DODGE] #etats refuses pendant la monte
|
||||||
@export var swallow_jump_on_mount : bool = true #si A sert aussi a sauter : l'appui qui monte ne declenche pas de saut
|
@export var swallow_jump_on_mount : bool = true #si A sert aussi a sauter : l'appui qui monte ne declenche pas de saut
|
||||||
@export var camera_follow_speed : float = 8.0 #vitesse a laquelle la camera monte a hauteur de selle
|
@export var camera_follow_speed : float = 8.0 #vitesse a laquelle la camera monte a hauteur de selle
|
||||||
|
|
||||||
|
|
@ -30,36 +36,48 @@ var mount_action : StringName:
|
||||||
var dismount_action : StringName:
|
var dismount_action : StringName:
|
||||||
get: return dismount_hint.action
|
get: return dismount_hint.action
|
||||||
|
|
||||||
const GROUNDED_STATES : Array[String] = ["Idle", "Walk", "Run"] #monter / descendre n'est possible que depuis ces etats
|
|
||||||
|
|
||||||
@onready var player : PlayerCharacter = get_parent()
|
|
||||||
@onready var visual_root : Node3D = %VisualRoot
|
|
||||||
@onready var cam_holder : Node3D = %CameraHolder
|
|
||||||
@onready var state_machine : StateMachine = %StateMachine
|
|
||||||
|
|
||||||
var mount : SentPokemon = null
|
var mount : SentPokemon = null
|
||||||
var profile : MountProfile = null
|
var profile : MountProfile = null
|
||||||
var candidate : SentPokemon = null
|
var candidate : SentPokemon = null
|
||||||
|
|
||||||
var _seat_applied : Vector3 = Vector3.ZERO
|
var _seat : MountSeat
|
||||||
var _cam_rise : float = 0.0
|
var _could_dismount : bool = false #derniere valeur de _can_switch_now() pendant la monte : les hints ne sont rafraichis que si elle change
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
process_physics_priority = -10 #avant la StateMachine : l'appui qui monte peut ainsi etre intercepte avant le saut
|
process_physics_priority = -10 #avant la StateMachine : l'appui qui monte est ainsi consomme avant que les etats ne le lisent
|
||||||
_ensure_action(mount_action, func():
|
_seat = MountSeat.new(player, player.visual_root, player.cam_holder)
|
||||||
var pad := InputEventJoypadButton.new()
|
InputRegistry.ensure(mount_action, [InputRegistry.pad_button(JOY_BUTTON_A), InputRegistry.key(KEY_E)])
|
||||||
pad.button_index = JOY_BUTTON_A
|
InputRegistry.ensure(dismount_action, [InputRegistry.pad_button(JOY_BUTTON_X), InputRegistry.key(KEY_F)])
|
||||||
InputMap.action_add_event(mount_action, pad)
|
|
||||||
var key := InputEventKey.new()
|
func _exit_tree() -> void:
|
||||||
key.physical_keycode = KEY_E
|
Hints.clear_hints(self)
|
||||||
InputMap.action_add_event(mount_action, key))
|
|
||||||
_ensure_action(dismount_action, func():
|
func is_mounted() -> bool:
|
||||||
var pad := InputEventJoypadButton.new()
|
return mount != null
|
||||||
pad.button_index = JOY_BUTTON_X
|
|
||||||
InputMap.action_add_event(dismount_action, pad)
|
func _physics_process(_delta : float) -> void:
|
||||||
var key := InputEventKey.new()
|
if mount != null:
|
||||||
key.physical_keycode = KEY_F
|
var can_dismount : bool = _can_switch_now()
|
||||||
InputMap.action_add_event(dismount_action, key))
|
if can_dismount != _could_dismount: #le hint de descente apparait / disparait (pas de descente en plein vol)
|
||||||
|
_could_dismount = can_dismount
|
||||||
|
_refresh_hints()
|
||||||
|
if Input.is_action_just_pressed(dismount_action) and can_dismount:
|
||||||
|
dismount()
|
||||||
|
return
|
||||||
|
|
||||||
|
_set_candidate(MountDetector.find_candidate(player, mount_range, max_height_diff) if _can_switch_now() else null)
|
||||||
|
if candidate != null and Input.is_action_just_pressed(mount_action):
|
||||||
|
mount_up(candidate)
|
||||||
|
|
||||||
|
func _process(delta : float) -> void:
|
||||||
|
if mount != null and !is_instance_valid(mount):
|
||||||
|
dismount() #le pokemon a disparu sans passer par recall()
|
||||||
|
_seat.update(delta, camera_follow_speed)
|
||||||
|
|
||||||
|
func _can_switch_now() -> bool:
|
||||||
|
return player.is_on_floor() and !player.is_crouching and !player.is_aiming and player.state_machine.curr_state is GroundedState #ni accroupi ni en visant (monter / descendre : depuis le sol seulement)
|
||||||
|
|
||||||
|
#---------- hints ----------
|
||||||
|
|
||||||
func _refresh_hints() -> void:
|
func _refresh_hints() -> void:
|
||||||
#une seule source de verite pour les hints de la monture
|
#une seule source de verite pour les hints de la monture
|
||||||
|
|
@ -71,79 +89,6 @@ func _refresh_hints() -> void:
|
||||||
list.append(mount_hint)
|
list.append(mount_hint)
|
||||||
Hints.show_hints(self, list)
|
Hints.show_hints(self, list)
|
||||||
|
|
||||||
func _exit_tree() -> void:
|
|
||||||
Hints.clear_hints(self)
|
|
||||||
|
|
||||||
func _ensure_action(action : StringName, add_events : Callable) -> void:
|
|
||||||
if InputMap.has_action(action): return
|
|
||||||
InputMap.add_action(action)
|
|
||||||
add_events.call()
|
|
||||||
|
|
||||||
func is_mounted() -> bool:
|
|
||||||
return mount != null
|
|
||||||
|
|
||||||
func _physics_process(_delta : float) -> void:
|
|
||||||
if mount != null:
|
|
||||||
var can_dismount : bool = _can_switch_now()
|
|
||||||
_refresh_hints()
|
|
||||||
if Input.is_action_just_pressed(dismount_action) and can_dismount:
|
|
||||||
dismount()
|
|
||||||
return
|
|
||||||
|
|
||||||
_set_candidate(_find_candidate() if _can_switch_now() else null)
|
|
||||||
if candidate != null and Input.is_action_just_pressed(mount_action):
|
|
||||||
mount_up(candidate)
|
|
||||||
|
|
||||||
func _process(delta : float) -> void:
|
|
||||||
if mount != null and !is_instance_valid(mount):
|
|
||||||
dismount() #le pokemon a disparu sans passer par recall()
|
|
||||||
|
|
||||||
var rise_target : float = 0.0
|
|
||||||
if mount != null:
|
|
||||||
#la monture suit le joueur (position + orientation du modele)
|
|
||||||
mount.global_position = player.global_position
|
|
||||||
var yaw : float = visual_root.global_rotation.y
|
|
||||||
if !SentPokemon.MODEL_FACES_PLUS_Z: yaw += PI
|
|
||||||
mount.global_rotation = Vector3(0.0, yaw, 0.0)
|
|
||||||
|
|
||||||
#le joueur est assis a la selle (seat_offset est exprime dans le repere de la monture)
|
|
||||||
var seat : Vector3 = mount.global_basis * profile.seat_offset
|
|
||||||
visual_root.position += seat - _seat_applied
|
|
||||||
_seat_applied = seat
|
|
||||||
rise_target = profile.seat_offset.y
|
|
||||||
|
|
||||||
#la camera monte progressivement a hauteur de selle (en delta : ne gene pas les autres reglages de hauteur)
|
|
||||||
var new_rise : float = lerpf(_cam_rise, rise_target, clampf(camera_follow_speed * delta, 0.0, 1.0))
|
|
||||||
if absf(new_rise - rise_target) < 0.001: new_rise = rise_target
|
|
||||||
cam_holder.position.y += new_rise - _cam_rise
|
|
||||||
_cam_rise = new_rise
|
|
||||||
|
|
||||||
#---------- detection ----------
|
|
||||||
|
|
||||||
func _can_switch_now() -> bool:
|
|
||||||
return player.is_on_floor() and state_machine.curr_state_name in GROUNDED_STATES
|
|
||||||
|
|
||||||
func _find_candidate() -> SentPokemon:
|
|
||||||
var best : SentPokemon = null
|
|
||||||
var best_dist : float = mount_range
|
|
||||||
for node in get_tree().get_nodes_in_group(SentPokemon.GROUP):
|
|
||||||
var s := node as SentPokemon
|
|
||||||
if s == null or s.is_queued_for_deletion() or s.rider != null: continue
|
|
||||||
if !_is_rideable_here(s): continue
|
|
||||||
var diff : Vector3 = s.global_position - player.global_position
|
|
||||||
if absf(diff.y) > max_height_diff: continue
|
|
||||||
var dist : float = Vector2(diff.x, diff.z).length()
|
|
||||||
if dist <= best_dist:
|
|
||||||
best_dist = dist
|
|
||||||
best = s
|
|
||||||
return best
|
|
||||||
|
|
||||||
func _is_rideable_here(s : SentPokemon) -> bool:
|
|
||||||
var p : MountProfile = MountRegistry.get_profile(s.pokemon.species)
|
|
||||||
if p == null: return false
|
|
||||||
#sur terre : une monture terrestre, ou volante (qui pourra decoller). Une monture aquatique seule reste a quai.
|
|
||||||
return p.can_use_surface(MountProfile.Surface.GROUND) or p.can_use_surface(MountProfile.Surface.AIR)
|
|
||||||
|
|
||||||
func _set_candidate(s : SentPokemon) -> void:
|
func _set_candidate(s : SentPokemon) -> void:
|
||||||
if s == candidate: return
|
if s == candidate: return
|
||||||
candidate = s
|
candidate = s
|
||||||
|
|
@ -158,49 +103,45 @@ func mount_up(s : SentPokemon) -> void:
|
||||||
profile = MountRegistry.get_profile(s.pokemon.species)
|
profile = MountRegistry.get_profile(s.pokemon.species)
|
||||||
s.rider = player
|
s.rider = player
|
||||||
s.recalled.connect(_on_mount_recalled)
|
s.recalled.connect(_on_mount_recalled)
|
||||||
|
_could_dismount = _can_switch_now()
|
||||||
_set_candidate(null)
|
_set_candidate(null)
|
||||||
_refresh_hints()
|
_refresh_hints()
|
||||||
|
_seat.attach(s, profile)
|
||||||
|
|
||||||
player.is_mounted = true
|
player.is_mounted = true
|
||||||
player.enter_mount_stats(profile.mount_speed, profile.mount_accel, profile.mount_deccel,
|
player.enter_mount_stats(profile.mount_speed, profile.mount_accel, profile.mount_deccel,
|
||||||
-1.0 if profile.can_use_surface(MountProfile.Surface.AIR) else profile.mount_jump_height) #monture volante : le vol libre remplacera le saut
|
-1.0 if profile.can_use_surface(MountProfile.Surface.AIR) else profile.mount_jump_height) #monture volante : le vol libre remplacera le saut
|
||||||
for state_name in blocked_states:
|
for state_name in blocked_states:
|
||||||
state_machine.set_state_blocked(state_name, true)
|
player.state_machine.set_state_blocked(state_name, true)
|
||||||
if swallow_jump_on_mount: _swallow_jump_this_frame()
|
player.can_crouch = false #pas de crouch a dos de pokemon (voir GroundedState)
|
||||||
|
|
||||||
|
#l'appui qui monte ne doit pas faire sauter (si A sert aussi a sauter) : on le consomme jusqu'a ce que le bouton soit relache
|
||||||
|
if swallow_jump_on_mount: player.player_input.consume(player.player_input.jump_action)
|
||||||
|
|
||||||
mounted.emit(s)
|
mounted.emit(s)
|
||||||
|
|
||||||
func dismount() -> void:
|
func dismount() -> void:
|
||||||
if mount == null and profile == null: return
|
if mount == null: return
|
||||||
var s : SentPokemon = mount
|
var s : SentPokemon = mount if is_instance_valid(mount) else null #le pokemon a pu disparaitre sans passer par recall()
|
||||||
mount = null
|
mount = null
|
||||||
profile = null
|
profile = null
|
||||||
_refresh_hints()
|
_refresh_hints()
|
||||||
|
_seat.detach()
|
||||||
|
|
||||||
player.is_mounted = false
|
player.is_mounted = false
|
||||||
player.exit_mount_stats()
|
player.exit_mount_stats()
|
||||||
_release_blocked_states_next_frame()
|
|
||||||
|
|
||||||
visual_root.position -= _seat_applied
|
#plus rien n'est bloque : l'appui qui descend ne declenche ni roulade ni crouch (consomme jusqu'au relachement du bouton)
|
||||||
_seat_applied = Vector3.ZERO
|
for state_name in blocked_states:
|
||||||
|
player.state_machine.set_state_blocked(state_name, false)
|
||||||
|
player.can_crouch = true
|
||||||
|
player.player_input.consume(player.player_input.dodge_action)
|
||||||
|
player.player_input.consume(player.player_input.crouch_action)
|
||||||
|
|
||||||
if is_instance_valid(s):
|
if s != null:
|
||||||
s.rider = null
|
s.rider = null
|
||||||
if s.recalled.is_connected(_on_mount_recalled): s.recalled.disconnect(_on_mount_recalled)
|
if s.recalled.is_connected(_on_mount_recalled): s.recalled.disconnect(_on_mount_recalled)
|
||||||
dismounted.emit(s)
|
dismounted.emit(s)
|
||||||
|
|
||||||
func _on_mount_recalled() -> void:
|
func _on_mount_recalled() -> void:
|
||||||
dismount() #le pokemon monte vient d'etre rappele (ex : relance de ce meme pokemon depuis le HUD)
|
dismount() #le pokemon monte vient d'etre rappele (ex : relance de ce meme pokemon depuis le HUD)
|
||||||
|
|
||||||
func _swallow_jump_this_frame() -> void:
|
|
||||||
state_machine.set_state_blocked("JumpState", true)
|
|
||||||
await get_tree().physics_frame
|
|
||||||
state_machine.set_state_blocked("JumpState", false)
|
|
||||||
|
|
||||||
func _release_blocked_states_next_frame() -> void:
|
|
||||||
#Dodge/Crouch restent refuses pendant la frame de la descente : si la touche de descente est aussi celle de la roulade,
|
|
||||||
#le meme appui ne declenche pas de roulade une fois descendu
|
|
||||||
await get_tree().physics_frame
|
|
||||||
if mount != null: return #on est remonte entre-temps : les etats restent bloques
|
|
||||||
for state_name in blocked_states:
|
|
||||||
state_machine.set_state_blocked(state_name, false)
|
|
||||||
|
|
|
||||||
55
game/mount/mount_seat.gd
Normal file
55
game/mount/mount_seat.gd
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
class_name MountSeat
|
||||||
|
|
||||||
|
#la selle : la monture suit le joueur (position + orientation du modele), le joueur est assis a la selle,
|
||||||
|
#et la camera monte progressivement a hauteur de selle. Pas de node : MountRider le cree et l'appelle chaque frame.
|
||||||
|
#
|
||||||
|
#les decalages (selle, hauteur de camera) sont appliques "en delta" sur visual_root et cam_holder, pour ne pas
|
||||||
|
#ecraser les autres reglages de position. Tout cela est regroupe ici : si un jour ces nodes recoivent un vrai
|
||||||
|
#decalage dedie (ex : un node Seat), il n'y aura que ce fichier a changer.
|
||||||
|
|
||||||
|
var _player : PlayerCharacter
|
||||||
|
var _visual_root : Node3D
|
||||||
|
var _cam_holder : Node3D
|
||||||
|
|
||||||
|
var _mount : SentPokemon = null
|
||||||
|
var _profile : MountProfile = null
|
||||||
|
var _seat_applied : Vector3 = Vector3.ZERO #decalage de selle actuellement ajoute a visual_root.position
|
||||||
|
var _cam_rise : float = 0.0 #hauteur actuellement ajoutee a cam_holder.position.y
|
||||||
|
|
||||||
|
func _init(player : PlayerCharacter, visual_root : Node3D, cam_holder : Node3D) -> void:
|
||||||
|
_player = player
|
||||||
|
_visual_root = visual_root
|
||||||
|
_cam_holder = cam_holder
|
||||||
|
|
||||||
|
func attach(mount : SentPokemon, profile : MountProfile) -> void:
|
||||||
|
_mount = mount
|
||||||
|
_profile = profile
|
||||||
|
|
||||||
|
func detach() -> void:
|
||||||
|
#le joueur retrouve sa position ; la camera redescend progressivement (update continue de tourner)
|
||||||
|
_visual_root.position -= _seat_applied
|
||||||
|
_seat_applied = Vector3.ZERO
|
||||||
|
_mount = null
|
||||||
|
_profile = null
|
||||||
|
|
||||||
|
func update(delta : float, camera_follow_speed : float) -> void:
|
||||||
|
var rise_target : float = 0.0
|
||||||
|
|
||||||
|
if _mount != null and is_instance_valid(_mount):
|
||||||
|
#la monture suit le joueur (position + orientation du modele)
|
||||||
|
_mount.global_position = _player.global_position
|
||||||
|
var yaw : float = _visual_root.global_rotation.y
|
||||||
|
if !SentPokemon.MODEL_FACES_PLUS_Z: yaw += PI
|
||||||
|
_mount.global_rotation = Vector3(0.0, yaw, 0.0)
|
||||||
|
|
||||||
|
#le joueur est assis a la selle (seat_offset est exprime dans le repere de la monture)
|
||||||
|
var seat : Vector3 = _mount.global_basis * _profile.seat_offset
|
||||||
|
_visual_root.position += seat - _seat_applied
|
||||||
|
_seat_applied = seat
|
||||||
|
rise_target = _profile.seat_offset.y
|
||||||
|
|
||||||
|
#la camera monte progressivement a hauteur de selle (en delta : ne gene pas les autres reglages de hauteur)
|
||||||
|
var new_rise : float = lerpf(_cam_rise, rise_target, clampf(camera_follow_speed * delta, 0.0, 1.0))
|
||||||
|
if absf(new_rise - rise_target) < 0.001: new_rise = rise_target
|
||||||
|
_cam_holder.position.y += new_rise - _cam_rise
|
||||||
|
_cam_rise = new_rise
|
||||||
1
game/mount/mount_seat.gd.uid
Normal file
1
game/mount/mount_seat.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://bjfm64yqkproy
|
||||||
|
|
@ -3,7 +3,7 @@ extends CanvasLayer
|
||||||
class_name AimReticle
|
class_name AimReticle
|
||||||
|
|
||||||
#reticule 2D au centre de l'ecran (visee libre / charge du lancer)
|
#reticule 2D au centre de l'ecran (visee libre / charge du lancer)
|
||||||
#cree a la volee, une seule instance partagee par AimState et ThrowState
|
#cree a la volee, une seule instance partagee : affichee / masquee par AimTargeting.update_reticle() (appele par tick() et par ThrowState)
|
||||||
|
|
||||||
static var _instance : AimReticle
|
static var _instance : AimReticle
|
||||||
|
|
||||||
|
|
|
||||||
179
game/player/aim/aim_targeting.gd
Normal file
179
game/player/aim/aim_targeting.gd
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
extends PlayerComponent
|
||||||
|
|
||||||
|
class_name AimTargeting
|
||||||
|
|
||||||
|
#la visee est un MODE superpose aux etats (comme la posture accroupie), plus un etat a part :
|
||||||
|
# - elle demarre quand un etat au sol voit l'appui sur la visee (begin_aim) ou quand la visee est maintenue a la fin d'une roulade / d'un lancer
|
||||||
|
# - elle dure tant que le bouton est maintenu : fin des qu'il est relache, ou en l'air. Pendant un lancer, elle attend la fin du lancer
|
||||||
|
# - les etats continuent leur travail (marcher, rouler, lancer) : GroundedState ralentit le deplacement et bloque le saut
|
||||||
|
#le composant gere aussi le ciblage : choix de la cible (auto-lock, changement, deverrouillage), camera de visee, reticule et hints.
|
||||||
|
#GroundedState et ThrowState appellent tick() chaque frame tant que la visee est active.
|
||||||
|
#composant a ajouter sous le PlayerCharacter (le nom du node est libre, voir PlayerComponent).
|
||||||
|
|
||||||
|
@export var throw_range : float = 20.0 #portee du lancer : sert aussi a ThrowResolver pour le point vise sans cible
|
||||||
|
@export var max_aim_angle : float = 35.0 #angle max (deg) entre l'axe camera et une cible pour qu'elle soit candidate
|
||||||
|
@export var auto_lock_angle : float = 12.0 #si une cible passe sous cet angle, on se verrouille dessus
|
||||||
|
@export var lose_range_margin : float = 1.25 #marge de distance avant de perdre une cible deja verrouillee
|
||||||
|
@export var check_line_of_sight : bool = true
|
||||||
|
@export_flags_3d_physics var line_of_sight_mask : int = 1
|
||||||
|
|
||||||
|
@export_group("Movement")
|
||||||
|
@export var aim_move_speed : float = 3.0 #vitesse de deplacement pendant la visee (remplace celle de la marche / course / crouch)
|
||||||
|
@export var aim_move_accel : float = 8.0
|
||||||
|
|
||||||
|
@export_group("Input")
|
||||||
|
@export var unlock_action : StringName = &"player_aim_unlock"
|
||||||
|
|
||||||
|
@export_group("Hints")
|
||||||
|
@export var switch_hint : Hint #switch_target.tres
|
||||||
|
|
||||||
|
const TARGET_GROUP : String = "aim_targets" #groupe des AimTarget (aussi utilise par ThrowResolver)
|
||||||
|
|
||||||
|
var active : bool = false #true tant que la visee est en cours
|
||||||
|
var auto_lock_blocked : bool = false #true apres un deverrouillage manuel, tant que la cible est encore au centre
|
||||||
|
|
||||||
|
var switch_target_action : StringName: #l'action vient du Hint : une seule definition
|
||||||
|
get: return switch_hint.action
|
||||||
|
|
||||||
|
func register_with_player() -> void:
|
||||||
|
player.aim_targeting = self
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
process_physics_priority = -5 #avant la StateMachine : les etats voient une visee deja a jour (fin de visee, en l'air...)
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
Hints.clear_hints(self)
|
||||||
|
|
||||||
|
#---------- mode visee ----------
|
||||||
|
|
||||||
|
func _physics_process(_delta : float) -> void:
|
||||||
|
if !active: return
|
||||||
|
var s : State = player.state_machine.curr_state
|
||||||
|
if s is AirborneState:
|
||||||
|
end_aim() #pas de visee en l'air : il faut reappuyer apres l'atterrissage
|
||||||
|
elif !player.player_input.aim_held() and !(s is ThrowState):
|
||||||
|
end_aim() #bouton relache (pendant un lancer, on attend la fin du lancer : la cible verrouillee compte)
|
||||||
|
|
||||||
|
func begin_aim() -> void:
|
||||||
|
if active: return
|
||||||
|
active = true
|
||||||
|
auto_lock_blocked = false
|
||||||
|
player.cam_holder.cam_aimed = true
|
||||||
|
var list : Array[Hint] = []
|
||||||
|
if switch_hint: list.append(switch_hint)
|
||||||
|
Hints.show_hints(self, list)
|
||||||
|
|
||||||
|
func end_aim() -> void:
|
||||||
|
if !active: return
|
||||||
|
active = false
|
||||||
|
release_target()
|
||||||
|
player.cam_holder.cam_aimed = false
|
||||||
|
AimReticle.hide_instance()
|
||||||
|
Hints.clear_hints(self)
|
||||||
|
|
||||||
|
func tick() -> void:
|
||||||
|
#appele chaque frame par les etats qui acceptent la visee (GroundedState, ThrowState pendant la charge)
|
||||||
|
if Input.is_action_just_pressed(unlock_action):
|
||||||
|
unlock() #deverrouillage manuel
|
||||||
|
elif switch_hint and Input.is_action_just_pressed(switch_target_action):
|
||||||
|
switch_target()
|
||||||
|
update_target()
|
||||||
|
update_reticle()
|
||||||
|
|
||||||
|
func unlock() -> void:
|
||||||
|
#deverrouillage manuel : pas de re-verrouillage automatique tant que la cible reste au centre
|
||||||
|
auto_lock_blocked = true
|
||||||
|
release_target()
|
||||||
|
|
||||||
|
func update_reticle() -> void:
|
||||||
|
#reticule libre au centre de l'ecran, seulement quand aucune cible n'est verrouillee (appele par tick(), et par ThrowState quand on charge un lancer sans viser)
|
||||||
|
if is_instance_valid(player.aim_target):
|
||||||
|
AimReticle.hide_instance() #cible verrouillee : son propre indicateur suffit
|
||||||
|
else:
|
||||||
|
AimReticle.show_instance(self)
|
||||||
|
|
||||||
|
#---------- ciblage ----------
|
||||||
|
|
||||||
|
func update_target() -> void:
|
||||||
|
var current : AimTarget = player.aim_target
|
||||||
|
|
||||||
|
#verrouillage existant : on le garde tant que distance et ligne de vue sont valides
|
||||||
|
#(l'angle avec la camera ne compte pas, le joueur peut deplacer la camera librement)
|
||||||
|
if is_instance_valid(current):
|
||||||
|
if is_target_valid(current, lose_range_margin, false): return
|
||||||
|
release_target()
|
||||||
|
|
||||||
|
#visee libre : auto-lock sur la meilleure cible proche du centre de la camera
|
||||||
|
var best : AimTarget = null
|
||||||
|
var best_angle : float = auto_lock_angle
|
||||||
|
for t in get_candidates():
|
||||||
|
var angle : float = get_angle_to_camera(t) - t.priority
|
||||||
|
if angle < best_angle:
|
||||||
|
best_angle = angle
|
||||||
|
best = t
|
||||||
|
|
||||||
|
if best == null:
|
||||||
|
auto_lock_blocked = false #le joueur a ecarte la camera : l'auto-lock est de nouveau permis
|
||||||
|
elif !auto_lock_blocked:
|
||||||
|
set_target(best)
|
||||||
|
|
||||||
|
func get_candidates() -> Array[AimTarget]:
|
||||||
|
var result : Array[AimTarget] = []
|
||||||
|
for node in get_tree().get_nodes_in_group(TARGET_GROUP):
|
||||||
|
var t := node as AimTarget
|
||||||
|
if t and is_target_valid(t, 1.0, true): result.append(t)
|
||||||
|
return result
|
||||||
|
|
||||||
|
func is_target_valid(t : AimTarget, range_mult : float, check_angle : bool) -> bool:
|
||||||
|
if !t.enabled: return false
|
||||||
|
if player.global_position.distance_to(t.global_position) > throw_range * range_mult: return false
|
||||||
|
if check_angle and get_angle_to_camera(t) > max_aim_angle: return false
|
||||||
|
if check_line_of_sight and !has_line_of_sight(t): return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
func get_angle_to_camera(t : AimTarget) -> float:
|
||||||
|
var cam : Camera3D = player.cam_holder.cam
|
||||||
|
var to_t : Vector3 = t.global_position - cam.global_position
|
||||||
|
return rad_to_deg((-cam.global_basis.z).angle_to(to_t))
|
||||||
|
|
||||||
|
func has_line_of_sight(t : AimTarget) -> bool:
|
||||||
|
var query := PhysicsRayQueryParameters3D.create(
|
||||||
|
player.cam_holder.cam.global_position, t.global_position, line_of_sight_mask, [player.get_rid()])
|
||||||
|
var hit : Dictionary = player.get_world_3d().direct_space_state.intersect_ray(query)
|
||||||
|
#pas d'obstacle, ou l'obstacle est le proprietaire de la cible lui-meme
|
||||||
|
return hit.is_empty() or hit["collider"].is_ancestor_of(t)
|
||||||
|
|
||||||
|
func switch_target() -> void:
|
||||||
|
#passe a la cible suivante, triee de gauche a droite par rapport a la camera (boucle)
|
||||||
|
var cam : Camera3D = player.cam_holder.cam
|
||||||
|
var forward : Vector3 = -cam.global_basis.z
|
||||||
|
var list : Array[AimTarget] = get_candidates()
|
||||||
|
|
||||||
|
#la cible actuelle reste dans la liste meme si la camera est decalee par rapport a elle
|
||||||
|
var current : AimTarget = player.aim_target
|
||||||
|
if is_instance_valid(current) and !list.has(current): list.append(current)
|
||||||
|
if list.size() < 2: return
|
||||||
|
|
||||||
|
list.sort_custom(func(a : AimTarget, b : AimTarget) -> bool:
|
||||||
|
return signed_angle(forward, a, cam) < signed_angle(forward, b, cam))
|
||||||
|
|
||||||
|
var idx : int = list.find(current)
|
||||||
|
idx = 0 if idx == -1 else (idx + 1) % list.size()
|
||||||
|
set_target(list[idx])
|
||||||
|
|
||||||
|
func signed_angle(forward : Vector3, t : AimTarget, cam : Camera3D) -> float:
|
||||||
|
var to_t : Vector3 = t.global_position - cam.global_position
|
||||||
|
return Vector2(forward.x, forward.z).angle_to(Vector2(to_t.x, to_t.z))
|
||||||
|
|
||||||
|
func set_target(t : AimTarget) -> void:
|
||||||
|
if player.aim_target == t: return
|
||||||
|
release_target()
|
||||||
|
player.aim_target = t
|
||||||
|
player.cam_holder.set_lock_target(t)
|
||||||
|
t.set_locked(true)
|
||||||
|
|
||||||
|
func release_target() -> void:
|
||||||
|
if !player: return
|
||||||
|
if is_instance_valid(player.aim_target): player.aim_target.set_locked(false)
|
||||||
|
player.aim_target = null
|
||||||
|
player.cam_holder.set_lock_target(null)
|
||||||
1
game/player/aim/aim_targeting.gd.uid
Normal file
1
game/player/aim/aim_targeting.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://qh738m0cmw8b
|
||||||
103
game/player/aim/throw_resolver.gd
Normal file
103
game/player/aim/throw_resolver.gd
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
extends PlayerComponent
|
||||||
|
|
||||||
|
class_name ThrowResolver
|
||||||
|
|
||||||
|
#resout un lancer : trajectoire de la balle, point d'atterrissage, envoi du pokemon et demande de combat.
|
||||||
|
#ThrowState ne gere plus que le deroule (charge, windup, recuperation) et appelle throw_ball().
|
||||||
|
#composant a ajouter sous le PlayerCharacter (le nom du node est libre, voir PlayerComponent).
|
||||||
|
#il vit aussi longtemps que le joueur : l'atterrissage est gere ici meme si le joueur a change d'etat entre-temps.
|
||||||
|
|
||||||
|
@export_group("Ball")
|
||||||
|
@export var ball_scene : PackedScene #modele de la balle (optionnel : sphere rouge par defaut)
|
||||||
|
@export var hand_height : float = 1.4
|
||||||
|
@export var ball_speed : float = 18.0
|
||||||
|
@export var min_flight_time : float = 0.25
|
||||||
|
@export var max_flight_time : float = 1.2
|
||||||
|
@export var arc_height : float = 1.5
|
||||||
|
|
||||||
|
@export_group("Landing")
|
||||||
|
@export_flags_3d_physics var ground_mask : int = 1
|
||||||
|
@export var land_distance_from_target : float = 1.5 #la balle s'arrete devant une cible de combat
|
||||||
|
@export var free_hit_radius : float = 2.0 #lancer sans cible verrouillee : rayon autour du point d'atterrissage pour trouver une cible de combat
|
||||||
|
|
||||||
|
func register_with_player() -> void:
|
||||||
|
player.throw_resolver = self
|
||||||
|
|
||||||
|
#---------- lancer ----------
|
||||||
|
|
||||||
|
func throw_ball(pokemon : PokemonInstance, target : Variant, free_aim_point : Vector3) -> void:
|
||||||
|
#target : AimTarget verrouille au moment du relachement (ou null), free_aim_point : point vise s'il n'y en a pas
|
||||||
|
var from : Vector3 = player.global_position + Vector3.UP * hand_height
|
||||||
|
var to : Vector3 = _get_landing_point(target, free_aim_point)
|
||||||
|
var flight_time : float = clampf(from.distance_to(to) / ball_speed, min_flight_time, max_flight_time)
|
||||||
|
|
||||||
|
var scene : Node = get_tree().current_scene
|
||||||
|
var ball : ThrownBall = ThrownBall.launch(scene, from, to, flight_time, arc_height, ball_scene)
|
||||||
|
ball.landed.connect(_on_ball_landed.bind(pokemon, target, from))
|
||||||
|
|
||||||
|
#---------- point vise ----------
|
||||||
|
|
||||||
|
func compute_free_aim_point() -> Vector3:
|
||||||
|
#point vise au centre de l'ecran : premier obstacle, sinon fin de portee ramenee au sol
|
||||||
|
var cam : Camera3D = player.cam_holder.cam
|
||||||
|
var origin : Vector3 = cam.global_position
|
||||||
|
var far : Vector3 = origin - cam.global_basis.z * player.aim_targeting.throw_range
|
||||||
|
var query := PhysicsRayQueryParameters3D.create(origin, far, ground_mask, [player.get_rid()])
|
||||||
|
var hit : Dictionary = player.get_world_3d().direct_space_state.intersect_ray(query)
|
||||||
|
if hit.is_empty(): return _snap_to_ground(far)
|
||||||
|
return _snap_to_ground(hit["position"] + hit["normal"] * 0.5)
|
||||||
|
|
||||||
|
func _get_landing_point(target : Variant, free_aim_point : Vector3) -> Vector3:
|
||||||
|
if !is_instance_valid(target): return free_aim_point
|
||||||
|
|
||||||
|
var target_pos : Vector3 = target.global_position
|
||||||
|
if _starts_battle(target):
|
||||||
|
#la balle s'arrete devant la cible, du cote du joueur
|
||||||
|
var to_player : Vector3 = player.global_position - target_pos
|
||||||
|
to_player.y = 0.0
|
||||||
|
if to_player.length() > land_distance_from_target:
|
||||||
|
target_pos += to_player.normalized() * land_distance_from_target
|
||||||
|
return _snap_to_ground(target_pos)
|
||||||
|
|
||||||
|
func _snap_to_ground(point : Vector3) -> Vector3:
|
||||||
|
var query := PhysicsRayQueryParameters3D.create(point + Vector3.UP * 2.0, point + Vector3.DOWN * 6.0, ground_mask, [player.get_rid()])
|
||||||
|
var hit : Dictionary = player.get_world_3d().direct_space_state.intersect_ray(query)
|
||||||
|
return hit["position"] if !hit.is_empty() else point
|
||||||
|
|
||||||
|
#---------- atterrissage ----------
|
||||||
|
|
||||||
|
func _on_ball_landed(landing_pos : Vector3, pokemon : PokemonInstance, target : Variant, thrower_pos : Vector3) -> void:
|
||||||
|
var scene : Node = get_tree().current_scene if is_inside_tree() else null
|
||||||
|
if scene == null: return
|
||||||
|
|
||||||
|
#lancer sans cible verrouillee : si la balle atterrit pres d'une cible de combat, c'est elle qui est visee
|
||||||
|
if !is_instance_valid(target):
|
||||||
|
target = _find_battle_target_near(landing_pos)
|
||||||
|
|
||||||
|
var target_valid : bool = is_instance_valid(target)
|
||||||
|
#le pokemon regarde la cible, ou dans la direction du lancer s'il n'y en a pas
|
||||||
|
var look_pos : Vector3 = target.global_position if target_valid else landing_pos + (landing_pos - thrower_pos)
|
||||||
|
var sent : SentPokemon = SentPokemon.send(scene, pokemon, landing_pos, look_pos)
|
||||||
|
|
||||||
|
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("ThrowResolver : la cible '%s' ne lance pas de combat (starts_battle faux : Encounter vide, ou aim_target.gd pas remplace)." % target.name)
|
||||||
|
|
||||||
|
func _find_battle_target_near(point : Vector3) -> AimTarget:
|
||||||
|
#cible de combat active la plus proche du point d'atterrissage, dans free_hit_radius
|
||||||
|
var best : AimTarget = null
|
||||||
|
var best_dist : float = free_hit_radius
|
||||||
|
for node in get_tree().get_nodes_in_group(AimTargeting.TARGET_GROUP):
|
||||||
|
var t := node as AimTarget
|
||||||
|
if t == null or !t.enabled or !_starts_battle(t): continue
|
||||||
|
var dist : float = point.distance_to(t.global_position)
|
||||||
|
if dist <= best_dist:
|
||||||
|
best_dist = dist
|
||||||
|
best = t
|
||||||
|
return best
|
||||||
|
|
||||||
|
func _starts_battle(target : Object) -> bool:
|
||||||
|
#AimTarget doit avoir : @export var starts_battle : bool = false (get() renvoie null si la propriete n'existe pas)
|
||||||
|
return target.get("starts_battle") == true
|
||||||
1
game/player/aim/throw_resolver.gd.uid
Normal file
1
game/player/aim/throw_resolver.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://bxt8qv1xyw2nl
|
||||||
|
|
@ -31,20 +31,18 @@ var cam_distance_mult : float = 1.0 #multiplicateur actuel (interpole)
|
||||||
@export var lock_on_speed : float = 8.0
|
@export var lock_on_speed : float = 8.0
|
||||||
var lock_target : Node3D = null
|
var lock_target : Node3D = null
|
||||||
|
|
||||||
#keybinding variables
|
#input : renseigne par InputManagementComponent, le CameraHolder ne connait aucun nom d'action
|
||||||
var aim_cam_action : StringName
|
var player_input : InputManagementComponent
|
||||||
var cam_zoom_in_action : StringName
|
|
||||||
var cam_zoom_out_action : StringName
|
|
||||||
var cam_look_up_action : StringName
|
|
||||||
var cam_look_down_action : StringName
|
|
||||||
var cam_look_left_action : StringName
|
|
||||||
var cam_look_right_action : StringName
|
|
||||||
|
|
||||||
#reference variables
|
#reference variables
|
||||||
@onready var spring_arm : SpringArm3D = %SpringArm3D
|
@export_group("References")
|
||||||
@onready var cam : Camera3D = %Camera3D
|
@export var spring_arm : SpringArm3D
|
||||||
|
@export var cam : Camera3D
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
if spring_arm == null or cam == null:
|
||||||
|
push_error("CameraHolder : assigne spring_arm et cam dans l'inspecteur.")
|
||||||
|
return
|
||||||
#set fov, capture mouse cursor, and enable camera
|
#set fov, capture mouse cursor, and enable camera
|
||||||
cam.fov = fov_val
|
cam.fov = fov_val
|
||||||
#Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
#Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||||
|
|
@ -58,10 +56,6 @@ func set_active(state : bool) -> void:
|
||||||
set_process(active)
|
set_process(active)
|
||||||
|
|
||||||
func _input(event: InputEvent) -> void:
|
func _input(event: InputEvent) -> void:
|
||||||
#change cam mode (default, aim)
|
|
||||||
#if event.is_action_pressed(aim_cam_action):
|
|
||||||
#cam_aimed = !cam_aimed
|
|
||||||
|
|
||||||
if event is InputEventMouseMotion:
|
if event is InputEventMouseMotion:
|
||||||
if has_lock_target(): return
|
if has_lock_target(): return
|
||||||
var viewport_transform: Transform2D = get_tree().root.get_final_transform()
|
var viewport_transform: Transform2D = get_tree().root.get_final_transform()
|
||||||
|
|
@ -92,8 +86,7 @@ func _process(delta: float) -> void:
|
||||||
if has_lock_target():
|
if has_lock_target():
|
||||||
lock_on_handling(delta)
|
lock_on_handling(delta)
|
||||||
else:
|
else:
|
||||||
var camera_motion = Input.get_vector(cam_look_left_action, cam_look_right_action, cam_look_up_action, cam_look_down_action)
|
if player_input: rotate_from_vector(player_input.read_look_vector() * joystick_sensibility)
|
||||||
rotate_from_vector(camera_motion * joystick_sensibility)
|
|
||||||
|
|
||||||
#handle zoom
|
#handle zoom
|
||||||
zoom_handling(delta)
|
zoom_handling(delta)
|
||||||
|
|
@ -108,7 +101,7 @@ func rotate_from_vector(vector : Vector2) -> void:
|
||||||
|
|
||||||
func zoom_handling(delta : float) -> void:
|
func zoom_handling(delta : float) -> void:
|
||||||
#zoom in/out cam, and clamp zoom value between min and max zoom values
|
#zoom in/out cam, and clamp zoom value between min and max zoom values
|
||||||
spring_arm.spring_length += Input.get_axis(cam_zoom_in_action, cam_zoom_out_action) * zoom_speed * delta
|
if player_input: spring_arm.spring_length += player_input.read_zoom_axis() * zoom_speed * delta
|
||||||
spring_arm.spring_length = clamp(spring_arm.spring_length, min_spring_length, max_spring_length)
|
spring_arm.spring_length = clamp(spring_arm.spring_length, min_spring_length, max_spring_length)
|
||||||
|
|
||||||
func switch_aim_side() -> void:
|
func switch_aim_side() -> void:
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://ba5gqdhohvdin" path="res://game/player/camera/camera_holder.gd" id="1_bvor0"]
|
[ext_resource type="Script" uid="uid://ba5gqdhohvdin" path="res://game/player/camera/camera_holder.gd" id="1_bvor0"]
|
||||||
|
|
||||||
[node name="CameraHolder" type="Node3D" unique_id=232451612]
|
[node name="CameraHolder" type="Node3D" unique_id=232451612 node_paths=PackedStringArray("spring_arm", "cam")]
|
||||||
script = ExtResource("1_bvor0")
|
script = ExtResource("1_bvor0")
|
||||||
|
spring_arm = NodePath("SpringArm3D")
|
||||||
|
cam = NodePath("SpringArm3D/Camera3D")
|
||||||
|
|
||||||
[node name="SpringArm3D" type="SpringArm3D" parent="." unique_id=1346420998]
|
[node name="SpringArm3D" type="SpringArm3D" parent="." unique_id=1346420998]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
extends Node
|
extends PlayerComponent
|
||||||
|
|
||||||
class_name InputManagementComponent
|
class_name InputManagementComponent
|
||||||
|
|
||||||
#instead of setting up keybinds in 3 differents scripts, it's centralized in this one only
|
#instead of setting up keybinds in 3 differents scripts, it's centralized in this one only
|
||||||
|
#
|
||||||
|
#point d'entree unique de l'input du joueur :
|
||||||
|
# - declare les noms des actions (exports ci-dessous) : c'est la seule definition, ni PlayerCharacter ni CameraHolder n'en gardent de copie
|
||||||
|
# (le CameraHolder lit la camera par read_look_vector / read_zoom_axis)
|
||||||
|
# - fournit les lectures utilisees par les etats (read_move_dir, wants_jump...) : plus aucun etat n'appelle Input directement
|
||||||
|
# sauf pour les actions portees par un Hint (cancel, unlock, switch target, mount, dismount), lues par leur proprietaire
|
||||||
|
|
||||||
@export_group("Keybind variables")
|
@export_group("Keybind variables")
|
||||||
@export_subgroup("Player character variables")
|
@export_subgroup("Player character variables")
|
||||||
|
|
@ -14,6 +20,7 @@ class_name InputManagementComponent
|
||||||
@export var jump_action : StringName = "player_jump"
|
@export var jump_action : StringName = "player_jump"
|
||||||
@export var dodge_action : StringName = "player_dodge"
|
@export var dodge_action : StringName = "player_dodge"
|
||||||
@export var crouch_action : StringName = "player_crouch"
|
@export var crouch_action : StringName = "player_crouch"
|
||||||
|
@export var throw_action : StringName = &"player_throw" #RT / clic gauche : maintenir pour charger, relacher pour lancer
|
||||||
@export_subgroup("Camera variables")
|
@export_subgroup("Camera variables")
|
||||||
@export var aim_cam_action : StringName = "player_aim_camera"
|
@export var aim_cam_action : StringName = "player_aim_camera"
|
||||||
@export var cam_zoom_in_action : StringName = "player_zoom_camera_in"
|
@export var cam_zoom_in_action : StringName = "player_zoom_camera_in"
|
||||||
|
|
@ -23,29 +30,92 @@ class_name InputManagementComponent
|
||||||
@export var cam_look_left_action : StringName = "player_look_left"
|
@export var cam_look_left_action : StringName = "player_look_left"
|
||||||
@export var cam_look_right_action : StringName = "player_look_right"
|
@export var cam_look_right_action : StringName = "player_look_right"
|
||||||
|
|
||||||
#references variables
|
func register_with_player() -> void:
|
||||||
@onready var play_char_ref : PlayerCharacter = $".."
|
player.player_input = self
|
||||||
@onready var cam_holder_ref : CameraHolder = %CameraHolder
|
|
||||||
|
|
||||||
# Called when the node enters the scene tree for the first time.
|
# Called when the node enters the scene tree for the first time.
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
attribute_keybinds()
|
player.cam_holder.player_input = self #le CameraHolder lit la camera ici, il ne connait aucun nom d'action
|
||||||
|
InputRegistry.ensure(throw_action, [InputRegistry.pad_axis(JOY_AXIS_TRIGGER_RIGHT, 1.0), InputRegistry.mouse(MOUSE_BUTTON_LEFT)])
|
||||||
|
|
||||||
func attribute_keybinds() -> void:
|
#---------- camera ----------
|
||||||
play_char_ref.move_forward_action = move_forward_action
|
|
||||||
play_char_ref.move_backward_action = move_backward_action
|
|
||||||
play_char_ref.move_left_action = move_left_action
|
|
||||||
play_char_ref.move_right_action = move_right_action
|
|
||||||
play_char_ref.run_action = run_action
|
|
||||||
play_char_ref.jump_action = jump_action
|
|
||||||
play_char_ref.dodge_action = dodge_action
|
|
||||||
play_char_ref.crouch_action = crouch_action
|
|
||||||
play_char_ref.aim_cam_action = aim_cam_action
|
|
||||||
|
|
||||||
cam_holder_ref.aim_cam_action = aim_cam_action
|
func read_look_vector() -> Vector2:
|
||||||
cam_holder_ref.cam_zoom_in_action = cam_zoom_in_action
|
#rotation de la camera a la manette / aux touches (la souris est geree par le CameraHolder lui-meme)
|
||||||
cam_holder_ref.cam_zoom_out_action = cam_zoom_out_action
|
return Input.get_vector(cam_look_left_action, cam_look_right_action, cam_look_up_action, cam_look_down_action)
|
||||||
cam_holder_ref.cam_look_up_action = cam_look_up_action
|
|
||||||
cam_holder_ref.cam_look_down_action = cam_look_down_action
|
func read_zoom_axis() -> float:
|
||||||
cam_holder_ref.cam_look_left_action = cam_look_left_action
|
return Input.get_axis(cam_zoom_in_action, cam_zoom_out_action)
|
||||||
cam_holder_ref.cam_look_right_action = cam_look_right_action
|
|
||||||
|
#---------- deplacement ----------
|
||||||
|
|
||||||
|
func read_move_dir() -> Vector2:
|
||||||
|
#direction voulue, relative a la camera
|
||||||
|
return read_move_raw().rotated(-player.cam_holder.global_rotation.y)
|
||||||
|
|
||||||
|
func read_move_raw() -> Vector2:
|
||||||
|
#direction brute du stick / des touches, sans tenir compte de la camera
|
||||||
|
return Input.get_vector(move_left_action, move_right_action, move_forward_action, move_backward_action)
|
||||||
|
|
||||||
|
#---------- input consomme ----------
|
||||||
|
|
||||||
|
#un appui consomme est ignore par tous les getters ci-dessous jusqu'a ce que le bouton soit relache.
|
||||||
|
#sert quand un meme bouton a deux roles (ex : A monte sur un pokemon ET fait sauter) : celui qui agit en premier consomme l'appui
|
||||||
|
#(avant : MountRider bloquait des etats pendant une frame avec un await). Les composants qui consomment doivent s'executer
|
||||||
|
#avant la StateMachine (process_physics_priority plus petit, comme MountRider).
|
||||||
|
|
||||||
|
var _consumed : Dictionary = {} #action -> true
|
||||||
|
|
||||||
|
func consume(action : StringName) -> void:
|
||||||
|
if Input.is_action_pressed(action): _consumed[action] = true #rien a consommer si le bouton n'est pas enfonce
|
||||||
|
|
||||||
|
func is_consumed(action : StringName) -> bool:
|
||||||
|
if !_consumed.has(action): return false
|
||||||
|
if Input.is_action_pressed(action): return true
|
||||||
|
_consumed.erase(action) #bouton relache : l'action redevient disponible
|
||||||
|
return false
|
||||||
|
|
||||||
|
func _just_pressed(action : StringName) -> bool:
|
||||||
|
return !is_consumed(action) and Input.is_action_just_pressed(action)
|
||||||
|
|
||||||
|
func _held(action : StringName) -> bool:
|
||||||
|
return !is_consumed(action) and Input.is_action_pressed(action)
|
||||||
|
|
||||||
|
#---------- actions au sol ----------
|
||||||
|
|
||||||
|
func wants_dodge() -> bool:
|
||||||
|
return _just_pressed(dodge_action) and player.dodge_cooldown <= 0.0
|
||||||
|
|
||||||
|
func wants_jump() -> bool:
|
||||||
|
#depart d'un saut depuis le sol : tient compte de auto_jump (rester appuye)
|
||||||
|
if player.auto_jump: return _held(jump_action)
|
||||||
|
return _just_pressed(jump_action)
|
||||||
|
|
||||||
|
func jump_pressed() -> bool:
|
||||||
|
return _just_pressed(jump_action)
|
||||||
|
|
||||||
|
func jump_released() -> bool:
|
||||||
|
return Input.is_action_just_released(jump_action)
|
||||||
|
|
||||||
|
func crouch_pressed() -> bool:
|
||||||
|
return _just_pressed(crouch_action)
|
||||||
|
|
||||||
|
func run_pressed() -> bool:
|
||||||
|
return _held(run_action)
|
||||||
|
|
||||||
|
func run_just_pressed() -> bool:
|
||||||
|
return _just_pressed(run_action)
|
||||||
|
|
||||||
|
#---------- visee et lancer ----------
|
||||||
|
|
||||||
|
func aim_pressed() -> bool:
|
||||||
|
return _just_pressed(aim_cam_action)
|
||||||
|
|
||||||
|
func aim_held() -> bool:
|
||||||
|
return _held(aim_cam_action)
|
||||||
|
|
||||||
|
func throw_pressed() -> bool:
|
||||||
|
return _just_pressed(throw_action)
|
||||||
|
|
||||||
|
func throw_held() -> bool:
|
||||||
|
return _held(throw_action)
|
||||||
34
game/player/input/input_registry.gd
Normal file
34
game/player/input/input_registry.gd
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
class_name InputRegistry
|
||||||
|
|
||||||
|
#declaration d'actions dans l'InputMap depuis le code : une seule implementation (avant : _ensure_action copie dans ThrowState et MountRider,
|
||||||
|
#et des blocs identiques dans AimState/ThrowState pour throw_action).
|
||||||
|
#ne fait rien si l'action existe deja (par exemple definie dans Projet > Parametres du projet > Map des entrees)
|
||||||
|
|
||||||
|
static func ensure(action : StringName, events : Array) -> void:
|
||||||
|
if InputMap.has_action(action): return
|
||||||
|
InputMap.add_action(action)
|
||||||
|
for event in events:
|
||||||
|
InputMap.action_add_event(action, event)
|
||||||
|
|
||||||
|
#---------- fabriques d'evenements ----------
|
||||||
|
|
||||||
|
static func pad_button(button : JoyButton) -> InputEventJoypadButton:
|
||||||
|
var event := InputEventJoypadButton.new()
|
||||||
|
event.button_index = button
|
||||||
|
return event
|
||||||
|
|
||||||
|
static func pad_axis(axis : JoyAxis, value : float) -> InputEventJoypadMotion:
|
||||||
|
var event := InputEventJoypadMotion.new()
|
||||||
|
event.axis = axis
|
||||||
|
event.axis_value = value
|
||||||
|
return event
|
||||||
|
|
||||||
|
static func mouse(button : MouseButton) -> InputEventMouseButton:
|
||||||
|
var event := InputEventMouseButton.new()
|
||||||
|
event.button_index = button
|
||||||
|
return event
|
||||||
|
|
||||||
|
static func key(keycode : Key) -> InputEventKey:
|
||||||
|
var event := InputEventKey.new()
|
||||||
|
event.physical_keycode = keycode
|
||||||
|
return event
|
||||||
1
game/player/input/input_registry.gd.uid
Normal file
1
game/player/input/input_registry.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dj1gn8g0eblyi
|
||||||
19
game/player/input/pokemon_recall.gd
Normal file
19
game/player/input/pokemon_recall.gd
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
class_name PokemonRecall
|
||||||
|
|
||||||
|
#regle unique du rappel, partagee par StateMachine, AimState et RecallInput (avant : 3 copies qui pouvaient se contredire) :
|
||||||
|
# - un appui sur RT rappelle le pokemon SELECTIONNE s'il est dehors
|
||||||
|
# - sinon, c'est un lancer (meme si un autre pokemon est deja sorti)
|
||||||
|
# - aucun pokemon selectionne : rien ne se passe
|
||||||
|
|
||||||
|
static func find_selected_out(tree : SceneTree) -> SentPokemon:
|
||||||
|
#le SentPokemon du pokemon selectionne s'il est actuellement dehors, sinon null
|
||||||
|
var selected : PokemonInstance = GameData.get_selected_pokemon()
|
||||||
|
if selected == null: return null
|
||||||
|
return SentPokemon.find_for(tree, selected)
|
||||||
|
|
||||||
|
static func recall_selected(tree : SceneTree) -> bool:
|
||||||
|
#rappelle le pokemon selectionne s'il est dehors. true si un rappel a eu lieu
|
||||||
|
var sent : SentPokemon = find_selected_out(tree)
|
||||||
|
if sent == null: return false
|
||||||
|
sent.recall()
|
||||||
|
return true
|
||||||
1
game/player/input/pokemon_recall.gd.uid
Normal file
1
game/player/input/pokemon_recall.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://cwbji81rt6t36
|
||||||
|
|
@ -1,26 +1,17 @@
|
||||||
class_name RecallInput
|
class_name RecallInput
|
||||||
extends Node
|
extends PlayerComponent
|
||||||
|
|
||||||
#rappel d'un pokemon hors visee : un appui sur RT / clic gauche rappelle le pokemon sorti.
|
#rappel du pokemon selectionne hors visee : un appui sur RT / clic gauche le rappelle s'il est dehors.
|
||||||
#- le pokemon selectionne (HUD) s'il est dehors, sinon le dernier sorti
|
#- seul le pokemon selectionne (HUD) est rappele : s'il n'est pas dehors, l'appui lance (voir StateMachine.try_start_throw)
|
||||||
#- pendant la visee et le lancer, c'est AimState / ThrowState qui gerent cet appui
|
#- pendant un lancer (ThrowState), c'est lui qui gere cet appui ; la visee n'est plus un etat, elle ne change rien ici
|
||||||
|
#- la regle elle-meme est dans PokemonRecall
|
||||||
#
|
#
|
||||||
#usage : ajouter ce node comme enfant de ton PlayerCharacter (il le detecte via son parent)
|
#usage : ajouter ce node sous ton PlayerCharacter (il le retrouve tout seul, voir PlayerComponent)
|
||||||
|
|
||||||
@export var player : PlayerCharacter
|
@export var ignored_states : Array[String] = [States.THROW] #noms de nodes d'etats (voir States)
|
||||||
@export var throw_action : StringName = &"player_throw"
|
|
||||||
@export var ignored_states : Array[String] = ["Aim", "Throw"] #valeurs de state_name
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
if player == null: player = get_parent() as PlayerCharacter
|
|
||||||
|
|
||||||
func _physics_process(_delta : float) -> void:
|
func _physics_process(_delta : float) -> void:
|
||||||
if player == null or !InputMap.has_action(throw_action): return
|
if player == null or !player.player_input.throw_pressed(): return
|
||||||
if !Input.is_action_just_pressed(throw_action): return
|
|
||||||
if player.state_machine.curr_state_name in ignored_states: return
|
if player.state_machine.curr_state_name in ignored_states: return
|
||||||
|
|
||||||
var target : SentPokemon = null
|
PokemonRecall.recall_selected(get_tree())
|
||||||
var selected : PokemonInstance = GameData.get_selected_pokemon()
|
|
||||||
if selected: target = SentPokemon.find_for(get_tree(), selected)
|
|
||||||
if target == null: target = SentPokemon.get_latest(get_tree())
|
|
||||||
if target: target.recall()
|
|
||||||
30
game/player/input/state_hints.gd
Normal file
30
game/player/input/state_hints.gd
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
extends PlayerComponent
|
||||||
|
|
||||||
|
class_name StateHints
|
||||||
|
|
||||||
|
#affiche dans le HUD les hints de l'etat courant du joueur (PlayerState.get_hints). La StateMachine, generique, ne connait pas le HUD :
|
||||||
|
#ce composant ecoute ses changements d'etat et les hints_changed des etats.
|
||||||
|
#composant a ajouter sous le PlayerCharacter (le nom du node est libre, voir PlayerComponent).
|
||||||
|
|
||||||
|
func register_with_player() -> void:
|
||||||
|
player.state_hints = self
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
var machine : StateMachine = player.state_machine
|
||||||
|
machine.state_changed.connect(_on_state_changed)
|
||||||
|
for child in machine.get_children():
|
||||||
|
if child is PlayerState: child.hints_changed.connect(_on_hints_changed.bind(child))
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
Hints.clear_hints(self)
|
||||||
|
|
||||||
|
func _on_state_changed(_from : State, _to : State) -> void:
|
||||||
|
publish()
|
||||||
|
|
||||||
|
func _on_hints_changed(state : PlayerState) -> void:
|
||||||
|
if state == player.state_machine.curr_state: publish()
|
||||||
|
|
||||||
|
func publish() -> void:
|
||||||
|
var state := player.state_machine.curr_state as PlayerState
|
||||||
|
if state and !state.keep_previous_hints:
|
||||||
|
Hints.show_hints(self, state.get_hints())
|
||||||
1
game/player/input/state_hints.gd.uid
Normal file
1
game/player/input/state_hints.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://b0j1jxv6uu2y8
|
||||||
|
|
@ -12,9 +12,12 @@ var last_input_dir : Vector2
|
||||||
var last_frame_position : Vector3
|
var last_frame_position : Vector3
|
||||||
var last_frame_velocity : Vector3
|
var last_frame_velocity : Vector3
|
||||||
var was_on_floor : bool = false
|
var was_on_floor : bool = false
|
||||||
var walk_or_run : String = "WalkState" #keep in memory if play char was walking or running before being in the air
|
enum Locomotion { WALK, RUN }
|
||||||
|
var locomotion : Locomotion = Locomotion.WALK #keep in memory if play char was walking or running before being in the air
|
||||||
|
var walk_or_run : String: #ANCIEN NOM, lecture seule : a supprimer des que plus aucun script ne l'utilise (chercher "walk_or_run" dans le projet)
|
||||||
|
get: return get_locomotion_state()
|
||||||
var is_dodging : bool = false #true while DodgeState is active ; hook this up to a hurtbox/i-frame check later
|
var is_dodging : bool = false #true while DodgeState is active ; hook this up to a hurtbox/i-frame check later
|
||||||
var is_crouching : bool = false #true while CrouchState is active ; hook this up to a collision-shape resize later
|
var is_crouching : bool = false #posture accroupie (voir GroundedState.toggle_crouch) ; hook this up to a collision-shape resize later
|
||||||
var is_mounted : bool = false #true tant que MountRider a le joueur sur un pokemon
|
var is_mounted : bool = false #true tant que MountRider a le joueur sur un pokemon
|
||||||
var _base_ground_stats : Dictionary = {}
|
var _base_ground_stats : Dictionary = {}
|
||||||
|
|
||||||
|
|
@ -67,32 +70,32 @@ var coyote_jump_on : bool = false
|
||||||
@onready var jump_gravity : float = ((-2.0 * jump_height) / (jump_time_to_peak * jump_time_to_peak)) * -1.0
|
@onready var jump_gravity : float = ((-2.0 * jump_height) / (jump_time_to_peak * jump_time_to_peak)) * -1.0
|
||||||
@onready var fall_gravity : float = ((-2.0 * jump_height) / (jump_time_to_descent * jump_time_to_descent)) * -1.0
|
@onready var fall_gravity : float = ((-2.0 * jump_height) / (jump_time_to_descent * jump_time_to_descent)) * -1.0
|
||||||
|
|
||||||
#keybinding variables
|
|
||||||
var move_forward_action : StringName
|
|
||||||
var move_backward_action : StringName
|
|
||||||
var move_left_action : StringName
|
|
||||||
var move_right_action : StringName
|
|
||||||
var run_action : StringName
|
|
||||||
var jump_action : StringName
|
|
||||||
var dodge_action : StringName
|
|
||||||
var crouch_action : StringName
|
|
||||||
var aim_cam_action : StringName
|
|
||||||
|
|
||||||
@export_group("Model variables")
|
@export_group("Model variables")
|
||||||
@export var model_rot_speed : float = 6.5
|
@export var model_rot_speed : float = 6.5
|
||||||
@export var follow_cam_pos_when_aimed : bool = false
|
@export var follow_cam_pos_when_aimed : bool = false
|
||||||
|
|
||||||
#references variables
|
#references variables : rien n'est cherche par son nom
|
||||||
@onready var visual_root = %VisualRoot
|
#- nodes de la scene : exports a assigner dans l'inspecteur (ils suivent un renommage ou un deplacement du node)
|
||||||
|
@export_group("References")
|
||||||
|
@export var visual_root : Node3D
|
||||||
#@onready var godot_plush_skin : GodotPlushSkin = %GodotPlushSkin
|
#@onready var godot_plush_skin : GodotPlushSkin = %GodotPlushSkin
|
||||||
#@onready var particles_manager : ParticlesManager = %ParticlesManager
|
#@onready var particles_manager : ParticlesManager = %ParticlesManager
|
||||||
@onready var cam_holder: CameraHolder = %CameraHolder
|
@export var cam_holder : CameraHolder
|
||||||
@onready var state_machine : StateMachine = %StateMachine
|
@export var state_machine : StateMachine
|
||||||
#@onready var foot_step_audio = %FootStepAudio
|
#@onready var foot_step_audio = %FootStepAudio
|
||||||
#@onready var impact_audio = %ImpactAudio
|
#@onready var impact_audio = %ImpactAudio
|
||||||
#@onready var wave_audio = %WaveAudio
|
#@onready var wave_audio = %WaveAudio
|
||||||
@onready var collision_shape_3d = %CollisionShape3D
|
@export var collision_shape_3d : CollisionShape3D
|
||||||
#@onready var floor_check : RayCast3D = %FloorRaycast
|
#@onready var floor_check : RayCast3D = %FloorRaycast
|
||||||
|
#- composants : ils s'enregistrent eux-memes (voir PlayerComponent), le joueur ne connait pas leur nom de node
|
||||||
|
var player_input : InputManagementComponent
|
||||||
|
var aim_targeting : AimTargeting #visee : mode superpose aux etats + ciblage (voir AimTargeting)
|
||||||
|
var throw_resolver : ThrowResolver #trajectoire de la balle, atterrissage, envoi du pokemon, combat
|
||||||
|
var state_hints : StateHints #affiche les hints de l'etat courant dans le HUD
|
||||||
|
|
||||||
|
var can_crouch : bool = true
|
||||||
|
var is_aiming : bool: #visee en cours (mode superpose aux etats, voir AimTargeting)
|
||||||
|
get: return aim_targeting != null and aim_targeting.active
|
||||||
|
|
||||||
#particles variables
|
#particles variables
|
||||||
#@onready var movement_dust = %MovementDust
|
#@onready var movement_dust = %MovementDust
|
||||||
|
|
@ -102,6 +105,8 @@ var aim_cam_action : StringName
|
||||||
var aim_target : AimTarget = null
|
var aim_target : AimTarget = null
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
|
_validate_wiring()
|
||||||
|
|
||||||
#set move variables, and value references
|
#set move variables, and value references
|
||||||
move_speed = walk_speed
|
move_speed = walk_speed
|
||||||
move_accel = walk_accel
|
move_accel = walk_accel
|
||||||
|
|
@ -114,6 +119,20 @@ func _ready():
|
||||||
|
|
||||||
#connect_footstep_audio_effect()
|
#connect_footstep_audio_effect()
|
||||||
|
|
||||||
|
func _validate_wiring() -> void:
|
||||||
|
#un seul endroit qui dit ce qui manque, au lieu d'une erreur obscure sur un null plus tard
|
||||||
|
var missing : Array[String] = []
|
||||||
|
if visual_root == null: missing.append("visual_root (export)")
|
||||||
|
if cam_holder == null: missing.append("cam_holder (export)")
|
||||||
|
if state_machine == null: missing.append("state_machine (export)")
|
||||||
|
if collision_shape_3d == null: missing.append("collision_shape_3d (export)")
|
||||||
|
if player_input == null: missing.append("un InputManagementComponent sous le joueur")
|
||||||
|
if aim_targeting == null: missing.append("un AimTargeting sous le joueur")
|
||||||
|
if throw_resolver == null: missing.append("un ThrowResolver sous le joueur")
|
||||||
|
if state_hints == null: missing.append("un StateHints sous le joueur")
|
||||||
|
if !missing.is_empty():
|
||||||
|
push_error("PlayerCharacter : dependances manquantes : " + ", ".join(PackedStringArray(missing)))
|
||||||
|
|
||||||
func _process(delta: float):
|
func _process(delta: float):
|
||||||
modify_model_orientation(delta)
|
modify_model_orientation(delta)
|
||||||
|
|
||||||
|
|
@ -225,7 +244,14 @@ func squash_and_strech(value : float, timing : float):
|
||||||
|
|
||||||
func update_run_toggle() -> void:
|
func update_run_toggle() -> void:
|
||||||
if continious_run:
|
if continious_run:
|
||||||
if Input.is_action_just_pressed(run_action):
|
if player_input.run_just_pressed():
|
||||||
walk_or_run = "WalkState" if walk_or_run == "RunState" else "RunState"
|
locomotion = Locomotion.WALK if is_running() else Locomotion.RUN
|
||||||
else:
|
else:
|
||||||
walk_or_run = "RunState" if Input.is_action_pressed(run_action) else "WalkState"
|
locomotion = Locomotion.RUN if player_input.run_pressed() else Locomotion.WALK
|
||||||
|
|
||||||
|
func is_running() -> bool:
|
||||||
|
return locomotion == Locomotion.RUN
|
||||||
|
|
||||||
|
func get_locomotion_state() -> String:
|
||||||
|
#etat au sol correspondant a la derniere allure (voir State.request_locomotion)
|
||||||
|
return States.RUN if is_running() else States.WALK
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,21 @@
|
||||||
[ext_resource type="Script" uid="uid://dy4wrpvpep1jq" path="res://game/player/states/state_machine.gd" id="3_o4naa"]
|
[ext_resource type="Script" uid="uid://dy4wrpvpep1jq" path="res://game/player/states/state_machine.gd" id="3_o4naa"]
|
||||||
[ext_resource type="Script" uid="uid://bf6hjwfa74ttj" path="res://game/player/states/idle_state.gd" id="4_38lxl"]
|
[ext_resource type="Script" uid="uid://bf6hjwfa74ttj" path="res://game/player/states/idle_state.gd" id="4_38lxl"]
|
||||||
[ext_resource type="Script" uid="uid://2agliol5dulc" path="res://game/player/states/walk_state.gd" id="4_lg056"]
|
[ext_resource type="Script" uid="uid://2agliol5dulc" path="res://game/player/states/walk_state.gd" id="4_lg056"]
|
||||||
[ext_resource type="Script" uid="uid://b1rvj44fyaea4" path="res://game/ui/hints/hint.gd" id="6_7qhxe"]
|
|
||||||
[ext_resource type="Script" uid="uid://dr5p8ygj25w6x" path="res://game/player/states/jump_state.gd" id="6_lwgph"]
|
[ext_resource type="Script" uid="uid://dr5p8ygj25w6x" path="res://game/player/states/jump_state.gd" id="6_lwgph"]
|
||||||
[ext_resource type="Resource" uid="uid://cdx4qndy5xwto" path="res://data/hints/jump.tres" id="7_56w3b"]
|
[ext_resource type="Resource" uid="uid://cdx4qndy5xwto" path="res://data/hints/jump.tres" id="7_56w3b"]
|
||||||
[ext_resource type="Script" uid="uid://dn5idrvu68k83" path="res://game/player/states/aim_state.gd" id="7_kxr4l"]
|
|
||||||
[ext_resource type="Script" uid="uid://dpcfffs3vt5co" path="res://game/player/states/run_state.gd" id="8_j4mf1"]
|
[ext_resource type="Script" uid="uid://dpcfffs3vt5co" path="res://game/player/states/run_state.gd" id="8_j4mf1"]
|
||||||
[ext_resource type="Script" uid="uid://c3jh22p4w3xco" path="res://game/player/states/throw_state.gd" id="8_k0r6x"]
|
[ext_resource type="Script" uid="uid://c3jh22p4w3xco" path="res://game/player/states/throw_state.gd" id="8_k0r6x"]
|
||||||
[ext_resource type="Script" uid="uid://bwojkp7yrwswt" path="res://game/player/states/dodge_state.gd" id="9_6abew"]
|
[ext_resource type="Script" uid="uid://bwojkp7yrwswt" path="res://game/player/states/dodge_state.gd" id="9_6abew"]
|
||||||
[ext_resource type="Script" uid="uid://d0577tj0c23bu" path="res://game/player/states/crouch_state.gd" id="10_mi63v"]
|
|
||||||
[ext_resource type="Resource" uid="uid://c4fqcdrf6il7v" path="res://data/hints/switch_target.tres" id="11_lapey"]
|
[ext_resource type="Resource" uid="uid://c4fqcdrf6il7v" path="res://data/hints/switch_target.tres" id="11_lapey"]
|
||||||
[ext_resource type="Resource" uid="uid://bow3d0e0fcto7" path="res://data/hints/cancel_throw.tres" id="12_iqmup"]
|
[ext_resource type="Resource" uid="uid://bow3d0e0fcto7" path="res://data/hints/cancel_throw.tres" id="12_iqmup"]
|
||||||
[ext_resource type="Script" uid="uid://c820hnwuwpmlg" path="res://game/player/input/recall_input.gd" id="13_siyau"]
|
[ext_resource type="Script" uid="uid://c820hnwuwpmlg" path="res://game/player/input/recall_input.gd" id="13_siyau"]
|
||||||
[ext_resource type="Script" uid="uid://c4vkkdl0an244" path="res://game/mount/mount_rider.gd" id="14_dkijf"]
|
[ext_resource type="Script" uid="uid://c4vkkdl0an244" path="res://game/mount/mount_rider.gd" id="14_dkijf"]
|
||||||
|
[ext_resource type="Script" uid="uid://sixraavy7k2h" path="res://game/player/states/inair_state.gd" id="16_lapey"]
|
||||||
[ext_resource type="Resource" uid="uid://c18ntr4wb56m2" path="res://data/hints/mount.tres" id="20_lapey"]
|
[ext_resource type="Resource" uid="uid://c18ntr4wb56m2" path="res://data/hints/mount.tres" id="20_lapey"]
|
||||||
[ext_resource type="Resource" uid="uid://cfl2q6f3sjy1v" path="res://data/hints/dismount.tres" id="21_iqmup"]
|
[ext_resource type="Resource" uid="uid://cfl2q6f3sjy1v" path="res://data/hints/dismount.tres" id="21_iqmup"]
|
||||||
|
[ext_resource type="Script" uid="uid://b0j1jxv6uu2y8" path="res://game/player/input/state_hints.gd" id="22_iqmup"]
|
||||||
|
[ext_resource type="Script" uid="uid://qh738m0cmw8b" path="res://game/player/aim/aim_targeting.gd" id="22_p12ry"]
|
||||||
|
[ext_resource type="Script" uid="uid://bxt8qv1xyw2nl" path="res://game/player/aim/throw_resolver.gd" id="23_6dxcy"]
|
||||||
|
|
||||||
[sub_resource type="Curve" id="Curve_kxr4l"]
|
[sub_resource type="Curve" id="Curve_kxr4l"]
|
||||||
_limits = [0.0, 10.0, 0.0, 1.0]
|
_limits = [0.0, 10.0, 0.0, 1.0]
|
||||||
|
|
@ -49,13 +50,18 @@ size = Vector3(0.1484375, 0.1640625, 0.907959)
|
||||||
|
|
||||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_3agn1"]
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_3agn1"]
|
||||||
|
|
||||||
[node name="PlayerCharacter" type="CharacterBody3D" unique_id=1830301535]
|
[node name="PlayerCharacter" type="CharacterBody3D" unique_id=1830301535 node_paths=PackedStringArray("visual_root", "cam_holder", "state_machine", "collision_shape_3d")]
|
||||||
script = ExtResource("1_pj0hh")
|
script = ExtResource("1_pj0hh")
|
||||||
nb_jumps_in_air_allowed = 0
|
nb_jumps_in_air_allowed = 0
|
||||||
|
coyote_jump_cooldown = 1.0
|
||||||
in_air_move_speed = Array[Curve]([SubResource("Curve_kxr4l"), SubResource("Curve_j4mf1")])
|
in_air_move_speed = Array[Curve]([SubResource("Curve_kxr4l"), SubResource("Curve_j4mf1")])
|
||||||
in_air_accel = Array[Curve]([SubResource("Curve_6abew"), SubResource("Curve_mi63v")])
|
in_air_accel = Array[Curve]([SubResource("Curve_6abew"), SubResource("Curve_mi63v")])
|
||||||
hit_wall_cut_velocity = true
|
hit_wall_cut_velocity = true
|
||||||
follow_cam_pos_when_aimed = true
|
follow_cam_pos_when_aimed = true
|
||||||
|
visual_root = NodePath("VisualRoot")
|
||||||
|
cam_holder = NodePath("CameraHolder")
|
||||||
|
state_machine = NodePath("StateMachine")
|
||||||
|
collision_shape_3d = NodePath("CollisionShape3D")
|
||||||
|
|
||||||
[node name="VisualRoot" type="Node3D" parent="." unique_id=1587327772]
|
[node name="VisualRoot" type="Node3D" parent="." unique_id=1587327772]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
@ -87,20 +93,12 @@ initial_state = NodePath("IdleState")
|
||||||
|
|
||||||
[node name="IdleState" type="Node" parent="StateMachine" unique_id=1417869633]
|
[node name="IdleState" type="Node" parent="StateMachine" unique_id=1417869633]
|
||||||
script = ExtResource("4_38lxl")
|
script = ExtResource("4_38lxl")
|
||||||
hints = Array[ExtResource("6_7qhxe")]([ExtResource("7_56w3b")])
|
|
||||||
|
|
||||||
[node name="WalkState" type="Node" parent="StateMachine" unique_id=1524475809]
|
[node name="WalkState" type="Node" parent="StateMachine" unique_id=1524475809]
|
||||||
script = ExtResource("4_lg056")
|
script = ExtResource("4_lg056")
|
||||||
hints = Array[ExtResource("6_7qhxe")]([ExtResource("7_56w3b")])
|
|
||||||
|
|
||||||
[node name="JumpState" type="Node" parent="StateMachine" unique_id=711160985]
|
[node name="JumpState" type="Node" parent="StateMachine" unique_id=711160985]
|
||||||
script = ExtResource("6_lwgph")
|
script = ExtResource("6_lwgph")
|
||||||
hints = Array[ExtResource("6_7qhxe")]([ExtResource("7_56w3b")])
|
|
||||||
|
|
||||||
[node name="AimState" type="Node" parent="StateMachine" unique_id=296560188]
|
|
||||||
script = ExtResource("7_kxr4l")
|
|
||||||
switch_hint = ExtResource("11_lapey")
|
|
||||||
cancel_hint = ExtResource("12_iqmup")
|
|
||||||
|
|
||||||
[node name="ThrowState" type="Node" parent="StateMachine" unique_id=1159570489]
|
[node name="ThrowState" type="Node" parent="StateMachine" unique_id=1159570489]
|
||||||
script = ExtResource("8_k0r6x")
|
script = ExtResource("8_k0r6x")
|
||||||
|
|
@ -108,14 +106,12 @@ cancel_hint = ExtResource("12_iqmup")
|
||||||
|
|
||||||
[node name="RunState" type="Node" parent="StateMachine" unique_id=1345421775]
|
[node name="RunState" type="Node" parent="StateMachine" unique_id=1345421775]
|
||||||
script = ExtResource("8_j4mf1")
|
script = ExtResource("8_j4mf1")
|
||||||
hints = Array[ExtResource("6_7qhxe")]([ExtResource("7_56w3b")])
|
|
||||||
|
|
||||||
[node name="DodgeState" type="Node" parent="StateMachine" unique_id=232179400]
|
[node name="DodgeState" type="Node" parent="StateMachine" unique_id=232179400]
|
||||||
script = ExtResource("9_6abew")
|
script = ExtResource("9_6abew")
|
||||||
keep_previous_hints = true
|
|
||||||
|
|
||||||
[node name="CrouchState" type="Node" parent="StateMachine" unique_id=381419367]
|
[node name="InairState" type="Node" parent="StateMachine" unique_id=1405519998]
|
||||||
script = ExtResource("10_mi63v")
|
script = ExtResource("16_lapey")
|
||||||
|
|
||||||
[node name="InputManagementComponent" type="Node" parent="." unique_id=1981951371]
|
[node name="InputManagementComponent" type="Node" parent="." unique_id=1981951371]
|
||||||
script = ExtResource("2_r5dqo")
|
script = ExtResource("2_r5dqo")
|
||||||
|
|
@ -128,3 +124,13 @@ script = ExtResource("14_dkijf")
|
||||||
mount_hint = ExtResource("20_lapey")
|
mount_hint = ExtResource("20_lapey")
|
||||||
dismount_hint = ExtResource("21_iqmup")
|
dismount_hint = ExtResource("21_iqmup")
|
||||||
jump_hint = ExtResource("7_56w3b")
|
jump_hint = ExtResource("7_56w3b")
|
||||||
|
|
||||||
|
[node name="AimTargeting" type="Node" parent="." unique_id=1285504499]
|
||||||
|
script = ExtResource("22_p12ry")
|
||||||
|
switch_hint = ExtResource("11_lapey")
|
||||||
|
|
||||||
|
[node name="ThrowResolver" type="Node" parent="." unique_id=1062063502]
|
||||||
|
script = ExtResource("23_6dxcy")
|
||||||
|
|
||||||
|
[node name="StateHints" type="Node" parent="." unique_id=686759706]
|
||||||
|
script = ExtResource("22_iqmup")
|
||||||
|
|
|
||||||
29
game/player/player_component.gd
Normal file
29
game/player/player_component.gd
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
class_name PlayerComponent
|
||||||
|
|
||||||
|
#base des composants du joueur (InputManagementComponent, AimTargeting, ThrowResolver, MountRider, RecallInput...).
|
||||||
|
#- le joueur est retrouve en remontant l'arbre : le composant peut etre place n'importe ou sous le PlayerCharacter
|
||||||
|
# (directement, ou dans un node de rangement), sans dependre de sa position exacte ni de get_parent().
|
||||||
|
#- un composant qui a une place dans le joueur (ex : player.aim_targeting) s'y enregistre lui-meme dans register_with_player() :
|
||||||
|
# le joueur n'a plus besoin de le chercher par son nom ($AimTargeting) et le node peut etre renomme librement.
|
||||||
|
#_enter_tree() est appele avant tous les _ready() : le joueur est donc deja renseigne quand un composant demarre.
|
||||||
|
|
||||||
|
var player : PlayerCharacter
|
||||||
|
|
||||||
|
func _enter_tree() -> void:
|
||||||
|
player = _find_player()
|
||||||
|
if player == null:
|
||||||
|
push_error("%s : ce composant doit etre place sous un PlayerCharacter." % name)
|
||||||
|
return
|
||||||
|
register_with_player()
|
||||||
|
|
||||||
|
func register_with_player() -> void:
|
||||||
|
pass #a surcharger : player.xxx = self
|
||||||
|
|
||||||
|
func _find_player() -> PlayerCharacter:
|
||||||
|
var n : Node = get_parent()
|
||||||
|
while n != null:
|
||||||
|
if n is PlayerCharacter: return n
|
||||||
|
n = n.get_parent()
|
||||||
|
return null
|
||||||
1
game/player/player_component.gd.uid
Normal file
1
game/player/player_component.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://jsb4l4g023o3
|
||||||
|
|
@ -1,254 +0,0 @@
|
||||||
extends State
|
|
||||||
|
|
||||||
class_name AimState
|
|
||||||
|
|
||||||
var state_name : String = "Aim"
|
|
||||||
|
|
||||||
var play_char : CharacterBody3D
|
|
||||||
|
|
||||||
@export var aim_action : StringName = &"player_aim_camera"
|
|
||||||
#@export var switch_target_action : StringName = &"player_aim_switch_target"
|
|
||||||
@export var unlock_action : StringName = &"player_aim_unlock"
|
|
||||||
@export var throw_action : StringName = &"player_throw" #RT / clic gauche (cree automatiquement s'il n'existe pas)
|
|
||||||
#@export var cancel_action : StringName = &"player_throw_cancel" #annule la charge d'un lancer (cree par ThrowState)
|
|
||||||
|
|
||||||
@export_group("Targeting")
|
|
||||||
@export var throw_range : float = 20.0
|
|
||||||
@export var max_aim_angle : float = 35.0 #angle max (deg) entre l'axe camera et une cible pour qu'elle soit candidate
|
|
||||||
@export var auto_lock_angle : float = 12.0 #si une cible passe sous cet angle, on se verrouille dessus
|
|
||||||
@export var lose_range_margin : float = 1.25 #marge de distance avant de perdre une cible deja verrouillee
|
|
||||||
@export var check_line_of_sight : bool = true
|
|
||||||
@export_flags_3d_physics var line_of_sight_mask : int = 1
|
|
||||||
|
|
||||||
@export_group("Movement")
|
|
||||||
@export var aim_move_speed : float = 3.0
|
|
||||||
@export var aim_move_accel : float = 8.0
|
|
||||||
|
|
||||||
@export_group("Hints")
|
|
||||||
@export var switch_hint : Hint #switch_target.tres
|
|
||||||
@export var cancel_hint : Hint #cancel_throw.tres (la meme ressource que ThrowState)
|
|
||||||
|
|
||||||
var switch_target_action : StringName:
|
|
||||||
get: return switch_hint.action
|
|
||||||
var cancel_action : StringName:
|
|
||||||
get: return cancel_hint.action
|
|
||||||
|
|
||||||
var auto_lock_blocked : bool = false #true apres un deverrouillage manuel, tant que la cible est encore au centre
|
|
||||||
var dodging_from_aim : bool = false #true quand on quitte Aim pour rouler, pour ne pas relacher la cible verrouillee
|
|
||||||
var came_from_crouch : bool = false #true si Aim a ete declenche depuis CrouchState, pour y retourner en sortant
|
|
||||||
var keep_aim_on_exit : bool = false #true quand on part vers ThrowState : camera et verrouillage restent en place
|
|
||||||
var charging_throw : bool = false: #true quand on arrive depuis ThrowState en pleine charge : RT est encore maintenue
|
|
||||||
set(value):
|
|
||||||
if charging_throw == value: return
|
|
||||||
charging_throw = value
|
|
||||||
hints_changed.emit()
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
if InputMap.has_action(throw_action): return
|
|
||||||
InputMap.add_action(throw_action)
|
|
||||||
var trigger := InputEventJoypadMotion.new()
|
|
||||||
trigger.axis = JOY_AXIS_TRIGGER_RIGHT
|
|
||||||
trigger.axis_value = 1.0
|
|
||||||
InputMap.action_add_event(throw_action, trigger)
|
|
||||||
var click := InputEventMouseButton.new()
|
|
||||||
click.button_index = MOUSE_BUTTON_LEFT
|
|
||||||
InputMap.action_add_event(throw_action, click)
|
|
||||||
|
|
||||||
func enter(play_char_ref : CharacterBody3D) -> void:
|
|
||||||
play_char = play_char_ref
|
|
||||||
auto_lock_blocked = false
|
|
||||||
#curr_state_name n'est pas encore mis a jour a ce stade : il reflete encore l'etat qu'on est en train de quitter
|
|
||||||
#si on revient de ThrowState, came_from_crouch a deja ete transmis par ThrowState : on ne l'ecrase pas
|
|
||||||
var previous : String = play_char.state_machine.curr_state_name
|
|
||||||
if previous != "Throw" and previous != "Dodge":
|
|
||||||
came_from_crouch = previous == "Crouch"
|
|
||||||
verifications()
|
|
||||||
|
|
||||||
func verifications() -> void:
|
|
||||||
play_char.floor_snap_length = 1.0
|
|
||||||
play_char.cam_holder.cam_aimed = true
|
|
||||||
|
|
||||||
func exit() -> void:
|
|
||||||
charging_throw = false
|
|
||||||
if keep_aim_on_exit:
|
|
||||||
keep_aim_on_exit = false
|
|
||||||
return
|
|
||||||
AimReticle.hide_instance()
|
|
||||||
if dodging_from_aim:
|
|
||||||
#on roule en visant : la camera et la cible verrouillee restent actives pendant la roulade
|
|
||||||
dodging_from_aim = false
|
|
||||||
return
|
|
||||||
release_target()
|
|
||||||
if play_char: play_char.cam_holder.cam_aimed = false
|
|
||||||
|
|
||||||
func physics_update(delta : float) -> void:
|
|
||||||
if applies(): return
|
|
||||||
play_char.gravity_apply(delta)
|
|
||||||
if input_management(): return
|
|
||||||
update_target()
|
|
||||||
update_reticle()
|
|
||||||
move(delta)
|
|
||||||
|
|
||||||
func applies() -> bool:
|
|
||||||
if !play_char.is_on_floor() and play_char.velocity.y < 0.0:
|
|
||||||
transitioned.emit(self, "InairState")
|
|
||||||
return true
|
|
||||||
return false
|
|
||||||
|
|
||||||
func input_management() -> bool:
|
|
||||||
if Input.is_action_just_pressed(play_char.dodge_action) and play_char.dodge_cooldown <= 0.0:
|
|
||||||
dodging_from_aim = true
|
|
||||||
transitioned.emit(self, "DodgeState")
|
|
||||||
return true
|
|
||||||
|
|
||||||
if !Input.is_action_pressed(aim_action):
|
|
||||||
if charging_throw and Input.is_action_pressed(throw_action) and can_throw():
|
|
||||||
transitioned.emit(self, "ThrowState") #visee relachee en pleine charge : on reprend la charge hors visee
|
|
||||||
return true
|
|
||||||
if came_from_crouch:
|
|
||||||
transitioned.emit(self, "CrouchState")
|
|
||||||
else:
|
|
||||||
transitioned.emit(self, play_char.walk_or_run if play_char.move_dir else "IdleState")
|
|
||||||
return true
|
|
||||||
|
|
||||||
if charging_throw:
|
|
||||||
if Input.is_action_just_pressed(cancel_action):
|
|
||||||
charging_throw = false #lancer annule, on reste en visee
|
|
||||||
elif !Input.is_action_pressed(throw_action):
|
|
||||||
charging_throw = false
|
|
||||||
if start_throw(true): return true #RT relachee : le lancer part avec la cible verrouillee
|
|
||||||
|
|
||||||
if Input.is_action_just_pressed(throw_action):
|
|
||||||
#si le pokemon selectionne est deja dehors, un nouvel appui le rappelle au lieu de relancer
|
|
||||||
var selected : PokemonInstance = GameData.get_selected_pokemon()
|
|
||||||
var already_out : SentPokemon = SentPokemon.find_for(get_tree(), selected) if selected else null
|
|
||||||
if already_out:
|
|
||||||
already_out.recall()
|
|
||||||
elif start_throw():
|
|
||||||
return true
|
|
||||||
|
|
||||||
if Input.is_action_just_pressed(unlock_action):
|
|
||||||
auto_lock_blocked = true
|
|
||||||
release_target()
|
|
||||||
elif Input.is_action_just_pressed(switch_target_action):
|
|
||||||
switch_target()
|
|
||||||
return false
|
|
||||||
|
|
||||||
func can_throw() -> bool:
|
|
||||||
if GameData.get_selected_pokemon() == null: return false #personne a envoyer
|
|
||||||
return play_char.state_machine.states.has("throwstate") #sinon : pas de node ThrowState dans la StateMachine
|
|
||||||
|
|
||||||
func start_throw(released : bool = false) -> bool:
|
|
||||||
if !can_throw(): return false
|
|
||||||
keep_aim_on_exit = true
|
|
||||||
var throw : Node = play_char.state_machine.states["throwstate"]
|
|
||||||
throw.resume_released = released #true : on saute la charge, le windup demarre directement
|
|
||||||
transitioned.emit(self, "ThrowState")
|
|
||||||
return true
|
|
||||||
|
|
||||||
func move(delta : float) -> void:
|
|
||||||
play_char.move_dir = Input.get_vector(play_char.move_left_action, play_char.move_right_action, play_char.move_forward_action, play_char.move_backward_action).rotated(-play_char.cam_holder.global_rotation.y)
|
|
||||||
|
|
||||||
if play_char.move_dir and play_char.is_on_floor():
|
|
||||||
play_char.velocity.x = lerp(play_char.velocity.x, play_char.move_dir.x * aim_move_speed, aim_move_accel * delta)
|
|
||||||
play_char.velocity.z = lerp(play_char.velocity.z, play_char.move_dir.y * aim_move_speed, aim_move_accel * delta)
|
|
||||||
else:
|
|
||||||
play_char.velocity.x = lerp(play_char.velocity.x, 0.0, play_char.move_deccel * delta)
|
|
||||||
play_char.velocity.z = lerp(play_char.velocity.z, 0.0, play_char.move_deccel * delta)
|
|
||||||
|
|
||||||
func update_reticle() -> void:
|
|
||||||
if is_instance_valid(play_char.aim_target):
|
|
||||||
AimReticle.hide_instance() #cible verrouillee : son propre indicateur suffit
|
|
||||||
else:
|
|
||||||
AimReticle.show_instance(self)
|
|
||||||
|
|
||||||
#---------- targeting ----------
|
|
||||||
|
|
||||||
func update_target() -> void:
|
|
||||||
var current : AimTarget = play_char.aim_target
|
|
||||||
|
|
||||||
#verrouillage existant : on le garde tant que distance et ligne de vue sont valides
|
|
||||||
#(l'angle avec la camera ne compte pas, le joueur peut deplacer la camera librement)
|
|
||||||
if is_instance_valid(current):
|
|
||||||
if is_target_valid(current, lose_range_margin, false): return
|
|
||||||
release_target()
|
|
||||||
|
|
||||||
#visee libre : auto-lock sur la meilleure cible proche du centre de la camera
|
|
||||||
var best : AimTarget = null
|
|
||||||
var best_angle : float = auto_lock_angle
|
|
||||||
for t in get_candidates():
|
|
||||||
var angle : float = get_angle_to_camera(t) - t.priority
|
|
||||||
if angle < best_angle:
|
|
||||||
best_angle = angle
|
|
||||||
best = t
|
|
||||||
|
|
||||||
if best == null:
|
|
||||||
auto_lock_blocked = false #le joueur a ecarte la camera : l'auto-lock est de nouveau permis
|
|
||||||
elif !auto_lock_blocked:
|
|
||||||
set_target(best)
|
|
||||||
|
|
||||||
func get_candidates() -> Array[AimTarget]:
|
|
||||||
var result : Array[AimTarget] = []
|
|
||||||
for node in get_tree().get_nodes_in_group("aim_targets"):
|
|
||||||
var t := node as AimTarget
|
|
||||||
if t and is_target_valid(t, 1.0, true): result.append(t)
|
|
||||||
return result
|
|
||||||
|
|
||||||
func is_target_valid(t : AimTarget, range_mult : float, check_angle : bool) -> bool:
|
|
||||||
if !t.enabled: return false
|
|
||||||
if play_char.global_position.distance_to(t.global_position) > throw_range * range_mult: return false
|
|
||||||
if check_angle and get_angle_to_camera(t) > max_aim_angle: return false
|
|
||||||
if check_line_of_sight and !has_line_of_sight(t): return false
|
|
||||||
return true
|
|
||||||
|
|
||||||
func get_angle_to_camera(t : AimTarget) -> float:
|
|
||||||
var cam : Camera3D = play_char.cam_holder.cam
|
|
||||||
var to_t : Vector3 = t.global_position - cam.global_position
|
|
||||||
return rad_to_deg((-cam.global_basis.z).angle_to(to_t))
|
|
||||||
|
|
||||||
func has_line_of_sight(t : AimTarget) -> bool:
|
|
||||||
var query := PhysicsRayQueryParameters3D.create(
|
|
||||||
play_char.cam_holder.cam.global_position, t.global_position, line_of_sight_mask, [play_char.get_rid()])
|
|
||||||
var hit : Dictionary = play_char.get_world_3d().direct_space_state.intersect_ray(query)
|
|
||||||
#pas d'obstacle, ou l'obstacle est le proprietaire de la cible lui-meme
|
|
||||||
return hit.is_empty() or hit["collider"].is_ancestor_of(t)
|
|
||||||
|
|
||||||
func switch_target() -> void:
|
|
||||||
#passe a la cible suivante, triee de gauche a droite par rapport a la camera (boucle)
|
|
||||||
var cam : Camera3D = play_char.cam_holder.cam
|
|
||||||
var forward : Vector3 = -cam.global_basis.z
|
|
||||||
var list : Array[AimTarget] = get_candidates()
|
|
||||||
|
|
||||||
#la cible actuelle reste dans la liste meme si la camera est decalee par rapport a elle
|
|
||||||
var current : AimTarget = play_char.aim_target
|
|
||||||
if is_instance_valid(current) and !list.has(current): list.append(current)
|
|
||||||
if list.size() < 2: return
|
|
||||||
|
|
||||||
list.sort_custom(func(a : AimTarget, b : AimTarget) -> bool:
|
|
||||||
return signed_angle(forward, a, cam) < signed_angle(forward, b, cam))
|
|
||||||
|
|
||||||
var idx : int = list.find(current)
|
|
||||||
idx = 0 if idx == -1 else (idx + 1) % list.size()
|
|
||||||
set_target(list[idx])
|
|
||||||
|
|
||||||
func signed_angle(forward : Vector3, t : AimTarget, cam : Camera3D) -> float:
|
|
||||||
var to_t : Vector3 = t.global_position - cam.global_position
|
|
||||||
return Vector2(forward.x, forward.z).angle_to(Vector2(to_t.x, to_t.z))
|
|
||||||
|
|
||||||
func set_target(t : AimTarget) -> void:
|
|
||||||
if play_char.aim_target == t: return
|
|
||||||
release_target()
|
|
||||||
play_char.aim_target = t
|
|
||||||
play_char.cam_holder.set_lock_target(t)
|
|
||||||
t.set_locked(true)
|
|
||||||
|
|
||||||
func release_target() -> void:
|
|
||||||
if !play_char: return
|
|
||||||
if is_instance_valid(play_char.aim_target): play_char.aim_target.set_locked(false)
|
|
||||||
play_char.aim_target = null
|
|
||||||
play_char.cam_holder.set_lock_target(null)
|
|
||||||
|
|
||||||
func get_hints() -> Array[Hint]:
|
|
||||||
var list : Array[Hint] = [switch_hint]
|
|
||||||
if charging_throw: list.append(cancel_hint)
|
|
||||||
return list
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
uid://dn5idrvu68k83
|
|
||||||
67
game/player/states/airborne_state.gd
Normal file
67
game/player/states/airborne_state.gd
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
extends PlayerState
|
||||||
|
|
||||||
|
class_name AirborneState
|
||||||
|
|
||||||
|
#base des etats en l'air (Jump, Inair), comme GroundedState pour le sol. Chaque etat enfant ne decrit que ses differences :
|
||||||
|
# - on_air_enter() : ce qui se passe a l'entree (JumpState saute, InairState applique un saut coupe)
|
||||||
|
# - check_apex() : condition pour changer d'etat en l'air (JumpState -> InairState quand le perso commence a descendre)
|
||||||
|
# - handle_input() : les inputs propres a l'etat (saut, relachement du saut...)
|
||||||
|
#le reste est commun : gravite, minuteurs de saut, atterrissage, collision avec un mur, controle en l'air.
|
||||||
|
|
||||||
|
#---------- cycle de vie ----------
|
||||||
|
|
||||||
|
func on_enter() -> void:
|
||||||
|
if player.floor_snap_length != 0.0: player.floor_snap_length = 0.0 #sinon le perso reste colle au sol
|
||||||
|
on_air_enter()
|
||||||
|
|
||||||
|
func on_air_enter() -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
#---------- boucle ----------
|
||||||
|
|
||||||
|
func physics_update(delta : float) -> void:
|
||||||
|
if check_air_transitions(delta): return
|
||||||
|
|
||||||
|
player.gravity_apply(delta)
|
||||||
|
|
||||||
|
if handle_input(): return
|
||||||
|
|
||||||
|
move(delta)
|
||||||
|
|
||||||
|
func check_air_transitions(delta : float) -> bool:
|
||||||
|
#atterrissage : retour a l'etat au sol correspondant a la derniere allure (marche / course)
|
||||||
|
if player.is_on_floor():
|
||||||
|
return request_ground_state(player.move_dir != Vector2.ZERO)
|
||||||
|
|
||||||
|
tick_air_timers(delta)
|
||||||
|
if check_apex(): return true
|
||||||
|
|
||||||
|
#si active, coupe la vitesse horizontale quand le perso touche un mur
|
||||||
|
if player.is_on_wall() and player.hit_wall_cut_velocity:
|
||||||
|
player.velocity.x = 0.0
|
||||||
|
player.velocity.z = 0.0
|
||||||
|
return false
|
||||||
|
|
||||||
|
func tick_air_timers(delta : float) -> void:
|
||||||
|
if player.jump_cooldown > 0.0: player.jump_cooldown -= delta
|
||||||
|
if player.coyote_jump_cooldown > 0.0: player.coyote_jump_cooldown -= delta
|
||||||
|
|
||||||
|
func check_apex() -> bool:
|
||||||
|
return false #JumpState surcharge
|
||||||
|
|
||||||
|
func handle_input() -> bool:
|
||||||
|
return false #Jump et Inair surchargent
|
||||||
|
|
||||||
|
#---------- controle en l'air ----------
|
||||||
|
|
||||||
|
func move(delta : float) -> void:
|
||||||
|
player.move_dir = input.read_move_dir()
|
||||||
|
if !player.move_dir or player.is_on_floor(): return
|
||||||
|
|
||||||
|
#selon la derniere allure au sol (marche ou course), on choisit la courbe correspondante pour les valeurs en l'air
|
||||||
|
var idx : int = player.locomotion #WALK = 0, RUN = 1 : l'index de la courbe correspondante
|
||||||
|
if idx >= player.in_air_move_speed.size() or idx >= player.in_air_accel.size(): return #courbes non assignees
|
||||||
|
|
||||||
|
var speed : float = player.in_air_move_speed[idx].sample(player.velocity.length())
|
||||||
|
var accel : float = player.in_air_accel[idx].sample(player.velocity.length())
|
||||||
|
accelerate(speed, accel, delta)
|
||||||
1
game/player/states/airborne_state.gd.uid
Normal file
1
game/player/states/airborne_state.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://d2fq1w57bakgq
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
extends State
|
|
||||||
|
|
||||||
class_name CrouchState
|
|
||||||
|
|
||||||
var state_name : String = "Crouch"
|
|
||||||
|
|
||||||
var play_char : CharacterBody3D
|
|
||||||
|
|
||||||
func enter(play_char_ref : CharacterBody3D) -> void:
|
|
||||||
play_char = play_char_ref
|
|
||||||
|
|
||||||
verifications()
|
|
||||||
|
|
||||||
func verifications() -> void:
|
|
||||||
#play_char.godot_plush_skin.set_state("crouch")
|
|
||||||
play_char.move_speed = play_char.crouch_speed
|
|
||||||
play_char.move_accel = play_char.crouch_accel
|
|
||||||
play_char.move_deccel = play_char.crouch_deccel
|
|
||||||
|
|
||||||
play_char.floor_snap_length = 1.0
|
|
||||||
if play_char.jump_cooldown > 0.0: play_char.jump_cooldown = -1.0
|
|
||||||
if play_char.nb_jumps_in_air_allowed < play_char.nb_jumps_in_air_allowed_ref: play_char.nb_jumps_in_air_allowed = play_char.nb_jumps_in_air_allowed_ref
|
|
||||||
if play_char.coyote_jump_cooldown < play_char.coyote_jump_cooldown_ref: play_char.coyote_jump_cooldown = play_char.coyote_jump_cooldown_ref
|
|
||||||
if play_char.has_cut_jump: play_char.has_cut_jump = false
|
|
||||||
|
|
||||||
play_char.is_crouching = true
|
|
||||||
|
|
||||||
func exit() -> void:
|
|
||||||
play_char.is_crouching = false
|
|
||||||
|
|
||||||
func physics_update(delta : float) -> void:
|
|
||||||
applies()
|
|
||||||
|
|
||||||
play_char.gravity_apply(delta)
|
|
||||||
|
|
||||||
input_management()
|
|
||||||
|
|
||||||
move(delta)
|
|
||||||
|
|
||||||
func applies() -> void:
|
|
||||||
#quitter le sol (ex: crouch au bord d'une plateforme) bascule vers InairState, comme IdleState
|
|
||||||
if !play_char.is_on_floor() and !play_char.is_on_wall():
|
|
||||||
transitioned.emit(self, "InairState")
|
|
||||||
#le saut est bloque en crouch : on ne consomme pas le jump buffer ici, il restera en attente
|
|
||||||
#et sera pris en compte des la sortie du crouch (Idle/Walk)
|
|
||||||
|
|
||||||
func input_management() -> void:
|
|
||||||
if Input.is_action_just_pressed(play_char.dodge_action) and play_char.dodge_cooldown <= 0.0:
|
|
||||||
transitioned.emit(self, "DodgeState")
|
|
||||||
|
|
||||||
#courir annule automatiquement le crouch
|
|
||||||
if play_char.walk_or_run == "RunState":
|
|
||||||
transitioned.emit(self, "RunState")
|
|
||||||
|
|
||||||
#bascule (toggle) : un nouvel appui ressort du crouch vers l'etat au sol approprie
|
|
||||||
if Input.is_action_just_pressed(play_char.crouch_action):
|
|
||||||
transitioned.emit(self, play_char.walk_or_run if play_char.move_dir else "IdleState")
|
|
||||||
|
|
||||||
if Input.is_action_just_pressed(play_char.aim_cam_action):
|
|
||||||
transitioned.emit(self, "AimState")
|
|
||||||
|
|
||||||
#pas de saut ici : le jump_action est volontairement ignore pendant le crouch
|
|
||||||
|
|
||||||
func move(delta : float) -> void:
|
|
||||||
play_char.move_dir = Input.get_vector(play_char.move_left_action, play_char.move_right_action, play_char.move_forward_action, play_char.move_backward_action).rotated(-play_char.cam_holder.global_rotation.y)
|
|
||||||
|
|
||||||
if play_char.move_dir and play_char.is_on_floor():
|
|
||||||
#deplacement accroupi, a vitesse reduite
|
|
||||||
play_char.velocity.x = lerp(play_char.velocity.x, play_char.move_dir.x * play_char.move_speed, play_char.move_accel * delta)
|
|
||||||
play_char.velocity.z = lerp(play_char.velocity.z, play_char.move_dir.y * play_char.move_speed, play_char.move_accel * delta)
|
|
||||||
else:
|
|
||||||
#pas de transition vers Idle ici : Crouch gere lui-meme l'immobilite, comme AimState
|
|
||||||
play_char.velocity.x = lerp(play_char.velocity.x, 0.0, play_char.move_deccel * delta)
|
|
||||||
play_char.velocity.z = lerp(play_char.velocity.z, 0.0, play_char.move_deccel * delta)
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
uid://d0577tj0c23bu
|
|
||||||
|
|
@ -1,56 +1,40 @@
|
||||||
extends State
|
extends PlayerState
|
||||||
|
|
||||||
class_name DodgeState
|
class_name DodgeState
|
||||||
|
|
||||||
var state_name : String = "Dodge"
|
|
||||||
|
|
||||||
var play_char : CharacterBody3D
|
|
||||||
|
|
||||||
@export var dodge_speed_curve : Curve #optionnel : controle le "falloff" de vitesse pendant la roulade (1.0 = pleine vitesse, 0.0 = arret). Laisser vide pour une vitesse constante.
|
@export var dodge_speed_curve : Curve #optionnel : controle le "falloff" de vitesse pendant la roulade (1.0 = pleine vitesse, 0.0 = arret). Laisser vide pour une vitesse constante.
|
||||||
|
|
||||||
var dodge_dir : Vector2 = Vector2.ZERO
|
var dodge_dir : Vector2 = Vector2.ZERO
|
||||||
var dodge_timer : float = 0.0
|
var dodge_timer : float = 0.0
|
||||||
var dodge_duration_ref : float = 0.0
|
var dodge_duration_ref : float = 0.0
|
||||||
var came_from_crouch : bool = false #true si l'esquive vient du crouch (directement, ou via Aim declenche depuis le crouch)
|
|
||||||
|
|
||||||
func enter(play_char_ref : CharacterBody3D) -> void:
|
|
||||||
play_char = play_char_ref
|
|
||||||
|
|
||||||
#curr_state_name n'est pas encore mis a jour a ce stade : il reflete encore l'etat qu'on quitte
|
|
||||||
var previous : String = play_char.state_machine.curr_state_name
|
|
||||||
if previous == "Crouch":
|
|
||||||
came_from_crouch = true
|
|
||||||
elif previous == "Aim":
|
|
||||||
var aim : Node = play_char.state_machine.states.get("aimstate")
|
|
||||||
came_from_crouch = aim != null and aim.came_from_crouch
|
|
||||||
else:
|
|
||||||
came_from_crouch = false
|
|
||||||
|
|
||||||
|
func on_enter() -> void:
|
||||||
|
AimReticle.hide_instance() #pas de reticule pendant la roulade (si la visee est maintenue, elle reprend apres)
|
||||||
verifications()
|
verifications()
|
||||||
|
|
||||||
func verifications() -> void:
|
func verifications() -> void:
|
||||||
play_char.floor_snap_length = 1.0
|
player.floor_snap_length = 1.0
|
||||||
play_char.is_dodging = true
|
player.is_dodging = true
|
||||||
play_char.dodge_cooldown = play_char.dodge_cooldown_ref
|
player.dodge_cooldown = player.dodge_cooldown_ref
|
||||||
|
|
||||||
#roule dans la direction actuelle de l'input, ou dans la derniere direction connue si aucune touche n'est tenue (comportement Arceus)
|
#roule dans la direction actuelle de l'input, ou dans la derniere direction connue si aucune touche n'est tenue (comportement Arceus)
|
||||||
dodge_dir = play_char.move_dir if play_char.move_dir != Vector2.ZERO else play_char.last_input_dir
|
dodge_dir = player.move_dir if player.move_dir != Vector2.ZERO else player.last_input_dir
|
||||||
if dodge_dir == Vector2.ZERO: dodge_dir = Vector2.DOWN #direction par defaut si le perso n'a encore jamais bouge
|
if dodge_dir == Vector2.ZERO: dodge_dir = Vector2.DOWN #direction par defaut si le perso n'a encore jamais bouge
|
||||||
dodge_dir = dodge_dir.normalized()
|
dodge_dir = dodge_dir.normalized()
|
||||||
|
|
||||||
dodge_timer = play_char.dodge_duration
|
dodge_timer = player.dodge_duration
|
||||||
dodge_duration_ref = play_char.dodge_duration
|
dodge_duration_ref = player.dodge_duration
|
||||||
|
|
||||||
#oriente immediatement le modele dans la direction de la roulade, verrouillee pour toute sa duree
|
#oriente immediatement le modele dans la direction de la roulade, verrouillee pour toute sa duree
|
||||||
play_char.visual_root.rotation.y = -dodge_dir.orthogonal().angle()
|
player.visual_root.rotation.y = -dodge_dir.orthogonal().angle()
|
||||||
|
|
||||||
func exit() -> void:
|
func exit() -> void:
|
||||||
play_char.is_dodging = false
|
player.is_dodging = false
|
||||||
|
|
||||||
func physics_update(delta : float) -> void:
|
func physics_update(delta : float) -> void:
|
||||||
applies()
|
applies()
|
||||||
|
|
||||||
play_char.gravity_apply(delta)
|
player.gravity_apply(delta)
|
||||||
|
|
||||||
move(delta)
|
move(delta)
|
||||||
|
|
||||||
|
|
@ -60,33 +44,25 @@ func physics_update(delta : float) -> void:
|
||||||
|
|
||||||
func applies() -> void:
|
func applies() -> void:
|
||||||
#si le perso quitte le sol en roulant (ex: roulade vers un bord), on bascule vers InairState comme les autres etats au sol
|
#si le perso quitte le sol en roulant (ex: roulade vers un bord), on bascule vers InairState comme les autres etats au sol
|
||||||
if !play_char.is_on_floor() and !play_char.is_on_wall():
|
if !player.is_on_floor() and !player.is_on_wall():
|
||||||
if play_char.velocity.y < 0.0:
|
if player.velocity.y < 0.0:
|
||||||
transitioned.emit(self, "InairState")
|
fall()
|
||||||
|
|
||||||
func move(_delta : float) -> void:
|
func move(_delta : float) -> void:
|
||||||
if !play_char.is_on_floor(): return
|
if !player.is_on_floor(): return
|
||||||
|
|
||||||
#falloff de vitesse : pleine vitesse par defaut, ou modele par dodge_speed_curve si assignee
|
#falloff de vitesse : pleine vitesse par defaut, ou modele par dodge_speed_curve si assignee
|
||||||
var ratio : float = 1.0
|
var ratio : float = 1.0
|
||||||
if dodge_speed_curve:
|
if dodge_speed_curve:
|
||||||
ratio = dodge_speed_curve.sample(1.0 - (dodge_timer / dodge_duration_ref))
|
ratio = dodge_speed_curve.sample(1.0 - (dodge_timer / dodge_duration_ref))
|
||||||
|
|
||||||
play_char.velocity.x = dodge_dir.x * play_char.dodge_speed * ratio
|
player.velocity.x = dodge_dir.x * player.dodge_speed * ratio
|
||||||
play_char.velocity.z = dodge_dir.y * play_char.dodge_speed * ratio
|
player.velocity.z = dodge_dir.y * player.dodge_speed * ratio
|
||||||
|
|
||||||
func end_dodge() -> void:
|
func end_dodge() -> void:
|
||||||
#a la fin de la roulade, retour a l'etat approprie
|
#a la fin de la roulade, retour a l'etat approprie (la posture accroupie est conservee : Idle / Walk la reappliquent)
|
||||||
if !play_char.is_on_floor():
|
if !player.is_on_floor():
|
||||||
transitioned.emit(self, "InairState")
|
fall()
|
||||||
elif Input.is_action_pressed(play_char.aim_cam_action):
|
|
||||||
#AimState ne verra que "Dodge" comme etat precedent : on lui transmet l'origine crouch
|
|
||||||
var aim : Node = play_char.state_machine.states.get("aimstate")
|
|
||||||
if aim: aim.came_from_crouch = came_from_crouch
|
|
||||||
transitioned.emit(self, "AimState") #le joueur a garde la visee enfoncee pendant la roulade
|
|
||||||
elif came_from_crouch:
|
|
||||||
transitioned.emit(self, "CrouchState")
|
|
||||||
elif play_char.move_dir:
|
|
||||||
transitioned.emit(self, play_char.walk_or_run)
|
|
||||||
else:
|
else:
|
||||||
transitioned.emit(self, "IdleState")
|
if input.aim_held(): player.aim_targeting.begin_aim() #visee enfoncee pendant la roulade (sans effet si la visee est deja active)
|
||||||
|
request_ground_state(player.move_dir != Vector2.ZERO)
|
||||||
|
|
|
||||||
124
game/player/states/grounded_state.gd
Normal file
124
game/player/states/grounded_state.gd
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
extends PlayerState
|
||||||
|
|
||||||
|
class_name GroundedState
|
||||||
|
|
||||||
|
#base des etats au sol (Idle, Walk, Run). Chaque etat enfant ne decrit que ses differences :
|
||||||
|
# - apply_standing_stats() : vitesse / accel / deccel de l'etat quand on est debout
|
||||||
|
# - on_move() / on_no_move() : ce qui se passe avec / sans direction d'input
|
||||||
|
# - check_locomotion_switch() : bascule Walk <-> Run
|
||||||
|
# - is_falling() : condition pour passer en InairState
|
||||||
|
#
|
||||||
|
#le crouch n'est plus un etat mais une POSTURE (player.is_crouching) :
|
||||||
|
# - elle se bascule ici (toggle_crouch) et survit aux etats Dodge / Throw : plus besoin de "came_from_crouch"
|
||||||
|
# - elle est annulee en courant (RunState) et en quittant le sol (State.fall())
|
||||||
|
#la visee (AimTargeting) est un mode du meme genre : tant qu'elle est active, deplacement ralenti, pas de saut, et tick() chaque frame.
|
||||||
|
# - accroupi : stats de deplacement accroupi, pas de saut, pas de jump buffer consomme
|
||||||
|
|
||||||
|
var allows_crouch : bool = true #false : l'appui sur crouch_action est ignore (Run)
|
||||||
|
|
||||||
|
#---------- cycle de vie ----------
|
||||||
|
|
||||||
|
func on_enter() -> void:
|
||||||
|
reset_ground_state()
|
||||||
|
apply_movement_stats()
|
||||||
|
|
||||||
|
func reset_ground_state() -> void:
|
||||||
|
#remet a zero tout ce qui concerne le saut quand on touche / reste au sol
|
||||||
|
player.floor_snap_length = 1.0
|
||||||
|
if player.jump_cooldown > 0.0: player.jump_cooldown = -1.0
|
||||||
|
if player.nb_jumps_in_air_allowed < player.nb_jumps_in_air_allowed_ref: player.nb_jumps_in_air_allowed = player.nb_jumps_in_air_allowed_ref
|
||||||
|
if player.coyote_jump_cooldown < player.coyote_jump_cooldown_ref: player.coyote_jump_cooldown = player.coyote_jump_cooldown_ref
|
||||||
|
if player.has_cut_jump: player.has_cut_jump = false
|
||||||
|
|
||||||
|
#---------- stats de deplacement ----------
|
||||||
|
|
||||||
|
func apply_movement_stats() -> void:
|
||||||
|
if player.is_crouching: apply_crouch_stats()
|
||||||
|
else: apply_standing_stats()
|
||||||
|
|
||||||
|
func apply_standing_stats() -> void:
|
||||||
|
pass #Walk / Run y copient leurs vitesses sur player ; Idle garde celles de l'etat precedent
|
||||||
|
|
||||||
|
func apply_crouch_stats() -> void:
|
||||||
|
player.move_speed = player.crouch_speed
|
||||||
|
player.move_accel = player.crouch_accel
|
||||||
|
player.move_deccel = player.crouch_deccel
|
||||||
|
|
||||||
|
func toggle_crouch() -> void:
|
||||||
|
player.is_crouching = !player.is_crouching
|
||||||
|
apply_movement_stats()
|
||||||
|
|
||||||
|
#---------- boucle ----------
|
||||||
|
|
||||||
|
func physics_update(delta : float) -> void:
|
||||||
|
if try_start_throw(): return
|
||||||
|
|
||||||
|
if check_ground_transitions(): return
|
||||||
|
|
||||||
|
player.gravity_apply(delta)
|
||||||
|
|
||||||
|
if handle_input(): return
|
||||||
|
|
||||||
|
if player.is_aiming: player.aim_targeting.tick()
|
||||||
|
|
||||||
|
move(delta)
|
||||||
|
|
||||||
|
func try_start_throw() -> bool:
|
||||||
|
#RT depuis le sol (debout ou accroupi, visee ou non) lance un pokemon : ce n'est plus la StateMachine qui s'en charge
|
||||||
|
if !input.throw_pressed(): return false
|
||||||
|
if !player.is_on_floor(): return false
|
||||||
|
|
||||||
|
if GameData.get_selected_pokemon() == null: return false
|
||||||
|
|
||||||
|
#pokemon selectionne deja dehors : l'appui le rappelle (voir RecallInput), on ne lance pas
|
||||||
|
if PokemonRecall.find_selected_out(get_tree()) != null: return false
|
||||||
|
|
||||||
|
if !player.state_machine.has_state(States.THROW): return false
|
||||||
|
return request_transition(States.THROW)
|
||||||
|
|
||||||
|
func check_ground_transitions() -> bool:
|
||||||
|
#etat du sol : on quitte le sol -> InairState ; jump buffer -> JumpState (pas accroupi : le buffer reste en attente)
|
||||||
|
if !player.is_on_floor():
|
||||||
|
if !player.is_on_wall() and is_falling():
|
||||||
|
return fall()
|
||||||
|
return false
|
||||||
|
|
||||||
|
if !player.is_crouching and !player.is_aiming and player.jump_buff_on:
|
||||||
|
player.buffered_jump = true
|
||||||
|
player.jump_buff_on = false
|
||||||
|
return request_transition(States.JUMP)
|
||||||
|
return false
|
||||||
|
|
||||||
|
func is_falling() -> bool:
|
||||||
|
#accroupi, on quitte le sol sans condition sur la vitesse verticale (comme IdleState, qui le surcharge)
|
||||||
|
return player.is_crouching or player.velocity.y < 0.0
|
||||||
|
|
||||||
|
func handle_input() -> bool:
|
||||||
|
#renvoie true si une transition a eu lieu. Priorite : roulade > courir (annule le crouch) > crouch > saut > visee > bascule marche/course
|
||||||
|
if input.wants_dodge() and request_transition(States.DODGE): return true
|
||||||
|
|
||||||
|
if player.is_crouching and player.is_running() and request_transition(States.RUN): return true
|
||||||
|
|
||||||
|
if allows_crouch and player.can_crouch and input.crouch_pressed(): toggle_crouch()
|
||||||
|
|
||||||
|
if !player.is_crouching and !player.is_aiming and input.wants_jump() and request_transition(States.JUMP): return true
|
||||||
|
if input.aim_pressed(): player.aim_targeting.begin_aim() #la visee n'est pas un etat : le deplacement continue, ralenti
|
||||||
|
return check_locomotion_switch()
|
||||||
|
|
||||||
|
func check_locomotion_switch() -> bool:
|
||||||
|
return false #Walk et Run surchargent
|
||||||
|
|
||||||
|
func move(delta : float) -> void:
|
||||||
|
player.move_dir = input.read_move_dir()
|
||||||
|
|
||||||
|
if player.move_dir and player.is_on_floor():
|
||||||
|
on_move(delta)
|
||||||
|
else:
|
||||||
|
on_no_move(delta)
|
||||||
|
|
||||||
|
func on_move(delta : float) -> void:
|
||||||
|
if player.is_aiming: accelerate(player.aim_targeting.aim_move_speed, player.aim_targeting.aim_move_accel, delta)
|
||||||
|
else: accelerate(player.move_speed, player.move_accel, delta)
|
||||||
|
|
||||||
|
func on_no_move(delta : float) -> void:
|
||||||
|
slow_down(delta)
|
||||||
1
game/player/states/grounded_state.gd.uid
Normal file
1
game/player/states/grounded_state.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://c4f2x2rqadgrx
|
||||||
|
|
@ -1,72 +1,9 @@
|
||||||
extends State
|
extends GroundedState
|
||||||
|
|
||||||
class_name IdleState
|
class_name IdleState
|
||||||
|
|
||||||
var state_name : String = "Idle"
|
func is_falling() -> bool:
|
||||||
|
return true #Idle bascule en InairState des qu'on n'est plus au sol, sans condition sur la vitesse verticale
|
||||||
|
|
||||||
var play_char : CharacterBody3D
|
func on_move(_delta : float) -> void:
|
||||||
|
request_locomotion() #direction donnee : on passe a Walk ou Run
|
||||||
func enter(char_ref : CharacterBody3D) -> void:
|
|
||||||
#pass play char reference
|
|
||||||
play_char = char_ref
|
|
||||||
|
|
||||||
verifications()
|
|
||||||
|
|
||||||
func verifications() -> void:
|
|
||||||
#manage the appliements that need to be set at the start of the state
|
|
||||||
#play_char.godot_plush_skin.set_state("idle")
|
|
||||||
play_char.floor_snap_length = 1.0
|
|
||||||
if play_char.jump_cooldown > 0.0: play_char.jump_cooldown = -1.0
|
|
||||||
if play_char.nb_jumps_in_air_allowed < play_char.nb_jumps_in_air_allowed_ref: play_char.nb_jumps_in_air_allowed = play_char.nb_jumps_in_air_allowed_ref
|
|
||||||
if play_char.coyote_jump_cooldown < play_char.coyote_jump_cooldown_ref: play_char.coyote_jump_cooldown = play_char.coyote_jump_cooldown_ref
|
|
||||||
if play_char.has_cut_jump: play_char.has_cut_jump = false
|
|
||||||
#if play_char.movement_dust.emitting: play_char.movement_dust.emitting = false
|
|
||||||
|
|
||||||
func physics_update(delta : float) -> void:
|
|
||||||
applies()
|
|
||||||
|
|
||||||
play_char.gravity_apply(delta)
|
|
||||||
|
|
||||||
input_management()
|
|
||||||
|
|
||||||
move(delta)
|
|
||||||
|
|
||||||
func applies() -> void:
|
|
||||||
#manage the appliements and state transitions that needs to be sets/checked/performed
|
|
||||||
#every time the play char pass through one of the following : floor-inair-onwall
|
|
||||||
if !play_char.is_on_floor() and !play_char.is_on_wall():
|
|
||||||
transitioned.emit(self, "InairState")
|
|
||||||
if play_char.is_on_floor():
|
|
||||||
if play_char.jump_buff_on:
|
|
||||||
play_char.buffered_jump = true
|
|
||||||
play_char.jump_buff_on = false
|
|
||||||
transitioned.emit(self, "JumpState")
|
|
||||||
|
|
||||||
func input_management() -> void:
|
|
||||||
#manage the state transitions depending on the actions inputs
|
|
||||||
if Input.is_action_just_pressed(play_char.dodge_action) and play_char.dodge_cooldown <= 0.0:
|
|
||||||
transitioned.emit(self, "DodgeState")
|
|
||||||
|
|
||||||
if Input.is_action_just_pressed(play_char.crouch_action):
|
|
||||||
transitioned.emit(self, "CrouchState")
|
|
||||||
|
|
||||||
if Input.is_action_pressed(play_char.jump_action) if play_char.auto_jump else Input.is_action_just_pressed(play_char.jump_action):
|
|
||||||
transitioned.emit(self, "JumpState")
|
|
||||||
|
|
||||||
#if Input.is_action_just_pressed("player_aim_camera"):
|
|
||||||
if Input.is_action_just_pressed(play_char.aim_cam_action):
|
|
||||||
transitioned.emit(self, "AimState")
|
|
||||||
|
|
||||||
func move(delta : float) -> void:
|
|
||||||
#manage the character movement
|
|
||||||
|
|
||||||
#get the move direction depending on the input
|
|
||||||
play_char.move_dir = Input.get_vector(play_char.move_left_action, play_char.move_right_action, play_char.move_forward_action, play_char.move_backward_action).rotated(-play_char.cam_holder.global_rotation.y)
|
|
||||||
|
|
||||||
if play_char.move_dir and play_char.is_on_floor():
|
|
||||||
#transition to corresponding state
|
|
||||||
transitioned.emit(self, play_char.walk_or_run)
|
|
||||||
else:
|
|
||||||
#apply smooth stop
|
|
||||||
play_char.velocity.x = lerp(play_char.velocity.x, 0.0, play_char.move_deccel * delta)
|
|
||||||
play_char.velocity.z = lerp(play_char.velocity.z, 0.0, play_char.move_deccel * delta)
|
|
||||||
|
|
|
||||||
37
game/player/states/inair_state.gd
Normal file
37
game/player/states/inair_state.gd
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
extends AirborneState
|
||||||
|
|
||||||
|
class_name InairState
|
||||||
|
|
||||||
|
@export var jump_buffer_time : float = 0.15 #appui sur saut juste avant d'atterrir : le saut part a l'atterrissage si l'appui date de moins de ce delai (en secondes)
|
||||||
|
|
||||||
|
var _buffer_id : int = 0 #identifie le dernier appui bufferise, pour ne pas qu'un ancien minuteur annule un appui plus recent
|
||||||
|
|
||||||
|
func on_air_enter() -> void:
|
||||||
|
#saut coupe (bouton relache pendant la montee, voir JumpState) : on freine la montee, une seule fois
|
||||||
|
if player.has_cut_jump:
|
||||||
|
player.has_cut_jump = false
|
||||||
|
if player.velocity.y > 0.0: player.velocity.y *= player.jump_cut_multiplier
|
||||||
|
|
||||||
|
func handle_input() -> bool:
|
||||||
|
if !input.jump_pressed(): return false
|
||||||
|
|
||||||
|
#coyote jump : on vient de quitter le sol sans sauter, le saut reste permis un court instant
|
||||||
|
#saut en l'air : il en reste (nb_jumps_in_air_allowed)
|
||||||
|
var coyote : bool = player.coyote_jump_cooldown > 0.0
|
||||||
|
if coyote or player.nb_jumps_in_air_allowed > 0:
|
||||||
|
player.coyote_jump_on = coyote #lu par JumpState.jump() : le coyote jump ne consomme pas de saut en l'air
|
||||||
|
if request_transition(States.JUMP): return true
|
||||||
|
player.coyote_jump_on = false #saut refuse (ex : etat bloque) : on ne laisse pas le drapeau traîner
|
||||||
|
return false
|
||||||
|
|
||||||
|
buffer_jump() #plus de saut possible : on garde l'appui un instant pour l'atterrissage
|
||||||
|
return false
|
||||||
|
|
||||||
|
func buffer_jump() -> void:
|
||||||
|
#jump_buff_on est consomme par GroundedState a l'atterrissage (-> JumpState). Il est retire apres jump_buffer_time
|
||||||
|
#quel que soit l'etat dans lequel on se trouve, d'ou le minuteur du SceneTree et pas un compteur dans physics_update
|
||||||
|
player.jump_buff_on = true
|
||||||
|
_buffer_id += 1
|
||||||
|
var id : int = _buffer_id
|
||||||
|
await get_tree().create_timer(jump_buffer_time).timeout
|
||||||
|
if id == _buffer_id: player.jump_buff_on = false
|
||||||
1
game/player/states/inair_state.gd.uid
Normal file
1
game/player/states/inair_state.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://sixraavy7k2h
|
||||||
|
|
@ -1,77 +1,30 @@
|
||||||
extends State
|
extends AirborneState
|
||||||
|
|
||||||
class_name JumpState
|
class_name JumpState
|
||||||
|
|
||||||
var state_name : String = "Jump"
|
func on_air_enter() -> void:
|
||||||
|
|
||||||
var play_char : CharacterBody3D
|
|
||||||
|
|
||||||
func enter(play_char_ref : CharacterBody3D) -> void:
|
|
||||||
play_char = play_char_ref
|
|
||||||
|
|
||||||
verifications()
|
|
||||||
|
|
||||||
jump()
|
|
||||||
|
|
||||||
func verifications() -> void:
|
|
||||||
#play_char.godot_plush_skin.set_state("jump")
|
#play_char.godot_plush_skin.set_state("jump")
|
||||||
if play_char.floor_snap_length != 0.0: play_char.floor_snap_length = 0.0
|
if player.jump_cooldown < player.jump_cooldown_ref: player.jump_cooldown = player.jump_cooldown_ref
|
||||||
if play_char.jump_cooldown < play_char.jump_cooldown_ref: play_char.jump_cooldown = play_char.jump_cooldown_ref
|
player.coyote_jump_cooldown = -1.0 #un saut consomme le coyote jump : pas de saut gratuit en retombant juste apres
|
||||||
#if play_char.movement_dust.emitting: play_char.movement_dust.emitting = false
|
#if player.movement_dust.emitting: player.movement_dust.emitting = false
|
||||||
|
|
||||||
func physics_update(delta : float) -> void:
|
|
||||||
applies(delta)
|
|
||||||
|
|
||||||
play_char.gravity_apply(delta)
|
|
||||||
|
|
||||||
input_management()
|
|
||||||
|
|
||||||
move(delta)
|
|
||||||
|
|
||||||
func applies(delta : float) -> void:
|
|
||||||
if !play_char.is_on_floor():
|
|
||||||
if play_char.jump_cooldown > 0.0: play_char.jump_cooldown -= delta
|
|
||||||
if play_char.coyote_jump_cooldown > 0.0: play_char.coyote_jump_cooldown -= delta
|
|
||||||
if play_char.velocity.y < 0.0:
|
|
||||||
transitioned.emit(self, "InairState")
|
|
||||||
|
|
||||||
if play_char.is_on_floor():
|
|
||||||
if play_char.move_dir: transitioned.emit(self, play_char.walk_or_run)
|
|
||||||
else: transitioned.emit(self, "IdleState")
|
|
||||||
|
|
||||||
if play_char.is_on_wall():
|
|
||||||
#if enabled, cut velocity when the play char hit a wall
|
|
||||||
if play_char.hit_wall_cut_velocity:
|
|
||||||
play_char.velocity.x = 0.0
|
|
||||||
play_char.velocity.z = 0.0
|
|
||||||
|
|
||||||
func input_management() -> void:
|
|
||||||
if Input.is_action_just_pressed(play_char.jump_action):
|
|
||||||
jump()
|
jump()
|
||||||
|
|
||||||
if Input.is_action_just_released(play_char.jump_action):
|
func check_apex() -> bool:
|
||||||
|
#le perso commence a descendre : la suite de la chute se passe dans InairState
|
||||||
|
return player.velocity.y < 0.0 and fall()
|
||||||
|
|
||||||
|
func handle_input() -> bool:
|
||||||
|
if input.jump_pressed():
|
||||||
|
jump()
|
||||||
|
|
||||||
|
if input.jump_released():
|
||||||
#cut jump in action, allow to manage jump height
|
#cut jump in action, allow to manage jump height
|
||||||
#(the longer you press the button, the more high the play char goes, similar to the Mario Bros games)
|
#(the longer you press the button, the more high the play char goes, similar to the Mario Bros games)
|
||||||
play_char.has_cut_jump = true
|
#le freinage de la montee est applique par InairState a son entree
|
||||||
transitioned.emit(self, "InairState")
|
player.has_cut_jump = true
|
||||||
|
return fall()
|
||||||
func move(delta : float) -> void:
|
return false
|
||||||
play_char.move_dir = Input.get_vector(play_char.move_left_action, play_char.move_right_action, play_char.move_forward_action, play_char.move_backward_action).rotated(-play_char.cam_holder.global_rotation.y)
|
|
||||||
|
|
||||||
#depending on the previous grounded state (walk or run)
|
|
||||||
#choose corresponding curve, to apply the correct in air values
|
|
||||||
if play_char.move_dir and !play_char.is_on_floor():
|
|
||||||
var in_air_move_speed_val : float
|
|
||||||
var in_air_accel_val : float
|
|
||||||
if play_char.walk_or_run == "WalkState":
|
|
||||||
in_air_move_speed_val = play_char.in_air_move_speed[0].sample(play_char.velocity.length())
|
|
||||||
in_air_accel_val = play_char.in_air_accel[0].sample(play_char.velocity.length())
|
|
||||||
elif play_char.walk_or_run == "RunState":
|
|
||||||
in_air_move_speed_val = play_char.in_air_move_speed[1].sample(play_char.velocity.length())
|
|
||||||
in_air_accel_val = play_char.in_air_accel[1].sample(play_char.velocity.length())
|
|
||||||
|
|
||||||
play_char.velocity.x = lerp(play_char.velocity.x, play_char.move_dir.x * in_air_move_speed_val, in_air_accel_val * delta)
|
|
||||||
play_char.velocity.z = lerp(play_char.velocity.z, play_char.move_dir.y * in_air_move_speed_val, in_air_accel_val * delta)
|
|
||||||
|
|
||||||
func jump() -> void:
|
func jump() -> void:
|
||||||
#manage the jump behaviour, depending of the different variables and states the character is
|
#manage the jump behaviour, depending of the different variables and states the character is
|
||||||
|
|
@ -79,34 +32,33 @@ func jump() -> void:
|
||||||
var can_jump : bool = false #jump condition
|
var can_jump : bool = false #jump condition
|
||||||
|
|
||||||
#in air jump
|
#in air jump
|
||||||
if !play_char.is_on_floor():
|
if !player.is_on_floor():
|
||||||
if play_char.coyote_jump_on:
|
if player.coyote_jump_on:
|
||||||
play_char.jump_cooldown = play_char.jump_cooldown_ref
|
player.jump_cooldown = player.jump_cooldown_ref
|
||||||
play_char.coyote_jump_cooldown = -1.0 #so that the character cannot immediately make another coyote jump
|
player.coyote_jump_cooldown = -1.0 #so that the character cannot immediately make another coyote jump
|
||||||
play_char.coyote_jump_on = false
|
player.coyote_jump_on = false
|
||||||
can_jump = true
|
can_jump = true
|
||||||
elif play_char.nb_jumps_in_air_allowed > 0:
|
elif player.nb_jumps_in_air_allowed > 0:
|
||||||
play_char.nb_jumps_in_air_allowed -= 1
|
player.nb_jumps_in_air_allowed -= 1
|
||||||
play_char.jump_cooldown = play_char.jump_cooldown_ref
|
player.jump_cooldown = player.jump_cooldown_ref
|
||||||
can_jump = true
|
can_jump = true
|
||||||
|
|
||||||
#on floor jump
|
#on floor jump
|
||||||
if play_char.is_on_floor():
|
if player.is_on_floor():
|
||||||
play_char.jump_cooldown = play_char.jump_cooldown_ref
|
player.jump_cooldown = player.jump_cooldown_ref
|
||||||
can_jump = true
|
can_jump = true
|
||||||
|
|
||||||
#jump buffering
|
#jump buffering
|
||||||
if play_char.buffered_jump:
|
if player.buffered_jump:
|
||||||
play_char.buffered_jump = false
|
player.buffered_jump = false
|
||||||
play_char.nb_jumps_in_air_allowed = play_char.nb_jumps_in_air_allowed_ref
|
player.nb_jumps_in_air_allowed = player.nb_jumps_in_air_allowed_ref
|
||||||
|
|
||||||
#apply jump
|
#apply jump
|
||||||
if can_jump:
|
if can_jump:
|
||||||
play_char.velocity.y = -play_char.jump_velocity
|
player.velocity.y = -player.jump_velocity
|
||||||
can_jump = false
|
can_jump = false
|
||||||
|
|
||||||
#play squash and strech effect, and display jump particles
|
#play squash and strech effect, and display jump particles
|
||||||
if play_char.is_on_floor():
|
if player.is_on_floor():
|
||||||
play_char.squash_and_strech(1.12, 0.1)
|
player.squash_and_strech(1.12, 0.1)
|
||||||
#play_char.particles_manager.display_particles(play_char.jump_particles, play_char)
|
#player.particles_manager.display_particles(player.jump_particles, player)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
# MountedState.gd
|
|
||||||
extends State
|
|
||||||
|
|
||||||
class_name MountedState
|
|
||||||
|
|
||||||
var state_name : String = "Mounted"
|
|
||||||
var play_char : CharacterBody3D
|
|
||||||
var profile : MountProfile # assigné par qui déclenche la monte, avant transitioned.emit(..., "MountedState")
|
|
||||||
|
|
||||||
func enter(play_char_ref : CharacterBody3D) -> void:
|
|
||||||
play_char = play_char_ref
|
|
||||||
play_char.move_speed = profile.mount_speed
|
|
||||||
play_char.move_accel = profile.mount_accel
|
|
||||||
play_char.move_deccel = profile.mount_deccel
|
|
||||||
# instancier profile.mount_scene_override ou profile.species.model_scene,
|
|
||||||
# le positionner, y attacher play_char selon seat_offset...
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
uid://b0slhbduwdqw2
|
|
||||||
48
game/player/states/player_state.gd
Normal file
48
game/player/states/player_state.gd
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
extends State
|
||||||
|
|
||||||
|
class_name PlayerState
|
||||||
|
|
||||||
|
#etat du joueur : ajoute a State le PlayerCharacter, l'input, les hints du HUD et les helpers de mouvement.
|
||||||
|
#les hints sont publiees par StateHints (un composant du joueur) : la StateMachine n'en sait rien.
|
||||||
|
|
||||||
|
signal hints_changed #a emettre quand get_hints() change alors que l'etat est actif
|
||||||
|
|
||||||
|
@export var hints : Array[Hint] = [] #cas simple : se regle dans l'inspecteur
|
||||||
|
@export var keep_previous_hints : bool = false #etats transitoires (Dodge...) : on ne touche pas aux hints affiches
|
||||||
|
|
||||||
|
var player : PlayerCharacter #renseigne par enter() : les etats n'ont plus besoin de le stocker eux-memes
|
||||||
|
var input : InputManagementComponent #lecture de l'input du joueur (raccourci vers player.player_input)
|
||||||
|
|
||||||
|
func enter(new_actor : Node) -> void:
|
||||||
|
player = new_actor as PlayerCharacter
|
||||||
|
input = player.player_input
|
||||||
|
super(new_actor)
|
||||||
|
|
||||||
|
func get_hints() -> Array[Hint]:
|
||||||
|
return hints #a surcharger seulement quand les hints dependent d'une condition
|
||||||
|
|
||||||
|
#---------- transitions du joueur ----------
|
||||||
|
|
||||||
|
func fall() -> bool:
|
||||||
|
#quitter le sol : la posture accroupie est annulee (on retombe debout), puis passage en InairState
|
||||||
|
player.is_crouching = false
|
||||||
|
return request_transition(States.INAIR)
|
||||||
|
|
||||||
|
func request_locomotion() -> bool:
|
||||||
|
#vers Walk ou Run, selon la derniere allure choisie (player.locomotion)
|
||||||
|
return request_transition(player.get_locomotion_state())
|
||||||
|
|
||||||
|
func request_ground_state(moving : bool) -> bool:
|
||||||
|
#retour au sol : Idle si le joueur est immobile, sinon Walk ou Run selon la derniere allure
|
||||||
|
return request_locomotion() if moving else request_transition(States.IDLE)
|
||||||
|
|
||||||
|
#---------- helpers de mouvement ----------
|
||||||
|
|
||||||
|
func accelerate(target_speed : float, accel : float, delta : float) -> void:
|
||||||
|
#vers la vitesse voulue dans la direction de move_dir
|
||||||
|
player.velocity.x = lerpf(player.velocity.x, player.move_dir.x * target_speed, accel * delta)
|
||||||
|
player.velocity.z = lerpf(player.velocity.z, player.move_dir.y * target_speed, accel * delta)
|
||||||
|
|
||||||
|
func slow_down(delta : float) -> void:
|
||||||
|
player.velocity.x = lerpf(player.velocity.x, 0.0, player.move_deccel * delta)
|
||||||
|
player.velocity.z = lerpf(player.velocity.z, 0.0, player.move_deccel * delta)
|
||||||
1
game/player/states/player_state.gd.uid
Normal file
1
game/player/states/player_state.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://8ehfi6tgdtb7
|
||||||
|
|
@ -1,70 +1,23 @@
|
||||||
extends State
|
extends GroundedState
|
||||||
|
|
||||||
class_name RunState
|
class_name RunState
|
||||||
|
|
||||||
var state_name : String = "Run"
|
func _init() -> void:
|
||||||
|
allows_crouch = false #pas de crouch en courant
|
||||||
|
|
||||||
var play_char : CharacterBody3D
|
func on_enter() -> void:
|
||||||
|
player.is_crouching = false #courir annule la posture accroupie
|
||||||
|
player.locomotion = PlayerCharacter.Locomotion.RUN #utilise par AirborneState pour choisir les courbes en l'air
|
||||||
|
super()
|
||||||
|
|
||||||
func enter(play_char_ref : CharacterBody3D) -> void:
|
func apply_standing_stats() -> void:
|
||||||
play_char = play_char_ref
|
player.move_speed = player.run_speed
|
||||||
|
player.move_accel = player.run_accel
|
||||||
|
player.move_deccel = player.run_deccel
|
||||||
|
|
||||||
verifications()
|
func check_locomotion_switch() -> bool:
|
||||||
|
return !player.is_running() and request_transition(States.WALK)
|
||||||
|
|
||||||
func verifications() -> void:
|
func on_no_move(_delta : float) -> void:
|
||||||
#play_char.godot_plush_skin.set_state("run")
|
if !player.continious_run: player.locomotion = PlayerCharacter.Locomotion.WALK
|
||||||
play_char.walk_or_run = "RunState" #utilise par JumpState/InairState pour choisir les courbes en l'air
|
request_transition(States.IDLE)
|
||||||
play_char.move_speed = play_char.run_speed
|
|
||||||
play_char.move_accel = play_char.run_accel
|
|
||||||
play_char.move_deccel = play_char.run_deccel
|
|
||||||
|
|
||||||
play_char.floor_snap_length = 1.0
|
|
||||||
if play_char.jump_cooldown > 0.0: play_char.jump_cooldown = -1.0
|
|
||||||
if play_char.nb_jumps_in_air_allowed < play_char.nb_jumps_in_air_allowed_ref: play_char.nb_jumps_in_air_allowed = play_char.nb_jumps_in_air_allowed_ref
|
|
||||||
if play_char.coyote_jump_cooldown < play_char.coyote_jump_cooldown_ref: play_char.coyote_jump_cooldown = play_char.coyote_jump_cooldown_ref
|
|
||||||
if play_char.has_cut_jump: play_char.has_cut_jump = false
|
|
||||||
|
|
||||||
func physics_update(delta : float) -> void:
|
|
||||||
applies()
|
|
||||||
|
|
||||||
play_char.gravity_apply(delta)
|
|
||||||
|
|
||||||
input_management()
|
|
||||||
|
|
||||||
move(delta)
|
|
||||||
|
|
||||||
func applies() -> void:
|
|
||||||
if !play_char.is_on_floor() and !play_char.is_on_wall():
|
|
||||||
if play_char.velocity.y < 0.0:
|
|
||||||
transitioned.emit(self, "InairState")
|
|
||||||
|
|
||||||
if play_char.is_on_floor():
|
|
||||||
if play_char.jump_buff_on:
|
|
||||||
#apply jump buffering
|
|
||||||
play_char.buffered_jump = true
|
|
||||||
play_char.jump_buff_on = false
|
|
||||||
transitioned.emit(self, "JumpState")
|
|
||||||
|
|
||||||
func input_management() -> void:
|
|
||||||
if Input.is_action_just_pressed(play_char.dodge_action) and play_char.dodge_cooldown <= 0.0:
|
|
||||||
transitioned.emit(self, "DodgeState")
|
|
||||||
|
|
||||||
if Input.is_action_pressed(play_char.jump_action) if play_char.auto_jump else Input.is_action_just_pressed(play_char.jump_action):
|
|
||||||
transitioned.emit(self, "JumpState")
|
|
||||||
|
|
||||||
if Input.is_action_just_pressed(play_char.aim_cam_action):
|
|
||||||
transitioned.emit(self, "AimState")
|
|
||||||
|
|
||||||
if play_char.walk_or_run == "WalkState":
|
|
||||||
transitioned.emit(self, "WalkState")
|
|
||||||
|
|
||||||
func move(delta : float) -> void:
|
|
||||||
play_char.move_dir = Input.get_vector(play_char.move_left_action, play_char.move_right_action, play_char.move_forward_action, play_char.move_backward_action).rotated(-play_char.cam_holder.global_rotation.y)
|
|
||||||
|
|
||||||
if play_char.move_dir and play_char.is_on_floor():
|
|
||||||
#apply smooth move
|
|
||||||
play_char.velocity.x = lerp(play_char.velocity.x, play_char.move_dir.x * play_char.move_speed, play_char.move_accel * delta)
|
|
||||||
play_char.velocity.z = lerp(play_char.velocity.z, play_char.move_dir.y * play_char.move_speed, play_char.move_accel * delta)
|
|
||||||
else:
|
|
||||||
if !play_char.continious_run: play_char.walk_or_run = "WalkState"
|
|
||||||
transitioned.emit(self, "IdleState")
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,26 @@ extends Node
|
||||||
|
|
||||||
class_name State
|
class_name State
|
||||||
|
|
||||||
signal transitioned #it's normal if this signal isn't used in this script, because it's purpose is to be used in the scripts that extends from the State class
|
#etat generique d'une StateMachine : il ne connait ni le joueur, ni le HUD, ni le jeu. Reutilisable pour un PNJ, un pokemon sauvage...
|
||||||
signal hints_changed #a emettre quand get_hints() change alors que l'etat est actif
|
#pour le joueur, voir PlayerState (qui ajoute le joueur, l'input, les hints et les helpers de mouvement).
|
||||||
|
#
|
||||||
|
#un etat surcharge on_enter() / exit() / update() / physics_update() et demande ses transitions avec request_transition().
|
||||||
|
|
||||||
@export var hints : Array[Hint] = [] #cas simple : se regle dans l'inspecteur
|
var actor : Node #le node pilote par la machine (CharacterBody3D, joueur, PNJ...), renseigne par enter()
|
||||||
@export var keep_previous_hints : bool = false #etats transitoires (Dodge...) : on ne touche pas aux hints affiches
|
var machine : StateMachine #renseigne par la StateMachine
|
||||||
|
|
||||||
func enter(_char_reference : CharacterBody3D) -> void:
|
#donnees echangees entre etats (renseignees par la StateMachine, jamais ecrites a la main dans un autre etat) :
|
||||||
|
var enter_data : Dictionary = {} #recues de l'etat precedent, lisibles dans on_enter() et ensuite
|
||||||
|
var outgoing_data : Dictionary = {} #envoyees a l'etat suivant par request_transition(), lisibles dans exit()
|
||||||
|
var next_state_name : String = "" #destination, renseignee avant exit() : exit() sait ou on va
|
||||||
|
|
||||||
|
#---------- cycle de vie ----------
|
||||||
|
|
||||||
|
func enter(new_actor : Node) -> void:
|
||||||
|
actor = new_actor
|
||||||
|
on_enter()
|
||||||
|
|
||||||
|
func on_enter() -> void: #a surcharger a la place de enter() : actor est deja assigne
|
||||||
pass
|
pass
|
||||||
|
|
||||||
func exit() -> void:
|
func exit() -> void:
|
||||||
|
|
@ -20,5 +33,11 @@ func update(_delta : float) -> void:
|
||||||
func physics_update(_delta : float) -> void:
|
func physics_update(_delta : float) -> void:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
func get_hints() -> Array[Hint]:
|
#---------- transitions ----------
|
||||||
return hints #a surcharger seulement quand les hints dependent d'une condition
|
|
||||||
|
func request_transition(new_state_name : String, data : Dictionary = {}) -> bool:
|
||||||
|
#demande une transition et renvoie true seulement si elle a vraiment eu lieu
|
||||||
|
#(la StateMachine peut la refuser : etat bloque, etat inconnu, ou demande d'un etat qui n'est plus l'etat courant)
|
||||||
|
#data : transmise a l'etat suivant (enter_data), et lisible par exit() de celui-ci (outgoing_data)
|
||||||
|
if machine == null: return false
|
||||||
|
return machine.request(self, new_state_name, data)
|
||||||
|
|
|
||||||
|
|
@ -2,91 +2,87 @@ extends Node
|
||||||
|
|
||||||
class_name StateMachine
|
class_name StateMachine
|
||||||
|
|
||||||
@export var initial_state : State
|
#machine a etats generique : elle ne connait ni le joueur, ni le HUD, ni le lancer. Reutilisable pour n'importe quel acteur
|
||||||
@export var throw_action : StringName = &"player_throw"
|
#(joueur, PNJ, pokemon sauvage...) : poser la StateMachine sous l'acteur, y mettre des nodes qui heritent de State.
|
||||||
|
#ce qui est propre au joueur vit ailleurs : le lancer dans GroundedState, les hints dans StateHints.
|
||||||
|
|
||||||
const THROW_ENTRY_STATES : Array[String] = ["Idle", "Walk", "Run", "Crouch"] #etats depuis lesquels RT lance sans viser
|
signal state_changed(from : State, to : State) #from est null pour l'etat initial
|
||||||
|
|
||||||
|
@export var initial_state : State
|
||||||
|
@export var actor : Node #le node pilote par la machine, passe a chaque etat. Par defaut : le parent
|
||||||
|
|
||||||
var curr_state : State
|
var curr_state : State
|
||||||
var curr_state_name : String
|
var curr_state_name : String: #nom du node de l'etat courant (ex : "IdleState")
|
||||||
var states : Dictionary = {}
|
get: return String(curr_state.name) if curr_state else ""
|
||||||
var blocked_states : Dictionary = {} #noms (en minuscules) des etats dont l'entree est refusee
|
var states : Dictionary = {} #nom du node (casse exacte) -> State
|
||||||
|
var blocked_states : Dictionary = {} #noms des etats dont l'entree est refusee
|
||||||
@onready var char_ref : CharacterBody3D = $".."
|
var _warned : Dictionary = {} #noms introuvables deja signales : un seul avertissement par nom, pas un par frame
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
if actor == null: actor = get_parent()
|
||||||
|
|
||||||
#get all the state childrens
|
#get all the state childrens
|
||||||
for child in get_children():
|
for child in get_children():
|
||||||
if child is State:
|
if child is State:
|
||||||
states[child.name.to_lower()] = child
|
states[String(child.name)] = child
|
||||||
child.transitioned.connect(on_state_child_transition)
|
child.machine = self
|
||||||
child.hints_changed.connect(_on_state_hints_changed.bind(child))
|
|
||||||
|
|
||||||
#if initial state, transition to it
|
#if initial state, transition to it
|
||||||
if initial_state:
|
if initial_state:
|
||||||
await get_tree().create_timer(0.1).timeout
|
await get_tree().create_timer(0.1).timeout
|
||||||
initial_state.enter(char_ref)
|
initial_state.enter(actor)
|
||||||
curr_state = initial_state
|
curr_state = initial_state
|
||||||
curr_state_name = curr_state.state_name
|
state_changed.emit(null, initial_state)
|
||||||
publish_hints()
|
|
||||||
|
|
||||||
func _process(delta : float) -> void:
|
func _process(delta : float) -> void:
|
||||||
if curr_state: curr_state.update(delta)
|
if curr_state: curr_state.update(delta)
|
||||||
|
|
||||||
|
|
||||||
func _physics_process(delta : float) -> void:
|
func _physics_process(delta : float) -> void:
|
||||||
if curr_state:
|
if curr_state: curr_state.physics_update(delta)
|
||||||
if try_start_throw(): return
|
|
||||||
curr_state.physics_update(delta)
|
|
||||||
|
|
||||||
func try_start_throw() -> bool:
|
#---------- requetes ----------
|
||||||
if !(curr_state_name in THROW_ENTRY_STATES): return false
|
|
||||||
if !Input.is_action_just_pressed(throw_action): return false
|
|
||||||
if !char_ref.is_on_floor(): return false
|
|
||||||
|
|
||||||
var selected : PokemonInstance = GameData.get_selected_pokemon()
|
func has_state(state_name : String) -> bool:
|
||||||
if selected == null: return false
|
return states.has(state_name)
|
||||||
|
|
||||||
#pokemon deja dehors : l'appui le rappelle au lieu de lancer
|
func is_in(state_name : String) -> bool:
|
||||||
var already_out : SentPokemon = SentPokemon.find_for(get_tree(), selected)
|
return curr_state_name == state_name
|
||||||
if already_out:
|
|
||||||
already_out.recall()
|
|
||||||
return false
|
|
||||||
|
|
||||||
if !states.has("throwstate"): return false
|
func is_blocked(state_name : String) -> bool:
|
||||||
on_state_child_transition(curr_state, "ThrowState")
|
return blocked_states.has(state_name)
|
||||||
return true
|
|
||||||
|
|
||||||
func on_state_child_transition(state : State, new_state_name : String) -> void:
|
|
||||||
#manage the transition from one state to another
|
|
||||||
|
|
||||||
if state != curr_state: return
|
|
||||||
|
|
||||||
var new_state = states.get(new_state_name.to_lower())
|
|
||||||
if !new_state: return
|
|
||||||
|
|
||||||
if blocked_states.has(new_state_name.to_lower()): return
|
|
||||||
|
|
||||||
#exit the current state
|
|
||||||
if curr_state: curr_state.exit()
|
|
||||||
|
|
||||||
#enter the new state
|
|
||||||
new_state.enter(char_ref)
|
|
||||||
|
|
||||||
curr_state = new_state
|
|
||||||
curr_state_name = curr_state.state_name
|
|
||||||
publish_hints()
|
|
||||||
|
|
||||||
func set_state_blocked(state_name : String, blocked : bool) -> void:
|
func set_state_blocked(state_name : String, blocked : bool) -> void:
|
||||||
if blocked: blocked_states[state_name.to_lower()] = true
|
if blocked: blocked_states[state_name] = true
|
||||||
else: blocked_states.erase(state_name.to_lower())
|
else: blocked_states.erase(state_name)
|
||||||
|
|
||||||
func _on_state_hints_changed(state : State) -> void:
|
#---------- transitions ----------
|
||||||
if state == curr_state: publish_hints()
|
|
||||||
|
|
||||||
func publish_hints() -> void:
|
func request(from : State, new_state_name : String, data : Dictionary = {}) -> bool:
|
||||||
if curr_state and !curr_state.keep_previous_hints:
|
#appelee par State.request_transition() : renvoie true si la transition a eu lieu
|
||||||
Hints.show_hints(self, curr_state.get_hints())
|
if from != curr_state: return false #l'etat demandeur n'est plus l'etat courant (transition deja faite pendant cette frame)
|
||||||
|
|
||||||
func _exit_tree() -> void:
|
var new_state : State = states.get(new_state_name)
|
||||||
Hints.clear_hints(self)
|
if new_state == null:
|
||||||
|
_warn_unknown_state(from, new_state_name)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if is_blocked(new_state_name): return false
|
||||||
|
|
||||||
|
#exit the current state : il connait sa destination, et les donnees qu'il envoie (outgoing_data) sont lisibles
|
||||||
|
from.next_state_name = new_state_name
|
||||||
|
from.outgoing_data = data
|
||||||
|
from.exit()
|
||||||
|
from.outgoing_data = {}
|
||||||
|
|
||||||
|
#enter the new state : il recoit les donnees de l'etat qu'on quitte
|
||||||
|
new_state.enter_data = data
|
||||||
|
new_state.enter(actor)
|
||||||
|
|
||||||
|
curr_state = new_state
|
||||||
|
state_changed.emit(from, new_state)
|
||||||
|
return true
|
||||||
|
|
||||||
|
func _warn_unknown_state(from : State, new_state_name : String) -> void:
|
||||||
|
if _warned.has(new_state_name): return
|
||||||
|
_warned[new_state_name] = true
|
||||||
|
push_warning("StateMachine : %s demande une transition vers '%s', mais aucun node d'etat ne porte ce nom (etats disponibles : %s). Verifier le nom du node dans la scene." % [from.name, new_state_name, ", ".join(PackedStringArray(states.keys()))])
|
||||||
|
|
|
||||||
15
game/player/states/states.gd
Normal file
15
game/player/states/states.gd
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
class_name States
|
||||||
|
|
||||||
|
#noms des etats : un etat s'identifie TOUJOURS par le nom de son node dans la StateMachine, avec la casse exacte.
|
||||||
|
#on ecrit States.IDLE et jamais "IdleState" a la main : une faute de frappe devient une erreur a l'edition au lieu d'une
|
||||||
|
#transition ignoree en silence. Pour un nouvel etat (ex : SwimState), ajouter sa constante ici, avec le nom du node.
|
||||||
|
#
|
||||||
|
#les groupes d'etats ne sont plus des listes de noms : on teste le type (curr_state is GroundedState / AirborneState).
|
||||||
|
|
||||||
|
const IDLE := "IdleState"
|
||||||
|
const WALK := "WalkState"
|
||||||
|
const RUN := "RunState"
|
||||||
|
const JUMP := "JumpState"
|
||||||
|
const INAIR := "InairState"
|
||||||
|
const DODGE := "DodgeState"
|
||||||
|
const THROW := "ThrowState"
|
||||||
1
game/player/states/states.gd.uid
Normal file
1
game/player/states/states.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dkwn6qhgojdt1
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
extends State
|
extends PlayerState
|
||||||
|
|
||||||
class_name ThrowState
|
class_name ThrowState
|
||||||
|
|
||||||
var state_name : String = "Throw"
|
|
||||||
|
|
||||||
var play_char : CharacterBody3D
|
|
||||||
|
|
||||||
@export var aim_action : StringName = &"player_aim_camera"
|
|
||||||
@export var throw_action : StringName = &"player_throw" #RT / clic gauche : maintenir pour charger, relacher pour lancer
|
|
||||||
#@export var cancel_action : StringName = &"player_throw_cancel" #B / clic droit (cree automatiquement s'il n'existe pas)
|
#@export var cancel_action : StringName = &"player_throw_cancel" #B / clic droit (cree automatiquement s'il n'existe pas)
|
||||||
|
|
||||||
@export var cancel_hint : Hint #cancel_throw.tres
|
@export var cancel_hint : Hint #cancel_throw.tres
|
||||||
|
|
@ -22,92 +16,35 @@ var cancel_action : StringName: #l'action vient du Hint : une seule definition
|
||||||
@export var windup_time : float = 0.25 #delai entre le relachement et le depart de la balle (a caler sur l'animation)
|
@export var windup_time : float = 0.25 #delai entre le relachement et le depart de la balle (a caler sur l'animation)
|
||||||
@export var recovery_time : float = 0.35 #temps de recuperation apres le lancer
|
@export var recovery_time : float = 0.35 #temps de recuperation apres le lancer
|
||||||
|
|
||||||
@export_group("Ball")
|
|
||||||
@export var ball_scene : PackedScene #modele de la balle (optionnel : sphere rouge par defaut)
|
|
||||||
@export var hand_height : float = 1.4
|
|
||||||
@export var ball_speed : float = 18.0
|
|
||||||
@export var min_flight_time : float = 0.25
|
|
||||||
@export var max_flight_time : float = 1.2
|
|
||||||
@export var arc_height : float = 1.5
|
|
||||||
@export var land_distance_from_target : float = 1.5 #la balle s'arrete devant une cible de combat
|
|
||||||
|
|
||||||
@export_group("Targeting")
|
|
||||||
@export var throw_range : float = 20.0
|
|
||||||
@export_flags_3d_physics var ground_mask : int = 1
|
|
||||||
@export var free_hit_radius : float = 2.0 #lancer sans cible verrouillee : rayon autour du point d'atterrissage pour trouver une cible de combat
|
|
||||||
|
|
||||||
var _elapsed : float = 0.0
|
var _elapsed : float = 0.0
|
||||||
var _released : bool = false #true des que RT est relachee (phase windup/recovery)
|
var _released : bool = false #true des que RT est relachee (phase windup/recovery)
|
||||||
var _thrown : bool = false
|
var _thrown : bool = false
|
||||||
var _keep_aim : bool = false #true quand on retourne a AimState : on garde camera et verrouillage
|
|
||||||
var _pokemon : PokemonInstance = null
|
var _pokemon : PokemonInstance = null
|
||||||
var _target : Variant = null #AimTarget verrouille au moment du relachement
|
var _target : Variant = null #AimTarget verrouille au moment du relachement
|
||||||
var _free_aim_point : Vector3 = Vector3.ZERO
|
var _free_aim_point : Vector3 = Vector3.ZERO
|
||||||
var _came_from_crouch : bool = false #true si le lancer vient du crouch (directement, ou via Aim declenche depuis le crouch)
|
|
||||||
var resume_released : bool = false #mis a true par AimState : RT a ete relachee pendant la visee, le lancer demarre directement en windup
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_ensure_action(throw_action, func():
|
InputRegistry.ensure(cancel_action, [InputRegistry.pad_button(JOY_BUTTON_B), InputRegistry.mouse(MOUSE_BUTTON_RIGHT)])
|
||||||
var trigger := InputEventJoypadMotion.new()
|
|
||||||
trigger.axis = JOY_AXIS_TRIGGER_RIGHT
|
|
||||||
trigger.axis_value = 1.0
|
|
||||||
InputMap.action_add_event(throw_action, trigger)
|
|
||||||
var click := InputEventMouseButton.new()
|
|
||||||
click.button_index = MOUSE_BUTTON_LEFT
|
|
||||||
InputMap.action_add_event(throw_action, click))
|
|
||||||
_ensure_action(cancel_action, func():
|
|
||||||
var pad := InputEventJoypadButton.new()
|
|
||||||
pad.button_index = JOY_BUTTON_B
|
|
||||||
InputMap.action_add_event(cancel_action, pad)
|
|
||||||
var click := InputEventMouseButton.new()
|
|
||||||
click.button_index = MOUSE_BUTTON_RIGHT
|
|
||||||
InputMap.action_add_event(cancel_action, click))
|
|
||||||
|
|
||||||
func _ensure_action(action : StringName, add_events : Callable) -> void:
|
func on_enter() -> void:
|
||||||
if InputMap.has_action(action): return
|
|
||||||
InputMap.add_action(action)
|
|
||||||
add_events.call()
|
|
||||||
|
|
||||||
func enter(play_char_ref : CharacterBody3D) -> void:
|
|
||||||
play_char = play_char_ref
|
|
||||||
_elapsed = 0.0
|
_elapsed = 0.0
|
||||||
_released = false
|
_released = false
|
||||||
_thrown = false
|
_thrown = false
|
||||||
_keep_aim = false
|
|
||||||
|
|
||||||
#curr_state_name n'est pas encore mis a jour a ce stade : il reflete encore l'etat qu'on quitte
|
|
||||||
var previous : String = play_char.state_machine.curr_state_name
|
|
||||||
if previous == "Crouch":
|
|
||||||
_came_from_crouch = true
|
|
||||||
elif previous == "Aim":
|
|
||||||
var aim : Node = play_char.state_machine.states.get("aimstate")
|
|
||||||
_came_from_crouch = aim != null and aim.came_from_crouch
|
|
||||||
else:
|
|
||||||
_came_from_crouch = false
|
|
||||||
|
|
||||||
_pokemon = GameData.get_selected_pokemon()
|
_pokemon = GameData.get_selected_pokemon()
|
||||||
_target = null
|
_target = null
|
||||||
_free_aim_point = Vector3.ZERO
|
_free_aim_point = Vector3.ZERO
|
||||||
|
|
||||||
play_char.floor_snap_length = 1.0
|
player.floor_snap_length = 1.0
|
||||||
#play_char.godot_plush_skin.set_state("throw")
|
#player.godot_plush_skin.set_state("throw")
|
||||||
|
|
||||||
if resume_released:
|
|
||||||
resume_released = false
|
|
||||||
release() #fige la cible verrouillee / le point vise et lance le windup
|
|
||||||
|
|
||||||
func exit() -> void:
|
func exit() -> void:
|
||||||
#retour a la visee : on garde tout tel quel ; sinon on libere camera et verrouillage (comme AimState)
|
AimReticle.hide_instance() #si la visee est encore active, GroundedState le reaffiche a la frame suivante
|
||||||
if _keep_aim:
|
|
||||||
_keep_aim = false
|
|
||||||
return
|
|
||||||
AimReticle.hide_instance()
|
|
||||||
_release_aim()
|
|
||||||
|
|
||||||
func physics_update(delta : float) -> void:
|
func physics_update(delta : float) -> void:
|
||||||
if applies(): return
|
if applies(): return
|
||||||
|
|
||||||
play_char.gravity_apply(delta)
|
player.gravity_apply(delta)
|
||||||
|
|
||||||
if _pokemon == null:
|
if _pokemon == null:
|
||||||
slow_down(delta)
|
slow_down(delta)
|
||||||
|
|
@ -126,8 +63,8 @@ func physics_update(delta : float) -> void:
|
||||||
finish() #annule avant que la balle parte
|
finish() #annule avant que la balle parte
|
||||||
return
|
return
|
||||||
|
|
||||||
if _thrown and Input.is_action_just_pressed(aim_action):
|
if _thrown and input.aim_pressed():
|
||||||
finish() #balle partie : on coupe la recuperation, finish() bascule en AimState
|
finish() #balle partie : on coupe la recuperation pour pouvoir viser tout de suite
|
||||||
return
|
return
|
||||||
|
|
||||||
_elapsed += delta
|
_elapsed += delta
|
||||||
|
|
@ -137,10 +74,7 @@ func physics_update(delta : float) -> void:
|
||||||
finish()
|
finish()
|
||||||
|
|
||||||
func applies() -> bool:
|
func applies() -> bool:
|
||||||
if !play_char.is_on_floor() and play_char.velocity.y < 0.0:
|
return !player.is_on_floor() and player.velocity.y < 0.0 and fall()
|
||||||
transitioned.emit(self, "InairState")
|
|
||||||
return true
|
|
||||||
return false
|
|
||||||
|
|
||||||
#---------- charge ----------
|
#---------- charge ----------
|
||||||
|
|
||||||
|
|
@ -149,67 +83,37 @@ func charge(delta : float) -> void:
|
||||||
finish() #annule : rien n'est lance
|
finish() #annule : rien n'est lance
|
||||||
return
|
return
|
||||||
|
|
||||||
if Input.is_action_just_pressed(aim_action):
|
if input.aim_pressed(): player.aim_targeting.begin_aim() #on peut se mettre a viser en pleine charge, sans perdre la charge
|
||||||
enter_aim_while_charging()
|
|
||||||
return
|
|
||||||
|
|
||||||
if !Input.is_action_pressed(throw_action):
|
if !input.throw_held():
|
||||||
release()
|
release()
|
||||||
return
|
return
|
||||||
|
|
||||||
move_while_charging(delta)
|
move_while_charging(delta)
|
||||||
update_reticle()
|
if player.is_aiming: player.aim_targeting.tick() #cible verrouillee / changement de cible pendant la charge
|
||||||
|
else: player.aim_targeting.update_reticle()
|
||||||
func enter_aim_while_charging() -> void:
|
|
||||||
#on passe en visee sans perdre la charge : AimState se charge de relancer le lancer au relachement de RT
|
|
||||||
var aim : Node = play_char.state_machine.states.get("aimstate")
|
|
||||||
if aim == null: return
|
|
||||||
aim.came_from_crouch = _came_from_crouch
|
|
||||||
aim.charging_throw = true
|
|
||||||
_keep_aim = true
|
|
||||||
transitioned.emit(self, "AimState")
|
|
||||||
|
|
||||||
func release() -> void:
|
func release() -> void:
|
||||||
#RT relachee : on fige la cible et le point vise, puis le windup demarre
|
#RT relachee : on fige la cible et le point vise, puis le windup demarre
|
||||||
_released = true
|
_released = true
|
||||||
_elapsed = 0.0
|
_elapsed = 0.0
|
||||||
_target = play_char.aim_target if is_instance_valid(play_char.aim_target) else null
|
_target = player.aim_target if is_instance_valid(player.aim_target) else null
|
||||||
_free_aim_point = _compute_free_aim_point()
|
_free_aim_point = player.throw_resolver.compute_free_aim_point()
|
||||||
|
|
||||||
func move_while_charging(delta : float) -> void:
|
func move_while_charging(delta : float) -> void:
|
||||||
play_char.move_dir = Input.get_vector(play_char.move_left_action, play_char.move_right_action, play_char.move_forward_action, play_char.move_backward_action).rotated(-play_char.cam_holder.global_rotation.y)
|
player.move_dir = input.read_move_dir()
|
||||||
|
|
||||||
if play_char.move_dir and play_char.is_on_floor():
|
if player.move_dir and player.is_on_floor():
|
||||||
play_char.velocity.x = lerp(play_char.velocity.x, play_char.move_dir.x * charge_move_speed, charge_move_accel * delta)
|
accelerate(charge_move_speed, charge_move_accel, delta)
|
||||||
play_char.velocity.z = lerp(play_char.velocity.z, play_char.move_dir.y * charge_move_speed, charge_move_accel * delta)
|
|
||||||
else:
|
else:
|
||||||
slow_down(delta)
|
slow_down(delta)
|
||||||
|
|
||||||
func slow_down(delta : float) -> void:
|
|
||||||
play_char.velocity.x = lerp(play_char.velocity.x, 0.0, play_char.move_deccel * delta)
|
|
||||||
play_char.velocity.z = lerp(play_char.velocity.z, 0.0, play_char.move_deccel * delta)
|
|
||||||
|
|
||||||
func finish() -> void:
|
func finish() -> void:
|
||||||
if Input.is_action_pressed(aim_action):
|
if input.aim_held(): player.aim_targeting.begin_aim() #visee enfoncee : elle continue apres le lancer (sans effet si deja active)
|
||||||
_keep_aim = true
|
|
||||||
#AimState ne verra que "Throw" comme etat precedent : on lui transmet l'origine crouch
|
|
||||||
var aim : Node = play_char.state_machine.states.get("aimstate")
|
|
||||||
if aim: aim.came_from_crouch = _came_from_crouch
|
|
||||||
transitioned.emit(self, "AimState")
|
|
||||||
return
|
|
||||||
|
|
||||||
if _came_from_crouch:
|
#accroupi ou non : la posture est conservee, Idle / Walk la reappliquent
|
||||||
transitioned.emit(self, "CrouchState")
|
var dir : Vector2 = input.read_move_raw()
|
||||||
return
|
request_ground_state(dir != Vector2.ZERO)
|
||||||
|
|
||||||
var dir : Vector2 = Input.get_vector(play_char.move_left_action, play_char.move_right_action, play_char.move_forward_action, play_char.move_backward_action)
|
|
||||||
transitioned.emit(self, play_char.walk_or_run if dir != Vector2.ZERO else "IdleState")
|
|
||||||
|
|
||||||
func update_reticle() -> void:
|
|
||||||
if is_instance_valid(play_char.aim_target):
|
|
||||||
AimReticle.hide_instance() #cible verrouillee : son propre indicateur suffit
|
|
||||||
else:
|
|
||||||
AimReticle.show_instance(self)
|
|
||||||
|
|
||||||
#---------- lancer ----------
|
#---------- lancer ----------
|
||||||
|
|
||||||
|
|
@ -217,88 +121,8 @@ func throw_ball() -> void:
|
||||||
_thrown = true
|
_thrown = true
|
||||||
hints_changed.emit()
|
hints_changed.emit()
|
||||||
AimReticle.hide_instance()
|
AimReticle.hide_instance()
|
||||||
|
#la balle est autonome : son atterrissage et l'envoi du pokemon sont geres par ThrowResolver, meme si le joueur change d'etat entre-temps
|
||||||
var from : Vector3 = play_char.global_position + Vector3.UP * hand_height
|
player.throw_resolver.throw_ball(_pokemon, _target, _free_aim_point)
|
||||||
var to : Vector3 = _get_landing_point()
|
|
||||||
var flight_time : float = clampf(from.distance_to(to) / ball_speed, min_flight_time, max_flight_time)
|
|
||||||
|
|
||||||
var scene : Node = play_char.get_tree().current_scene
|
|
||||||
var ball : ThrownBall = ThrownBall.launch(scene, from, to, flight_time, arc_height, ball_scene)
|
|
||||||
#la balle est autonome : l'atterrissage est gere ici meme si le joueur a change d'etat entre-temps
|
|
||||||
ball.landed.connect(_on_ball_landed.bind(_pokemon, _target, from))
|
|
||||||
|
|
||||||
func _get_landing_point() -> Vector3:
|
|
||||||
if !is_instance_valid(_target): return _free_aim_point
|
|
||||||
|
|
||||||
var target_pos : Vector3 = _target.global_position
|
|
||||||
if _starts_battle(_target):
|
|
||||||
#la balle s'arrete devant la cible, du cote du joueur
|
|
||||||
var to_player : Vector3 = play_char.global_position - target_pos
|
|
||||||
to_player.y = 0.0
|
|
||||||
if to_player.length() > land_distance_from_target:
|
|
||||||
target_pos += to_player.normalized() * land_distance_from_target
|
|
||||||
return _snap_to_ground(target_pos)
|
|
||||||
|
|
||||||
func _compute_free_aim_point() -> Vector3:
|
|
||||||
#point vise au centre de l'ecran : premier obstacle, sinon fin de portee ramenee au sol
|
|
||||||
var cam : Camera3D = play_char.cam_holder.cam
|
|
||||||
var origin : Vector3 = cam.global_position
|
|
||||||
var far : Vector3 = origin - cam.global_basis.z * throw_range
|
|
||||||
var query := PhysicsRayQueryParameters3D.create(origin, far, ground_mask, [play_char.get_rid()])
|
|
||||||
var hit : Dictionary = play_char.get_world_3d().direct_space_state.intersect_ray(query)
|
|
||||||
if hit.is_empty(): return _snap_to_ground(far)
|
|
||||||
return _snap_to_ground(hit["position"] + hit["normal"] * 0.5)
|
|
||||||
|
|
||||||
func _snap_to_ground(point : Vector3) -> Vector3:
|
|
||||||
var query := PhysicsRayQueryParameters3D.create(point + Vector3.UP * 2.0, point + Vector3.DOWN * 6.0, ground_mask, [play_char.get_rid()])
|
|
||||||
var hit : Dictionary = play_char.get_world_3d().direct_space_state.intersect_ray(query)
|
|
||||||
return hit["position"] if !hit.is_empty() else point
|
|
||||||
|
|
||||||
func _starts_battle(target : Object) -> bool:
|
|
||||||
#AimTarget doit avoir : @export var starts_battle : bool = false
|
|
||||||
return "starts_battle" in target and target.starts_battle
|
|
||||||
|
|
||||||
func _on_ball_landed(landing_pos : Vector3, pokemon : PokemonInstance, target : Variant, thrower_pos : Vector3) -> void:
|
|
||||||
var scene : Node = get_tree().current_scene if is_inside_tree() else null
|
|
||||||
if scene == null: return
|
|
||||||
|
|
||||||
#lancer sans cible verrouillee : si la balle atterrit pres d'une cible de combat, c'est elle qui est visee
|
|
||||||
if !is_instance_valid(target):
|
|
||||||
target = _find_battle_target_near(landing_pos)
|
|
||||||
|
|
||||||
var target_valid : bool = is_instance_valid(target)
|
|
||||||
#le pokemon regarde la cible, ou dans la direction du lancer s'il n'y en a pas
|
|
||||||
var look_pos : Vector3 = target.global_position if target_valid else landing_pos + (landing_pos - thrower_pos)
|
|
||||||
var sent : SentPokemon = SentPokemon.send(scene, pokemon, landing_pos, look_pos)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
func _find_battle_target_near(point : Vector3) -> AimTarget:
|
|
||||||
#cible de combat active la plus proche du point d'atterrissage, dans free_hit_radius
|
|
||||||
var best : AimTarget = null
|
|
||||||
var best_dist : float = free_hit_radius
|
|
||||||
for node in get_tree().get_nodes_in_group("aim_targets"):
|
|
||||||
var t := node as AimTarget
|
|
||||||
if t == null or !t.enabled or !_starts_battle(t): continue
|
|
||||||
var dist : float = point.distance_to(t.global_position)
|
|
||||||
if dist <= best_dist:
|
|
||||||
best_dist = dist
|
|
||||||
best = t
|
|
||||||
return best
|
|
||||||
|
|
||||||
#---------- nettoyage ----------
|
|
||||||
|
|
||||||
func _release_aim() -> void:
|
|
||||||
#meme comportement que AimState.exit() / release_target()
|
|
||||||
if !play_char: return
|
|
||||||
if is_instance_valid(play_char.aim_target): play_char.aim_target.set_locked(false)
|
|
||||||
play_char.aim_target = null
|
|
||||||
play_char.cam_holder.set_lock_target(null)
|
|
||||||
play_char.cam_holder.cam_aimed = false
|
|
||||||
|
|
||||||
func get_hints() -> Array[Hint]:
|
func get_hints() -> Array[Hint]:
|
||||||
var list : Array[Hint] = []
|
var list : Array[Hint] = []
|
||||||
|
|
|
||||||
|
|
@ -1,73 +1,14 @@
|
||||||
extends State
|
extends GroundedState
|
||||||
|
|
||||||
class_name WalkState
|
class_name WalkState
|
||||||
|
|
||||||
var state_name : String = "Walk"
|
func apply_standing_stats() -> void:
|
||||||
|
player.move_speed = player.walk_speed
|
||||||
|
player.move_accel = player.walk_accel
|
||||||
|
player.move_deccel = player.walk_deccel
|
||||||
|
|
||||||
var play_char : CharacterBody3D
|
func check_locomotion_switch() -> bool:
|
||||||
|
return player.is_running() and request_transition(States.RUN)
|
||||||
func enter(play_char_ref : CharacterBody3D) -> void:
|
|
||||||
play_char = play_char_ref
|
|
||||||
|
|
||||||
verifications()
|
|
||||||
|
|
||||||
func verifications() -> void:
|
|
||||||
#play_char.godot_plush_skin.set_state("walk")
|
|
||||||
play_char.move_speed = play_char.walk_speed
|
|
||||||
play_char.move_accel = play_char.walk_accel
|
|
||||||
play_char.move_deccel = play_char.walk_deccel
|
|
||||||
|
|
||||||
play_char.floor_snap_length = 1.0
|
|
||||||
if play_char.jump_cooldown > 0.0: play_char.jump_cooldown = -1.0
|
|
||||||
if play_char.nb_jumps_in_air_allowed < play_char.nb_jumps_in_air_allowed_ref: play_char.nb_jumps_in_air_allowed = play_char.nb_jumps_in_air_allowed_ref
|
|
||||||
if play_char.coyote_jump_cooldown < play_char.coyote_jump_cooldown_ref: play_char.coyote_jump_cooldown = play_char.coyote_jump_cooldown_ref
|
|
||||||
if play_char.has_cut_jump: play_char.has_cut_jump = false
|
|
||||||
#if play_char.movement_dust.emitting: play_char.movement_dust.emitting = false
|
|
||||||
|
|
||||||
func physics_update(delta : float) -> void:
|
|
||||||
applies()
|
|
||||||
|
|
||||||
play_char.gravity_apply(delta)
|
|
||||||
|
|
||||||
input_management()
|
|
||||||
|
|
||||||
move(delta)
|
|
||||||
|
|
||||||
func applies() -> void:
|
|
||||||
if !play_char.is_on_floor() and !play_char.is_on_wall():
|
|
||||||
if play_char.velocity.y < 0.0:
|
|
||||||
transitioned.emit(self, "InairState")
|
|
||||||
|
|
||||||
if play_char.is_on_floor():
|
|
||||||
if play_char.jump_buff_on:
|
|
||||||
#apply jump buffering
|
|
||||||
play_char.buffered_jump = true
|
|
||||||
play_char.jump_buff_on = false
|
|
||||||
transitioned.emit(self, "JumpState")
|
|
||||||
|
|
||||||
func input_management() -> void:
|
|
||||||
if Input.is_action_just_pressed(play_char.dodge_action) and play_char.dodge_cooldown <= 0.0:
|
|
||||||
transitioned.emit(self, "DodgeState")
|
|
||||||
|
|
||||||
if Input.is_action_just_pressed(play_char.crouch_action):
|
|
||||||
transitioned.emit(self, "CrouchState")
|
|
||||||
|
|
||||||
if Input.is_action_pressed(play_char.jump_action) if play_char.auto_jump else Input.is_action_just_pressed(play_char.jump_action) :
|
|
||||||
transitioned.emit(self, "JumpState")
|
|
||||||
|
|
||||||
if Input.is_action_just_pressed(play_char.aim_cam_action):
|
|
||||||
transitioned.emit(self, "AimState")
|
|
||||||
|
|
||||||
if play_char.walk_or_run == "RunState":
|
|
||||||
transitioned.emit(self, "RunState")
|
|
||||||
|
|
||||||
func move(delta : float) -> void:
|
|
||||||
play_char.move_dir = Input.get_vector(play_char.move_left_action, play_char.move_right_action, play_char.move_forward_action, play_char.move_backward_action).rotated(-play_char.cam_holder.global_rotation.y)
|
|
||||||
|
|
||||||
if play_char.move_dir and play_char.is_on_floor():
|
|
||||||
#apply smooth move
|
|
||||||
play_char.velocity.x = lerp(play_char.velocity.x, play_char.move_dir.x * play_char.move_speed, play_char.move_accel * delta)
|
|
||||||
play_char.velocity.z = lerp(play_char.velocity.z, play_char.move_dir.y * play_char.move_speed, play_char.move_accel * delta)
|
|
||||||
else:
|
|
||||||
transitioned.emit(self, "IdleState")
|
|
||||||
|
|
||||||
|
func on_no_move(_delta : float) -> void:
|
||||||
|
request_transition(States.IDLE)
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,7 @@ player_target={
|
||||||
player_throw={
|
player_throw={
|
||||||
"deadzone": 0.2,
|
"deadzone": 0.2,
|
||||||
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":5,"axis_value":1.0,"script":null)
|
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":5,"axis_value":1.0,"script":null)
|
||||||
|
, 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":2,"position":Vector2(455, 12),"global_position":Vector2(464, 60),"factor":1.0,"button_index":2,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
player_crouch={
|
player_crouch={
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue