Godot Version
4.3
Question
I’m using a raycast attached at the forward of a CharacterBody to get the normal of a face, and then adjust the players basis to the normal. So they can effectively walk on walks, or ceilings. It’s working alright except at a few specific angles that completely don’t line up.
I’m wondering what I’m doing wrong, and would appreciate help.
Also if anyone could recommend a direction on where I should hit the books, and what I fundamentally need to understand and learn to actually grasp what’s going on here. I’ve been wracking my brain at these walls I keep hitting, so I’ve been re-learning math from the ground up to hopefully get into the more complicated subjects of Algebra, and further eventually. I understand the more complicated stuff I wanna do, this is inevitable.
Node Structure
Code
extends CharacterBody3D
@export var move_speed = 5.0
@export var jump_height = 5.0
@export var turn_speed = 5.0
const SPEED = 5.0
const JUMP_VELOCITY = 4.5
@onready var wall_change_detection: RayCast3D = %WallChangeDetection
@onready var wall_change_detection_2: RayCast3D = %WallChangeDetection2
@onready var wall_change_detection_3: RayCast3D = %WallChangeDetection3
@onready var player_camera: PlayerCamera = $PlayerCamera
@onready var mesh: MeshInstance3D = $Mesh
var on_wall : bool = false
var on_ceiling : bool = false
var upright : bool = true
var transitioning : bool = false
var xform : Transform3D
func _unhandled_input(event: InputEvent) -> void:
if Input.is_action_just_pressed("interact"):
global_transform = align_with_normal(global_transform, Vector3(0,0,-1).normalized())
func _physics_process(delta: float) -> void:
if wall_change_detection.is_colliding():
global_transform = align_with_normal(global_transform, wall_change_detection.get_collision_normal().normalized())
### Add the gravity.
#if upright:
#velocity.y += -5 * delta
if Input.is_action_pressed("movement"):
var dir = Input.get_vector("move_backward", "move_forward", "move_right", "move_left").normalized()
dir = dir.rotated(player_camera.yaw_node.rotation.y)
var target_rotation = atan2(dir.y, dir.x) - rotation.y
mesh.rotation.y = lerp_angle(mesh.rotation.y, target_rotation, turn_speed * delta)
translate(Vector3(-dir.y, 0, -dir.x) * move_speed * delta)
move_and_slide()
func align_with_normal(xform, normal):
xform.basis.x = -xform.basis.z.cross(normal)
xform.basis.y = normal
xform.basis = xform.basis.orthonormalized()
return xform
I would potentially enable debug shapes to see if the visible mesh actually aligns with its collision shape. Or see if its hitting something invisible or a corner.
I dont see anything glaringly wrong, you could probably simplify by using the basis version of look_at function using the normal for up and the target could just be some arbitrary point in the same direction as the raycast. Or just the collision point.
Look_at will square the y to the z (according to the docs). While your method squares the y from the x during orthonormalize.
That just flips it a different way unfortunately unless I’m using it wrong.
I’m learning Quaternions, and Euler Angles at the moment to try and remedy this issue in the future. There’s a big lack of understanding on angles, and rotations here for me for what I’m trying to accomplish.
You can pass a third parameter to use the model front , or +z world axis for looking. A basis z axis by default is positive, but is really represents the -z direction in the real world. If you just say true for the third parameter it should un-flip it.
Atan2 is an interesting use. I like to just use vector basis multiplication to do input rotations. Although it could be cheaper to do it your way and also stay on a flat plane.
1 Like
I eventually got it working using that method using the second and third parameters that you mentioned, but I opted a different approach with similar results. Based on this post here(credit). With some tweaking, it’s moving in all the proper directions, and feels great so far; and no weird crashes when I change the basis anymore. 
The issue now is that it’s falling apart at the mesh level with how I’m rotating it since I chose to rotate the mesh like this:
if Input.is_action_pressed("movement"):
var dir = Input.get_vector("move_backward", "move_forward", "move_right", "move_left").normalized()
dir = dir.rotated(player_camera.yaw_node.rotation.y)
var target_rotation = atan2(dir.y, dir.x) - rotation.y
mesh.rotation.y = lerp_angle(mesh.rotation.y, target_rotation, turn_speed * delta)
translate(Vector3(-dir.y, 0, -dir.x) * move_speed * delta)
Video
Current Code So Far:
extends CharacterBody3D
@export var move_speed = 5.0
@export var jump_height = 5.0
@export var turn_speed = 5.0
@onready var wall_change_detection: RayCast3D = %WallChangeDetection
@onready var player_camera: PlayerCamera = $PlayerCamera
@onready var mesh: MeshInstance3D = $Mesh
var transitioning : bool = false
func _input(event: InputEvent) -> void:
if event.is_action_pressed("interact") and not transitioning:
if wall_change_detection.is_colliding():
align_to_new_normal(wall_change_detection.get_collision_normal(), wall_change_detection.get_collision_point())
func _physics_process(delta: float) -> void:
### Add the gravity.
#if upright:
#velocity.y += -5 * delta
if Input.is_action_pressed("movement"):
var dir = Input.get_vector("move_backward", "move_forward", "move_right", "move_left").normalized()
dir = dir.rotated(player_camera.yaw_node.rotation.y)
var target_rotation = atan2(dir.y, dir.x) - rotation.y
mesh.rotation.y = lerp_angle(mesh.rotation.y, target_rotation, turn_speed * delta)
translate(Vector3(-dir.y, 0, -dir.x) * move_speed * delta)
move_and_slide()
func align_to_new_normal(new_normal:Vector3, attach_point:Vector3):
transitioning = true
var original = self.transform.basis.y
var cosa = original.dot(new_normal)
var alpha = acos(cosa)
var axis = original.cross(new_normal)
axis = axis.normalized()
var t = get_tree().create_tween()
t.tween_property(self, "transform", self.transform.rotated(axis, alpha), 0.2)
t.parallel().tween_property(self, "global_position", wall_change_detection.get_collision_point(), 0.2)
t.tween_callback(func(): transitioning = false)
Nevermind, I’m an idiot, and stopped subtracting the rotation of the node’s y from the headed direction and it seems to be working flawlessly now.
Have proper wall walking and moving. Now to make it look pretty and more functional.
BEFORE
var target_rotation = atan2(dir.y, dir.x) - rotation.y
AFTER
var target_rotation = atan2(dir.y, dir.x)