Files
2026-06-07 16:58:24 +01:00

190 lines
7.0 KiB
C#

using UnityEngine;
using VRC.SDKBase;
using System.Reflection;
using UnityEngine.Rendering.PostProcessing;
namespace VRC.SDK3.ClientSim
{
/// <summary>
/// Wrapper for the VRC_SceneDescriptor.
/// </summary>
public class ClientSimSceneManager : IClientSimSceneManager
{
// Using a variable here to allow for testability when not on android platform.
private bool _isAndroid =
#if UNITY_ANDROID
true;
#else
false;
#endif
// Only used for tests to verify android path in copy camera.
internal static ClientSimSceneManager CreateTestInstance(bool isAndroid)
{
return new ClientSimSceneManager { _isAndroid = isAndroid };
}
private int _spawnOrder = 0;
private VRC_SceneDescriptor _descriptor => VRC_SceneDescriptor.Instance;
public bool HasSceneDescriptor()
{
return _descriptor != null;
}
public void SetupCamera(Camera camera)
{
if (!HasSceneDescriptor())
{
throw new ClientSimException("Cannot get reference camera when there is no scene descriptor.");
}
Camera refCamera = null;
if (_descriptor.ReferenceCamera != null)
{
refCamera = _descriptor.ReferenceCamera.GetComponent<Camera>();
}
if (refCamera != null)
{
CopyCameraValues(refCamera, camera);
}
else
{
this.LogWarning("Reference camera is not set in the SceneDescriptor!");
// Force camera near settings
camera.nearClipPlane = Mathf.Clamp(camera.nearClipPlane, 0.01f, 0.05f);
}
}
public float GetRespawnHeight()
{
if (!HasSceneDescriptor())
{
throw new ClientSimException("Cannot get respawn height when there is no scene descriptor.");
}
return _descriptor.RespawnHeightY;
}
public bool ShouldObjectsDestroyAtRespawnHeight()
{
if (!HasSceneDescriptor())
{
throw new ClientSimException("Cannot get respawn height setting when there is no scene descriptor.");
}
return _descriptor.ObjectBehaviourAtRespawnHeight == VRC_SceneDescriptor.RespawnHeightBehaviour.Destroy;
}
public void ResetSpawnOrder()
{
_spawnOrder = 0;
}
public Transform GetSpawnPoint(bool remote = false)
{
if (!HasSceneDescriptor())
{
throw new ClientSimException("Trying to get a Spawn Point but there is no Scene Descriptor. Add a SceneDescriptor or the VRCWorldPrefab to your scene.");
}
if (_descriptor.spawns.Length == 0 || _descriptor.spawns[0] == null)
{
throw new ClientSimException("Trying to get a Spawn Point but the Scene Descriptor doesn't have one. Add a Transform to the 'Spawns' array in the VRC Scene Descriptor component.");
}
// Remote players always restart the list, so for now, only first spawn
if (_descriptor.spawnOrder == VRC_SceneDescriptor.SpawnOrder.First ||
_descriptor.spawnOrder == VRC_SceneDescriptor.SpawnOrder.Demo ||
remote)
{
return _descriptor.spawns[0];
}
if (_descriptor.spawnOrder == VRC_SceneDescriptor.SpawnOrder.Random)
{
int spawn = Random.Range(0, _descriptor.spawns.Length);
return _descriptor.spawns[spawn];
}
if (_descriptor.spawnOrder == VRC_SceneDescriptor.SpawnOrder.Sequential)
{
Transform spawn = _descriptor.spawns[_spawnOrder];
_spawnOrder = (_spawnOrder + 1) % _descriptor.spawns.Length;
return spawn;
}
// Fallback to first spawn point
return _descriptor.spawns[0];
}
public Transform GetSpawnPoint(int index)
{
if (!HasSceneDescriptor())
{
throw new ClientSimException("Cannot get spawn point when there is no scene descriptor.");
}
if (index < 0 || index >= _descriptor.spawns.Length || _descriptor.spawns[index] == null)
{
this.LogWarning($"Using spawn point 0 instead of {index}, which is invalid.");
index = 0;
}
// Fallback to first spawn point
return _descriptor.spawns[index];
}
public float GetSpawnRadius()
{
if (!HasSceneDescriptor())
{
throw new ClientSimException("Cannot get spawn radius when there is no scene descriptor.");
}
return _descriptor.spawnRadius;
}
private void CopyCameraValues(Camera refCamera, Camera camera)
{
if (refCamera == null)
{
return;
}
camera.farClipPlane = refCamera.farClipPlane;
camera.nearClipPlane = Mathf.Clamp(refCamera.nearClipPlane, 0.01f, 0.05f);
camera.clearFlags = refCamera.clearFlags;
camera.backgroundColor = refCamera.backgroundColor;
camera.allowHDR = refCamera.allowHDR;
PostProcessLayer refPostProcessLayer = refCamera.GetComponent<PostProcessLayer>();
if (refPostProcessLayer != null)
{
if (_isAndroid)
{
Debug.LogWarning("Post processing is not supported on Android");
}
else
{
// VRChatBug: VRChat ignores if the PostProcessingLayer is disabled on the reference camera and always
// enables it for the player's camera.
PostProcessLayer postProcessLayer = camera.gameObject.AddComponent<PostProcessLayer>();
postProcessLayer.volumeLayer = refPostProcessLayer.volumeLayer;
postProcessLayer.volumeTrigger = refPostProcessLayer.volumeTrigger != refPostProcessLayer.transform
? refPostProcessLayer.volumeTrigger
: camera.transform;
// Use reflection to copy over resources : https://github.com/Unity-Technologies/PostProcessing/issues/467
FieldInfo resourcesInfo = typeof(PostProcessLayer).GetField("m_Resources", BindingFlags.NonPublic | BindingFlags.Instance);
PostProcessResources postProcessResources = resourcesInfo.GetValue(refPostProcessLayer) as PostProcessResources;
postProcessLayer.Init(postProcessResources);
}
}
refCamera.gameObject.SetActive(false);
}
}
}