Unity 编辑器脚本:如何创建自定义弹出窗口以选择特定类

问题描述

我想创建一个带有下拉菜单自定义检查器。我想这样做是为了在从基接口继承的多个类之间进行选择。

因此,我创建了一个脚本,该脚本覆盖了扩展 ScriptableObject 的 CreatureSO 类的认编辑器。我知道有两种方法可以使用目标变量和序列化对象访问类 Creature 的属性。我想使用 serializedobject 方法,因为 它包含的功能

我当前的代码

CreatureSO.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


[CreateAssetMenu(fileName = "Creature",menuName = "Custom/Creature/Instance")]
public class CreatureSO : ScriptableObject
{
    public Sprite sprite;
    public Vector3 position;
    public Vector2 size;
    public CreatureStats stats;

    //[HideInInspector] 
    [SerializeReference] public IEngine engine; //this is the property

}

IEngine.cs

using System;
using UnityEngine;
using UnityEngine.InputSystem;

public interface IEngine 
{
    
}

PlayerEngine.cs

using System;
using UnityEngine;
using UnityEngine.InputSystem;

[System.Serializable]
public class PlayerEngine: IEngine
{
    private Creature _creature;
    private Keyboard keyboard;

    private bool inputCtrl;

    public void Start(Creature creature)
    {
        keyboard = Keyboard.current;
        _creature = creature;
    }

    private void FixedUpdate()
    {

        //Movement
        _creature.isMovingX = keyboard.aKey.ispressed ^ keyboard.dKey.ispressed;
        _creature.inputX = !_creature.isMovingX ? 0 : keyboard.aKey.ispressed ? -1 : 1;
        _creature.isWalking = _creature.isMovingX && _creature.isGrounded && !inputCtrl;
        _creature.isRunning = _creature.isMovingX && _creature.isGrounded && inputCtrl;
        _creature.isDashing = keyboard.sKey.ispressed;
        _creature.direction = _creature.inputX != 0 ? (int)_creature.inputX : _creature.direction;


        //wall;
        _creature.isWalled = _creature.controller.collisionInfo.left || _creature.controller.collisionInfo.right;
        _creature.isFullWalled = _creature.controller.collisionInfo.fullLeft || _creature.controller.collisionInfo.fullRight;

        //Jump
        _creature.isJumping = keyboard.spaceKey.ispressed;

        //attack
        _creature.isAttacking = false;
        //if (_creature.stats.canAttack)
        {
            _creature.isAttacking = keyboard.wKey.ispressed;
           // _creature.stats.canAttack = false;
        }
        if (!keyboard.wKey.ispressed)
            //_creature.stats.canAttack = true;

        //running
        inputCtrl = keyboard.leftCtrlKey.ispressed;

        //vapor

        //_creature.stats.hasVapor = _creature.stats.vaporCount > 0;

        //Habilities

        //dashHab.FixedUpdate(this);
    }
}

EnemyEngine


public class EnemyEngine: IEngine
{

    private Creature _creature;



    public void Init(Creature creature)
    {
        _creature = creature;
        
    }

    public void Update()
    {

        //Currently this engine does nothing
        //In the future it will detect his surroundings and command some actions to the Creature.
        //for example: (pseudocode)
        
        // if( ! Raycast( front ) ) {
        //
        //  Creature.move.forward();
        //}


    }
}

CreatureSO.cs


using UnityEngine;
using UnityEditor;

enum EngineType { Player,Enemy };

[CustomEditor(typeof(CreatureSO))]
public class CreatureSOEditor : Editor
{

    EngineType engineType;

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        serializedobject.Update();
        SerializedProperty engine = serializedobject.FindProperty("engine");
        engineType = (EngineType)EditorGUILayout.EnumPopup("Engine",engineType);
        engine.objectReferenceValue = SelectEngine();

        serializedobject.ApplyModifiedProperties();
    }

    public IEngine SelectEngine()
    {
        switch(engineType)
        {
            case EngineType.Player:
                PlayerEngine playerEngine = new PlayerEngine();
                return playerEngine;
            default:
                return null;
        }
    }
}


Creature.cs

using System;
using System.Collections.Generic;
using UnityEngine;

public class Creature: MonoBehavIoUr
{

    public CreatureSO instance;

    public IHability[] _actions;
    public IEngine engine;


    //public ItemPickup itemToPick;
    //public GameState gameState;
    //public Inventory inventory;

    [HideInInspector] private GameObject _actionsGO;
    [HideInInspector] private CreatureStats stats;

    [HideInInspector] public BoxCollider2D BoxCollider;
    [HideInInspector] public Controller2D controller;
    [HideInInspector] public Animator animator;
    [HideInInspector] public GameObject spriteObject;
    [HideInInspector] public SpriteRenderer spriteRenderer;


    public Collider2D attackCollider;
    public Collider2D bodyCollider;

    public Vector2 veLocity;

    public int direction;

    public float inputX;

    public bool isMovingX;
    public bool isWalking;
    public bool isRunning;
    public bool isJumping;
    public bool isAttacking;
    public bool isGrounded;
    public bool isWalled;
    public bool isFullWalled;
    public bool isDashing;
    public bool canPickup;


    private ContactFilter2D contactFilter;


    public void Start()
    {

        BoxCollider = gameObject.AddComponent<BoxCollider2D>();
        BoxCollider.size = instance.size;

        controller = Controller2D.Attach(gameObject,BoxCollider);

        spriteObject = new GameObject();
        spriteObject.transform.parent = gameObject.transform;
        spriteObject.transform.localPosition = new Vector3();
        spriteObject.transform.localScale = new Vector3(instance.size.x/10,instance.size.y/10);
        spriteRenderer = spriteObject.AddComponent(typeof(SpriteRenderer)) as SpriteRenderer;
        spriteRenderer.sprite = instance.sprite;
        spriteRenderer.size = instance.size;


        animator = gameObject.AddComponent(typeof(Animator)) as Animator;
        stats = instance.stats;

        _actionsGO = new GameObject("Actions");
        _actionsGO.transform.SetParent(this.transform);

        ScriptableObject[] actionStats = stats.actionStats;
        _actions = new IHability[actionStats.Length];

        IHability currentAction; 


        for (int i  =0; i < actionStats.Length; i++)
        {
            switch (actionStats[i])
            {
                case WalkingStats stats:
                    currentAction = _actionsGO.AddComponent<WalkingHab>();
                    ((WalkingHab)currentAction).Init(this,(WalkingStats)actionStats[i]);
                    break;

                case FallingStats stats:
                    currentAction = _actionsGO.AddComponent<FallingHab>();
                    ((FallingHab)currentAction).Init(this,(FallingStats)actionStats[i]);
                    break;

                case JumpingStats stats:
                    currentAction = _actionsGO.AddComponent<JumpHab>();
                    ((JumpHab)currentAction).Init(this,(JumpingStats)actionStats[i]);
                    break;

                case AirMovingStats stats:
                    currentAction = _actionsGO.AddComponent<AirMovingHab>();
                    ((AirMovingHab)currentAction).Init(this,(AirMovingStats)actionStats[i]);
                    break;

                case DashingStats stats:
                    currentAction = _actionsGO.AddComponent<DashingHab>();
                    ((DashingHab)currentAction).Init(this,(DashingStats)actionStats[i]);
                    break;

                case GrabbingStats stats:
                    currentAction = _actionsGO.AddComponent<GrabbingHab>();
                    ((GrabbingHab)currentAction).Init(this,(GrabbingStats)actionStats[i]);
                    break;
                case JumpDiagonalStats stats:
                    currentAction = _actionsGO.AddComponent<JumpDiagonalHab>();
                    ((JumpDiagonalHab)currentAction).Init(this,(JumpDiagonalStats)actionStats[i]);
                    break;

                case AttackStats stats:
                    currentAction = _actionsGO.AddComponent<AttackHab>();
                    ((AttackHab)currentAction).Init(this,(AttackStats)actionStats[i]);
                    break;

                default:
                    return;
            }

            _actions[i] = currentAction;

        }

        direction = 1;
        //this.health = stats.maxHealth;

        contactFilter = new ContactFilter2D();
        contactFilter.useLayerMask = true;
        //contactFilter.layerMask = stats.attackLayerMask;
    }


    public void Update()
    {
    }
    

    public void FixedUpdate()
    {
        isGrounded = controller.collisionInfo.below;

        if (isGrounded || controller.collisionInfo.above) veLocity.y = Mathf.Sign(veLocity.y) * 0.1f;

        for (int i = 0; i < _actions.Length; i++)
        {
            if (!_actions[i].CanExecute()) continue;

            _actions[i].Execute();

        }


        if (this.direction != Mathf.Sign(this.transform.localScale.x))
        {
            this.transform.localScale = Vector3.Scale(this.transform.localScale,new Vector3(-1,1,1));
        }
        this.transform.Translate(this.controller.Move(veLocity * Time.deltaTime));
        veLocity.x = 0;
    }

    //public abstract void pickupAction(ItemPickup item);


    public void setVeLocityX(float veLocityX)
    {
        this.veLocity.x = veLocityX;
    }

    public void setVeLocityY(float veLocityY)
    {
        this.veLocity.y = veLocityY;
    }

    public void addVeLocityY(float veLocityY)
    {
        this.veLocity.y += veLocityY;
    }

    public void addVeLocityX(float veLocityX)
    {
        //if (veLocity.x < stats.speedXMax)
            this.veLocity.x += veLocityX;
    }


    public void attack()
    {
        List<Collider2D> enemies = new List<Collider2D>();
        Physics2D.OverlapCollider(this.attackCollider,contactFilter,enemies);

        foreach (Collider2D enemy in enemies)
        {
            //Enemy a = enemy.gameObject.GetComponent<Enemy>();
            //a.damage(10);
        }
    }
}



我试图用这些类实现的是创建一个通用对象 Creature,我可以用一个 scriptableObject 轻松定义它。因此,当我按下播放按钮时,所有带有 Creature 组件的 GameObjects 都会使用列表中的一些 CreatureSO。引擎是生物的大脑,它定义了生物会做什么。我希望生物有不同的行为,这就是为什么我需要不同的引擎。

这是我目前的代码。我的问题是我无法正确地将任何 IEngine 对象转换为 serializedProperty。

Unity 显示以下错误

Assets\3_Game\Core\ScriptableObjects\Editor\CreatureSOEditor.cs(18,39): error CS0266: 
Cannot implicitly convert type 'IEngine' to 'UnityEngine.Object'. 
An explicit conversion exists (are you missing a cast?)

using (object) cast 也会出错。有什么建议吗??

另外,是否有任何学习编辑器脚本的资源?不是统一学习平台。

谢谢。

解决方法

这(还)不是一个完整的答案,因为我们需要知道 PlayerEngineEnemyEngine 究竟是什么样子。


根据目前的信息,在

engine.objectReferenceValue = SelectEngine();

SerializedProperty.objectReferenceValue 顾名思义需要一个 UnityEngine.Object 类型的引用,例如GameObjectComponentScriptableObject 以及基本上所有内置资产类型。

不要与 System.Object 混淆,别名 objectc# 中代表!


您实现 IEngine 的类都没有继承自 UnityEngine.Object

而且总的来说,接口不会从任何一个继承。

即使可以,您很可能也不希望每次完成 Inspector 序列化时都创建一个新实例。


为了提供此问题的可能解决方案,我们需要详细了解您的 IEngine 实现之间的确切区别。