Files
tactical-shooter/client/template/scripts/Player_Character/player_character.gd
T
shawn ad48f38ca5 feat: integrate ChaffGames FPS template as local player controller
Replaces the overhead box-mesh view with a full FPS character:

- mouse look, WASD movement, jump (Space), crouch (C), sprint (Shift), lean (Q/E)

- 6 weapons (LMB shoot, R reload, scroll switch, F melee, G drop)

- Crosshair HUD, ammo counter, sprint bar

- Client sends position to server at 20Hz for multiplayer visibility

- Server broadcasts positions to all peers

- Remote players shown as boxes (placeholder)

source: https://godotengine.org/asset-library/asset/2652 (MIT license)
2026-07-02 00:26:45 -04:00

267 lines
8.2 KiB
GDScript

extends CharacterBody3D
@onready var camera = %Camera
@export var subviewport_camera: Camera3D
@export var main_camera:Camera3D
@export var animation_tree: AnimationTree
var camera_rotation: Vector2 = Vector2(0.0,0.0)
var mouse_sensitivity = 0.001
var crouched: bool = false
var crouch_blocked: bool = false
@export_category("Crouch Parametres")
@export var enable_crouch: bool = true
@export var crouch_toggle: bool = false
@export var crouch_collision: ShapeCast3D
@export_range(0.0,3.0) var crouch_speed_reduction = 2.0
@export_range(0.0,0.50) var crouch_blend_speed = .2
enum {GROUND_CROUCH = -1, STANDING = 0, AIR_CROUCH = 1}
@export_category("Lean Parametres")
@export var enable_lean: bool = true
@export_range(0.0,1.0) var lean_speed: float = .2
@export var right_lean_collision: ShapeCast3D
@export var left_lean_collision: ShapeCast3D
var lean_tween
enum {LEFT = 1, CENTRE = 0, RIGHT = -1}
@export_category("speed Parameters")
@export var enable_sprint: bool = true
@export var sprint_timer: Timer
@export var sprint_cooldown_time: float = 3.0
@export var sprint_time: float = 1.0
@export var sprint_replenish_rate: float = 0.30
@export var acceleration: float = 120
@export_range(0.01,1.0) var air_acceleration_modifier: float = 0.1
var sprint_on_cooldown: bool = false
var sprint_time_remaining: float = sprint_time
@onready var sprint_bar: Range = $CanvasLayer/SprintBar
const NORMAL_speed = 1
@export_range(1.0,3.0) var sprint_speed: float = 2.0
@export_range(0.1,1.0) var walk_speed: float = 0.5
var speed_modifier: float = NORMAL_speed
@export_category("Jump Parameters")
@export var coyote_timer: Timer
@export var jump_peak_time: float = .5
@export var jump_fall_time: float = .5
@export var jump_height: float = 2.0
@export var jump_distance: float = 4.0
@export var coyote_time: float = .1
@export var jump_buffer_time: float = .2
# Get the gravity from the project settings to be synced with RigidBody nodes.
var jump_gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
var fall_gravity: float
var jump_velocity: float
var base_speed: float
var _speed: float
var jump_available: bool = true
var jump_buffer: bool = false
func _ready() -> void:
update_camera_rotation()
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
calculate_movement_parameters()
func update_camera_rotation() -> void:
var current_rotation = get_rotation()
camera_rotation.x = current_rotation.y
camera_rotation.y = current_rotation.x
func _input(event: InputEvent) -> void:
if event.is_action_pressed("ui_cancel"):
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
else:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
if event is InputEventMouseMotion:
var MouseEvent = event.relative * mouse_sensitivity
camera_look(MouseEvent)
if enable_crouch:
if event.is_action_pressed("crouch"):
crouch()
if event.is_action_released("crouch"):
if !crouch_toggle and crouched:
crouch()
if enable_lean:
if Input.is_action_just_released("lean_left") or Input.is_action_just_released("lean_right"):
if !(Input.is_action_pressed("lean_right") or Input.is_action_pressed("lean_left")):
lean(CENTRE)
if Input.is_action_just_pressed("lean_left"):
lean(LEFT)
if Input.is_action_just_pressed("lean_right"):
lean(RIGHT)
if enable_sprint:
if Input.is_action_just_pressed("sprint") and !crouched:
if !sprint_on_cooldown:
speed_modifier = sprint_speed
sprint_timer.start(sprint_time_remaining)
if Input.is_action_just_released("sprint") or Input.is_action_just_released("walk"):
if !(Input.is_action_pressed("walk") or Input.is_action_pressed("sprint")):
speed_modifier = NORMAL_speed
exit_sprint()
if Input.is_action_just_pressed("walk") and !crouched:
speed_modifier = walk_speed
func calculate_movement_parameters() -> void:
jump_gravity = (2*jump_height)/pow(jump_peak_time,2)
fall_gravity = (2*jump_height)/pow(jump_fall_time,2)
jump_velocity = jump_gravity * jump_peak_time
base_speed = jump_distance/(jump_peak_time+jump_fall_time)
_speed = base_speed
func lean(blend_amount: int) -> void:
if is_on_floor():
if lean_tween:
lean_tween.kill()
lean_tween = get_tree().create_tween()
lean_tween.tween_property(animation_tree,"parameters/lean_blend/blend_amount", blend_amount, lean_speed)
func lean_collision() -> void:
animation_tree["parameters/left_collision_blend/blend_amount"] = lerp(
float(animation_tree["parameters/left_collision_blend/blend_amount"]),float(left_lean_collision.is_colliding()),lean_speed
)
animation_tree["parameters/right_collision_blend/blend_amount"] = lerp(
float(animation_tree["parameters/right_collision_blend/blend_amount"]),float(right_lean_collision.is_colliding()),lean_speed
)
func crouch() -> void:
var Blend
if !crouch_collision.is_colliding():
if crouched:
Blend = STANDING
else:
speed_modifier = NORMAL_speed
exit_sprint()
if is_on_floor():
Blend = GROUND_CROUCH
else:
Blend = AIR_CROUCH
var blend_tween = get_tree().create_tween()
blend_tween.tween_property(animation_tree,"parameters/Crouch_Blend/blend_amount",Blend,crouch_blend_speed)
crouched = !crouched
else:
crouch_blocked = true
func camera_look(Movement: Vector2) -> void:
camera_rotation += Movement
transform.basis = Basis()
camera.transform.basis = Basis()
rotate_object_local(Vector3(0,1,0),-camera_rotation.x) # first rotate in Y
camera.rotate_object_local(Vector3(1,0,0), -camera_rotation.y) # then rotate in X
camera_rotation.y = clamp(camera_rotation.y,-1.5,1.2)
func exit_sprint() -> void:
if !sprint_timer.is_stopped():
sprint_time_remaining = sprint_timer.time_left
sprint_timer.stop()
func sprint_replenish(delta) -> void:
var sprint_bar_Value
if !sprint_on_cooldown and (speed_modifier != sprint_speed):
if is_on_floor():
sprint_time_remaining = move_toward(sprint_time_remaining, sprint_time, delta*sprint_replenish_rate)
sprint_bar_Value= (sprint_time_remaining/sprint_time)*100
else:
sprint_bar_Value = (sprint_timer.time_left/sprint_time)*100
sprint_bar.value = sprint_bar_Value
if sprint_bar_Value == 100:
sprint_bar.hide()
else:
sprint_bar.show()
func _process(_delta: float) -> void:
if subviewport_camera:
subviewport_camera.global_transform = main_camera.global_transform
func _physics_process(_delta: float) -> void:
sprint_replenish(_delta)
lean_collision()
if crouched and crouch_blocked:
if !crouch_collision.is_colliding():
crouch_blocked = false
if !Input.is_action_pressed("crouch") and !crouch_toggle:
crouch()
# Add the gravity.
var _acceleration
if not is_on_floor():
_acceleration = acceleration*air_acceleration_modifier
if coyote_timer.is_stopped():
coyote_timer.start(coyote_time)
if velocity.y>0:
velocity.y -= jump_gravity * _delta
else:
velocity.y -= fall_gravity * _delta
else:
_acceleration = acceleration
jump_available = true
coyote_timer.stop()
_speed = (base_speed / max((float(crouched)*crouch_speed_reduction),1)) * speed_modifier
if jump_buffer:
jump()
jump_buffer = false
# Handle Jump.
if Input.is_action_just_pressed("ui_accept"):
if jump_available:
if crouched:
crouch()
else:
lean(CENTRE)
jump()
else:
jump_buffer = true
get_tree().create_timer(jump_buffer_time).timeout.connect(on_jump_buffer_timeout)
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
var input_dir = Input.get_vector("left", "right", "up", "down")
var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
velocity.x = move_toward(velocity.x, direction.x * _speed, _acceleration*_delta)
velocity.z = move_toward(velocity.z, direction.z * _speed, _acceleration*_delta)
move_and_slide()
func jump()->void:
velocity.y = jump_velocity
jump_available = false
func _on_sprint_timer_timeout() -> void:
sprint_on_cooldown = true
get_tree().create_timer(sprint_cooldown_time).timeout.connect(_on_sprint_cooldown_timeout)
speed_modifier = NORMAL_speed
sprint_time_remaining = 0
func _on_sprint_cooldown_timeout():
sprint_on_cooldown = false
func _on_coyote_timer_timeout() -> void:
jump_available = false
func on_jump_buffer_timeout()->void:
jump_buffer = false