当前位置:   article > 正文

Unity編輯器案列_customeditor没有及时刷新

customeditor没有及时刷新

【Unity】编辑器小教程

目录(?)[+]

  1. 写在前面
  2. 场景一
  3. 场景二
  4. 场景三
  5. 场景四
  6. 场景五
  7. 场景六
  8. 场景七
  9. 场景八
  10. 场景九

写在前面

Unity最强大的地方之一是它扩展性非常强的编辑器。Unite Europe 2016上有一个视频专门讲编辑器编程的:

这里大概记录一下里面的关键点。

场景一

关注点

  • 绘制重要区域,Gizmos.DrawXXX
  • OnDrawGizmos和OnDrawGizmosSelected回调函数
  • 点击Gizmos按钮就可以在Game视图也看到线框了

 

这里写图片描述

 

  1. // OnDrawGizmos()会在编辑器的Scene视图刷新的时候被调用
  2. // 我们可以在这里绘制一些用于Debug的数据
  3. void OnDrawGizmos()
  4. {
  5. Gizmos.color = new Color( 1f, 0f, 0f, 1f );
  6. Gizmos.DrawWireCube( transform.position + BoxCollider.center, BoxCollider.size );
  7. Gizmos.color = new Color( 1f, 0f, 0f, 0.3f );
  8. Gizmos.DrawCube( transform.position + BoxCollider.center, BoxCollider.size );
  9. }
  10. // OnDrawGizmosSelect()类似于OnDrawGizmos(),它会在当该组件所属的物体被选中时被调用
  11. void OnDrawGizmosSelected()
  12. {
  13. Gizmos.color = new Color( 1f, 1f, 0f, 1f );
  14. Gizmos.DrawWireCube( transform.position + BoxCollider.center, BoxCollider.size );
  15. Gizmos.color = new Color( 1f, 1f, 0f, 0.3f );
  16. Gizmos.DrawCube( transform.position + BoxCollider.center, BoxCollider.size );
  17. }
  •  

场景二

关注点

  • 组织面板上的参数,添加滑动条、Header、空白等

 

这里写图片描述

 

  1. [Space( 10 )]
  2. public float MaximumHeight;
  3. public float MinimumHeight;
  4. [Header( "Safe Frame" )]
  5. [Range( 0f, 1f )]
  6. public float SafeFrameTop;
  7. [Range( 0f, 1f )]
  8. public float SafeFrameBottom;

注意到上面面板的最小面有个Camera Height,调节它可以改变摄像机的高度。这个改变是可以发生在编辑器模式下的,而且也不需要脚本添加ExecuteInEditor。这是通过实现自定义的Editor脚本来实现的:

  1. using UnityEngine;
  2. using UnityEditor;
  3. using System.Collections;
  4. // 我们可以通过为一个类定义它的Editor类型的[CustomEditor]来自定义该类的绘制界面
  5. // 这需要把这个文件放在Editor目录下
  6. [CustomEditor( typeof( GameCamera ) )]
  7. public class GameCameraEditor : Editor
  8. {
  9. GameCamera m_Target;
  10. // 重载OnInspectorGUI()来绘制自己的编辑器
  11. public override void OnInspectorGUI()
  12. {
  13. // target可以让我们得到当前绘制的Component对象
  14. m_Target = (GameCamera)target;
  15. // DrawDefaultInspector告诉Unity按照默认的方式绘制面板,这种方法在我们仅仅想要自定义某几个属性的时候会很有用
  16. DrawDefaultInspector();
  17. DrawCameraHeightPreviewSlider();
  18. }
  19. void DrawCameraHeightPreviewSlider()
  20. {
  21. GUILayout.Space( 10 );
  22. Vector3 cameraPosition = m_Target.transform.position;
  23. cameraPosition.y = EditorGUILayout.Slider( "Camera Height", cameraPosition.y, m_Target.MinimumHeight, m_Target.MaximumHeight );
  24. if( cameraPosition.y != m_Target.transform.position.y )
  25. {
  26. // 改变状态前,使用该方法来记录操作,以便之后Undo
  27. Undo.RecordObject( m_Target, "Change Camera Height" );
  28. m_Target.transform.position = cameraPosition;
  29. }
  30. }
  31. }
  •  

场景三

关注点

  • 自定义绘制List对象
  • 使用serializedObject来修改参数的话Unity会自动有各种帮助函数,例如自动添加Undo功能
  • 如果直接修改参数的话,需要使用EditorUtility.SetDirty来告诉Unity需要保存数据
  • BeginChangeCheck()和EndChangeCheck()会检测它们之间的GUI有没有被修改,如果修改了的话可以据此修改参数
  • Undo.RecordObject可以为下一步修改添加Undo/Redo
  • EditorUtility.DisplayDialog可以打开内置对话框

 

这里写图片描述

 

  1. 首先在面板上隐藏默认的List绘制方法,使用HideInInspector隐藏属性:

    1. public class PistonE03 : MonoBehaviour
    2. {
    3. public float Speed;
    4. public Vector3 AddForceWhenHittingPlayer;
    5. //We are hiding this in the inspector because we want to draw our own custom
    6. //inspector for it.
    7. [HideInInspector]
    8. public List<PistonState> States = new List<PistonState>();
    9. ......
  2. 为了让PistonState可以显示在面板上,需要序列化PistonState:

    1. //[System.Serializable] tells unity to serialize this class if
    2. //it's used in a public array or as a public variable in a component
    3. [System.Serializable]
    4. public class PistonState
    5. {
    6. public string Name;
    7. public Vector3 Position;
    8. }
  3. 实现自定义的绘制方程:

    1. [CustomEditor( typeof( PistonE03 ) )]
    2. public class PistonE03Editor : Editor
    3. {
    4. PistonE03 m_Target;
    5. public override void OnInspectorGUI()
    6. {
    7. m_Target = (PistonE03)target;
    8. DrawDefaultInspector();
    9. DrawStatesInspector();
    10. }
    11. //Draw a beautiful and useful custom inspector for our states array
    12. void DrawStatesInspector()
    13. {
    14. GUILayout.Space( 5 );
    15. GUILayout.Label( "States", EditorStyles.boldLabel );
    16. for( int i = 0; i < m_Target.States.Count; ++i )
    17. {
    18. DrawState( i );
    19. }
    20. DrawAddStateButton();
    21. }

    DrawDefaultInspector:先绘制默认的,DrawStatesInspector:自定义绘制面板函数。
    
  4. DrawState函数:

    1. void DrawState( int index )
    2. {
    3. if( index < 0 || index >= m_Target.States.Count )
    4. {
    5. return;
    6. }
    7. // 在我们的serializedObject中找到States变量
    8. // serializedObject允许我们方便地访问和修改参数,Unity会提供一系列帮助函数。例如,我们可以通过serializedObject来修改组件值,而不是直接修改,Unity会自动创建Undo和Redo功能
    9. SerializedProperty listIterator = serializedObject.FindProperty( "States" );
    10. GUILayout.BeginHorizontal();
    11. {
    12. // 如果是在实例化的prefab上修改参数,我们可以模仿Unity默认的途径来让修改过的而且未被Apply的值显示成粗体
    13. if( listIterator.isInstantiatedPrefab == true )
    14. {
    15. //The SetBoldDefaultFont functionality is usually hidden from us but we can use some tricks to
    16. //access the method anyways. See the implementation of our own EditorGUIHelper.SetBoldDefaultFont
    17. //for more info
    18. EditorGUIHelper.SetBoldDefaultFont( listIterator.GetArrayElementAtIndex( index ).prefabOverride );
    19. }
    20. GUILayout.Label( "Name", EditorStyles.label, GUILayout.Width( 50 ) );
    21. // BeginChangeCheck()和EndChangeCheck()会检测它们之间的GUI有没有被修改
    22. EditorGUI.BeginChangeCheck();
    23. string newName = GUILayout.TextField( m_Target.States[ index ].Name, GUILayout.Width( 120 ) );
    24. Vector3 newPosition = EditorGUILayout.Vector3Field( "", m_Target.States[ index ].Position );
    25. // 如果修改了的话EndChangeCheck()就会返回true,此时我们就可以进行一些操作例如存储变化的数值
    26. if( EditorGUI.EndChangeCheck() )
    27. {
    28. //Create an Undo/Redo step for this modification
    29. Undo.RecordObject( m_Target, "Modify State" );
    30. m_Target.States[ index ].Name = newName;
    31. m_Target.States[ index ].Position = newPosition;
    32. // 如果我们直接修改属性,而没有通过serializedObject,那么Unity并不会保存这些数据,Unity只会保存那些标识为dirty的属性
    33. EditorUtility.SetDirty( m_Target );
    34. }
    35. EditorGUIHelper.SetBoldDefaultFont( false );
    36. if( GUILayout.Button( "Remove" ) )
    37. {
    38. EditorApplication.Beep();
    39. // 可以很方便的显示一个包含特定按钮的对话框,例如是否同意删除
    40. if( EditorUtility.DisplayDialog( "Really?", "Do you really want to remove the state '" + m_Target.States[ index ].Name + "'?", "Yes", "No" ) == true )
    41. {
    42. Undo.RecordObject( m_Target, "Delete State" );
    43. m_Target.States.RemoveAt( index );
    44. EditorUtility.SetDirty( m_Target );
    45. }
    46. }
    47. }
    48. GUILayout.EndHorizontal();

场景四

关注点

  • 可排序的数组面板,通过使用ReorderableList来实现的,以及它的各个回调函数

 

这里写图片描述

 

  1. using UnityEngine;
  2. using UnityEditor;
  3. // UnityEditorInternal是Unity内部使用、还未开放给用用户的一些库,可能有一些很有意思的类,例如ReorderableList,但注意可能会随着新版本发生变化
  4. using UnityEditorInternal;
  5. using System.Collections;
  6. // CanEditMultipleObjects告诉Unity,当我们选择同一种类型的多个组件时,我们自定义的面板是可以支持同时修改所有选中的组件的
  7. // 如果我们在修改参数时使用的是serializedObject,那么这个功能Unity会自动完成的
  8. // 但如果我们是直接使用"target"来访问和修改参数的话,这个变量只能访问到选中的第一个组件
  9. // 此时我们可以使用"targets"来得到所有选中的相同组件
  10. [CanEditMultipleObjects]
  11. [CustomEditor( typeof( PistonE04Pattern ) )]
  12. public class PistonE04PatternEditor : Editor
  13. {
  14. // UnityEditorInternal中提供了一种可排序的列表面板显示类
  15. ReorderableList m_List;
  16. PistonE03 m_Piston;
  17. // OnEnable会在自定义面板被打开的时候调用,例如当选中一个包含了PistonE04Pattern的gameobject时
  18. void OnEnable()
  19. {
  20. if( target == null )
  21. {
  22. return;
  23. }
  24. FindPistonComponent();
  25. CreateReorderableList();
  26. SetupReoirderableListHeaderDrawer();
  27. SetupReorderableListElementDrawer();
  28. SetupReorderableListOnAddDropdownCallback();
  29. }
  30. void FindPistonComponent()
  31. {
  32. m_Piston = ( target as PistonE04Pattern ).GetComponent<PistonE03>();
  33. }
  34. void CreateReorderableList()
  35. {
  36. // ReorderableList是一个非常棒的查看数组类型变量的实现类。它位于UnityEditorInternal中,这意味着Unity并没有觉得该类足够好到可以开放给公众
  37. // 更多关于ReorderableLists的内容可参考:
  38. // http://va.lent.in/unity-make-your-lists-functional-with-reorderablelist/
  39. m_List = new ReorderableList(
  40. serializedObject,
  41. serializedObject.FindProperty( "Pattern" ),
  42. true, true, true, true );
  43. }
  44. void SetupReoirderableListHeaderDrawer()
  45. {
  46. // ReorderableList有一系列回调函数来让我们重载绘制这些数组
  47. // 这里我们使用drawHeaderCallback来绘制表格的头headers
  48. // 每个回调会接受一个Rect变量,它包含了该元素绘制的位置
  49. // 因此我们可以使用这个变量来决定我们把当前的元素绘制在哪里
  50. m_List.drawHeaderCallback =
  51. ( Rect rect ) =>
  52. {
  53. EditorGUI.LabelField(
  54. new Rect( rect.x, rect.y, rect.width - 60, rect.height ),
  55. "State" );
  56. EditorGUI.LabelField(
  57. new Rect( rect.x + rect.width - 60, rect.y, 60, rect.height ),
  58. "Delay" );
  59. };
  60. }
  61. void SetupReorderableListElementDrawer()
  62. {
  63. // drawElementCallback会定义列表中的每个元素是如何被绘制的
  64. // 同样,保证我们绘制的元素是相对于Rect参数绘制的
  65. m_List.drawElementCallback =
  66. ( Rect rect, int index, bool isActive, bool isFocused ) =>
  67. {
  68. var element = m_List.serializedProperty.GetArrayElementAtIndex( index );
  69. rect.y += 2;
  70. float delayWidth = 60;
  71. float nameWidth = rect.width - delayWidth;
  72. EditorGUI.PropertyField(
  73. new Rect( rect.x, rect.y, nameWidth - 5, EditorGUIUtility.singleLineHeight ),
  74. element.FindPropertyRelative( "Name" ), GUIContent.none );
  75. EditorGUI.PropertyField(
  76. new Rect( rect.x + nameWidth, rect.y, delayWidth, EditorGUIUtility.singleLineHeight ),
  77. element.FindPropertyRelative( "DelayAfterwards" ), GUIContent.none );
  78. };
  79. }
  80. void SetupReorderableListOnAddDropdownCallback()
  81. {
  82. // onAddDropdownCallback定义当我们点击列表下面的[+]按钮时发生的事件
  83. // 在本例里,我们想要显示一个下拉菜单来给出预定义的一些States
  84. m_List.onAddDropdownCallback =
  85. ( Rect buttonRect, ReorderableList l ) =>
  86. {
  87. if( m_Piston.States == null || m_Piston.States.Count == 0 )
  88. {
  89. EditorApplication.Beep();
  90. EditorUtility.DisplayDialog( "Error", "You don't have any states defined in the PistonE03 component", "Ok" );
  91. return;
  92. }
  93. var menu = new GenericMenu();
  94. foreach( PistonState state in m_Piston.States )
  95. {
  96. menu.AddItem( new GUIContent( state.Name ),
  97. false,
  98. OnReorderableListAddDropdownClick,
  99. state );
  100. }
  101. menu.ShowAsContext();
  102. };
  103. }
  104. // 这个回调函数会在用户选择了[+]下拉菜单中的某一项后调用
  105. void OnReorderableListAddDropdownClick( object target )
  106. {
  107. PistonState state = (PistonState)target;
  108. int index = m_List.serializedProperty.arraySize;
  109. m_List.serializedProperty.arraySize++;
  110. m_List.index = index;
  111. SerializedProperty element = m_List.serializedProperty.GetArrayElementAtIndex( index );
  112. element.FindPropertyRelative( "Name" ).stringValue = state.Name;
  113. element.FindPropertyRelative( "DelayAfterwards" ).floatValue = 0f;
  114. serializedObject.ApplyModifiedProperties();
  115. }
  116. public override void OnInspectorGUI()
  117. {
  118. GUILayout.Space( 5 );
  119. EditorGUILayout.PropertyField( serializedObject.FindProperty( "DelayPatternAtBeginning" ) );
  120. serializedObject.ApplyModifiedProperties();
  121. serializedObject.Update();
  122. m_List.DoLayoutList();
  123. serializedObject.ApplyModifiedProperties();
  124. }
  125. }

场景五

关注点

  • 实现了一个可以在编辑器状态下预览效果的编辑器窗口

 

这里写图片描述

 

  1. using UnityEngine;
  2. // 要实现自定义窗口需要包含进来UnityEditor
  3. using UnityEditor;
  4. using System.Collections;
  5. // EditorWindow是另一个非常有用的Editor类。我们可以靠它来定义自己的窗口
  6. public class PreviewPlaybackWindow : EditorWindow
  7. {
  8. // MenuItem可以让我们在菜单栏中打开这个窗口
  9. [MenuItem( "Window/Preview Playback Window" )]
  10. static void OpenPreviewPlaybackWindow()
  11. {
  12. EditorWindow.GetWindow<PreviewPlaybackWindow>( false, "Playback" );
  13. // 另一个有用的写法是下面这样
  14. // 可以让我们访问到窗口的属性,例如定义最小尺寸等
  15. //EditorWindow window = EditorWindow.GetWindow<PreviewPlaybackWindow>( false, "Playback" );
  16. //window.minSize = new Vector2(100.0f, 100.0f);
  17. }
  18. float m_PlaybackModifier;
  19. float m_LastTime;
  20. void OnEnable()
  21. {
  22. // Update函数会每秒调用30次来刷新编辑器界面
  23. // 我们可以据此来注册自己的编辑器Update函数
  24. EditorApplication.update -= OnUpdate;
  25. EditorApplication.update += OnUpdate;
  26. }
  27. void OnDisable()
  28. {
  29. EditorApplication.update -= OnUpdate;
  30. }
  31. void OnUpdate()
  32. {
  33. if( m_PlaybackModifier != 0f )
  34. {
  35. // PreviewTime是自定义的一个类:
  36. //public class PreviewTime
  37. //{
  38. // public static float Time
  39. // {
  40. // get
  41. // {
  42. // if( Application.isPlaying == true )
  43. // {
  44. // return UnityEngine.Time.timeSinceLevelLoad;
  45. // }
  46. // // EditorPrefsle类似于PlayerPrefs但只在编辑器状态下工作
  47. // // 我们可以据此来存储变量,基本我们关闭了编辑器该变量也可以长久保存
  48. // return EditorPrefs.GetFloat( "PreviewTime", 0f );
  49. // }
  50. // set
  51. // {
  52. // EditorPrefs.SetFloat( "PreviewTime", value );
  53. // }
  54. // }
  55. //}
  56. // m_PlaybackModifier是用于控制预览播放速率的变量
  57. // 当它不为0的时候,说明需要刷新界面,更新时间
  58. PreviewTime.Time += ( Time.realtimeSinceStartup - m_LastTime ) * m_PlaybackModifier;
  59. // 当预览时间改变时,我们需要确保重绘这个窗口以便我们可以立即看到它的更新
  60. // 而Unity只会在它认为该窗口需要重绘时(例如我们移动了窗口)才会重绘
  61. // 因此我们可以调用Repaint函数来强制马上重绘
  62. Repaint();
  63. // 由于预览时间发生了变化,我们也希望可以立刻重绘Scene视图的界面
  64. SceneView.RepaintAll();
  65. }
  66. m_LastTime = Time.realtimeSinceStartup;
  67. }
  68. void OnGUI()
  69. {
  70. // 绘制各个按钮来控制预览时间
  71. float seconds = Mathf.Floor( PreviewTime.Time % 60 );
  72. float minutes = Mathf.Floor( PreviewTime.Time / 60 );
  73. GUILayout.Label( "Preview Time: " + minutes + ":" + seconds.ToString( "00" ) );
  74. GUILayout.Label( "Playback Speed: " + m_PlaybackModifier );
  75. GUILayout.BeginHorizontal();
  76. {
  77. if( GUILayout.Button( "|<", GUILayout.Height( 30 ) ) )
  78. {
  79. PreviewTime.Time = 0f;
  80. SceneView.RepaintAll();
  81. }
  82. if( GUILayout.Button( "<<", GUILayout.Height( 30 ) ) )
  83. {
  84. m_PlaybackModifier = -5f;
  85. }
  86. if( GUILayout.Button( "<", GUILayout.Height( 30 ) ) )
  87. {
  88. m_PlaybackModifier = -1f;
  89. }
  90. if( GUILayout.Button( "||", GUILayout.Height( 30 ) ) )
  91. {
  92. m_PlaybackModifier = 0f;
  93. }
  94. if( GUILayout.Button( ">", GUILayout.Height( 30 ) ) )
  95. {
  96. m_PlaybackModifier = 1f;
  97. }
  98. if( GUILayout.Button( ">>", GUILayout.Height( 30 ) ) )
  99. {
  100. m_PlaybackModifier = 5f;
  101. }
  102. }
  103. GUILayout.EndHorizontal();
  104. }
  105. }
  • 为了在编辑器状态下可以查看到cube的运动,我们还需要实现OnDrawGizmos来绘制一些线框表示运动。原理就是使用PreviewTime.Time来控制运动。

场景六

关注点

  • 在Scene视图中,鼠标的位置绘制特定的Handle

 

这里写图片描述

 

  1. using UnityEngine;
  2. using UnityEditor;
  3. using System.Collections;
  4. // [InitializeOnLoad]可以确保这个类的构造器在编辑器加载时就被调用
  5. [InitializeOnLoad]
  6. public class LevelEditorE06CubeHandle : Editor
  7. {
  8. public static Vector3 CurrentHandlePosition = Vector3.zero;
  9. public static bool IsMouseInValidArea = false;
  10. static Vector3 m_OldHandlePosition = Vector3.zero;
  11. static LevelEditorE06CubeHandle()
  12. {
  13. //The OnSceneGUI delegate is called every time the SceneView is redrawn and allows you
  14. //to draw GUI elements into the SceneView to create in editor functionality
  15. // OnSceneGUI委托在Scene视图每次被重绘时被调用
  16. // 这允许我们可以在Scene视图绘制自定义的GUI元素
  17. SceneView.onSceneGUIDelegate -= OnSceneGUI;
  18. SceneView.onSceneGUIDelegate += OnSceneGUI;
  19. }
  20. void OnDestroy()
  21. {
  22. SceneView.onSceneGUIDelegate -= OnSceneGUI;
  23. }
  24. static void OnSceneGUI( SceneView sceneView )
  25. {
  26. if( IsInCorrectLevel() == false )
  27. {
  28. return;
  29. }
  30. bool isLevelEditorEnabled = EditorPrefs.GetBool( "IsLevelEditorEnabled", true );
  31. //Ignore this. I am using this because when the scene GameE06 is opened we haven't yet defined any On/Off buttons
  32. //for the cube handles. That comes later in E07. This way we are forcing the cube handles state to On in this scene
  33. {
  34. if( UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene().name == "GameE06" )
  35. {
  36. isLevelEditorEnabled = true;
  37. }
  38. }
  39. if( isLevelEditorEnabled == false )
  40. {
  41. return;
  42. }
  43. // 更新Handle的位置
  44. UpdateHandlePosition();
  45. // 检查鼠标所在的位置是否有效
  46. UpdateIsMouseInValidArea( sceneView.position );
  47. // 检测是否需要重新绘制Handle
  48. UpdateRepaint();
  49. DrawCubeDrawPreview();
  50. }
  51. //I will use this type of function in many different classes. Basically this is useful to
  52. //be able to draw different types of the editor only when you are in the correct scene so we
  53. //can have an easy to follow progression of the editor while hoping between the different scenes
  54. static bool IsInCorrectLevel()
  55. {
  56. return UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene().name == "GameE06"
  57. || UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene().name == "GameE07"
  58. || UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene().name == "GameE08"
  59. || UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene().name == "GameE09";
  60. }
  61. static void UpdateIsMouseInValidArea( Rect sceneViewRect )
  62. {
  63. // 确保cube handle只在需要的区域内绘制
  64. // 在本例我们就是当鼠标移动到自定义的GUI上或更低的位置上时,就简单地隐藏掉handle
  65. bool isInValidArea = Event.current.mousePosition.y < sceneViewRect.height - 35;
  66. if( isInValidArea != IsMouseInValidArea )
  67. {
  68. IsMouseInValidArea = isInValidArea;
  69. SceneView.RepaintAll();
  70. }
  71. }
  72. static void UpdateHandlePosition()
  73. {
  74. if( Event.current == null )
  75. {
  76. return;
  77. }
  78. Vector2 mousePosition = new Vector2( Event.current.mousePosition.x, Event.current.mousePosition.y );
  79. Ray ray = HandleUtility.GUIPointToWorldRay( mousePosition );
  80. RaycastHit hit;
  81. if( Physics.Raycast( ray, out hit, Mathf.Infinity, 1 << LayerMask.NameToLayer( "Level" ) ) == true )
  82. {
  83. Vector3 offset = Vector3.zero;
  84. if( EditorPrefs.GetBool( "SelectBlockNextToMousePosition", true ) == true )
  85. {
  86. offset = hit.normal;
  87. }
  88. CurrentHandlePosition.x = Mathf.Floor( hit.point.x - hit.normal.x * 0.001f + offset.x );
  89. CurrentHandlePosition.y = Mathf.Floor( hit.point.y - hit.normal.y * 0.001f + offset.y );
  90. CurrentHandlePosition.z = Mathf.Floor( hit.point.z - hit.normal.z * 0.001f + offset.z );
  91. CurrentHandlePosition += new Vector3( 0.5f, 0.5f, 0.5f );
  92. }
  93. }
  94. static void UpdateRepaint()
  95. {
  96. //If the cube handle position has changed, repaint the scene
  97. if( CurrentHandlePosition != m_OldHandlePosition )
  98. {
  99. SceneView.RepaintAll();
  100. m_OldHandlePosition = CurrentHandlePosition;
  101. }
  102. }
  103. static void DrawCubeDrawPreview()
  104. {
  105. if( IsMouseInValidArea == false )
  106. {
  107. return;
  108. }
  109. Handles.color = new Color( EditorPrefs.GetFloat( "CubeHandleColorR", 1f ), EditorPrefs.GetFloat( "CubeHandleColorG", 1f ), EditorPrefs.GetFloat( "CubeHandleColorB", 0f ) );
  110. DrawHandlesCube( CurrentHandlePosition );
  111. }
  112. static void DrawHandlesCube( Vector3 center )
  113. {
  114. Vector3 p1 = center + Vector3.up * 0.5f + Vector3.right * 0.5f + Vector3.forward * 0.5f;
  115. Vector3 p2 = center + Vector3.up * 0.5f + Vector3.right * 0.5f - Vector3.forward * 0.5f;
  116. Vector3 p3 = center + Vector3.up * 0.5f - Vector3.right * 0.5f - Vector3.forward * 0.5f;
  117. Vector3 p4 = center + Vector3.up * 0.5f - Vector3.right * 0.5f + Vector3.forward * 0.5f;
  118. Vector3 p5 = center - Vector3.up * 0.5f + Vector3.right * 0.5f + Vector3.forward * 0.5f;
  119. Vector3 p6 = center - Vector3.up * 0.5f + Vector3.right * 0.5f - Vector3.forward * 0.5f;
  120. Vector3 p7 = center - Vector3.up * 0.5f - Vector3.right * 0.5f - Vector3.forward * 0.5f;
  121. Vector3 p8 = center - Vector3.up * 0.5f - Vector3.right * 0.5f + Vector3.forward * 0.5f;
  122. // 我们可以使用Handles类来在Scene视图绘制3D物体
  123. // 如果实现恰当的话,我们甚至可以和handles进行交互,例如Unity的移动工具
  124. Handles.DrawLine( p1, p2 );
  125. Handles.DrawLine( p2, p3 );
  126. Handles.DrawLine( p3, p4 );
  127. Handles.DrawLine( p4, p1 );
  128. Handles.DrawLine( p5, p6 );
  129. Handles.DrawLine( p6, p7 );
  130. Handles.DrawLine( p7, p8 );
  131. Handles.DrawLine( p8, p5 );
  132. Handles.DrawLine( p1, p5 );
  133. Handles.DrawLine( p2, p6 );
  134. Handles.DrawLine( p3, p7 );
  135. Handles.DrawLine( p4, p8 );
  136. }
  137. }
  •  

场景七

关注点

  • 在Scene视图绘制自定义的工具条

 

这里写图片描述

 

  1. using UnityEngine;
  2. using UnityEditor;
  3. using System.Collections;
  4. [InitializeOnLoad]
  5. public class LevelEditorE07ToolsMenu : Editor
  6. {
  7. //This is a public variable that gets or sets which of our custom tools we are currently using
  8. //0 - No tool selected
  9. //1 - The block eraser tool is selected
  10. //2 - The "Add block" tool is selected
  11. public static int SelectedTool
  12. {
  13. get
  14. {
  15. return EditorPrefs.GetInt( "SelectedEditorTool", 0 );
  16. }
  17. set
  18. {
  19. if( value == SelectedTool )
  20. {
  21. return;
  22. }
  23. EditorPrefs.SetInt( "SelectedEditorTool", value );
  24. switch( value )
  25. {
  26. case 0:
  27. EditorPrefs.SetBool( "IsLevelEditorEnabled", false );
  28. Tools.hidden = false;
  29. break;
  30. case 1:
  31. EditorPrefs.SetBool( "IsLevelEditorEnabled", true );
  32. EditorPrefs.SetBool( "SelectBlockNextToMousePosition", false );
  33. EditorPrefs.SetFloat( "CubeHandleColorR", Color.magenta.r );
  34. EditorPrefs.SetFloat( "CubeHandleColorG", Color.magenta.g );
  35. EditorPrefs.SetFloat( "CubeHandleColorB", Color.magenta.b );
  36. //Hide Unitys Tool handles (like the move tool) while we draw our own stuff
  37. Tools.hidden = true;
  38. break;
  39. default:
  40. EditorPrefs.SetBool( "IsLevelEditorEnabled", true );
  41. EditorPrefs.SetBool( "SelectBlockNextToMousePosition", true );
  42. EditorPrefs.SetFloat( "CubeHandleColorR", Color.yellow.r );
  43. EditorPrefs.SetFloat( "CubeHandleColorG", Color.yellow.g );
  44. EditorPrefs.SetFloat( "CubeHandleColorB", Color.yellow.b );
  45. //Hide Unitys Tool handles (like the move tool) while we draw our own stuff
  46. Tools.hidden = true;
  47. break;
  48. }
  49. }
  50. }
  51. static LevelEditorE07ToolsMenu()
  52. {
  53. SceneView.onSceneGUIDelegate -= OnSceneGUI;
  54. SceneView.onSceneGUIDelegate += OnSceneGUI;
  55. // EditorApplication.hierarchyWindowChanged可以让我们知道是否在编辑器加载了一个新的场景
  56. EditorApplication.hierarchyWindowChanged -= OnSceneChanged;
  57. EditorApplication.hierarchyWindowChanged += OnSceneChanged;
  58. }
  59. void OnDestroy()
  60. {
  61. SceneView.onSceneGUIDelegate -= OnSceneGUI;
  62. EditorApplication.hierarchyWindowChanged -= OnSceneChanged;
  63. }
  64. static void OnSceneChanged()
  65. {
  66. if( IsInCorrectLevel() == true )
  67. {
  68. Tools.hidden = LevelEditorE07ToolsMenu.SelectedTool != 0;
  69. }
  70. else
  71. {
  72. Tools.hidden = false;
  73. }
  74. }
  75. static void OnSceneGUI( SceneView sceneView )
  76. {
  77. if( IsInCorrectLevel() == false )
  78. {
  79. return;
  80. }
  81. DrawToolsMenu( sceneView.position );
  82. }
  83. static bool IsInCorrectLevel()
  84. {
  85. return UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene().name == "GameE07"
  86. || UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene().name == "GameE08"
  87. || UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene().name == "GameE09";
  88. }
  89. static void DrawToolsMenu( Rect position )
  90. {
  91. // 通过使用Handles.BeginGUI(),我们可以开启绘制Scene视图的GUI元素
  92. Handles.BeginGUI();
  93. //Here we draw a toolbar at the bottom edge of the SceneView
  94. // 这里我们在Scene视图的底部绘制了一个工具条
  95. GUILayout.BeginArea( new Rect( 0, position.height - 35, position.width, 20 ), EditorStyles.toolbar );
  96. {
  97. string[] buttonLabels = new string[] { "None", "Erase", "Paint" };
  98. // GUILayout.SelectionGrid提供了一个按钮工具条
  99. // 通过把它的返回值存储在SelectedTool里可以让我们根据不同的按钮来实现不同的行为
  100. SelectedTool = GUILayout.SelectionGrid(
  101. SelectedTool,
  102. buttonLabels,
  103. 3,
  104. EditorStyles.toolbarButton,
  105. GUILayout.Width( 300 ) );
  106. }
  107. GUILayout.EndArea();
  108. Handles.EndGUI();
  109. }
  110. }
  •  

场景八

关注点

  • 可以在场景七的基础上,点击相应按钮后增加或删除Cube

新的编辑器脚本逻辑和场景七类似,重点在于回调函数OnSceneGUI:

  1. static void OnSceneGUI(SceneView sceneView)
  2. {
  3. if (IsInCorrectLevel() == false)
  4. {
  5. return;
  6. }
  7. if (LevelEditorE07ToolsMenu.SelectedTool == 0)
  8. {
  9. return;
  10. }
  11. // 通过创建一个新的ControlID我们可以把鼠标输入的Scene视图反应权从Unity默认的行为中抢过来
  12. // FocusType.Passive意味着这个控制权不会接受键盘输入而只关心鼠标输入
  13. int controlId = GUIUtility.GetControlID(FocusType.Passive);
  14. // 如果是鼠标左键被点击同时没有其他特定按键按下的话
  15. if (Event.current.type == EventType.mouseDown &&
  16. Event.current.button == 0 &&
  17. Event.current.alt == false &&
  18. Event.current.shift == false &&
  19. Event.current.control == false)
  20. {
  21. if (LevelEditorE06CubeHandle.IsMouseInValidArea == true)
  22. {
  23. if (LevelEditorE07ToolsMenu.SelectedTool == 1)
  24. {
  25. // 如果选择的是erase按键(从场景七的静态变量SelectedTool判断得到),移除Cube
  26. RemoveBlock(LevelEditorE06CubeHandle.CurrentHandlePosition);
  27. }
  28. if (LevelEditorE07ToolsMenu.SelectedTool == 2)
  29. {
  30. /// 如果选择的是add按键(从场景七的静态变量SelectedTool判断得到),添加Cube
  31. AddBlock(LevelEditorE06CubeHandle.CurrentHandlePosition);
  32. }
  33. }
  34. }
  35. // 如果按下了Escape,我们就自动取消选择当前的按钮
  36. if (Event.current.type == EventType.keyDown &&
  37. Event.current.keyCode == KeyCode.Escape)
  38. {
  39. LevelEditorE07ToolsMenu.SelectedTool = 0;
  40. }
  41. // 把我们自己的controlId添加到默认的control里,这样Unity就会选择我们的控制权而非Unity默认的Scene视图行为
  42. HandleUtility.AddDefaultControl(controlId);
  43. }
  •  

场景九

关注点

  • 使用Scriptable Object把一些Prefab预览在Scene视图上

 

这里写图片描述

 

Scriptable Object是一个相当于自定义Assets对象的类。下面是LevelBlocks的定义。它包含了一个LevelBlockData的数组来存储可选的Prefab对象。

  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. //[System.Serializable] tells unity to serialize this class if
  5. //it's used in a public array or as a public variable in a component
  6. [System.Serializable]
  7. public class LevelBlockData
  8. {
  9. public string Name;
  10. public GameObject Prefab;
  11. }
  12. //[CreateAssetMenu] creates an entry in the default Create menu of the ProjectView so you can easily create an instance of this ScriptableObject
  13. [CreateAssetMenu]
  14. public class LevelBlocks : ScriptableObject
  15. {
  16. //This ScriptableObject simply stores a list of blocks. It kind of acts like a database in that it stores rows of data
  17. public List<LevelBlockData> Blocks = new List<LevelBlockData>();
  18. }

我们之后就可以在Hierency视图创建一个LevelBlock资源,Editor类则会加载这个资源来得到相应的数据。

  1. static LevelEditorE09ScriptableObject()
  2. {
  3. SceneView.onSceneGUIDelegate -= OnSceneGUI;
  4. SceneView.onSceneGUIDelegate += OnSceneGUI;
  5. //Make sure we load our block database. Notice the path here, which means the block database has to be in this specific location so we can find it
  6. //LoadAssetAtPath is a great way to load an asset from the project
  7. m_LevelBlocks = AssetDatabase.LoadAssetAtPath<LevelBlocks>( "Assets/E09 - Scriptable Object/LevelBlocks.asset" );
  8. }
  •  

Unite 2016上还有另一个专门讲Scriptable Object的视频,强烈建议看一下:

10

0

 

 

  相关文章推荐

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小桥流水78/article/detail/806562
推荐阅读
相关标签
  

闽ICP备14008679号