I am trying to create a characterBody3D that the player moves around, and I would like it to be able to rotate around a cylinder like in F-zero-X. Someone already solved this issue when using a rigidbody, where you can apply relative forces, helping to rotate the player:
Move Inside Pipe Like F-Zero GX
and I would like to replicate this using a characterBody3D and calculating its rotation instead of using forces. So I followed the recommendations from the previous post and added 3 raycasts around my characterBody.
The difference is that, since I am using a characterBody instead of a rigidbody, I can't rotate it using forces...So I calculate the average normal across the collisions of the 3 raycasts weighed by the distance to the collision point:
var distanceA = Raycast1.get_collision_point().distance_to(Raycast1.global_position)
var distanceB = Raycast2.get_collision_point().distance_to(Raycast2.global_position)
var distanceC = Raycast3.get_collision_point().distance_to(Raycast3.global_position)
var TargetNormal= (1/distanceA * Raycast1.get_collision_normal() + 1/distanceB *Raycast2.get_collision_normal() + 1/distanceC * Raycast3.get_collision_normal()).normalized()
The problem is that I don't know where to go from here. If I compare my characterBody's basis.y to the TargetNormal, I should know how much to rotate my characterBody every tick, right? However, I'm not sure how to do that or if that would solve the problem. Help, please?
So I figured out how to do it. Instead of 3, I used 4 evenly spaced raycasts on the front, rear, right, and left sides of my ship.
Then I calculated the rotation of the ship across the x axis (front and rear raycasts) and z (left and right raycasts) depending on their distance to collision. I also created some "gravity" to pull the ship towards the track. Take into account that my ship will be moving at very high speeds so I used very high gravity to keep it on the track, for normal gravity you probably want it closer to 9. Here is the code for the gravity (only using front and rear raycasts):
var distanceFront = RaycastFront.get_collision_point().distance_to(RaycastFront.global_position)
var distanceBack = RaycastBack.get_collision_point().distance_to(RaycastBack.global_position)
var upwardsForce = -16
var alldistancesAccomplished : bool = true
if distanceFront < .8:
upwardsForce += 4 /distanceFront
else:
alldistancesAccomplished = false
#this is
upwardsForce -= 32
if distanceBack < .8:
upwardsForce += 4 /distanceBack
else:
alldistancesAccomplished = false
upwardsForce -= 8
if upwardsForce > 0 and alldistancesAccomplished:
upwardsForce = -velocity.dot(basis.y)/delta
velocity += (upwardsForce * delta * basis.y)
The upwardsForce variable acts as a gravitational/magnetic pull towards the track and also helps levitate the body from the ground, but if you don't add the last if, it will perpetually bob up and down in the limit of your magnetic field. If that is what you want, you can remove that line. Here, is the code to rotate along the x axis (using front and rear raycasts) but the z axis would use the same principle:
var angleAcrossX : float = 0
var rotation_stength = PI/8
if abs(distanceFront - distanceBack) > 0.001:
if distanceFront < 1:
angleAcrossX += (1-distanceFront) * rotation_stength
if distanceBack < 1:
angleAcrossX += (1-distanceBack) * (-rotation_stength)
angleAcrossX = angleAcrossX
transform.basis = transform.basis.rotated(basis.x,angleAcrossX)