using System; using Framework.Geometry; using UnityEditor; using UnityEngine; namespace Framework.Actors.Platformer { /// Allows the user to edit the abilities in the inspector window [CustomEditor(typeof(Abilities))] public class AbilitiesEditor : Editor { /// Height of the diagram to illustrate the jump trajectory private const float DiagramHeight = 150.0f; /// Name of the buttons for selecting to number of possible jumps private static readonly string[] JumpCountNames = {"None", "1", "2", "3", "4"}; /// Maximum running speed the developer can configure private const float MaximumRunSpeed = 30.0f; /// Maximum jump height the developer can configure private const float MaximumJumpHeight = 10.0f; #region struct GuiClippingScope /// Scope that enter a GUI clipping area for its duration private struct GuiClippingScope : IDisposable { /// Initialized a new GUI clipping scope /// Clipping rectangle the scope will set public GuiClippingScope(Rect clippingRectangle) { GUI.BeginClip(clippingRectangle); } /// Leaves the clipping rectangle again public void Dispose() { GUI.EndClip(); } } #endregion // struct GuiClippingScope #region struct GlMatrixStackScope /// Pushes the current GL matrix onto the stack for its duration private struct GlMatrixStackScope : IDisposable { /// Pushes the current GL matrix onto the stack /// Not used public GlMatrixStackScope(int dummy) { GL.PushMatrix(); } /// Pops the GL matrix from the stack again public void Dispose() { GL.PopMatrix(); } } #endregion // struct GlMatrixStackScope #region struct GlCommandScope /// Begins a GL command and ends it after the scope expires private struct GlCommandScope : IDisposable { /// Begins a GL command for the specified primitive topology /// Primitive topology that will be drawn public GlCommandScope(int mode) { GL.Begin(mode); } /// Ends the GL command again public void Dispose() { GL.End(); } } #endregion // struct GlCommandScope private bool areVelocityControlsExpanded; /// Called when the editor is added to the inspector panel protected void OnEnable() { Shader shader = Shader.Find("Hidden/Internal-Colored"); this.material = new Material(shader); } /// Called when the editor is removed from the inspector panel protected void OnDisable() { DestroyImmediate(this.material); } /// Called when the inspector GUI is layouted or rendered public override void OnInspectorGUI() { Abilities abilities = target as Abilities; if(abilities == null) { Debug.LogError( "Abilities editor shown for a component that was not an instance of " + "the abilities class." ); return; } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel("Jump Count"); abilities.JumpCount = GUILayout.SelectionGrid(abilities.JumpCount, JumpCountNames, 5); } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel("Air Control"); abilities.AirControlFactor = GUILayout.HorizontalSlider( abilities.AirControlFactor, 0.0f, 1.0f ); } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel("Jump Height"); abilities.JumpHeight = GUILayout.VerticalSlider( abilities.JumpHeight, MaximumJumpHeight, 0.0f, GUILayout.MaxHeight(DiagramHeight) ); drawJumpCurves(abilities); } if(abilities.SquatMovement != SquatMovementMode.Prohibit) { using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel("Sneak/Crawl Speed"); abilities.SneakingSpeed = GUILayout.HorizontalSlider( abilities.SneakingSpeed, 0.0f, MaximumRunSpeed ); } } EditorGUILayout.MinMaxSlider( "Walk+Run Speed", ref abilities.WalkingSpeed, ref abilities.RunningSpeed, 0.0f, MaximumRunSpeed ); using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel("Dash Speed"); abilities.DashingSpeed = GUILayout.HorizontalSlider( abilities.DashingSpeed, 0.0f, MaximumRunSpeed ); } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel("Speed for Floor Slide"); abilities.SpeedForFloorSlide= GUILayout.HorizontalSlider( abilities.SpeedForFloorSlide, 0.0f, MaximumRunSpeed ); } this.areVelocityControlsExpanded = EditorGUILayout.Foldout( this.areVelocityControlsExpanded, "Speed in Numbers", toggleOnLabelClick: true ); if(this.areVelocityControlsExpanded) { using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField(" ", GUILayout.Width(10.0f)); EditorGUILayout.PrefixLabel("Walking Speed"); string walkingSpeedString = GUILayout.TextField( abilities.WalkingSpeed.ToString("0.0"), 6 ); float.TryParse(walkingSpeedString, out abilities.WalkingSpeed); } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField(" ", GUILayout.Width(10.0f)); EditorGUILayout.PrefixLabel("Running Speed"); string runningSpeedString = GUILayout.TextField( abilities.RunningSpeed.ToString("0.0"), 6 ); float.TryParse(runningSpeedString, out abilities.RunningSpeed); } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField(" ", GUILayout.Width(10.0f)); EditorGUILayout.PrefixLabel("Dash Speed"); string dashSpeedString = GUILayout.TextField( abilities.DashingSpeed.ToString("0.0"), 6 ); float.TryParse(dashSpeedString, out abilities.DashingSpeed); } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField(" ", GUILayout.Width(10.0f)); EditorGUILayout.PrefixLabel("Sneak Speed"); string sneakingSpeedString = GUILayout.TextField( abilities.SneakingSpeed.ToString("0.0"), 6 ); float.TryParse(sneakingSpeedString, out abilities.SneakingSpeed); } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField(" ", GUILayout.Width(10.0f)); EditorGUILayout.PrefixLabel("Speed for Floor Slide"); string floorSlideSpeedString = GUILayout.TextField( abilities.SpeedForFloorSlide.ToString("0.0"), 6 ); float.TryParse(floorSlideSpeedString, out abilities.SpeedForFloorSlide); } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField(" ", GUILayout.Width(10.0f)); EditorGUILayout.PrefixLabel("Maximum Jump Height"); string jumpHeightdString = GUILayout.TextField( abilities.JumpHeight.ToString("0.0"), 6 ); float.TryParse(jumpHeightdString, out abilities.JumpHeight); } } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel("Can Sprint"); abilities.CanSprint = EditorGUILayout.Toggle(abilities.CanSprint); } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel("Can Dash"); abilities.CanDash = EditorGUILayout.Toggle(abilities.CanDash); } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel("Can Dash In-Air"); abilities.CanAirDash = EditorGUILayout.Toggle(abilities.CanAirDash); } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel("Can Wall-Jump"); abilities.CanWallJump = EditorGUILayout.Toggle(abilities.CanWallJump); } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel("Slide down Walls"); abilities.CanWallSlide = EditorGUILayout.Toggle(abilities.CanWallSlide); } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel("Wall Hang Mode"); abilities.WallHanging = (LedgeHangMode)EditorGUILayout.EnumPopup(abilities.WallHanging); } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel("Pole Hang Mode"); abilities.PoleHanging = (LedgeHangMode)EditorGUILayout.EnumPopup(abilities.PoleHanging); } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel("Can Squat"); abilities.CanSquat = EditorGUILayout.Toggle(abilities.CanSquat); } using(new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PrefixLabel("Squat Movement Mode"); abilities.SquatMovement = (SquatMovementMode)EditorGUILayout.EnumPopup( abilities.SquatMovement ); } //base.OnInspectorGUI(); } /// Draws the jump curves currently set up by the developer /// Abilities the editor is letting the developer edit private void drawJumpCurves(Abilities abilities) { Rect controlRectangle = GUILayoutUtility.GetRect(10, 1000, DiagramHeight, DiagramHeight); if(Event.current.type == EventType.Repaint) { using(new GuiClippingScope(controlRectangle)) { // Adjust control rectangle for inside clipping scope controlRectangle = new Rect(0.0f, 0.0f, controlRectangle.width, controlRectangle.height); // World coordinate area that will be displayed within the illustration Rect viewRectangle; { float maximumJumpOffVelocity = Trajectory.GetJumpOffVelocity(MaximumJumpHeight); viewRectangle = new Rect( 0.0f, MaximumJumpHeight, Trajectory.GetTimeInFlight(maximumJumpOffVelocity) * MaximumRunSpeed, -MaximumJumpHeight // flip in Y (control coordinates are Y-down) ); } GL.PushMatrix(); this.material.SetPass(0); // Fill the background glFillRectangle(controlRectangle, inspectorControlBackgroundColor); // Draw the jump trajectory for a jump from walking speed glDrawJumpTrajectory( controlRectangle, viewRectangle, abilities.WalkingSpeed, abilities.JumpHeight, Color.cyan ); // Draw the jump trajectory for a jump from running speed glDrawJumpTrajectory( controlRectangle, viewRectangle, abilities.RunningSpeed, abilities.JumpHeight, Color.green ); GL.PopMatrix(); } // GUI clipping rectangle } } /// /// Draws the jump trajectory for a given horizontal velocity and jump height /// /// Pixel coordinates covered by the control /// Rectangle of the world coordinates in the view /// Horizontal velocity of the jumping actor /// Height of the jump the actor will perform /// Color of the jump trajectory line private static void glDrawJumpTrajectory( Rect controlRectangle, Rect viewRectangle, float horizontalVelocity, float jumpHeight, Color color ) { using(new GlCommandScope(GL.LINE_STRIP)) { GL.Color(color); // Jump-off velocity required to reach the desired height float initialVelocity = Trajectory.GetJumpOffVelocity(jumpHeight); // Draw the jump trajectory curve by stepping 1 unit in world coordinates. // This will guarantee optimal resolution. int startX = Mathf.FloorToInt(controlRectangle.xMin); int endX = Mathf.CeilToInt(controlRectangle.xMax); for(int pixelX = startX; pixelX < endX; ++pixelX) { // Calculate the time at which the actor would reach this X coordinate float time; { float windowX = (pixelX - controlRectangle.x) / controlRectangle.width; float worldX = windowX * viewRectangle.width + viewRectangle.x; time = worldX / horizontalVelocity; } // Calculate at which height in the parabola trajectory the actor should be now float y = Trajectory.GetParabolaY(initialVelocity, time); // Convert the world Y coordinate into pixel Y coordinate float pixelY; { float windowY = (y - viewRectangle.y) / viewRectangle.height; pixelY = windowY * controlRectangle.height + controlRectangle.y; } GL.Vertex3((float)pixelX, pixelY, 0.0f); // If the actor has fallen back to the floor, stop drawing if(y < 0.0f) { break; } } } } /// Issues the GL commands to fill a rectangle /// Coordinates of the filled rectangle /// Color the rectangle will be filled with private static void glFillRectangle(Rect rectangle, Color color) { using(new GlCommandScope(GL.QUADS)) { GL.Color(color); GL.Vertex3(rectangle.xMin, rectangle.yMin, 0.0f); GL.Vertex3(rectangle.xMax, rectangle.yMin, 0.0f); GL.Vertex3(rectangle.xMax, rectangle.yMax, 0.0f); GL.Vertex3(rectangle.xMin, rectangle.yMax, 0.0f); } } /// Issues the GL commands to draw a rectangle /// Coordinates of the rectangle /// Color of the rectangle's outline private static void glDrawRectangle(Rect rectangle, Color color) { using(new GlCommandScope(GL.LINE_STRIP)) { GL.Color(color); GL.Vertex3(rectangle.xMin, rectangle.yMin, 0.0f); GL.Vertex3(rectangle.xMax, rectangle.yMin, 0.0f); GL.Vertex3(rectangle.xMax, rectangle.yMax, 0.0f); GL.Vertex3(rectangle.xMin, rectangle.yMax, 0.0f); } } /// Background color the inspector panel itself private static Color inspectorPanelBackgroundColor { get { if(EditorGUIUtility.isProSkin) { return (Color)new Color32(56, 56, 56, 255); } else { return (Color)new Color32(224, 224, 224, 255); } } } /// Background color of controls in the inspector panel private static Color inspectorControlBackgroundColor { get { if(EditorGUIUtility.isProSkin) { return (Color)new Color32(76, 76, 76, 255); } else { return (Color)new Color32(224, 224, 224, 255); } } } /// Material that is used to draw in the inspector diagram private Material material; } } // namespace Framework.Actors.Platformer