CustomPropertyDrawer in unity3d for inherited classes - c#

I'm trying to do something I think is relatively simple, but maybe I'm way off base. I don't know. I have a class that inherits from ScriptableObject, like such:
using UnityEngine;
class Automobile : ScriptableObject {
[SerializedField]
private int _baseCost = 0;
public int BaseCost { get { return _baseCost;} set {_baseCost = value;} }
// More fields and logic
void OnValidate() {
BaseCost = _baseCost;
}
}
Then I have a class that inherits from Automobile, like such:
public class Car : Automobile {
}
This class is actually empty, there may be logic added to this later, right now I'm just keep Cars separate from Trucks, Motorcycles, and who knows what else I'll think up. I used this script from the unity wiki to create a bunch of asset files for different cars. All are located under the \resources\cars folder so I can load them at runtime
I then have a player class that contains an array of cars (and trucks, but for brevity I'm excluding all that)
using UnityEngine;
public class Player : MonoBehaviour {
[SerializeField]
private Car[] _cars;
public Car[] Cars { get { return _cars; } set { _cars = value; } }
void Start () {
// Load all cars into array
Cars = Resources.LoadAll<Car>("Cars")
}
}
To cap this all off, and to actually explain the issue I'm having, I want to create a custom property drawer for every object that is derived from the Automobile class. So I created a script in the editor folder like this:
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(Automobile), true)]
public class AutomobileDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
EditorGUI.BeginProperty(position, label, property);
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
Rect BaseCLabel = new Rect(position.x, position.y, 200, position.height);
Rect BaseC = new Rect(position.x + 35, position.y, 15, position.height);
EditorGUI.LabelField(BaseVLabel, "Base Cost");
// This is my error line
EditorGUI.PropertyField(BaseV, property.FindPropertyRelative("_baseCost"),GUIContent.none);
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
//base.OnGUI(position, property, label);
}
The issue I'm facing is that FindPropertyRelative always returns null. I'm almost 100% confident that due to the levels of inheritance I need to build a path (which would present a problem when I move on to trucks as the path will most likely be different), but I have no idea what it should be. Any suggestions?
EDIT: I forgot to mention that the Automobile class utilizes the OnValidate function. I added it to the code.

The issue seems to have been that classes that inherit from ScriptableObject do not seem to work well with propertydrawers. By removing the inheritance from my Automobiles class, and simply applying the [Serializable] attribute to the class declaration I was able to get the propertydrawer working correctly.
I am hoping that I am wrong about this, as I have some classes that I need to leave inheriting from ScriptableObject and hopefully somebody will see this and correct me, but until that time, this is my solution.

Related

Serialize a custom class in a custom inspector in Unity

I need to serialize a class in a custom inspector (using visually the Editor) like as doing in a Monobehaviour script like this:
[System.Serializable]
public class CustomClass
{
int myInt
}
public class OtherClass : MonoBehaviour
{
[SerializeField] CustomClass customClass;
}
which gives this result:
result wanted and given using the code above, where DamageEffect = CustomClass and Damage = myInt
In my custom editor, I'd like something like this:
[CustomEditor(typeof(CardObject))]
class AnotherClassEditor : Editor
{
public override void OnInspectorGUI()
{
[SerializeField] CustomClass customclass;
}
}
but, as expected, it points out an error.
I also tried with EditorGUILayout.ObjectField() but I haven't been able to, I'm not so experienced so please try to keep the answers simple.
Actually, I need this serialization to happen only when an enum is equal to a certain value, the overall script is something like this:
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
[CreateAssetMenu(fileName = "Card", menuName = "CardObject")]
public class CardObject : ScriptableObject
{
public List<CardEffectType> effectsTypes;
//other...
[HideInInspector] public List<CardEffect> effects;
//other...
}
#if UNITY_EDITOR
[CustomEditor(typeof(CardObject))]
class CardObjectEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
CardObject cardObject = (CardObject)target;
foreach(CardEffectType effectType in cardObject.effectsTypes)
{
switch (effectType)
{
case CardEffectType.DamageEffect:
{
//!!!
}
}
}
}
}
#endif
public enum CardEffectType
{
DamageEffect, //other...
}
I found some workarounds but the result is not as when a class is serialized in a Monobehaviour.
When you just want to show something like how Unity would, you can use EditorGUILayout.PropertyField(), but it asks for a SerializedProperty, what you need to get from the SerializedObject, not from the actual target. Something like this:
[CustomEditor(typeof(MyType))]
public class MyTypeEditor : Editor
{
private SerializedProperty _variableIWantToShow;
private void OnEnable()
{
_variableIWantToShow = serializedObject.FindProperty("<name-of-the-variable>");
}
public override void OnInspectorGUI()
{
// ...
if (ShowVariable) EditorGUILayout.PropertyField(_variableIWantToShow);
// ...
}
}
You can manage collections (array, list, etc.) as SerializedProperty, but it adds some complexity.
https://docs.unity3d.com/ScriptReference/SerializedProperty.html
https://answers.unity.com/questions/682932/using-generic-list-with-serializedproperty-inspect.html
Just make your int myInt public, or add [SerializeField] attribute to it - inspector is only designed to work with serialisable fields (public fields are serialised by default in unity editor), currently your myInt is private hence only visible from within
As mentioned in an answer by h4i, proper way to display objects in the editor is using SerializedProperty, casting 'target' seems like a good idea at start but is only useful if you want to call methods on the object, it will fail in several other cases.
What you may want to consider is declaring a PropertyDrawer while Editors service a single monobehaviour type, a PropertyDrawer handles displaying every instance of a serializable class, and will be used every time a default editor uses a PropertyField internally. This will probably match your use case better

Can a Scriptable Object class be inside a namespace in Unity3D?

I'm restructuring my code and created an namespace with the intent to put all the scriptable object classes inside it. (I'm attaching the code and screenshot below for the sake of organization.)
When I go back to Unity, it seems fine. It also displays the option to create in the menu.
But when I check the object, it seems to have no script attached to it.
Some people have shown that Unity is capable of having more than one like in this post from Unity's Forum.
The guy even lists the same behavior as mine in the Case #4. But I can't understand why my code isn't behaving like his Case #2. I'm thinking that's caused by the Attribute, but I don't know how to display the option to create the asset.. Can someone help me?
edit: I did try using a Editor class as the post but got the same result.
namespace BBMM.UI.ScriptableObjects
{
[CreateAssetMenu(fileName = "New Size Preset", menuName = "BBMM/Size Preset")]
public class SO_UI_SizePreset : ScriptableObject
{
public string presetName;
public Vector2 realSize;
public UnitType unit;
}
[CreateAssetMenu(fileName = "New Object", menuName = "BBMM/Object Container")]
public class SO_UI_ObjectList_Item : ScriptableObject
{
public string objectName;
public GameObject prefab;
}
}
Here's the asset created using the code above:
First of all make sure each class is in a separate file with matching name!
SO_UI_SizePreset.cs
namespace BBMM.UI.ScriptableObjects
{
[CreateAssetMenu(fileName = "New Size Preset", menuName = "BBMM/Size Preset")]
public class SO_UI_SizePreset : ScriptableObject
{
public string presetName;
public Vector2 realSize;
public UnitType unit;
}
}
SO_UI_ObjectList_Item.cs
namespace BBMM.UI.ScriptableObjects
{
[CreateAssetMenu(fileName = "New Object", menuName = "BBMM/Object Container")]
public class SO_UI_ObjectList_Item : ScriptableObject
{
public string objectName;
public GameObject prefab;
}
}
Then if you created the SO asset first and then changed the namespace Unity might loose the connection (depending on how exactly you made that change).
-> you can either recreate them or go to the Inspector top-right corner menu, enable Debug mode and drag&drop the correct script into the Script field.

Create new default object for custom PropertyDrawer

When you create new class and mark it as [System.Serializable] your inspector will create and show its default object for property of new class' type in your MonoBehaviour component.
When creating custom PropertyDrawer though you need to create this default object on your own and put its reference into SerializedProperty.objectReferenceValue (as far as I understand).
But this field is of type UnityEngine.Object and my new class cant be assigned there. How to overcome it? Inheriting your class from UnityEngine.Object doesnt help as SerializedProperty.objectReferenceValue is still null, even after assigning in there the newly created object (which is actually of the same type – UnityEngine.Object).
I hope I understood your question correctly, taken from the Unity documentation:
using UnityEngine;
public enum IngredientUnit { Spoon, Cup, Bowl, Piece }
// Custom serializable class
[Serializable]
public class Ingredient
{
public string name;
public int amount = 1;
public IngredientUnit unit;
}
public class Recipe : MonoBehaviour
{
public Ingredient potionResult;
public Ingredient[] potionIngredients;
}
[CustomPropertyDrawer(typeof(Ingredient))]
public class IngredientDrawerUIE : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
// Create property container element.
var container = new VisualElement();
// Create property fields.
var amountField = new PropertyField(property.FindPropertyRelative("amount"));
var unitField = new PropertyField(property.FindPropertyRelative("unit"));
var nameField = new PropertyField(property.FindPropertyRelative("name"), "Fancy Name");
// Add fields to the container.
container.Add(amountField);
container.Add(unitField);
container.Add(nameField);
return container;
}
}
So when you view a GameObject with the Recipe component on it, Unity's inspector will show something like this:
So you do not need to inherit from anything, simply mark the class you want to create a property drawer as Serializable, and create a property drawer class for it (Make sure to place it in the Editor folder, or create a assembly definition file which targets the editor only if you are working with assembly definition files).

Changing Player Sprite through Shop

I'm trying to create a shop where you can buy a different player sprite from in-game currency. (The shop is a separate scene from the level) I was told that using scriptableobject is the way to go, so I made the following:
[CreateAssetMenu(fileName = "New Sprite", menuName = "Player Sprites")]
public class PlayerSprites : ScriptableObject
{
public string spriteName;
public int cost;
public Sprite sprite;
}
And I just added to the player script
public SpriteRenderer spriteRenderer;
void Start()
{
spriteRenderer = GetComponent<SpriteRenderer>();
}
I'm not really sure where to go from here... how to render the sprite on to the player from a different scene when the sprite button is pressed... Any help is greatly appreciated thank you!
Though your question is quite hazy and I don't really see what you tried so far:
As you have it right now you will need one ScriptableObject for each Sprite item ... I don't think that's what you want. You should rather use one ScriptableObject storing the information of all Sprite items.
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "Assets/New Store", menuName = "Sprites-Store")]
public class SpriteStoreContainer : ScriptableObject
{
public List<StoreSpriteItem> SpriteItems = new List<StoreSpriteItem>();
// you can/should also implement methods here as in any usual component!
}
Also make sure your fileName starts with Assets/
And a separate class for the Items which uses [System.Serializable] so you can display it in the Inspector.
using UnityEngine;
[System.Serializable]
public class StoreSpriteItem
{
public string spriteName;
public int cost;
public Sprite sprite;
public bool IsAvailable;
// also here you could/should implement some methods e.g.
public void BuyItem()
{
IsAvailable = true;
}
}
And back in Unity:
Now you first have to Instantiate the ScriptableObject asset:
Go to the Project View (Assets) -> right mouse click -> in the menu click on Create -> click on Sprites-Store
This should create a new asset called New Store(.asset) under Assets
Now in the Inspector of this created asset you fill in the information you need. You should see our List SpriteItems with Size = 0.
To create elements just increase the Size value and hit enter(Carefull: Unity doesn't ask if you change this value => take care you don't delete items by reducing this value accidently later)
Now you can adjust all information for those SpriteItems
Later wherever you need access to the information of this Asset you can just use the reference as any other Component => you can assign it e.g. via the Inspector using a public field e.g.
using UnityEngine;
public class ScriptThatUsesStore : MonoBehaviour
{
public SpriteStoreContainer Store;
public void DoSomthingWithStore()
{
// example: just get the first List element
Sprite someSprite = Store.SpriteItems[0].sprite;
}
}
and than access the data in it. Though I strongly recommend you rather implement some methods in the ScriptableObject like e.g. BuyItem, GetSprite, etc.

How to Clone a child of an abstract class in WP7 XNA

I'm working on a game for WP7 with XNA. Here is my structure:
public abstract class enemy
{}
Child elements:
public class genericEnemy : enemy{}
...
public class snake : enemy {}
etc...
In WP7, a lot of things have been moved around and/or removed (especially with Serialization) it seems. Despite much searching, I haven't been able to find a solution. I'm trying to duplicate the child elements.
For example: On loading a level, I pass an array of three different enemies into the loading phase. During loading, I need to duplicate each of those enemies so that 20 of each are flying around doing their own thing during gameplay.
All the solutions I've seen refer to things that are not present in the WP7 library.
There's no "library" way of doing this as far as I know. One solution would be:
1) Declare a Clone() method in enemy that returns a copy of that enemy.
abstract class Enemy {
public abstract Enemy Clone();
}
2) Implement it in every concrete type, so a Snake creates a new Snake, etc. Example:
class Snake : Enemy {
int speed;
public override void Enemy Clone() {
var clone = new Snake();
clone.speed = speed;
return clone;
}
}
3) Now any object of a concrete type knows how to clone itself, so if you have an array of Enemies, you can call Clone() on each and it will create the proper concrete type in the proper way.
Create an enemy factory that can create enemies from an id of some sorts. While loading your level, you can then call the factory when you need to create an enemy:
class EnemyFactory
{
Enemy CreateEnemy(int id)
{
if (id == 0)
return new Snake();
return new GenericEnemy();
}
}
void LoadLevel()
{
// bla bla
Level level = new Level();
int enemyId = LoadFromFile();
level.AddEnemy(EnemyFactory.CreateEnemy(enemyId));
}
This way you get rid of the nasty cloning code, and you can control all enemy instantiation in the factory class.
use an abstract method that calls a copy constructor:
public abstract class Enemy
{
private readonly int mEnemyData;
protected Enemy(Enemy pEnemy)
{
mEnemyData = pEnemy.mEnemyData;
}
public abstract Enemy Clone();
}
public sealed class GenericEnemy : Enemy
{
private readonly double mGenericEnemyData;
private GenericEnemy(GenericEnemy pGenericEnemy)
: base(pGenericEnemy)
{
mGenericEnemyData = pGenericEnemy.mGenericEnemyData;
}
public override Enemy Clone()
{
return new GenericEnemy(this);
}
}

Categories

Resources