using System; using System.Collections.Generic; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace FairyGUI { /// /// /// public enum FitScreen { None, FitSize, FitWidthAndSetMiddle, FitHeightAndSetCenter } /// /// /// [ExecuteInEditMode] [AddComponentMenu("FairyGUI/UI Panel")] public class UIPanel : MonoBehaviour, EMRenderTarget { /// /// /// public Container container { get; private set; } /// /// /// public string packageName; /// /// /// public string componentName; /// /// /// public FitScreen fitScreen; /// /// /// public int sortingOrder; [SerializeField] string packagePath; [SerializeField] RenderMode renderMode = RenderMode.ScreenSpaceOverlay; [SerializeField] Camera renderCamera = null; [SerializeField] Vector3 position; [SerializeField] Vector3 scale = new Vector3(1, 1, 1); [SerializeField] Vector3 rotation = new Vector3(0, 0, 0); [SerializeField] bool fairyBatching = false; [SerializeField] bool touchDisabled = false; [SerializeField] Vector2 cachedUISize; [SerializeField] HitTestMode hitTestMode = HitTestMode.Default; [SerializeField] bool setNativeChildrenOrder = false; [System.NonSerialized] int screenSizeVer; [System.NonSerialized] Rect uiBounds; //Track bounds even when UI is not created, edit mode GComponent _ui; [NonSerialized] bool _created; List _renders; void OnEnable() { if (Application.isPlaying) { if (this.container == null) { CreateContainer(); if (!string.IsNullOrEmpty(packagePath) && UIPackage.GetByName(packageName) == null) UIPackage.AddPackage(packagePath); } } else { //不在播放状态时我们不在OnEnable创建,因为Prefab也会调用OnEnable,延迟到Update里创建(Prefab不调用Update) //每次播放前都会disable/enable一次。。。 if (container != null)//如果不为null,可能是因为Prefab revert, 而不是因为Assembly reload, OnDestroy(); EMRenderSupport.Add(this); screenSizeVer = 0; uiBounds.position = position; uiBounds.size = cachedUISize; if (uiBounds.size == Vector2.zero) uiBounds.size = new Vector2(30, 30); } } void OnDisable() { if (!Application.isPlaying) EMRenderSupport.Remove(this); } void Start() { if (!_created && Application.isPlaying) CreateUI_PlayMode(); } void Update() { if (screenSizeVer != StageCamera.screenSizeVer) HandleScreenSizeChanged(); } void OnDestroy() { if (container != null) { if (!Application.isPlaying) EMRenderSupport.Remove(this); if (_ui != null) { _ui.Dispose(); _ui = null; } container.Dispose(); container = null; } _renders = null; } void CreateContainer() { if (!Application.isPlaying) { Transform t = this.transform; int cnt = t.childCount; while (cnt > 0) { GameObject go = t.GetChild(cnt - 1).gameObject; if (go.name == "UI(AutoGenerated)") { #if (UNITY_2018_3_OR_NEWER && UNITY_EDITOR) if (PrefabUtility.IsPartOfPrefabInstance(go)) PrefabUtility.UnpackPrefabInstance(PrefabUtility.GetOutermostPrefabInstanceRoot(gameObject), PrefabUnpackMode.Completely, InteractionMode.AutomatedAction); #endif UnityEngine.Object.DestroyImmediate(go); } cnt--; } } this.container = new Container(this.gameObject); this.container.renderMode = renderMode; this.container.renderCamera = renderCamera; this.container.touchable = !touchDisabled; this.container._panelOrder = sortingOrder; this.container.fairyBatching = fairyBatching; if (Application.isPlaying) { SetSortingOrder(this.sortingOrder, true); if (this.hitTestMode == HitTestMode.Raycast) { ColliderHitTest hitArea = new ColliderHitTest(); hitArea.collider = this.gameObject.AddComponent(); this.container.hitArea = hitArea; } if (setNativeChildrenOrder) { CacheNativeChildrenRenderers(); this.container.onUpdate += () => { int cnt = _renders.Count; int sv = UpdateContext.current.renderingOrder++; for (int i = 0; i < cnt; i++) { Renderer r = _renders[i]; if (r != null) _renders[i].sortingOrder = sv; } }; } } } /// /// /// public GComponent ui { get { if (!_created && Application.isPlaying) { if (!string.IsNullOrEmpty(packagePath) && UIPackage.GetByName(packageName) == null) UIPackage.AddPackage(packagePath); CreateUI_PlayMode(); } return _ui; } } /// /// /// public void CreateUI() { if (_ui != null) { _ui.Dispose(); _ui = null; } CreateUI_PlayMode(); } /// /// Change the sorting order of the panel in runtime. /// /// sorting order value /// false if you dont want the default sorting behavior. e.g. call Stage.SortWorldSpacePanelsByZOrder later. public void SetSortingOrder(int value, bool apply) { this.sortingOrder = value; container._panelOrder = value; if (apply) Stage.inst.ApplyPanelOrder(container); } /// /// /// /// public void SetHitTestMode(HitTestMode value) { if (this.hitTestMode != value) { this.hitTestMode = value; BoxCollider collider = this.gameObject.GetComponent(); if (this.hitTestMode == HitTestMode.Raycast) { if (collider == null) collider = this.gameObject.AddComponent(); ColliderHitTest hitArea = new ColliderHitTest(); hitArea.collider = collider; this.container.hitArea = hitArea; if (_ui != null) UpdateHitArea(); } else { this.container.hitArea = null; if (collider != null) Component.Destroy(collider); } } } /// /// /// public void CacheNativeChildrenRenderers() { if (_renders == null) _renders = new List(); else _renders.Clear(); Transform t = this.container.cachedTransform; int cnt = t.childCount; for (int i = 0; i < cnt; i++) { GameObject go = t.GetChild(i).gameObject; if (go.name != "GComponent") _renders.AddRange(go.GetComponentsInChildren(true)); } cnt = _renders.Count; for (int i = 0; i < cnt; i++) { Renderer r = _renders[i]; if ((r is SkinnedMeshRenderer) || (r is MeshRenderer)) { //Set the object rendering in Transparent Queue as UI objects if (r.sharedMaterial != null) r.sharedMaterial.renderQueue = 3000; } } } void CreateUI_PlayMode() { _created = true; if (string.IsNullOrEmpty(packageName) || string.IsNullOrEmpty(componentName)) return; _ui = (GComponent)UIPackage.CreateObject(packageName, componentName); if (_ui != null) { _ui.position = position; if (scale.x != 0 && scale.y != 0) _ui.scale = scale; _ui.rotationX = rotation.x; _ui.rotationY = rotation.y; _ui.rotation = rotation.z; if (this.container.hitArea != null) { UpdateHitArea(); _ui.onSizeChanged.Add(UpdateHitArea); _ui.onPositionChanged.Add(UpdateHitArea); } this.container.AddChildAt(_ui.displayObject, 0); HandleScreenSizeChanged(); } else Debug.LogError("Create " + packageName + "/" + componentName + " failed!"); } void UpdateHitArea() { ColliderHitTest hitArea = this.container.hitArea as ColliderHitTest; if (hitArea != null) { ((BoxCollider)hitArea.collider).center = new Vector3(_ui.xMin + _ui.width / 2, -_ui.yMin - _ui.height / 2); ((BoxCollider)hitArea.collider).size = _ui.size; } } void CreateUI_EditMode() { if (!EMRenderSupport.packageListReady || UIPackage.GetByName(packageName) == null) return; DisplayObject.hideFlags = HideFlags.DontSaveInEditor; GObject obj = UIPackage.CreateObject(packageName, componentName); if (obj != null && !(obj is GComponent)) { obj.Dispose(); Debug.LogWarning("Not a GComponnet: " + packageName + "/" + componentName); return; } _ui = (GComponent)obj; if (_ui != null) { _ui.displayObject.gameObject.hideFlags |= HideFlags.HideInHierarchy; _ui.gameObjectName = "UI(AutoGenerated)"; _ui.position = position; if (scale.x != 0 && scale.y != 0) _ui.scale = scale; _ui.rotationX = rotation.x; _ui.rotationY = rotation.y; _ui.rotation = rotation.z; this.container.AddChildAt(_ui.displayObject, 0); cachedUISize = _ui.size; uiBounds.size = cachedUISize; HandleScreenSizeChanged(); } } void HandleScreenSizeChanged() { if (!Application.isPlaying) DisplayObject.hideFlags = HideFlags.DontSaveInEditor; screenSizeVer = StageCamera.screenSizeVer; int width = Screen.width; int height = Screen.height; if (this.container != null) { Camera cam = container.GetRenderCamera(); if (cam.targetDisplay != 0 && cam.targetDisplay < Display.displays.Length) { width = Display.displays[cam.targetDisplay].renderingWidth; height = Display.displays[cam.targetDisplay].renderingHeight; } if (this.container.renderMode != RenderMode.WorldSpace) { StageCamera sc = cam.GetComponent(); if (sc == null) sc = StageCamera.main.GetComponent(); this.container.scale = new Vector2(sc.unitsPerPixel * UIContentScaler.scaleFactor, sc.unitsPerPixel * UIContentScaler.scaleFactor); } } width = Mathf.CeilToInt(width / UIContentScaler.scaleFactor); height = Mathf.CeilToInt(height / UIContentScaler.scaleFactor); if (_ui != null) { switch (fitScreen) { case FitScreen.FitSize: _ui.SetSize(width, height); _ui.SetXY(0, 0, true); break; case FitScreen.FitWidthAndSetMiddle: _ui.SetSize(width, _ui.sourceHeight); _ui.SetXY(0, (int)((height - _ui.sourceHeight) / 2), true); break; case FitScreen.FitHeightAndSetCenter: _ui.SetSize(_ui.sourceWidth, height); _ui.SetXY((int)((width - _ui.sourceWidth) / 2), 0, true); break; } UpdateHitArea(); } else { switch (fitScreen) { case FitScreen.FitSize: uiBounds.position = new Vector2(0, 0); uiBounds.size = new Vector2(width, height); break; case FitScreen.FitWidthAndSetMiddle: uiBounds.position = new Vector2(0, (int)((height - cachedUISize.y) / 2)); uiBounds.size = new Vector2(width, cachedUISize.y); break; case FitScreen.FitHeightAndSetCenter: uiBounds.position = new Vector2((int)((width - cachedUISize.x) / 2), 0); uiBounds.size = new Vector2(cachedUISize.x, height); break; } } } #region edit mode functions void OnUpdateSource(object[] data) { if (Application.isPlaying) return; this.packageName = (string)data[0]; this.packagePath = (string)data[1]; this.componentName = (string)data[2]; if ((bool)data[3]) { if (container == null) return; if (_ui != null) { _ui.Dispose(); _ui = null; } } } public void ApplyModifiedProperties(bool sortingOrderChanged, bool fitScreenChanged) { if (container != null) { container.renderMode = renderMode; container.renderCamera = renderCamera; if (sortingOrderChanged) { container._panelOrder = sortingOrder; if (Application.isPlaying) SetSortingOrder(sortingOrder, true); else EMRenderSupport.orderChanged = true; } container.fairyBatching = fairyBatching; } if (_ui != null) { if (fitScreen == FitScreen.None) _ui.position = position; if (scale.x != 0 && scale.y != 0) _ui.scale = scale; _ui.rotationX = rotation.x; _ui.rotationY = rotation.y; _ui.rotation = rotation.z; } if (fitScreen == FitScreen.None) uiBounds.position = position; screenSizeVer = 0;//force HandleScreenSizeChanged be called if (fitScreenChanged && this.fitScreen == FitScreen.None) { if (_ui != null) _ui.SetSize(_ui.sourceWidth, _ui.sourceHeight); uiBounds.size = cachedUISize; } } public void MoveUI(Vector3 delta) { if (fitScreen != FitScreen.None) return; this.position += delta; if (_ui != null) _ui.position = position; uiBounds.position = position; } public Vector3 GetUIWorldPosition() { if (_ui != null) return _ui.displayObject.cachedTransform.position; else return this.container.cachedTransform.TransformPoint(uiBounds.position); } void OnDrawGizmos() { if (Application.isPlaying || this.container == null) return; Vector3 pos, size; if (_ui != null) { Gizmos.matrix = _ui.displayObject.cachedTransform.localToWorldMatrix; pos = new Vector3(_ui.width / 2, -_ui.height / 2, 0); size = new Vector3(_ui.width, _ui.height, 0); } else { Gizmos.matrix = this.container.cachedTransform.localToWorldMatrix; pos = new Vector3(uiBounds.x + uiBounds.width / 2, -uiBounds.y - uiBounds.height / 2, 0); size = new Vector3(uiBounds.width, uiBounds.height, 0); } Gizmos.color = new Color(0, 0, 0, 0); Gizmos.DrawCube(pos, size); Gizmos.color = Color.white; Gizmos.DrawWireCube(pos, size); } public int EM_sortingOrder { get { return sortingOrder; } } public void EM_BeforeUpdate() { if (container == null) CreateContainer(); if (packageName != null && componentName != null && _ui == null) CreateUI_EditMode(); if (screenSizeVer != StageCamera.screenSizeVer) HandleScreenSizeChanged(); } public void EM_Update(UpdateContext context) { DisplayObject.hideFlags = HideFlags.DontSaveInEditor; container.Update(context); if (setNativeChildrenOrder) { CacheNativeChildrenRenderers(); int cnt = _renders.Count; int sv = context.renderingOrder++; for (int i = 0; i < cnt; i++) { Renderer r = _renders[i]; if (r != null) r.sortingOrder = sv; } } } public void EM_Reload() { if (_ui != null) { _ui.Dispose(); _ui = null; } } #endregion } }