extends CharacterBody3D # Member variables const QUAKE = 0.0625 / 2 const g = -800 * QUAKE const MAX_SPEED = 320 * QUAKE const MAX_AIR_SPEED = 30 * QUAKE const FRICTION = 4# * QUAKE const STOP_SPEED = 100 * QUAKE const ACCEL = 15# * QUAKE const AIR_ACCEL = 2# * QUAKE const FORWARD_SPEED = 200 * QUAKE const SIDE_SPEED = 350 * QUAKE const UP_SPEED = 270 * QUAKE const JUMP_SPEED = 270 * QUAKE @export var interact_range: float = 5.0 @export var hold_force = 400 @export var max_hold_velocity: float = 1000.0 # const DEACCEL= 8 # const MAX_SLOPE_ANGLE = 30https://www.youtube.com/watch?v=v3zT3Z5apaM @onready var _camera = $CSGMesh3D/Camera3D @onready var _ball = $Ball @onready var _collider = $CollisionShape3D @onready var _flashlight: SpotLight3D = $Flashlight var _paused = false var _noclip = false var _held_object: Object var _held_object_distance: float func _ready(): Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) func _process(_delta): # Do this on the render tick so it doesnt jitter at higher FPS _ball.global_position = _camera.global_position - _camera.global_transform.basis.z * interact_range func _physics_process(delta): # Shamelessly stolen code from quake :) var fmove = 0.0 var smove = 0.0 var upmove = 0.0 if Input.is_action_pressed("move_forward"): fmove += -FORWARD_SPEED if Input.is_action_pressed("move_backward"): fmove += FORWARD_SPEED if Input.is_action_pressed("move_left"): smove += -SIDE_SPEED if Input.is_action_pressed("move_right"): smove += SIDE_SPEED #if Input.is_action_pressed("up"): # upmove += UP_SPEED if Input.is_action_pressed("run"): fmove *= 2.0 smove *= 2.0 var wishvel = Vector3() if _noclip: wishvel = _camera.global_basis.z * fmove + _camera.global_basis.x * smove else: wishvel = basis.z * fmove + basis.x * smove if _noclip: wishvel.y += upmove var wishdir = wishvel.normalized() var wishspeed = wishvel.length() if wishspeed > MAX_SPEED: wishvel *= MAX_SPEED / wishspeed wishspeed = MAX_SPEED if Input.is_action_just_pressed("jump") and is_on_floor(): velocity.y += JUMP_SPEED var vel = velocity if _noclip: vel = wishvel * 2 elif is_on_floor(): vel = _apply_friction(vel, delta) vel = _apply_accel(vel, wishdir, wishspeed, delta) else: vel = _apply_air_accel(vel, wishvel, wishspeed, delta) if not _noclip and not is_on_floor(): vel.y += g * delta velocity = vel move_and_slide() if _held_object: _apply_holding_force(delta) func _apply_friction(vel: Vector3, delta: float): var speed = sqrt(vel.x ** 2 + vel.z ** 2) if(speed == 0.0): return vel # TODO: edge detect friction? var friction = FRICTION var control = 0.0 if speed < STOP_SPEED: control = STOP_SPEED else: control = speed var newspeed = speed - delta * control * friction if newspeed < 0.0: newspeed = 0.0 else: newspeed /= speed return vel * newspeed func _apply_accel(vel: Vector3, wishdir: Vector3, wishspeed: float, delta: float): var currentspeed = vel.dot(wishdir) var addspeed = wishspeed - currentspeed if addspeed <= 0.0: return vel var accelspeed = ACCEL * delta * wishspeed if accelspeed > addspeed: accelspeed = addspeed return vel + accelspeed * wishdir func _apply_air_accel(vel: Vector3, wishvel: Vector3, wishspeed: float, delta: float): var wishveloc = wishvel.normalized() var wishspd = wishvel.length() if wishspd > MAX_AIR_SPEED: wishspd = MAX_AIR_SPEED var currentspeed = vel.dot(wishveloc) var addspeed = wishspd - currentspeed if addspeed <= 0: return vel var accelspeed = AIR_ACCEL * wishspeed * delta if accelspeed > addspeed: accelspeed = addspeed return vel + accelspeed * wishveloc func _apply_holding_force(delta): var hold_point = _camera.global_position - _camera.global_transform.basis.z * _held_object_distance var delta_position = hold_point - _held_object.global_position var move_speed = min(delta_position.length() * hold_force, max_hold_velocity) _held_object.linear_velocity = delta_position.normalized() * move_speed * delta func _input(event): if event.is_action_pressed("pause"): _paused = not _paused if _paused: Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) else: Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) if _paused: return if event.is_action_pressed('noclip'): _noclip = not _noclip if _noclip: _collider.disabled = true else: _collider.disabled = false if event.is_action_pressed('flashlight'): _flashlight.visible = not _flashlight.visible # Camera if event is InputEventMouseMotion: rotation.y += -event.relative.x * 0.005 _camera.rotation.x += -event.relative.y * 0.005 _camera.rotation.x = clamp(_camera.rotation.x, deg_to_rad(-90), deg_to_rad(90)) # Rotate flashlight _flashlight.rotation.x = _camera.rotation.x if event.is_action_pressed("interact"): # Handle dropping things if _held_object: #_held_object.drop() _held_object = null return var space_state = get_world_3d().direct_space_state # use global coordinates, not local to node var ray_end = _camera.global_position - _camera.global_transform.basis.z * interact_range var query = PhysicsRayQueryParameters3D.create(_camera.global_position, ray_end) # Don't collide with ourselves query.exclude = [self] var result = space_state.intersect_ray(query) if result.has('collider'): var other: Node3D = result.collider if other.has_method('interact'): other.interact(self) if other.is_in_group('can_hold'): _held_object = other _held_object_distance = (Vector3(result.position) - _camera.global_position).length() func force_drop(): if _held_object: _held_object = null