Godot Version
4.5
Question
How do I make the sprite rotation only work depending on the slope direction and player rotation
extends CharacterBody3D
class_name Player
@export var small_speed = 17.5
@export var big_speed = 35.0
var speed_timer = 0
var SPEED = 17.5
const JUMP_VEVO = 8
enum states{Normal,Air,Spindash,Roll}
@export var music = Node3D
# Trick Template [Name,Time,Score]
var tricks = [["",0,0],["Wall Jump",0.5,50],["Big Boost",2,200],["Show Off",0.1,10],["Small Boost",0.25,25]]
var trickdone = tricks[0]:
set(new_value):
var old_trick = trickdone
trickdone = new_value
if trickdone != old_trick:
stuff()
var tricksdone = []
var score = 0:
set(new_value):
var old = score
score = new_value
if score != old:
scoreing()
var timer = 0
var vevo = Vector3.ZERO
var state = states.Normal
var savevevo = vevo
signal list(value: Array)
signal scoring(valueee: float)
signal timing(valuee: float)
signal coininger(valueeee: float)
signal adding(val: float)
var time = 0:
set(new_value):
var old = time
time = new_value
if time != old:
thing()
var direction = 0
var animation = 0
var slope = 0
var size = 0
var adding_up = 0:
set(new_value):
var oldd = adding_up
adding_up = new_value
if adding_up != oldd:
timingtwo()
var coin = 0:
set(new_value):
var oldd = coin
coin = new_value
if coin != oldd:
coining()
@export var gravity_scale = 1.2
var surface_normal = Vector3.ZERO
@export var camera = Node3D
var big_boost = false
var timert = 0
var taunt = false
# Build a stable orthonormal basis that uses `new_y` as the up vector
func scoreing():
scoring.emit(score)
func timingtwo():
adding.emit(adding_up)
func coining():
coininger.emit(coin)
func thing():
if(tricksdone.size() >= 1):
print(timer)
timing.emit(1-timer)
else:
timing.emit(0)
func none():
list.emit(tricksdone)
func stuff():
if (tricksdone.size() == 0 or tricksdone[0] != trickdone[0] or trickdone[0] == "Show Off") and (trickdone[0] != ""):
tricksdone.insert(0,trickdone[0])
score += (trickdone[2] * clamp(tricksdone.count(trickdone[0])/10.0,1.0,10.0))
adding_up += (trickdone[2] * clamp(tricksdone.count(trickdone[0])/10.0,1.0,10.0))
timer -= trickdone[1]
list.emit(tricksdone)
func get_surface_tangent() -> Vector3:
var up = surface_normal
var forward = -global_transform.basis.z
return (forward - up * forward.dot(up)).normalized()
func align_with_y(xform: Transform3D, new_y: Vector3) -> Transform3D:
new_y = new_y.normalized()
# choose a forward vector that's not parallel to new_y
var forward = -xform.basis.z
if abs(forward.dot(new_y)) > 0.999: # almost parallel -> pick another axis
forward = xform.basis.x
forward = (forward - new_y * forward.dot(new_y)).normalized() # project onto plane orthogonal to new_y
var right = forward.cross(new_y).normalized()
# set orthonormal basis: x = right, y = new_y, z = -forward (convention: z points forward)
var b = Basis()
b.x = right
b.y = new_y
b.z = -forward
xform.basis = b.orthonormalized()
return xform
func _process(delta: float) -> void:
timer = clamp(timer,-1,2)
$Sprite3D.rotation.x = camera.rotation.x - global_rotation.x
$Sprite3D.rotation.z = camera.rotation.z - global_rotation.z
speed_timer -= delta
if(big_boost)&&(timert<0.1)&&(taunt):
timert += delta
else:
taunt = false
trickdone = tricks[0]
if(adding_up > 1000.0):
speed_timer = 5
if(speed_timer > 0):
music.play_speed = lerp(float(music.play_speed),3.0,delta * 5.0)
SPEED = lerp(SPEED,big_speed,5.0 * delta)
else:
music.play_speed = lerp(music.play_speed,1.0,delta * 5.0)
SPEED = lerp(SPEED,small_speed,5.0 * delta)
print(str(timer)+" stuff")
print(surface_normal)
time += delta * 30
if(tricksdone.size() >= 1):
timer += delta
else:
adding_up = 0
$Sprite3D.frame_coords.x = fmod(time,40)
$Sprite3D.frame_coords.y = slope + (direction * 5)
print(round(angle() / 45))
slope = round(angle() / 45)
var p_fwd = -camera.global_transform.basis.z
var fwd = -$Node3D.transform.basis.z
var left = -$Node3D.transform.basis.x
var l_dot = left.dot(p_fwd)
var f_dot = fwd.dot(p_fwd)
$Sprite3D.flip_v = (angle() > 180)
if f_dot < -0.85:
if (angle() < 181):
direction=0
else:
direction=4
elif f_dot > 0.85:
if (angle() < 181):
direction=4
else:
direction=0
else:
$Sprite3D.flip_h = l_dot < 0
if abs(f_dot) < 0.3:
direction=2
elif f_dot < 0:
if (angle() < 181):
direction=1
else:
direction=3
else:
if (angle() < 181):
direction=3
else:
direction=1
func _physics_process(delta : float) -> void:
print(str(tricksdone)+" Ohio")
if(timer>1)&&(tricksdone.size()>1):
trickdone = tricks[0]
timer = 0
print(str(tricksdone.count("Show Off"))+" Simga")
print(str(((tricksdone.size()) - tricksdone.count("Show Off")))+" Sigma")
if tricksdone.count("Show Off") > ((tricksdone.size())-tricksdone.count("Show Off")):
$AudioStreamPlayer.play()
tricksdone.clear()
stuff()
else:
tricksdone.clear()
stuff()
none()
pass
if(($Sprite3D2.position.y + 10)/10) > 0:
size = ($Sprite3D2.position.y + 10) / 10
else:
size = 0
$Sprite3D2.scale = Vector3(size,size,size)
$Sprite3D2.global_position = $RayCast3D2.get_collision_point() + (Vector3(0,0.10,0)*Basis(-basis.x,basis.y,-basis.z))
match state:
states.Normal:
Idle(delta)
states.Air:
Jump(delta)
func converting():
print(abs(angle()))
velocity = global_transform.basis * vevo
func Jump(delta: float):
if(big_boost && Input.is_action_just_pressed("taunt")) && timer != 0:
taunt = true
trickdone = tricks[3]
$AudioStreamPlayer2.play()
$AudioStreamPlayer2.pitch_scale = (tricksdone.count("Show Off")+5)/5
timert = 0
surface_normal = Vector3(0,1,0)
if vevo.y < 20.5 && (!$RayCast3D.is_colliding()) && (!$Node3D/RayCast3D.is_colliding() && !$Node3D/RayCast3D2.is_colliding()):
up_direction = lerp(up_direction,Vector3.UP,delta * 5)
rotation.y = lerp(rotation.y,0.0,delta * 5)
global_transform = lerp(global_transform,align_with_y(global_transform,Vector3(0,1,0)),delta * 5)
# Gravity (only when not on floor)
if not $RayCast3D.is_colliding():
if(round(global_basis.y) != Vector3(0,1,0)):
vevo = savevevo
global_transform = lerp(global_transform,align_with_y(global_transform, Vector3(0,1,0)),delta * 20)
if(vevo.y > 10) && (!big_boost):
big_boost = true
taunt = false
timert = 0.2
trickdone = tricks[2]
elif(!big_boost) && (!Input.is_action_pressed("ui_accept")):
taunt = false
timert = 0.2
trickdone = tricks[4]
vevo += Vector3(0,-9.8 * gravity_scale,0) * delta
else:
big_boost = false
state = states.Normal
if($RayCast3D.is_colliding()):
state = states.Normal
horizontal(delta)
converting()
move_and_slide()
func angle():
var angle_rad := acos(clamp(surface_normal.dot(Vector3.UP), -1.0, 1.0))
return rad_to_deg(angle_rad)
func horizontal(delta: float):
var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
var direction_local := Vector3(input_dir.x, 0.0, input_dir.y)
if direction_local.length() > 0.001:
$Node3D.rotation.y = -input_dir.angle() + -1.57079637050629
direction_local = direction_local.normalized()
vevo.x = move_toward(vevo.x,direction_local.x * SPEED, SPEED * delta * 2.0)
vevo.z = move_toward(vevo.z,direction_local.z * SPEED, SPEED * delta * 2.0)
else:
# smoothly decelerate (tweak the third argument if you want faster/slower stop)
vevo.x = move_toward(vevo.x, 0.0, SPEED * delta * 1.5)
vevo.z = move_toward(vevo.z, 0.0, SPEED * delta * 1.5)
func Idle(delta: float):
#Slope Collision
if(abs(angle()) > 45) && (Vector3(vevo.x,0,vevo.z).length() < 2):
vevo.y = 10
vevo += Vector3((surface_normal.cross(Vector3.UP) * -0.65).z,0,(surface_normal.cross(Vector3.UP) * -0.65).x)
# Get valid collision normals from raycasts, only if they hit.
var n_sum = Vector3.ZERO
var count = 0
if $Node3D/RayCast3D.is_colliding():
n_sum += $Node3D/RayCast3D.get_collision_normal()
count += 1
if $Node3D/RayCast3D2.is_colliding():
n_sum += $Node3D/RayCast3D2.get_collision_normal()
count += 1
#Ground Collision
var n = Vector3.UP
$RayCast3D.target_position = Vector3(0,-0.4,0)
if count > 0:
n = (n_sum / float(count)).normalized()
surface_normal = n
# Align to slope when on floor
if $RayCast3D.is_colliding():
vevo.y = 0
up_direction = n
global_transform = lerp(global_transform,align_with_y(global_transform, n),delta * 20)
else:
up_direction = Vector3.UP
state = states.Air
# Jump
if Input.is_action_pressed("ui_accept") and $RayCast3D.is_colliding():
if(abs(angle())>=90)&&(trickdone != tricks[1]):
trickdone = tricks[1]
vevo.y = JUMP_VEVO
# Input-based movement in *local* X/Z (X = right, Z = forward)
# If falling, snap to floor (optional)
if vevo.y < 0 && $RayCast3D.is_colliding():
apply_floor_snap()
# **Important:** convert local `vevo` into global velocity correctly
# Use basis.xform to transform a local vector into global space
if(abs(angle()) > 90):
savevevo = Vector3((global_transform.basis * vevo).x,(global_transform.basis * vevo).z,(global_transform.basis * vevo).y)
else:
savevevo = global_transform.basis * vevo
horizontal(delta)
converting()
print(global_transform.basis * vevo)
# Move; pass the slope normal as the up direction so CharacterBody3D handles slopes correctly
move_and_slide()