Files
godot-demo-projects/3d/platformer/player/player.gd
2025-10-11 05:03:59 -07:00

206 lines
6.3 KiB
GDScript

class_name Player
extends CharacterBody3D
enum _Anim {
FLOOR,
AIR,
}
const SHOOT_TIME: float = 1.5
const SHOOT_SCALE: float = 2.0
const CHAR_SCALE := Vector3(0.3, 0.3, 0.3)
const MAX_SPEED: float = 6.0
const TURN_SPEED: float = 40.0
const JUMP_VELOCITY: float = 12.5
const BULLET_SPEED: float = 20.0
const AIR_IDLE_DEACCEL: bool = false
const ACCEL: float = 14.0
const DEACCEL: float = 14.0
const AIR_ACCEL_FACTOR: float = 0.5
const SHARP_TURN_THRESHOLD: float = deg_to_rad(140.0)
var movement_dir := Vector3()
var jumping: bool = false
var prev_shoot: bool = false
var shoot_blend: float = 0.0
# Number of coins collected.
var coins: int = 0
@onready var initial_position := position
@onready var gravity: Vector3 = ProjectSettings.get_setting("physics/3d/default_gravity") * \
ProjectSettings.get_setting("physics/3d/default_gravity_vector")
@onready var _camera := $Target/Camera3D as Camera3D
@onready var _animation_tree := $AnimationTree as AnimationTree
func _physics_process(delta: float) -> void:
if Input.is_action_pressed(&"reset_position") or global_position.y < -12:
# Player hit the reset button or fell off the map.
position = initial_position
velocity = Vector3.ZERO
# We teleported the player on the lines above. Reset interpolation
# to prevent it from interpolating from the old player position
# to the new position.
reset_physics_interpolation()
# Update coin count and its "parallax" copies.
# This gives text a pseudo-3D appearance while still using Label3D instead of the more limited TextMesh.
%CoinCount.text = str(coins)
%CoinCount.get_node(^"Parallax").text = str(coins)
%CoinCount.get_node(^"Parallax2").text = str(coins)
%CoinCount.get_node(^"Parallax3").text = str(coins)
%CoinCount.get_node(^"Parallax4").text = str(coins)
velocity += gravity * delta
var anim := _Anim.FLOOR
var vertical_velocity := velocity.y
var horizontal_velocity := Vector3(velocity.x, 0, velocity.z)
var horizontal_direction := horizontal_velocity.normalized()
var horizontal_speed := horizontal_velocity.length()
# Player input.
var cam_basis := _camera.get_global_transform().basis
var movement_vec2 := Input.get_vector(&"move_left", &"move_right", &"move_forward", &"move_back")
var movement_direction := cam_basis * Vector3(movement_vec2.x, 0, movement_vec2.y)
movement_direction.y = 0
movement_direction = movement_direction.normalized()
var jump_attempt := Input.is_action_pressed(&"jump")
if is_on_floor():
var sharp_turn := horizontal_speed > 0.1 and \
acos(movement_direction.dot(horizontal_direction)) > SHARP_TURN_THRESHOLD
if movement_direction.length() > 0.1 and not sharp_turn:
if horizontal_speed > 0.001:
horizontal_direction = adjust_facing(
horizontal_direction,
movement_direction,
delta,
1.0 / horizontal_speed * TURN_SPEED,
Vector3.UP
)
else:
horizontal_direction = movement_direction
if horizontal_speed < MAX_SPEED:
horizontal_speed += ACCEL * delta
else:
horizontal_speed -= DEACCEL * delta
if horizontal_speed < 0:
horizontal_speed = 0
horizontal_velocity = horizontal_direction * horizontal_speed
var mesh_xform := ($Player/Skeleton as Node3D).get_transform()
var facing_mesh := -mesh_xform.basis[0].normalized()
facing_mesh = (facing_mesh - Vector3.UP * facing_mesh.dot(Vector3.UP)).normalized()
if horizontal_speed > 0:
facing_mesh = adjust_facing(
facing_mesh,
movement_direction,
delta,
1.0 / horizontal_speed * TURN_SPEED,
Vector3.UP
)
var m3 := Basis(
-facing_mesh,
Vector3.UP,
-facing_mesh.cross(Vector3.UP).normalized()
).scaled(CHAR_SCALE)
$Player/Skeleton.set_transform(Transform3D(m3, mesh_xform.origin))
if not jumping and jump_attempt:
vertical_velocity = JUMP_VELOCITY
jumping = true
$SoundJump.play()
else:
anim = _Anim.AIR
if movement_direction.length() > 0.1:
horizontal_velocity += movement_direction * (ACCEL * AIR_ACCEL_FACTOR * delta)
if horizontal_velocity.length() > MAX_SPEED:
horizontal_velocity = horizontal_velocity.normalized() * MAX_SPEED
elif AIR_IDLE_DEACCEL:
horizontal_speed = horizontal_speed - (DEACCEL * AIR_ACCEL_FACTOR * delta)
if horizontal_speed < 0:
horizontal_speed = 0
horizontal_velocity = horizontal_direction * horizontal_speed
if Input.is_action_just_released(&"jump") and velocity.y > 0.0:
# Reduce jump height if releasing the jump key before reaching the apex.
vertical_velocity *= 0.7
if jumping and vertical_velocity < 0:
jumping = false
velocity = horizontal_velocity + Vector3.UP * vertical_velocity
if is_on_floor():
movement_dir = velocity
move_and_slide()
if shoot_blend > 0:
shoot_blend *= 0.97
if (shoot_blend < 0):
shoot_blend = 0
var shoot_attempt := Input.is_action_pressed(&"shoot")
if shoot_attempt and not prev_shoot:
shoot_blend = SHOOT_TIME
var bullet := preload("res://player/bullet/bullet.tscn").instantiate() as Bullet
bullet.set_transform($Player/Skeleton/Bullet.get_global_transform().orthonormalized())
get_parent().add_child(bullet)
bullet.set_linear_velocity(
$Player/Skeleton/Bullet.get_global_transform().basis[2].normalized() * BULLET_SPEED
)
bullet.add_collision_exception_with(self)
$SoundShoot.play()
prev_shoot = shoot_attempt
if is_on_floor():
# How much the player should be blending between the "idle" and "walk/run" animations.
_animation_tree[&"parameters/run/blend_amount"] = horizontal_speed / MAX_SPEED
# How much the player should be running (as opposed to walking). 0.0 = fully walking, 1.0 = fully running.
_animation_tree[&"parameters/speed/blend_amount"] = minf(1.0, horizontal_speed / (MAX_SPEED * 0.5))
_animation_tree[&"parameters/state/blend_amount"] = anim
_animation_tree[&"parameters/air_dir/blend_amount"] = clampf(-velocity.y / 4 + 0.5, 0, 1)
_animation_tree[&"parameters/gun/blend_amount"] = minf(shoot_blend, 1.0)
func adjust_facing(facing: Vector3, target: Vector3, step: float, adjust_rate: float, \
current_gn: Vector3) -> Vector3:
var normal := target
var t := normal.cross(current_gn).normalized()
var x := normal.dot(facing)
var y := t.dot(facing)
var ang := atan2(y,x)
if absf(ang) < 0.001:
return facing
var s := signf(ang)
ang = ang * s
var turn := ang * adjust_rate * step
var a: float
if ang < turn:
a = ang
else:
a = turn
ang = (ang - a) * s
return (normal * cos(ang) + t * sin(ang)) * facing.length()