extends CharacterBody3D const EYE_HEIGHT_STAND = 1.6 const EYE_HEIGHT_CROUCH = 1.4 const MOVEMENT_SPEED_GROUND = 70.0 const MOVEMENT_SPEED_AIR = 13.0 const MOVEMENT_SPEED_CROUCH_MODIFIER = 0.5 const MOVEMENT_SPEED_SPRINT_MODIFIER = 1.375 const MOVEMENT_FRICTION_GROUND = 12.5 const MOVEMENT_FRICTION_AIR = 2.25 const MOVEMENT_JUMP_VELOCITY = 9.0 var _mouse_motion := Vector2() var _selected_block := 6 @onready var head: Node3D = $Head @onready var camera: Camera3D = $Head/Camera3D @onready var raycast: RayCast3D = $Head/RayCast3D @onready var camera_attributes: CameraAttributes = $Head/Camera3D.attributes @onready var selected_block_texture: TextureRect = $SelectedBlock @onready var voxel_world: Node = $"../VoxelWorld" @onready var crosshair: CenterContainer = $"../PauseMenu/Crosshair" @onready var aim_preview: MeshInstance3D = $AimPreview @onready var neutral_fov: float = camera.fov func _ready() -> void: Input.mouse_mode = Input.MOUSE_MODE_CAPTURED func _process(_delta: float) -> void: # Mouse movement. _mouse_motion.y = clampf(_mouse_motion.y, -1560, 1560) transform.basis = Basis.from_euler(Vector3(0, _mouse_motion.x * -0.001, 0)) head.transform.basis = Basis.from_euler(Vector3(_mouse_motion.y * -0.001, 0, 0)) # Block selection. var ray_position := raycast.get_collision_point() var ray_normal := raycast.get_collision_normal() if Input.is_action_just_pressed(&"pick_block"): # Block picking. var block_global_position: Vector3 = (ray_position - ray_normal / 2).floor() var block_sub_position: Vector3 = block_global_position.posmod(16) var chunk_position: Vector3 = (block_global_position - block_sub_position) / 16 _selected_block = voxel_world.get_block_in_chunk(chunk_position, block_sub_position) else: # Block prev/next keys. if Input.is_action_just_pressed(&"prev_block"): _selected_block -= 1 if Input.is_action_just_pressed(&"next_block"): _selected_block += 1 _selected_block = wrapi(_selected_block, 1, 30) # Set the appropriate texture. var uv := Chunk.calculate_block_uvs(_selected_block) selected_block_texture.texture.region = Rect2(uv[0] * 512, Vector2.ONE * 64) # Block breaking/placing. if crosshair.visible and raycast.is_colliding(): aim_preview.visible = true var ray_current_block_position := Vector3i((ray_position - ray_normal / 2).floor()) aim_preview.global_position = Vector3(ray_current_block_position) + Vector3(0.5, 0.5, 0.5) var breaking := Input.is_action_just_pressed(&"break") var placing := Input.is_action_just_pressed(&"place") # Either both buttons were pressed or neither are, so stop. if breaking == placing: return if breaking: var block_global_position := ray_current_block_position voxel_world.set_block_global_position(block_global_position, 0) elif placing: # Calculate the position of the block to be placed. var block_global_position := Vector3i((ray_position + ray_normal / 2).floor()) voxel_world.set_block_global_position(block_global_position, _selected_block) else: aim_preview.visible = false func _physics_process(delta: float) -> void: camera_attributes.dof_blur_far_enabled = Settings.fog_enabled camera_attributes.dof_blur_far_distance = Settings.fog_distance * 1.5 camera_attributes.dof_blur_far_transition = Settings.fog_distance * 0.125 # Crouching. var crouching: bool = Input.is_action_pressed(&"crouch") var sprinting: bool = Input.is_action_pressed(&"move_sprint") head.transform.origin.y = lerpf(head.transform.origin.y, EYE_HEIGHT_CROUCH if crouching else EYE_HEIGHT_STAND, 1.0 - exp(-delta * 16.0)) # Keyboard movement. var movement_vec2: Vector2 = Input.get_vector("move_left", "move_right", "move_forward", "move_back") var movement: Vector3 = transform.basis * (Vector3(movement_vec2.x, 0, movement_vec2.y)) if is_on_floor(): movement *= MOVEMENT_SPEED_GROUND else: movement *= MOVEMENT_SPEED_AIR if crouching: movement *= MOVEMENT_SPEED_CROUCH_MODIFIER sprinting = false var target_fov: float = neutral_fov if sprinting: movement *= MOVEMENT_SPEED_SPRINT_MODIFIER target_fov = neutral_fov * 1.25 camera.fov = lerpf(camera.fov, target_fov, 1.0 - exp(-delta * 10.0)) # Gravity. if not is_on_floor(): var factor: float = 3.0 - clampf(velocity.y / -MOVEMENT_JUMP_VELOCITY, 0.0, 2.0) velocity += get_gravity() * (delta * factor) velocity += Vector3(movement.x, 0, movement.z) * delta # Apply horizontal friction. var friction_delta := exp(-(MOVEMENT_FRICTION_GROUND if is_on_floor() else MOVEMENT_FRICTION_AIR) * delta) velocity = Vector3(velocity.x * friction_delta, velocity.y, velocity.z * friction_delta) move_and_slide() # Jumping, applied next frame. if is_on_floor() and Input.is_action_pressed(&"jump"): velocity.y = MOVEMENT_JUMP_VELOCITY func _input(event: InputEvent) -> void: if event is InputEventMouseMotion: if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED: _mouse_motion += event.screen_relative func chunk_pos() -> Vector3i: return Vector3i((transform.origin / Chunk.CHUNK_SIZE).floor())