using UnityEngine; using UnityEngine.UI; using UnityEngine.XR; using System; using System.Collections; using System.Collections.Generic; using SimpleJSON; using System.Linq; using System.IO; using MeshVR; // VAMStory - VAMSFovController v1.0 (Optimized, Fixed, & UI Scaled) // // Controls the FOV of the main camera safely without UI distortion namespace VAMStory { public class VAMSFovController : MVRScript { // Static string public static string PLUGIN_PATH; public static string EXCEPTION_PREFIX = "VAMSFovController - Exception caught: "; // JSONStorables public JSONStorableFloat FOVValue; public JSONStorableFloat A_ForceFovValue; public JSONStorableAction A_DisableFov; public JSONStorableAction A_EnableFov; public JSONStorableBool ScaleUI; // Misc protected float _originalFOV = 0f; protected bool _fovEnabled = true; // OPTIMIZATION: Cache components and UI scaling math private Camera _cachedCamera; private Transform _vamUITransform; private Vector3 _originalUIScale; private float _originalTan; public override void Init() { try { PLUGIN_PATH = Utils.GetPluginPath(this); _originalFOV = SuperController.singleton.monitorCameraFOV; _originalTan = Mathf.Tan(_originalFOV * 0.5f * Mathf.Deg2Rad); // Look up the monitor camera _cachedCamera = Camera.allCameras.FirstOrDefault(c => c.name.Contains("Monitor")); if (_cachedCamera == null) { _cachedCamera = SuperController.singleton.GetComponentInChildren(); } if (_cachedCamera == null) { _cachedCamera = Camera.main; } FOVValue = new JSONStorableFloat("FOV Value", _originalFOV, FOVValueCallback, 1f, 100f){ isStorable = false }; // Grab the slider to walk up the hierarchy and find the root VaM UI Canvas UIDynamicSlider fovSlider = CreateSlider(FOVValue); if (fovSlider != null) { Canvas parentCanvas = fovSlider.GetComponentInParent(); if (parentCanvas != null) { _vamUITransform = parentCanvas.rootCanvas.transform; _originalUIScale = _vamUITransform.localScale; } } Utils.CreateStaticDescriptionText(this,"VAMSFovControllerTitle","VAMSFovController",true,40,TextAnchor.MiddleRight); Utils.CreateStaticDescriptionText(this,"Right UI Main Title","Allows you to control the FOV of the main camera. Can be used to enforce a FOV for a desktop scene, or animate FOV for a cinematic scene.",true,135,TextAnchor.MiddleRight); A_ForceFovValue = new JSONStorableFloat("Force FOV Value", _originalFOV, ForceFOVValueCallback, 1f, 100f){ isStorable = false }; A_DisableFov = new JSONStorableAction("Disable FOV", () => { _fovEnabled = false; }); A_EnableFov = new JSONStorableAction("Enable FOV", () => { _fovEnabled = true; }); ScaleUI = new JSONStorableBool("Scale UI", true, ScaleUICallback); CreateToggle(ScaleUI); RegisterFloat(FOVValue); RegisterFloat(A_ForceFovValue); RegisterAction(A_DisableFov); RegisterAction(A_EnableFov); RegisterBool(ScaleUI); } catch(Exception e) { SuperController.LogError(EXCEPTION_PREFIX + e); } } void Start() { } void Update() { } protected void SetFovValue(float fovVal) { // Hard gate: when the plugin is disabled in VAM, never touch the FOV — even if // Timeline keeps writing to FOVValue / Force FOV Value, those writes become no-ops. if (!enabled) return; if (ScaleUI != null && ScaleUI.val) { // ORIGINAL path: set VAM's monitorCameraFOV. // VAM internally rebuilds/rescales the UI as a side effect — looks correct, but // is what causes the per-frame perf hit when animating FOV. SuperController.singleton.monitorCameraFOV = fovVal; } else { // OPTIMIZED path: write directly to the camera component. // Skips VAM's UI rebuild entirely — smooth for animation, but UI will not be // rescaled to compensate (intended for cinematic shots where UI is hidden). if (_cachedCamera != null) { _cachedCamera.fieldOfView = fovVal; } } } // ************************** // Callbacks & similar // ************************** private void FOVValueCallback(float fovVal) { if (_fovEnabled == false) return; SetFovValue(fovVal); } private void ForceFOVValueCallback(float fovVal) { SetFovValue(fovVal); } private void ScaleUICallback(bool val) { // Re-apply the current FOV through whichever path is now active, so the UI state // matches expectations immediately on toggle (rebuild on ON, no rebuild on OFF). if (FOVValue != null) { SetFovValue(FOVValue.val); } } // ************************** // Local Tools // ************************** private void logDebug( string debugText ) { SuperController.LogMessage( debugText ); } // ************************** // Plugin states // ************************** void OnEnable() { if( FOVValue != null && _cachedCamera != null) { SetFovValue(FOVValue.val); } } void OnDisable() { // Restore original FOV via VAM's path so the UI rebuilds properly. SuperController.singleton.monitorCameraFOV = _originalFOV; } // ************************** // Time to cleanup ! // ************************** void OnDestroy() { // Restore original FOV via VAM's path so the UI rebuilds properly. SuperController.singleton.monitorCameraFOV = _originalFOV; } } }