/**************************************************************************** * Copyright 2019 Nreal Techonology Limited. All rights reserved. * * This file is part of NRSDK. * * https://www.nreal.ai/ * *****************************************************************************/ namespace NRKernal { using AOT; using System; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.UI; #if UNITY_EDITOR using UnityEditor; #endif /// A nr virtual displayer. [HelpURL("https://developer.nreal.ai/develop/unity/customize-phone-controller")] [ScriptOrder(NativeConstants.NRVIRTUALDISPLAY_ORDER)] public class NRVirtualDisplayer : SingletonBehaviour, ISystemButtonStateReceiver { /// /// Event queue for all listeners interested in onDisplayScreenChanged events. /// public static event Action OnDisplayScreenChangedEvent; /// State of the system button. [NonSerialized] public static SystemInputState SystemButtonState = new SystemInputState(); /// The camera. [SerializeField] Camera m_UICamera; /// The virtual controller. [SerializeField] MultiScreenController m_VirtualController; private ISystemButtonStateProvider m_ISystemButtonStateProvider; /// The screen resolution. private Vector2 m_ScreenResolution; /// Not supported on runtime. private const float ScaleFactor = 1f; /// The virtual display FPS. public static int VirtualDisplayFPS = 24; /// The current time. private float m_CurrentTime; public enum DisplayMode { None, Unity, AndroidNative } private DisplayMode m_DisplayMode = DisplayMode.AndroidNative; public static DisplayMode displayMode { get { if (Instance != null) { return Instance.m_DisplayMode; } else { return DisplayMode.AndroidNative; } } } #if UNITY_EDITOR private static RenderTexture m_ControllerScreen; #endif private NRDisplaySubsystem m_Subsystem; public NRDisplaySubsystem Subsystem { get { if (m_Subsystem == null) { string str_match = NRDisplaySubsystemDescriptor.Name; List descriptors = new List(); NRSubsystemManager.GetSubsystemDescriptors(descriptors); foreach (var des in descriptors) { if (des.id.Equals(str_match)) { m_Subsystem = des.Create(); } } } return m_Subsystem; } } /// Event queue for all listeners interested in OnMultiDisplayInited events. public event Action OnMultiDisplayInitialized; /// True to run in background. public static bool RunInBackground = true; /// True if is initialize, false if not. private bool m_IsInitialized = false; private void OnApplicationPause(bool pause) { if (!m_IsInitialized || isDirty) { return; } if (RunInBackground) { if (pause) { this.Pause(); } else { this.Resume(); } } else { NRDevice.ForceKill(); } } /// Starts this object. void Start() { if (isDirty) return; this.CreateDisplay(); } private void CreateDisplay() { if (m_IsInitialized) return; NRDebugger.Info("[NRVirtualDisplayer] Create display."); try { NRDevice.Instance.Init(); } catch (Exception e) { NRDebugger.Error("[NRVirtualDisplayer] NRDevice init error:" + e.ToString()); throw; } Subsystem.ListenMainScrResolutionChanged(OnDisplayResolutionChanged); Subsystem.Start(); #if !UNITY_EDITOR && UNITY_ANDROID if (m_VirtualController == null) { var phoneScreenReplayceTool = FindObjectOfType(); if (phoneScreenReplayceTool == null) { NRDebugger.Info("[NRVirtualDisplayer] Use default phone sceen provider."); this.BindVirtualDisplayProvider(new NRDefaultPhoneScreenProvider()); } else { NRDebugger.Info("[NRVirtualDisplayer] Use replayced phone sceen provider."); this.BindVirtualDisplayProvider(phoneScreenReplayceTool.CreatePhoneScreenProvider()); } } else { this.BindVirtualDisplayProvider(null); } #else this.BindVirtualDisplayProvider(null); #endif NRSessionManager.Instance.VirtualDisplayer = this; NRDebugger.Info("[NRVirtualDisplayer] Initialize"); m_IsInitialized = true; OnMultiDisplayInitialized?.Invoke(); } private void Pause() { if (!m_IsInitialized) { return; } Subsystem.Pause(); } private void Resume() { if (!m_IsInitialized) { return; } Subsystem.Resume(); } private void Update() { if (!m_IsInitialized) return; #if UNITY_EDITOR UpdateEmulator(); if (m_VirtualController) { m_VirtualController.gameObject.SetActive(NRInput.EmulateVirtualDisplayInEditor); } #endif if (m_DisplayMode == DisplayMode.Unity) { if (Subsystem.running) { m_CurrentTime += Time.deltaTime; } if (Subsystem.running && m_CurrentTime > (1f / VirtualDisplayFPS)) { m_CurrentTime = 0; m_UICamera?.Render(); } } } /// Destories this object. public void Stop() { Subsystem.Stop(); #if UNITY_EDITOR m_ControllerScreen?.Release(); m_ControllerScreen = null; #endif } /// /// Base OnDestroy method that destroys the Singleton's unique instance. Called by Unity when /// destroying a MonoBehaviour. Scripts that extend Singleton should be sure to call /// base.OnDestroy() to ensure the underlying static Instance reference is properly cleaned up. new void OnDestroy() { if (isDirty) return; base.OnDestroy(); this.Stop(); } /// If m_VirtualController is null, use android native /// fragment as the virtual controller provider. private void BindVirtualDisplayProvider(NRPhoneScreenProviderBase provider) { if (m_ISystemButtonStateProvider != null) { return; } bool targetOSX = false; #if UNITY_EDITOR targetOSX = EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.StandaloneOSX; #endif if (Application.platform == RuntimePlatform.OSXPlayer || targetOSX) { // No virtual controller on Mac m_DisplayMode = DisplayMode.None; m_ISystemButtonStateProvider = null; if (m_VirtualController != null) { m_VirtualController.gameObject.SetActive(false); } } else if (provider != null && m_VirtualController == null && Application.platform == RuntimePlatform.Android) { m_DisplayMode = DisplayMode.AndroidNative; m_ISystemButtonStateProvider = provider; NRDebugger.Info("[NRVirtualDisplayer] Bind android native controller"); } else { m_DisplayMode = DisplayMode.Unity; transform.position = Vector3.one * 99999f; m_ISystemButtonStateProvider = m_VirtualController; NRDebugger.Info("[NRVirtualDisplayer] Bind unity virtual controller"); #if UNITY_EDITOR var canvas = transform.GetComponentInChildren(); var scaler = canvas.transform.GetComponent(); if (scaler != null) { scaler.enabled = false; } SetVirtualDisplayResolution(); InitEmulator(); #else if (m_UICamera != null) { this.m_UICamera.enabled = false; } #endif } m_ISystemButtonStateProvider?.BindReceiver(this); } public void OnDataReceived(SystemButtonState state) { lock (SystemButtonState) { state.TransformTo(SystemButtonState); } } /// Updates the resolution described by size. /// The size. public void UpdateResolution(Vector2 size) { #if UNITY_EDITOR NRPhoneScreen.Resolution = size; this.SetVirtualDisplayResolution(); this.UpdateEmulatorScreen(size * ScaleFactor); #endif var m_PointRaycaster = gameObject.GetComponentInChildren(); if (m_PointRaycaster != null) { m_PointRaycaster.UpdateScreenSize(size * ScaleFactor); } if (m_ISystemButtonStateProvider != null && m_ISystemButtonStateProvider is NRPhoneScreenProviderBase) { ((NRPhoneScreenProviderBase)m_ISystemButtonStateProvider).ResizeView((int)size.x, (int)size.y); } } #if UNITY_EDITOR /// Sets virtual display resolution. private void SetVirtualDisplayResolution() { m_ScreenResolution = NRPhoneScreen.Resolution; if (m_ControllerScreen != null) { m_ControllerScreen.Release(); } m_ControllerScreen = new RenderTexture( (int)(m_ScreenResolution.x * ScaleFactor), (int)(m_ScreenResolution.y * ScaleFactor), 24 ); m_UICamera.targetTexture = m_ControllerScreen; m_UICamera.aspect = m_ScreenResolution.x / m_ScreenResolution.y; m_UICamera.orthographicSize = 6; } #endif /// Executes the 'display resolution changed' action. /// The width. /// The height. [MonoPInvokeCallback(typeof(NRDisplayResolutionCallback))] public static void OnDisplayResolutionChanged(int w, int h) { NRDebugger.Info("[NRVirtualDisplayer] Display resolution changed width:{0} height:{1}", w, h); MainThreadDispather.QueueOnMainThread(delegate () { NRVirtualDisplayer.Instance.UpdateResolution(new Vector2(w, h)); OnDisplayScreenChangedEvent?.Invoke(new Resolution() { width = w, height = h }); }); } #region Emulator #if UNITY_EDITOR /// The emulator touch. private static Vector2 m_EmulatorTouch = Vector2.zero; /// The emulator phone screen anchor. private Vector2 m_EmulatorPhoneScreenAnchor; /// Width of the emulator raw image. private float m_EmulatorRawImageWidth; /// Height of the emulator raw image. private float m_EmulatorRawImageHeight; /// The emulator phone raw image. private RawImage emulatorPhoneRawImage; /// Gets emulator screen touch. /// The emulator screen touch. public static Vector2 GetEmulatorScreenTouch() { return m_EmulatorTouch; } /// Initializes the emulator. private void InitEmulator() { GameObject emulatorVirtualController = new GameObject("NREmulatorVirtualController"); DontDestroyOnLoad(emulatorVirtualController); Canvas controllerCanvas = emulatorVirtualController.AddComponent(); controllerCanvas.renderMode = RenderMode.ScreenSpaceOverlay; GameObject rawImageObj = new GameObject("RamImage"); rawImageObj.transform.parent = controllerCanvas.transform; emulatorPhoneRawImage = rawImageObj.AddComponent(); emulatorPhoneRawImage.raycastTarget = false; UpdateEmulatorScreen(m_ScreenResolution * ScaleFactor); } /// Updates the emulator screen described by size. /// The size. private void UpdateEmulatorScreen(Vector2 size) { float scaleRate = 0.18f; m_EmulatorRawImageWidth = size.x * scaleRate; m_EmulatorRawImageHeight = size.y * scaleRate; emulatorPhoneRawImage.rectTransform.pivot = Vector2.right; emulatorPhoneRawImage.rectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Right, 0f, 0f); emulatorPhoneRawImage.rectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Bottom, 0f, 0f); emulatorPhoneRawImage.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, m_EmulatorRawImageWidth); emulatorPhoneRawImage.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, m_EmulatorRawImageHeight); emulatorPhoneRawImage.texture = m_ControllerScreen; Vector2 gameViewSize = Handles.GetMainGameViewSize(); m_EmulatorPhoneScreenAnchor = new Vector2(gameViewSize.x - m_EmulatorRawImageWidth, 0f); } /// Updates the emulator. private void UpdateEmulator() { if (NRInput.EmulateVirtualDisplayInEditor && Input.GetMouseButton(0) && Input.mousePosition.x > m_EmulatorPhoneScreenAnchor.x && Input.mousePosition.y < (m_EmulatorPhoneScreenAnchor.y + m_EmulatorRawImageHeight)) { m_EmulatorTouch.x = (Input.mousePosition.x - m_EmulatorPhoneScreenAnchor.x) / m_EmulatorRawImageWidth * 2f - 1f; m_EmulatorTouch.y = (Input.mousePosition.y - m_EmulatorPhoneScreenAnchor.y) / m_EmulatorRawImageHeight * 2f - 1f; } else { m_EmulatorTouch = Vector2.zero; } } #endif #endregion } }