mirror of
https://github.com/godotengine/godot-demo-projects.git
synced 2025-12-15 13:00:07 +01:00
206 lines
6.3 KiB
GDScript
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()
|