Help with buttons, keeping players on platform (video)

Godot Version

v4.2.2.stable.official [15073afe3]

Question

Hello. I am attempting to make a game where the player sits on the platform and presses buttons to move the platform. In the video, you will see my code and a demonstration of what I have currently. The one button rotates the platform, but I will also have another button that moves the platform forward. This leads me to my questions:

  1. How do I lock my player’s view onto the button for as long as they hold it down? (In the video, the platform turns, causing the player’s view to slip off of the button and the platform to stop)
  2. How do I make the player move with the platform so they don’t slide off of it? (I try to make the player a child of the platform, but that causes issues with rotation order (I guess) and makes the player move in random directions regardless of where they are facing)
  3. If I wanted to add a second button that moves the platform forward, how do I make sure that that button only moves the platform in the direction it is facing? (The axis will probably remain the same regardless of where I turn the platform)

Any help is appreciated, thank you!

Might work best if your mech test platform is an AnimatableBody3D

I just tested it out and it seems to have the same issue. Thanks for the response, though

Hmm. I haven’t done anything exactly like this, but I have some thoughts. Haven’t looked at your code (video is not a good format for it, if you need help with specific parts, copy and paste them, and then use the </> button in the post editor with the code highlighted to make sure indentation and syntax highlighting shows up in the post), but I looked at the part where you show the actual game.

First, does the player need to be able to move while holding down one of these buttons? If not, then you could lock the player’s movement + rotation when they start pressing the button, make the player a child of the platform, and then reverse all of that the moment they let go.

Otherwise, you can calculate manually how much the player should move and rotate. You’re already calculating somewhere how much the platform should rotate, so it should be simple enough to rotate the player by the same amount - but of course, if the player is standing anywhere but at the exact center of the platform, then they need to be moved as well. Fortunately, the math isn’t terribly complicated:

  1. calculate the vector from the center of the platform to the player
  2. rotate that vector by the same angle as the platform got rotated - there’s a handy method for it in the Vector3 class called rotated
  3. add this vector to the platform’s position - this gives you the new position you wanna put the player at

Here’s an example of the same kind of thing - in this case, I was rotating a box around my first-person player character (with add_velocity being a custom method on the box, since there were other things also affecting its movement):

func box_move(angle_radians: float):
    # step 1
    var box_position = selected_object.global_position
    var dir = box_position - global_position
    # step 2
    var rotated_dir = dir.rotated(Vector3(0, 1, 0), angle_radians)
    # step 3
    var new_position = transform.origin + rotated_dir
    selected_object.add_velocity(new_position - box_position)

Thank you for the response @tayacan!

No, the player does not need to be able to move while holding down one of these buttons. Could you provide an example or explanation as to how you achieve your first method (lock the player’s movement + rotation when they start pressing the button, make the player a child of the platform, and then reverse all of that the moment they let go) through gdscript? Sorry about the video, I’ll add my code below:

FPS Controller:

extends CharacterBody3D

var speed
var vel = Vector3()
const WALK_SPEED = 5.0
const SPRINT_SPEED = 8.0
const JUMP_VELOCITY = 4.5
const SENSITIVITY = 0.003

#bob variables
const BOB_FREQ = 2.0
const BOB_AMP = 0.08
var t_bob = 0.0

#fov variables
const BASE_FOV = 75.0
const FOV_CHANGE = 1.5

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")

@onready var head = $Head
@onready var camera = $Head/Camera3D
@onready var mech = $".."
@onready var body = $"."

func _ready():
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)

func _unhandled_input(event):
	if event is InputEventMouseMotion:
		head.rotate_y(-event.relative.x * SENSITIVITY)
		camera.rotate_x(-event.relative.y * SENSITIVITY)
		camera.rotation.x = clamp(camera.rotation.x, deg_to_rad(-40), deg_to_rad(60))

func _physics_process(delta):
	# Add the gravity.
	if not is_on_floor():
		velocity.y -= gravity * delta

	# Handle jump.
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = JUMP_VELOCITY

	#Handle sprint.
	if Input.is_action_pressed("sprint"):
		speed = SPRINT_SPEED
	else:
		speed = WALK_SPEED
	# Get the input direction and handle the movement/deceleration.
	# As good practice, you should replace UI actions with custom gameplay actions.
	var input_dir = Input.get_vector("left", "right", "forward", "backward")
	var direction = (head.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	if is_on_floor():
		if direction:
			velocity.x = direction.x * speed
			velocity.z = direction.z * speed
		else:
			velocity.x = lerp(velocity.x, direction.x * speed, delta * 7.0)
			velocity.z = lerp(velocity.z, direction.z * speed, delta * 7.0)
	else:
		velocity.x = lerp(velocity.x, direction.x * speed, delta * 3.0)
		velocity.z = lerp(velocity.z, direction.z * speed, delta * 3.0)
	
	#Head bob
	t_bob += delta * velocity.length() * float(is_on_floor())
	camera.transform.origin = _headbob(t_bob)

	#FOV
	var velocity_clamped = clamp(velocity.length(), 0.5, SPRINT_SPEED * 2)
	var target_fov = BASE_FOV + FOV_CHANGE * velocity_clamped
	camera.fov = lerp(camera.fov, target_fov, delta * 8.0)

	move_and_slide()
	
func _headbob(time) -> Vector3:
	var pos = Vector3.ZERO
	pos.y = sin(time * BOB_FREQ) * BOB_AMP
	pos.x = cos(time * BOB_FREQ / 2) * BOB_AMP
	return pos
	
func new_parent():
	reparent($"../MechtestPlatform")

Button

extends CollisionObject3D
class_name Interactable

signal interacted(body)
signal released(body)

@export var prompt_message = "Interact"

func interact(body):
	interacted.emit(body)
	
func release(body):
	released.emit(body)

Interact Raycast

extends RayCast3D

@onready var prompt = $Prompt
@onready var camera = $"../.."
# Called when the node enters the scene tree for the first time.
func _ready():
	pass # Replace with function body.


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _physics_process(delta):
	prompt.text = ""
	
	if is_colliding():
		var collider = get_collider()
		
		if collider is Interactable:
			prompt.text = collider.prompt_message
			
			if Input.is_action_pressed("interact"):
				collider.interact(owner)
				#camera.look_at(collider.global_position, Vector3.UP)
			#if Input.is_action_just_released("interact"):
			else:
				collider.release(owner)

Platform

extends Node3D


# Called when the node enters the scene tree for the first time.
func _ready():
	pass # Replace with function body.


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _physics_process(delta):
	pass

func right(body):
	rotate_y(.01)
	get_node(".")
	
	
func right_stop(body):
	rotate_y(0)
	

func _on_button_interacted(_body):
	pass # Replace with function body.


func _on_button_released(_body):
	pass # Replace with function body.

I mean, it sounds like it’s not gonna work for you, if you need the player to be able to move, but something like:

player script:

var can_move = true
var old_parent = null

func parent_to_platform(platform : Node3D):
	can_move = false
	old_parent = get_parent()
	reparent(platform)

func reset_parent():
	reparent(old_parent)
	can_move = true

func _physics_process(delta):
	# Add the gravity.
	if not is_on_floor():
		velocity.y -= gravity * delta

	if can_move:
		# all your movement code based on user input here
		# (but not the call to move_and_slide)
	else:
		velocity.x = 0
		velocity.z = 0

	# head bob and FOV stuff here

	# move_and_slide either way, so gravity always gets applied
	move_and_slide()

func _unhandled_input(event):
	if can_move and event is InputEventMouseMotion:
		# camera rotation code here

Then you can call parent_to_platform when the player starts pressing the button, and reset_parent when they stop. You’ll need some way of getting a reference to the platform, though… perhaps via the button, if it’s a child of the platform?

Hey! Thanks for all your help, I managed to figure it out! I ended up having to fake the movement by moving the world underneath the platform instead of the platform itself.