//********************************** Banshee Engine (www.banshee3d.com) **************************************************// //**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************// using bs; namespace bs.Editor { /** @addtogroup Windows * @{ */ /// /// Displays in-game viewport in the editor. /// public class GameWindow : EditorWindow { private const int HeaderHeight = 20; private static readonly Color BG_COLOR = Color.VeryDarkGray; private readonly AspectRatio[] aspectRatios = { new AspectRatio(16, 9), new AspectRatio(16, 10), new AspectRatio(5, 4), new AspectRatio(4, 3), new AspectRatio(3, 2) }; private int selectedAspectRatio = 0; private GUIRenderTexture renderTextureGUI; private GUITexture renderTextureBg; private GUILabel noCameraLabel; private Camera currentCamera; private bool onDemandDisabled; /// /// Opens the game window. /// [MenuItem("Windows/Game", ButtonModifier.CtrlAlt, ButtonCode.G, 6000)] private static void OpenGameWindow() { OpenWindow(); } /// /// Starts execution of the game in the game window. /// [MenuItem("Tools/Play", 9300)] [ToolbarItem("Play", ToolbarIcon.Play, "Play", 1800, true)] private static void Play() { GameWindow gameWindow = GetWindow(); SceneWindow sceneWindow = GetWindow(); PlayInEditorState state = PlayInEditor.State; if (state != PlayInEditorState.Playing) { gameWindow.Active = true; gameWindow.HasFocus = true; } else { sceneWindow.Active = true; sceneWindow.HasFocus = true; } if (state == PlayInEditorState.Paused) PlayInEditor.State = PlayInEditorState.Playing; else { if(state == PlayInEditorState.Playing) PlayInEditor.State = PlayInEditorState.Stopped; else PlayInEditor.State = PlayInEditorState.Playing; } } /// /// Pauses the execution of the game on the current frame. /// [MenuItem("Tools/Pause", 9299)] [ToolbarItem("Pause", ToolbarIcon.Pause, "Pause", 1799)] private static void Pause() { if (PlayInEditor.State == PlayInEditorState.Paused) PlayInEditor.State = PlayInEditorState.Playing; else PlayInEditor.State = PlayInEditorState.Paused; } /// /// Moves the execution of the game by one frame forward. /// [MenuItem("Tools/Step", 9298)] [ToolbarItem("Step", ToolbarIcon.Step, "Frame step", 1798)] private static void Step() { PlayInEditor.FrameStep(); } /// protected override LocString GetDisplayName() { return new LocEdString("Game"); } private void OnInitialize() { GUILayoutY mainLayout = GUI.AddLayoutY(); string[] aspectRatioTitles = new string[aspectRatios.Length + 1]; aspectRatioTitles[0] = "Free"; for (int i = 0; i < aspectRatios.Length; i++) aspectRatioTitles[i + 1] = aspectRatios[i].width + ":" + aspectRatios[i].height; GUIListBoxField aspectField = new GUIListBoxField(aspectRatioTitles, new LocEdString("Aspect ratio")); aspectField.OnSelectionChanged += OnAspectRatioChanged; GUILayoutY buttonLayoutVert = mainLayout.AddLayoutY(); GUILayoutX buttonLayout = buttonLayoutVert.AddLayoutX(); buttonLayout.AddElement(aspectField); buttonLayout.AddFlexibleSpace(); buttonLayoutVert.AddFlexibleSpace(); renderTextureGUI = new GUIRenderTexture(null); renderTextureBg = new GUITexture(Builtin.WhiteTexture); renderTextureBg.SetTint(BG_COLOR); noCameraLabel = new GUILabel(new LocEdString("(No main camera in scene)")); GUIPanel rtPanel = mainLayout.AddPanel(); rtPanel.AddElement(renderTextureGUI); GUIPanel bgPanel = rtPanel.AddPanel(1); bgPanel.AddElement(renderTextureBg); GUILayoutY alignLayoutY = rtPanel.AddLayoutY(); alignLayoutY.AddFlexibleSpace(); GUILayoutX alignLayoutX = alignLayoutY.AddLayoutX(); alignLayoutX.AddFlexibleSpace(); alignLayoutX.AddElement(noCameraLabel); alignLayoutX.AddFlexibleSpace(); alignLayoutY.AddFlexibleSpace(); UpdateRenderTexture(Width, Height); currentCamera = Scene.Camera; bool hasMainCamera = currentCamera != null; renderTextureGUI.Active = hasMainCamera; noCameraLabel.Active = !hasMainCamera; ToggleOnDemandDrawing(EditorApplication.IsOnDemandDrawingEnabled()); NotifyNeedsRedraw(); } private void OnEditorUpdate() { Camera camera = Scene.Camera; if (camera != currentCamera) { if (currentCamera != null) currentCamera.Flags &= ~CameraFlag.OnDemand; if(!onDemandDisabled) camera.Flags |= CameraFlag.OnDemand; currentCamera = camera; } bool hasMainCamera = camera != null; renderTextureGUI.Active = hasMainCamera; noCameraLabel.Active = !hasMainCamera; } private void OnDestroy() { if (currentCamera != null) currentCamera.Flags &= ~CameraFlag.OnDemand; EditorApplication.MainRenderTarget = null; } /// /// Notifies the system that the 3D viewport should be redrawn. /// internal void NotifyNeedsRedraw() { Camera camera = Scene.Camera; camera?.NotifyNeedsRedraw(); } /// /// Enables or disables on-demand drawing. When enabled the 3D viewport will only be redrawn when /// is called. If disabled the viewport will be redrawn every frame. /// Normally you always want to keep this disabled unless you know the viewport will require updates /// every frame (e.g. when a game is running, or when previewing animations). /// /// True to enable on-demand drawing, false otherwise. internal void ToggleOnDemandDrawing(bool enabled) { onDemandDisabled = !enabled; if (currentCamera == null) return; if (enabled) currentCamera.Flags |= CameraFlag.OnDemand; else currentCamera.Flags &= ~CameraFlag.OnDemand; } /// /// Creates or rebuilds the main render texture. Should be called at least once before using the /// game window. Should be called whenever the window is resized. /// /// Width of the scene render target, in pixels. /// Height of the scene render target, in pixels. private void UpdateRenderTexture(int width, int height) { height = height - HeaderHeight; int rtWidth = MathEx.Max(20, width); int rtHeight = MathEx.Max(20, height); if (selectedAspectRatio != 0) // 0 is free aspect { AspectRatio aspectRatio = aspectRatios[selectedAspectRatio - 1]; int visibleAreaHeight = rtHeight; float aspectInv = aspectRatio.height/(float)aspectRatio.width; rtHeight = MathEx.RoundToInt(rtWidth*aspectInv); if (rtHeight > visibleAreaHeight) { rtHeight = visibleAreaHeight; float aspect = aspectRatio.width / (float)aspectRatio.height; rtWidth = MathEx.RoundToInt(rtHeight * aspect); } } RenderTexture renderTexture = new RenderTexture(PixelFormat.RGBA8, rtWidth, rtHeight) { Priority = 1}; EditorApplication.MainRenderTarget = renderTexture; renderTextureGUI.RenderTexture = renderTexture; int offsetX = (width - rtWidth)/2; int offsetY = (height - rtHeight)/2; Rect2I rtBounds = new Rect2I(offsetX, offsetY, rtWidth, rtHeight); renderTextureGUI.Bounds = rtBounds; Rect2I bgBounds = new Rect2I(0, 0, width, height); renderTextureBg.Bounds = bgBounds; NotifyNeedsRedraw(); } /// /// Triggered when the user selects a new aspect ratio from the drop down box. /// /// Index of the aspect ratio the user selected. private void OnAspectRatioChanged(int idx) { selectedAspectRatio = idx; UpdateRenderTexture(Width, Height); } /// protected override void WindowResized(int width, int height) { UpdateRenderTexture(width, height); base.WindowResized(width, height); } /// /// Camera aspect ratio as numerator and denominator. /// struct AspectRatio { /// /// Creates a new object that holds the aspect ratio. /// /// Numerator of the aspect ratio. /// Denominator of the aspect ratio. public AspectRatio(int width, int height) { this.width = width; this.height = height; } public int width; public int height; } } /** @} */ }