Update Camera and add RunState
This commit is contained in:
parent
f19cb0e796
commit
3f8c100f17
|
|
@ -129,3 +129,6 @@ func lock_on_handling(delta : float) -> void:
|
|||
var weight : float = clampf(lock_on_speed * delta, 0.0, 1.0)
|
||||
rotation.y = lerp_angle(rotation.y, target_yaw, weight)
|
||||
rotation.x = lerp_angle(rotation.x, target_pitch, weight)
|
||||
|
||||
func set_lock_target(t : Node3D) -> void:
|
||||
lock_target = t
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@ mesh = SubResource("CapsuleMesh_3agn1")
|
|||
unique_name_in_owner = true
|
||||
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, -0.009075999, 1.6803148, 0.01983509)
|
||||
fov_val = 30.0
|
||||
aim_cam_pos = Vector3(1.4, 0.9, 0)
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1800756522]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -82,5 +81,7 @@ script = ExtResource("6_lwgph")
|
|||
[node name="AimState" type="Node" parent="StateMachine" unique_id=296560188]
|
||||
script = ExtResource("7_kxr4l")
|
||||
|
||||
[node name="RunState" type="Node" parent="StateMachine" unique_id=1345421775]
|
||||
|
||||
[node name="InputManagementComponent" type="Node" parent="." unique_id=1981951371]
|
||||
script = ExtResource("2_r5dqo")
|
||||
|
|
|
|||
|
|
@ -7,15 +7,14 @@ var state_name : String = "Aim"
|
|||
var play_char : CharacterBody3D
|
||||
|
||||
@export var aim_action : StringName = &"player_aim_camera"
|
||||
@export var next_target_action : StringName = &"player_aim_next_target"
|
||||
@export var prev_target_action : StringName = &"player_aim_prev_target"
|
||||
@export var switch_target_action : StringName = &"player_aim_switch_target"
|
||||
@export var unlock_action : StringName = &"player_aim_unlock"
|
||||
|
||||
@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 la cible pour etre candidate
|
||||
@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 avant de perdre une cible deja verrouillee
|
||||
@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
|
||||
|
||||
|
|
@ -23,13 +22,16 @@ var play_char : CharacterBody3D
|
|||
@export var aim_move_speed : float = 3.0
|
||||
@export var aim_move_accel : float = 8.0
|
||||
|
||||
var auto_lock_blocked : bool = false #true apres un deverrouillage manuel, tant que la cible est encore au centre
|
||||
|
||||
func enter(play_char_ref : CharacterBody3D) -> void:
|
||||
play_char = play_char_ref
|
||||
auto_lock_blocked = false
|
||||
verifications()
|
||||
|
||||
func verifications() -> void:
|
||||
play_char.floor_snap_length = 1.0
|
||||
play_char.cam_holder.cam_aimed = true #adapte si ton CameraHolder gere ca autrement
|
||||
play_char.cam_holder.cam_aimed = true
|
||||
|
||||
func exit() -> void:
|
||||
release_target()
|
||||
|
|
@ -54,11 +56,10 @@ func input_management() -> bool:
|
|||
return true
|
||||
|
||||
if Input.is_action_just_pressed(unlock_action):
|
||||
auto_lock_blocked = true
|
||||
release_target()
|
||||
elif Input.is_action_just_pressed(next_target_action):
|
||||
cycle_target(1)
|
||||
elif Input.is_action_just_pressed(prev_target_action):
|
||||
cycle_target(-1)
|
||||
elif Input.is_action_just_pressed(switch_target_action):
|
||||
switch_target()
|
||||
return false
|
||||
|
||||
func move(delta : float) -> void:
|
||||
|
|
@ -76,9 +77,10 @@ func move(delta : float) -> void:
|
|||
func update_target() -> void:
|
||||
var current : AimTarget = play_char.aim_target
|
||||
|
||||
#verrouillage existant : on le garde tant qu'il est valide
|
||||
#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): return
|
||||
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
|
||||
|
|
@ -89,19 +91,23 @@ func update_target() -> void:
|
|||
if angle < best_angle:
|
||||
best_angle = angle
|
||||
best = t
|
||||
if best: set_target(best)
|
||||
|
||||
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): result.append(t)
|
||||
if t and is_target_valid(t, 1.0, true): result.append(t)
|
||||
return result
|
||||
|
||||
func is_target_valid(t : AimTarget, range_mult : float) -> bool:
|
||||
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 get_angle_to_camera(t) > max_aim_angle * 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
|
||||
|
||||
|
|
@ -117,17 +123,22 @@ func has_line_of_sight(t : AimTarget) -> bool:
|
|||
#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 cycle_target(direction : int) -> void:
|
||||
#trie les cibles de gauche a droite (par rapport a la camera) et passe a la suivante/precedente
|
||||
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()
|
||||
if list.is_empty(): return
|
||||
|
||||
#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(play_char.aim_target)
|
||||
idx = 0 if idx == -1 else posmod(idx + direction, list.size())
|
||||
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:
|
||||
|
|
@ -138,11 +149,11 @@ func set_target(t : AimTarget) -> void:
|
|||
if play_char.aim_target == t: return
|
||||
release_target()
|
||||
play_char.aim_target = t
|
||||
play_char.cam_holder.lock_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.lock_target = null
|
||||
play_char.cam_holder.set_lock_target(null)
|
||||
|
|
|
|||
|
|
@ -51,9 +51,12 @@ func input_management() -> void:
|
|||
if Input.is_action_just_pressed(play_char.aim_cam_action):
|
||||
transitioned.emit(self, "AimState")
|
||||
|
||||
if Input.is_action_just_pressed(play_char.run_action):
|
||||
if play_char.walk_or_run == "WalkState": play_char.walk_or_run = "RunState"
|
||||
elif play_char.walk_or_run == "RunState": play_char.walk_or_run = "WalkState"
|
||||
if play_char.continious_run:
|
||||
if Input.is_action_just_pressed(play_char.run_action):
|
||||
if play_char.walk_or_run == "WalkState": play_char.walk_or_run = "RunState"
|
||||
elif play_char.walk_or_run == "RunState": play_char.walk_or_run = "WalkState"
|
||||
else:
|
||||
play_char.walk_or_run = "RunState" if Input.is_action_pressed(play_char.run_action) else "WalkState"
|
||||
|
||||
func move(delta : float) -> void:
|
||||
#manage the character movement
|
||||
|
|
|
|||
|
|
@ -79,16 +79,16 @@ func jump() -> void:
|
|||
var can_jump : bool = false #jump condition
|
||||
|
||||
#in air jump
|
||||
#if !play_char.is_on_floor():
|
||||
#if play_char.coyote_jump_on:
|
||||
#play_char.jump_cooldown = play_char.jump_cooldown_ref
|
||||
#play_char.coyote_jump_cooldown = -1.0 #so that the character cannot immediately make another coyote jump
|
||||
#play_char.coyote_jump_on = false
|
||||
#can_jump = true
|
||||
#elif play_char.nb_jumps_in_air_allowed > 0:
|
||||
#play_char.nb_jumps_in_air_allowed -= 1
|
||||
#play_char.jump_cooldown = play_char.jump_cooldown_ref
|
||||
#can_jump = true
|
||||
if !play_char.is_on_floor():
|
||||
if play_char.coyote_jump_on:
|
||||
play_char.jump_cooldown = play_char.jump_cooldown_ref
|
||||
play_char.coyote_jump_cooldown = -1.0 #so that the character cannot immediately make another coyote jump
|
||||
play_char.coyote_jump_on = false
|
||||
can_jump = true
|
||||
elif play_char.nb_jumps_in_air_allowed > 0:
|
||||
play_char.nb_jumps_in_air_allowed -= 1
|
||||
play_char.jump_cooldown = play_char.jump_cooldown_ref
|
||||
can_jump = true
|
||||
|
||||
#on floor jump
|
||||
if play_char.is_on_floor():
|
||||
|
|
|
|||
74
game/player/StateMachine/RunState.gd
Normal file
74
game/player/StateMachine/RunState.gd
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
extends State
|
||||
|
||||
class_name RunState
|
||||
|
||||
var state_name : String = "Run"
|
||||
|
||||
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("run")
|
||||
play_char.walk_or_run = "RunState" #utilise par JumpState/InairState pour choisir les courbes en l'air
|
||||
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_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 play_char.continious_run:
|
||||
#course continue : un nouvel appui sur la touche repasse a la marche
|
||||
if Input.is_action_just_pressed(play_char.run_action):
|
||||
stop_running()
|
||||
else:
|
||||
#course maintenue : on court tant que la touche est enfoncee
|
||||
if !Input.is_action_pressed(play_char.run_action):
|
||||
stop_running()
|
||||
|
||||
func stop_running() -> void:
|
||||
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")
|
||||
1
game/player/StateMachine/RunState.gd.uid
Normal file
1
game/player/StateMachine/RunState.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dpcfffs3vt5co
|
||||
|
|
@ -52,7 +52,8 @@ func input_management() -> void:
|
|||
if Input.is_action_just_pressed(play_char.aim_cam_action):
|
||||
transitioned.emit(self, "AimState")
|
||||
|
||||
if Input.is_action_just_pressed(play_char.run_action):
|
||||
var wants_run : bool = Input.is_action_just_pressed(play_char.run_action) if play_char.continious_run else Input.is_action_pressed(play_char.run_action)
|
||||
if wants_run:
|
||||
play_char.walk_or_run = "RunState"
|
||||
transitioned.emit(self, "RunState")
|
||||
|
||||
|
|
|
|||
|
|
@ -5,15 +5,60 @@ class_name AimTarget
|
|||
signal locked_changed(is_locked : bool)
|
||||
|
||||
@export var enabled : bool = true
|
||||
@export var priority : float = 0.0 #plus c'est haut, plus la cible est favorisée
|
||||
@export var indicator : Node3D #optionnel : reticule/sprite affiché quand la cible est verrouillée
|
||||
@export var priority : float = 0.0 #plus c'est haut, plus la cible est favorisee
|
||||
|
||||
@export_group("Indicator")
|
||||
@export var indicator : Node3D #optionnel : ton propre visuel (remplace le losange par defaut)
|
||||
@export var show_default_diamond : bool = true
|
||||
@export var diamond_size : float = 0.3
|
||||
@export var diamond_color : Color = Color(1.0, 0.85, 0.2, 0.95)
|
||||
@export var diamond_offset : Vector3 = Vector3.ZERO #decalage par rapport a la node (ex. (0, 0.5, 0) pour le mettre au-dessus)
|
||||
|
||||
var is_locked : bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("aim_targets")
|
||||
|
||||
if indicator == null and show_default_diamond:
|
||||
indicator = create_diamond()
|
||||
add_child(indicator)
|
||||
|
||||
if indicator: indicator.visible = false
|
||||
|
||||
func create_diamond() -> MeshInstance3D:
|
||||
#carre incline a 45 degres, oriente vers la camera
|
||||
#(la rotation est appliquee directement aux sommets, car le billboard ignore la rotation de la node)
|
||||
var half : float = diamond_size * 0.3
|
||||
var corners : Array[Vector3] = [
|
||||
Vector3(-half, -half, 0.0),
|
||||
Vector3(half, -half, 0.0),
|
||||
Vector3(half, half, 0.0),
|
||||
Vector3(-half, half, 0.0),
|
||||
]
|
||||
for i in corners.size():
|
||||
corners[i] = corners[i].rotated(Vector3.BACK, deg_to_rad(45.0))
|
||||
|
||||
var st := SurfaceTool.new()
|
||||
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
||||
for i in [0, 1, 2, 0, 2, 3]:
|
||||
st.add_vertex(corners[i])
|
||||
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
mat.albedo_color = diamond_color
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
mat.billboard_mode = BaseMaterial3D.BILLBOARD_ENABLED
|
||||
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||
mat.no_depth_test = true #reste visible meme a travers le modele de la cible
|
||||
mat.render_priority = 100
|
||||
|
||||
var mesh_instance := MeshInstance3D.new()
|
||||
mesh_instance.mesh = st.commit()
|
||||
mesh_instance.material_override = mat
|
||||
mesh_instance.position = diamond_offset
|
||||
mesh_instance.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
return mesh_instance
|
||||
|
||||
func set_locked(value : bool) -> void:
|
||||
if is_locked == value: return
|
||||
is_locked = value
|
||||
|
|
|
|||
|
|
@ -144,21 +144,16 @@ player_crouch={
|
|||
"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":7,"pressure":0.0,"pressed":true,"script":null)
|
||||
]
|
||||
}
|
||||
player_aim_next_target={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":14,"pressure":0.0,"pressed":true,"script":null)
|
||||
]
|
||||
}
|
||||
player_aim_prev_target={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":true,"script":null)
|
||||
]
|
||||
}
|
||||
player_aim_unlock={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
player_aim_switch_target={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":true,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[physics]
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue