Changing gravity and wrapping around blocks

Godot Version

4.5.1

Question

Hello I want to recreate a game like “Kula Word” (PS1)
There is a ball that has 3 commands:

  • go forward 1 block
  • rotate 90 degrees left or right
  • jump forward 2 blocks

The ball moves on 2x2x2 cubes, with ortogonal movements (so it should be simple)
when the ball comes to an edge, the ball can wrap around that cube and the gravity changes

I am strugling to change gravity and rotation of the ball.
here is the script I’ve made…

extends CharacterBody3D

enum STATE {IDLE, MOVING, DEAD, WRAP}

@export var roll_speed = 0.5  # Secondi per completare un movimento
@export var jump_height = 4.0  # Altezza del salto
@export var grid_size = 2.0  # Dimensione di un blocco

@onready var ball: CSGSphere3D = $CSGSphere3D
@onready var ray_next_block: RayCast3D = $RayNextBlock
@onready var animation_player: AnimationPlayer = $AnimationPlayer

var state: STATE = STATE.IDLE
var initial_pos = .0
var target_pos = .0
var gravity_dir = Vector3.DOWN

func _physics_process(delta):
	# Solo gravità
	if not is_on_floor() and state not in [STATE.DEAD, STATE.WRAP]:
		velocity += gravity_dir * 9.8
	else:
		velocity = Vector3.ZERO
		if state != STATE.WRAP:
			state = STATE.IDLE
	
	move_and_slide()
	
	if state != STATE.IDLE:
		return
	
	# Input movimento - SOLO UI_UP per avanzare di 1 blocco
	if Input.is_action_just_pressed("ui_up"):
		move_forward_one_block()
	
	# Input salto - SOLO UI_ACCEPT per saltare di 2 blocchi
	elif Input.is_action_just_pressed("ui_accept"):
		jump_forward_two_blocks()
		
	if Input.is_action_just_pressed("ui_left"):
		dorotate(90)
	elif Input.is_action_just_pressed("ui_right"):
		dorotate(-90)

func dorotate(degrees: int):
	if state != STATE.IDLE:
		return
	state = STATE.MOVING
	
	var rotate_tween = create_tween()
	var current_rotation = rotation_degrees.y
	var target_rotation = current_rotation + degrees
	
	#change this to rotate around transform up direction
	rotate_tween.tween_property(self, "rotation_degrees:y", target_rotation, roll_speed)
	rotate_tween.finished.connect(_on_move_finished)

func move_forward_one_block():
	if state != STATE.IDLE:
		return
	state = STATE.MOVING
	
	# Direzione avanti (basata sulla rotazione della palla o globale)
	if not ray_next_block.is_colliding():
		wrap_block()
		return
	var forward_direction = -transform.basis.z
	var target_position = position + (forward_direction * grid_size)
	
	# Animazione di rotolamento
	var tween = create_tween()
	tween.set_parallel()
	tween.tween_property(self, "position", target_position, roll_speed)
	animation_player.play("rotate")
	tween.finished.connect(_on_move_finished)

func wrap_block():
	var forward_direction = -transform.basis.z
	var target_position = position + (forward_direction * grid_size)
	var target2 = target_position + (Vector3.RIGHT.cross(forward_direction) * grid_size)
	
	gravity_dir = -forward_direction.normalized()
	
	var tween = create_tween()
	tween.tween_property(self, "position", target_position, roll_speed)
	tween.tween_property(self, "rotation:x", rotation.x + deg_to_rad(-90), roll_speed)
	tween.tween_property(self, "position", target2, roll_speed)
	tween.finished.connect(_on_move_finished)
	#TODO fix to grid position?

func jump_forward_two_blocks():
	if state != STATE.IDLE:
		return
	state = STATE.MOVING
	
	var forward_direction = -transform.basis.z
	target_pos = position + (forward_direction * grid_size * 2)
	initial_pos = position
	
	# Animazione di salto con arco parabolico
	var tween = create_tween()
	tween.set_parallel(true)
	# Movimento orizzontale + arco verticale
	tween.tween_method(_update_jump_position, 0.0, 1.0, roll_speed * 1.5)
	
	animation_player.play("rotate")
	#tween.finished.connect(_on_move_finished)

func _update_jump_position(t: float):
	# Interpolazione lineare per il movimento orizzontale
	var horizontal_pos = initial_pos.lerp(target_pos, t)	
	# Arco parabolico per il movimento verticale
	var arc_height = jump_height * sin(t * PI)
	
	position = Vector3(horizontal_pos.x, initial_pos.y + arc_height, horizontal_pos.z)

func _on_move_finished():
	state = STATE.IDLE

func die():
	state = STATE.DEAD
	animation_player.play("die")
	
func goto_gameover():
	get_tree().change_scene_to_file("res://levels/GameOver.tscn")

Struggling in what way? Don’t expect people to deduce that by reading the code and imagining how it would run. The more specific question you ask, the more likely you get some answers. Right now it just looks like you are expecting someone to figure out the code to make your game for you.

This is not a trivial mechanic you aimed at. Break it down and solve in smaller pieces.