Reputation: 85
I would like to create a custom inspector with a dropdown menu. I want to do this to select between multiple classes that inherit from a base interface.
So I've created a script that overrides the default editor for the class CreatureSO that extends ScriptableObject. I know that there are two ways to access the properties of the class Creature with the target variable and with serializedObject. I want to use the serializedObject method because of the features that it includes.
My current code:
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);
}
}
}
What I'm trying to achieve with these classes is to create a generic object Creature that I can easily define with one scriptableObject. So when I press the play button all GameObjects with a Creature component will use some CreatureSO from a list. The engine is the brain of the Creature and defines what the Creature will do. I want the Creatures to have different behaviors and that's why I need different engines.
This is my code so far. My problem is that I can't correctly cast any IEngine object to the serializedProperty.
Unity shows the following error:
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 also gives an error. Any suggestion??
Also if there are any resources for learning editor scripting? not the unity learn platform.
Thanks.
Upvotes: 1
Views: 2006
Reputation: 90813
This is not (yet) a complete answer since we would need to know what exactly PlayerEngine
and EnemyEngine
look like.
With the current information though, in
engine.objectReferenceValue = SelectEngine();
SerializedProperty.objectReferenceValue
as the name suggests expects a reference of type UnityEngine.Object
the mother class of e.g. GameObject
, Component
, ScriptableObject
and basically every built-in asset type.
It is not to be confused with System.Object
which the alias object
stands for in c#
!
None of your classes implementing IEngine
does inherit from UnityEngine.Object
either.
And anyway in general an interface does not inherit from either one.
And even if it would, you most probably wouldn't want to create a new instance each and every time the Inspector serialization is done.
For giving possible solutions to this issue we would need to know more about what exactly the differences between your IEngine
implementations are.
Upvotes: 0