[ExecuteAlways]
最简单的方法当然是直接给Mono加上[ExecuteAlways]修饰,这样Mono中的Awake,Update等等都可以在编辑模式下按照原本的时机运行。
[ExecuteAlways]
public class TestScript : MonoBehaviour {
void TestMethod(){
Debug.Log("TestMethod");
}
void Update(){
TestMethod();
}
}
OnValidate
除了ExecuteAlways以外,还可以用OnValidate,OnValidate 方法会在脚本中的变量发生更改时自动调用,适用于与 Inspector 参数联动的逻辑。
public class TestScript : MonoBehaviour {
public float value;
private void OnValidate()
{
// 在编辑器中参数变更时自动调用
Debug.Log($"Value changed to: {value}");
}
}
OnDrawGizmos+SceneView.RepaintAll()
有的时候如果想让某段代码能在编辑模式下像Update一样一直运行,但又不想用[ExecuteAlways]或者觉得没必要,就可以用这个方法,需要在SceneView里打开Gizmos的开关
public class TestScript : MonoBehaviour {
void TestMethod(){
Debug.Log("TestMethod");
}
//force update on editormode
void OnDrawGizmos() {
if (!Application.isPlaying) {
//call you method
TestMethod();
}
//force Repaint
if (!Application.isPlaying) {
UnityEditor.EditorApplication.QueuePlayerLoopUpdate();
UnityEditor.SceneView.RepaintAll();
}
}
}
扩展
一般来说上面提到的几个算是够用了,但我在使用OnDrawGizmos方法的时候,依旧觉得太麻烦,就没有什么更方便的方法吗,于是我想到了C#的Attribute,如果可以像[ExecuteAlways]那样简单的标记一个方法,让这方法可以不断的被调用,那写起来不就方便多了吗,于是
EditorUpdateMethodManager
//==============================================================================
// Filename: EditorUpdateMethodManager
// Description:
//
// Version: 1.0
// Compiler: 2022.3.40f1 (cbdda657d2f0)
// Author: Akota
// CreateTime: 2024-12-02 10:14:40
//==============================================================================
using System;
using UnityEngine;
#if UNITY_EDITOR
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
#endif
[ExecuteAlways]
public class EditorUpdateMethodManager : MonoBehaviour {
private void Awake() {
if (Application.isPlaying) {
Destroy(gameObject);
}
}
#if UNITY_EDITOR
private List<RegisteredMethod> RegisteredMethods = new List<RegisteredMethod>();
private void OnEnable() {
if (!Application.isPlaying) {
RegisterMethodsFromScene();
EditorApplication.hierarchyChanged += RegisterMethodsFromScene; // 监听层级变化
}
}
private void OnDisable() {
if (!Application.isPlaying) {
EditorApplication.hierarchyChanged -= RegisterMethodsFromScene; // 移除监听
}
}
private void Update() {
if (!Application.isPlaying) {
foreach (var registeredMethod in RegisteredMethods) {
// 仅调用激活的 MonoBehaviour 实例上的方法
if (registeredMethod.MonoInstance != null &&
registeredMethod.MonoInstance.enabled &&
registeredMethod.MonoInstance.gameObject.activeInHierarchy) {
registeredMethod.Method.Invoke(registeredMethod.MonoInstance, null);
}
}
}
}
/// <summary>
/// 注册当前场景中的所有标记方法
/// </summary>
public void RegisterMethodsFromScene() {
RegisteredMethods.Clear();
// 获取当前场景中所有 MonoBehaviour
var monoBehaviours = FindObjectsOfType<MonoBehaviour>(true); // 包括未激活的对象
foreach (var mono in monoBehaviours) {
// 查找带有 RegisterEditorUpdateMethodAttribute 的方法
var methods = mono.GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(m => m.GetCustomAttribute<EditorUpdateMethodAttribute>() != null);
foreach (var method in methods) {
// 记录方法和实例
RegisteredMethods.Add(new RegisteredMethod(mono, method));
}
}
}
/// <summary>
/// 注册方法信息
/// </summary>
private class RegisteredMethod {
public MonoBehaviour MonoInstance { get; }
public MethodInfo Method { get; }
public RegisteredMethod(MonoBehaviour monoInstance, MethodInfo method) {
MonoInstance = monoInstance;
Method = method;
}
}
#endif
}
/// <summary>
/// 标记可以被 EditorUpdateMethodManager 调用的方法
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class EditorUpdateMethodAttribute : Attribute {
}
原理也很简单,就是利用[ExecuteAlways]来标记EditorUpdateMethodManager,然后在EditorUpdateMethodManager里面查找当前场景下的所有Mono里被EditorUpdateMethod标记的方法,并在Manager的Update中统一调用。
示例
public class TestScript : MonoBehaviour {
[EditorUpdateMethod]
void TestMethod(){
Debug.Log("TestMethod");
}
}
要是手动在场景里创建一个Manager还是嫌麻烦的话
EditorUpdateManagerInitializer
//==============================================================================
// Filename: EditorUpdateManagerInitializer
// Description:
//
// Version: 1.0
// Compiler: 2022.3.40f1 (cbdda657d2f0)
// Author: Akota
// CreateTime: 2024-12-02 10:16:02
//==============================================================================
#if UNITY_EDITOR
using System;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
[InitializeOnLoad]
public static class EditorUpdateManagerInitializer {
static EditorUpdateManagerInitializer() {
// 监听层级变化
EditorApplication.hierarchyChanged += EnsureEditorUpdateMethodManagerExists;
EnsureEditorUpdateMethodManagerExists(); // 初次调用
}
/// <summary>
/// 检查场景中是否存在标记方法,并确保 EditorUpdateMethodManager 存在
/// </summary>
private static void EnsureEditorUpdateMethodManagerExists() {
// 检查场景中是否存在 EditorUpdateMethodManager
var existingManager = GameObject.FindObjectOfType<EditorUpdateMethodManager>();
if (existingManager == null) {
if (HasRegisterEditorUpdateMethodsInScene()) {
// 没有 EditorUpdateMethodManager,创建一个
var managerObject = new GameObject("EditorUpdateMethodManager");
managerObject.AddComponent<EditorUpdateMethodManager>();
// 标记场景为脏
Undo.RegisterCreatedObjectUndo(managerObject, "Create EditorUpdateMethodManager");
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
Debug.Log("[EditorUpdateManagerInitializer] 自动创建了 EditorUpdateMethodManager");
}
}
else {
// 如果不需要且已有 Manager,则移除
//Debug.Log("[EditorUpdateManagerInitializer] 场景中没有标记方法,移除了 EditorUpdateMethodManager");
//Undo.DestroyObjectImmediate(existingManager.gameObject);
//EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
}
}
/// <summary>
/// 检查场景中是否有被 RegisterEditorUpdateMethodAttribute 标记的方法
/// </summary>
private static bool HasRegisterEditorUpdateMethodsInScene() {
// 获取当前场景中所有 MonoBehaviour
var monoBehaviours = GameObject.FindObjectsOfType<MonoBehaviour>(true); // 包括未激活的对象
foreach (var mono in monoBehaviours) {
// 检查该 MonoBehaviour 类型是否有标记的方法
var methods = mono.GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(m => m.GetCustomAttribute<EditorUpdateMethodAttribute>() != null);
if (methods.Any()) {
return true;
}
}
return false;
}
}
#endif
这段代码会自动检测场景中是否有被EditorUpdateMethod修饰的方法,有的话就自动创建Manager
更新
考虑到要在场景里创建物体,可能显得有点累赘,于是新版放弃[ExecuteAlways],改为利用EditorApplication.update来实现,更精简了
//==============================================================================
// Filename: EditorUpdateMethodManager
// Description:
//
// Version: 1.0
// Compiler: 2022.3.40f1 (cbdda657d2f0)
// Author: Akota
// CreateTime: 2024-12-02 14:13:12
//==============================================================================
#if UNITY_EDITOR
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
public class EditorUpdateMethodManager{
public static EditorUpdateMethodManager instance = null;
[InitializeOnLoadMethod]
public static void Init() {
instance = new EditorUpdateMethodManager();
}
public EditorUpdateMethodManager() {
if (!Application.isPlaying) {
RegisterMethodsFromScene();
}
EditorApplication.hierarchyChanged += RegisterMethodsFromScene; // 监听层级变化
EditorApplication.update += Update;
}
private List<RegisteredMethod> RegisteredMethods = new List<RegisteredMethod>();
void Update() {
if (!Application.isPlaying) {
foreach (var registeredMethod in RegisteredMethods) {
// 仅调用激活的 MonoBehaviour 实例上的方法
if (registeredMethod.MonoInstance != null &&
registeredMethod.MonoInstance.enabled &&
registeredMethod.MonoInstance.gameObject.activeInHierarchy) {
registeredMethod.Method.Invoke(registeredMethod.MonoInstance, null);
}
}
}
}
/// <summary>
/// 注册当前场景中的所有标记方法
/// </summary>
public void RegisterMethodsFromScene() {
RegisteredMethods.Clear();
// 获取当前场景中所有 MonoBehaviour
var monoBehaviours = Object.FindObjectsOfType<MonoBehaviour>(true); // 包括未激活的对象
foreach (var mono in monoBehaviours) {
// 查找带有 RegisterEditorUpdateMethodAttribute 的方法
var methods = mono.GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(m => m.GetCustomAttribute<EditorUpdateMethodAttribute>() != null);
foreach (var method in methods) {
// 记录方法和实例
RegisteredMethods.Add(new RegisteredMethod(mono, method));
}
}
}
/// <summary>
/// 注册方法信息
/// </summary>
private class RegisteredMethod {
public MonoBehaviour MonoInstance { get; }
public MethodInfo Method { get; }
public RegisteredMethod(MonoBehaviour monoInstance, MethodInfo method) {
MonoInstance = monoInstance;
Method = method;
}
}
}
#endif
/// <summary>
/// 标记可以被 EditorUpdateMethodManager 调用的方法
/// </summary>
[System.AttributeUsage(System.AttributeTargets.Method)]
public class EditorUpdateMethodAttribute : System.Attribute {
}
其他
还有其他的一些,比如编写CustomEditor,在Editor中一直调用方法,又或者用Editor画个按钮,通过在Inspector上手动点击来调用代码,当然也可以用Odin插件,直接用Button修饰Mono中的方法,就会在Inspector上出现按钮了。
结语
代码写的很随便也很丑陋,望包涵