using UnityEngine; using VRC.SDKBase; using System.Reflection; using UnityEngine.Rendering.PostProcessing; namespace VRC.SDK3.ClientSim { /// /// Wrapper for the VRC_SceneDescriptor. /// 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(); } 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(); 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.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); } } }