How can I change a Camera3D position based on its current rotation?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By WalterBennet

Hello,

I’m new to Godot and I’ve been trying to figure out a way to implement a leaning mechanic in my 3D character controller.

I’ve recently gotten stuck trying to figure out how to move the camera x and z position based on the current y rotation of the camera.

The current system that I’ve got only allows the camera’s position to be moved either positively or negatively from a base point along the x axis. This is obviously insufficient as the player can turn the camera 90° on the y axis and render the whole camera position movement incoherent with its current direction. Basically, no matter where the camera is currently looking at, its position is always gonna be moved on the x axis when leaning rendering the whole mechanic pointless.

I’ve also tried to make it so that the x axis of the camera would always point toward the current position where the player is looking at (rotating the axis along the camera basically) but I couldn’t figure out a way of doing it.

I’ve run out of ideas and I’m asking for you guys help to hopefully guide me toward a solution. I’ve linked both the script for the character controller (the leaning method is at the bottom, _leaning) and the player scene. Feel free to ask for more clarification if needed, I’m not certain I’m explaining the whole issue properly.

Hope you all have a good day, cheer!


Character controller (player.gd)

class_name Player extends CharacterBody3D 

@export_category("Player Variables")
@export_range(1, 35, 1) var walking_speed: float = 4 # m/s
@export_range(1, 35, 1) var running_speed: float = 3 # m/s
@export_range(10, 400, 1) var acceleration: float = 40 # m/s^2
@export_range(0.1, 3.0, 0.1) var jump_height: float = 1 # m

@export_category("Player Controls")
@export var always_run: bool = false
@export var enable_camera_z_rotation: bool = true

@export_category("Camera Variables")
@export_range(0.1, 9.25, 0.05, "or_greater") var camera_sensitivity: float = 6.2
@export_range(75, 120, 1) var camera_fov: float = 90
@export_range(0, 0.1, 0.01) var rotation_amount: float = 0.06

var jumping: bool = false
var running: bool = false
var leaning: bool = false
var mouse_captured: bool = false

var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")

var move_dir: Vector2 # Input direction for movement
var look_dir: Vector2 # Input direction for look/aim

var walk_vel: Vector3 # Walking velocity 
var grav_vel: Vector3 # Gravity velocity 
var jump_vel: Vector3 # Jumping velocity

var base_position_x: float

@onready var camera: Camera3D = $Camera

func _ready() -> void:
	capture_mouse()

func _input(event: InputEvent) -> void:
	running = always_run != Input.is_action_pressed("run")
	leaning = Input.is_action_pressed("lean_left") or Input.is_action_pressed("lean_right")
	if event is InputEventMouseMotion: look_dir = event.relative * 0.01
	if Input.is_action_just_pressed("jump"): jumping = true
	if Input.is_action_just_pressed("exit"):
		capture_mouse() if (Input.get_mouse_mode() == Input.MOUSE_MODE_VISIBLE) else release_mouse()

func _physics_process(delta: float) -> void:
	if mouse_captured: _rotate_camera(delta)
	velocity = _walk(delta) + _gravity(delta) + _jump(delta)
	_running(delta)
	_leaning(delta)
	move_and_slide()

func capture_mouse() -> void:
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	mouse_captured = true

func release_mouse() -> void:
	Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
	mouse_captured = false

func _rotate_camera(delta: float, sens_mod: float = 1.0) -> void:
	look_dir += Input.get_vector("look_left", "look_right", "look_up", "look_down")
	camera.rotation.z = lerp(camera.rotation.z, (-move_dir.x * (rotation_amount if (enable_camera_z_rotation && !leaning) else 0.0)), delta * 10)
	camera.rotation.y -= look_dir.x * camera_sensitivity * sens_mod * delta
	camera.rotation.x = clamp(camera.rotation.x - look_dir.y * camera_sensitivity * sens_mod * delta, -1.5, 1.5)
	look_dir = Vector2.ZERO

func _walk(delta: float) -> Vector3:
	move_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_backwards")
	var _forward: Vector3 = camera.transform.basis * Vector3(move_dir.x, 0, move_dir.y)
	var walk_dir: Vector3 = Vector3(_forward.x, 0, _forward.z).normalized()
	walk_vel = walk_vel.move_toward(walk_dir * (walking_speed + (running_speed if running else 0.0)) * move_dir.length(), acceleration * delta)
	return walk_vel

func _gravity(delta: float) -> Vector3:
	grav_vel = Vector3.ZERO if is_on_floor() else grav_vel.move_toward(Vector3(0, velocity.y - gravity, 0), gravity * delta)
	return grav_vel

func _jump(delta: float) -> Vector3:
	if jumping:
		if is_on_floor(): jump_vel = Vector3(0, sqrt(4 * jump_height * gravity), 0)
		jumping = false
		return jump_vel
	jump_vel = Vector3.ZERO if is_on_floor() else jump_vel.move_toward(Vector3.ZERO, gravity * delta)
	return jump_vel

func _running(delta: float) -> void:
	if running:
		camera.fov = lerp(camera.fov, camera_fov + 15, delta * 10) if running else lerp(camera.fov, camera_fov, delta * 10)

# This is the method that I've been struggling with
func _leaning(delta: float) -> void:
	if leaning:
		camera.rotation.z = clamp(lerp(camera.rotation.z, 0.7 if Input.is_action_pressed("lean_left") else -0.7, delta * 3), -0.25, 0.25)
		camera.position.x = clamp(lerp(camera.position.x, -0.30 if Input.is_action_pressed("lean_left") else 0.30, delta * 6), base_position_x - 1.0, base_position_x + 1.0)
	else:
		camera.position.x = lerp(camera.position.x, base_position_x, delta * 4)
		if camera.position.x == base_position_x: base_position_x = camera.position.x

**Player scene (player.tscn)**
[gd_scene load_steps=3 format=3 uid="uid://cvq4dvoyms4vw"]

[ext_resource type="Script" path="res://player/player.gd" id="1_2f8j2"]

[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_3rsb5"]
radius = 0.45
height = 1.8

[node name="Player" type="CharacterBody3D"]
script = ExtResource("1_2f8j2")

[node name="CShape" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0)
shape = SubResource("CapsuleShape3D_3rsb5")

[node name="Camera" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.7, 0)
fov = 90.0
:bust_in_silhouette: Reply From: TRAILtheGREAT

I also have a leaning mechanic in my game. The way I made it work was by giving the camera an extra parent. When the camera needs to rotate vertically, I change its local x rotation. When it needs to rotate horizontally, I change the local y rotation of its parent. This way I can rotate the parent (or in my case the entire player) and I don’t have to the player’s orientation into account when rotating my camera. As en example here is a simplified version of my camera code:

func _rotate_camera(mouse_delta : Vector2):
     camera_transform.rotation.x -= mouse_delta.y * look_sens

func _rotate_body(mouse_delta : Vector2):
    body_transform.rotation.y -= mouse_delta.x * look_sens

Yeah that did the trick, thanks a lot mate!

WalterBennet | 2023-04-29 11:17