Godot Version
4.2 stable
Question
Hi! I have a multiplayer game and with the regular Camera3D each player controls and views the game from their own player. So I know that works.
I have the same setup using XRcameras, but when players join their view is stuck to player 1’s, even if that player is not using VR (however, for reference, the xrcamera node is still on all player scenes, just not being used.) They still control their own character as expected though, outside of the camera being stuck to player 1’s position
The multiplayer synchronizer has the xrcamera and xrorigin positions & rotations.
Curious if there’s any resources on getting multiplayer with VR to work that I could look at?
Here’s the player code for good measure:
extends CharacterBody3D
const SPEED = 5.0
const JUMP_VELOCITY = 4.5
@export var force = 5
@export var mouse_sens = 0.005
@export var cam_sens = SPEED / 2
var flying = false
var camera_anglev=0
var DiscScene = preload("res://scenes/frolf_disc.tscn")
# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
@onready var xr_camera = $XROrigin3D/XRCamera3D
@onready var camera = $CamPivot/Camera3D
@onready var cam_pivot = $CamPivot
@onready var throwpoint = $CamPivot/Camera3D/ThrowPoint
@onready var menu = $CamPivot/Menu
func _enter_tree():
set_multiplayer_authority(str(name).to_int())
#TODO: Separate XR camera & player somehow so there's only one instance?
func _ready():
#Only run this if we're allowed
if (Global.singleplayer == false and not is_multiplayer_authority()):
return
#Avoid falling through the world for some reason when first connecting to server
elif (Global.singleplayer == false and is_multiplayer_authority()):
respawn()
#Setup VR vs flatscreen
var xr_interface = XRServer.find_interface("OpenXR")
if xr_interface and xr_interface.is_initialized():
xr_camera.current = true
# Turn off v-sync!
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
# Change our main viewport to output to the HMD
get_viewport().use_xr = true
else:
camera.current = true
#xr_camera.queue_free()
print("OpenXR not initialized, fallback to flatscreen.")
func _unhandled_input(event):
#Only run this if we're allowed
if (Global.singleplayer == false and not is_multiplayer_authority()): return
#Pause button
if event.is_action_pressed("ui_cancel"):
#Release mouse & toggle pause
if menu.visible:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
else:
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
menu.visible = !menu.visible
#If paused, don't move camera
if menu.visible == true:
return
#toggle flying mode
if event.is_action_pressed("fly"):
flying = !flying
if event.is_action_pressed("respawn"):
respawn()
#Capture mouse
if event is InputEventMouseButton:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
#Camera Movement
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
if event is InputEventMouseMotion:
cam_pivot.rotate_y(-event.relative.x * mouse_sens)
camera.rotate_x(-event.relative.y * mouse_sens)
func respawn():
global_position = get_tree().root.get_node("World/ParkingLot").global_position
#Helper - Update disc position & return the node
@rpc("call_local")
func spawn_disc():
#Add disc to scene
var disc = DiscScene.instantiate()
disc.position = throwpoint.position #Local position avoids glitches
disc.freeze = true #Avoid applying gravity while holding
add_child(disc) #Add after position to avoid glitches
@rpc("call_local")
func update_disc():
var disc = get_node("FrolfDisc")
disc.global_position = throwpoint.global_position
disc.rotation = Vector3(camera.rotation.x, cam_pivot.rotation.y, 0)
return disc
@rpc("call_local")
func throw_disc():
#Throw disc
var disc = update_disc()
disc.freeze = false
disc.reparent(get_tree().root)
disc.apply_impulse(-disc.basis.z * force)
func _physics_process(delta):
#Only run this if we're allowed
if (Global.singleplayer == false and not is_multiplayer_authority()): return
#Exit if paused
if menu.visible == true:
return
# Add the gravity.
if not is_on_floor() and not flying:
velocity.y -= gravity * delta
# Handle jump.
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = JUMP_VELOCITY
#Throw the disc
var disc_held = get_node_or_null("FrolfDisc")
if Input.is_action_just_pressed("throw"):
spawn_disc.rpc()
elif disc_held and !Input.is_action_just_released("throw"):
#Hold disc until throw
update_disc.rpc()
elif disc_held and Input.is_action_just_released("throw"):
throw_disc.rpc()
# Get the input & look direction, handle the movement/deceleration.
var input_dir = Input.get_vector("left", "right", "forward", "backward")
var rotate_dir = Input.get_vector("look_right", "look_left", "look_down", "look_up")
var direction = (cam_pivot.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if rotate_dir:
cam_pivot.rotate_y(rotate_dir.x * delta * cam_sens)
camera.rotate_x(rotate_dir.y * delta * cam_sens)
if direction:
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
#Make sure the camera doesn't look too far up or down
camera.rotation.x = clampf(camera.rotation.x, -deg_to_rad(45), deg_to_rad(70))
move_and_slide()