Unity PropertyDrawer access parent object - c#

I have a class similar to this one:
class Foo : MonoBehaviour {
public Car a;
[MyProperty]
public Trunk b;
}
I've implemented MyPropertyAttribute and a MyPropertyDrawer which inherits PropertyDrawer to create custom inspector for MyProperty decorated attributes.
Now, my issues is that I want to access somehow the actual object instance of the class Foo who owns that b instance.
In other words, I have:
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
this._property = property;
Car c = property.serializedObject.targetObject as Car;
Foo ownerOfCar = ...; // <<< Idk how to get this instance
}
I want to use the Foo reference to change other properties in the class, for example the b variable, which's a Trunk.
I've tried many properties and methods (like objectReferenceValue, tweaking the serializedObject values etc.) but none really worked :(.
Is it possible?

Related

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).

Unity field containing ScriptableObject not serializing

I'm having a problem getting a field to serialize properly.
It links to a custom type which extends ScriptableObject. A custom editor is creating the instance of this object, and it's not being saved as an asset file. It's my understanding at the moment that you can do this - Unity should be saving this data into the scene instead of the project automatically.
First up, the MonoBehaviour class. This I've set up to hold an instance of Bar and a string just to double check the serialization is working at all. I use fields with exposing properties, so I've done it here as well as I know reflection is involved.
public class Foo : MonoBehaviour
{
[SerializeField]
private Bar bar;
[SerializeField]
private string check;
public Bar Bar { get { return this.bar; } set { this.bar = value; } }
}
Next, the ScriptableObject based Bar class. This is the class I want to instance and link, but not save as an asset file.
[Serializable]
public class Bar : ScriptableObject
{
[SerializeField]
private string data;
}
Finally, a CustomEditor which allows me to create and link an instance of Bar:
[CustomEditor(typeof(Foo))]
public class StreamableResourceTestEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (GUILayout.Button("Link unsaved Bar asset"))
{
Bar bar = ScriptableObject.CreateInstance<Bar>();
bar.name = "Unsaved asset";
Foo foo = (Foo)this.target;
foo.Bar = bar;
EditorUtility.SetDirty(foo);
}
}
}
Before running, having entered data and pressed the button:
During running:
After running:
The string serializes properly.
However, the ScriptableObject fails to be serialized into the runtime data. Not only that, after running the project the instance is still linked, but has lost it's type!
Does anyone know what the problem is with this?
Update:
Just tested this without extending Bar from ScriptableObject, and creating it as a normal C# instance instead of with ScriptableObject.CreateInstance<Bar> and it all works as expected.
Whatever the problem is, it relates to extending from ScriptableObject.

How to access base class from within subclass?

I have an abstract class called BaseMonster. This "base" class holds several values, such as float dyingTime =2, that will be the same for all monster subclasses. But since there are a lot of values like this, I didn't want to pass them into the object call when you "new" a monster.
So my question is: for subclass Ghoul, which inherits from BaseMonster, how can I pull the value for dyingTime from BaseMonster?
EDIT: To clarify, I need to have these values in Ghoul in order to run certain movement calculations that are different than the regular BaseMonster.
You should study the concepts of inheritance (BaseMonster being a base class for Ghoul) and access modifiers which govern which fields, methods and property are accessible from where.
You can find all access modifiers here
Also see this simple example:
public class Base
{
private float a;
protected float b;
public float c;
}
public class Sub : Base
{
public void DoSomething()
{
float x = base.a; // Error cannot access private member a
// Note that putting base before b and c here is optional
// though it does help with naming conflicts
// if Sub would also have a member b you could differentiate the two
// using this.b and base.b
float y = base.b; // Works
float z = base.c; // Works
}
}

C# forcing static data member in subclass

Is it possible to force a static data member in inherited subclasses?
Here is my problem/thought process:
I want to make a "GameObject' base class that all other objects inherit from to lend to polymorphism.
I want each inherited class to have a static data member list of vertexes to render the polygons. The exact process would be:
Create object based on static list of vertexes
Apply textures
Rotate object based on instanced object's rotation variables
Transform object based on instanced object's world coordinates
for all objects that inherit from Game Object, I'd like to guarantee that it has the static vertex list
Should I care if it has the list or not, or should I just care that it has a draw method (guaranteed by an interface iRender)?
By virtue of inheritance, a protected static List<Vertex> in the base class is automatically part of any derived classes. This should work as long as you don't need each derived class to have its own list separate from the list in any parent class.
There is a single instance of a static class member declared in a base class for all descendants and the base class itself. Therefore a single list of vertices in a base class will not do. What can be done:
- in the base class declare
private static Dictionary<string, List<vertex>> PointsPerClass =
new Dictionary<string, List<vertex>>();
You did not say how descendants specify their vertices, but let's say there a virtual base class method overwritten by all descendants:
protected virtual FillStaticVertices(List<vertex> aVertices)
{
}
Then the base class has public method:
public GameObject CreateObject()
{
string key = GetType().FullName;
List<vertex> points = null;
if (PointsPerClass.ContainsKey(key))
points = PointsPerClass[key];
else {
points = new List<vertex>();
PointsPerClass.Add(key, points);
FillStaticVertices(points);
}
// CreateObjectFromPoints is an abstract method implemented by descendants
GameObject gameObj = CreateObjectFromPoints(points);
// apply textures, rotations, etc
return gameObj;
}
If the GameObject is an interface, then you can enforce the existence of a static Property.
interface GameObject
{
static List<vertex> vertices {get; set;}
//other common code
};
Alternatively, you could just have the list of vertices as a protected data member in GameObject itself, and have the derived classes access it.

C# polymorphism question once again - overriding fields?

This time I have problem with virtual fields.
I have core class for my game objects. This class contains a field with Model class object. Model's object contains values such as position etc.
Now - while drawing I need to read position of each object from it's model. The problem starts when instead of default model class I'm using derived. Example:
abstract class GenericGameObject { public DefaultGameObjectModel Model = new DefaultGameObjectModel(); }
class Missile : GenericGameObject { public new MissileModel Model = new MissileModel(); }
class DefaultGameObjectModel { public Vector2 Position = new Vector2(){X=0}; }
class MissileModel : DefaultGameObjectModel { }
Missile m = new Missile();
m.Model.Position.X = 10;
// NOT OK! ((GenericGameObject)m).Model.Position.X == 0
I tried to make Model defined as virtual property instead of field, but this fails because
derived properties have to be of same type as their base. Casting is futile because there will be many other model types. What can I do if I want to read a value from derived class, not from base?
I asked this question already but the answer didn't brought any solution. Explaination:
to use interface IGameObjectModel
Concept is good, but I have to enforce fields. Interfaces can't define fields so I have to define property. But then I can't do IGameObjectModel.Position.X=10 because Position is not a field.
to make GenericGameObject a generic type such as GenericGameObject and Missile a type derived from GenericGameObject
I couldn't then cast a missile to GenericGameObject and generally store those object on same list. Of course I could make main base type which those two could inherit from, but then I wouldn't have access to Model field.
to make model a property instead of field.
It is impossible to change property type in derived class.
Whad can I do?
In this case your best approach would be to assign the value of your parent field to be an instance of your derived class, then either cast it back to your derived class or hold on to a reference of your derived class (probably better).
Or you could go down this road, which I like the best...
abstract class GenericGameObject
{
public DefaultGameObjectModel Model
{
get { return ModelInternal; }
}
protected abstract DefaultGameObjectModel ModelInternal { get; }
}
class Missile : GenericGameObject
{
private MissileModel model = new MissileModel();
public override DefaultGameObjectModel ModelInternal
{
get { return model; }
}
public new MissileModel Model
{
get { return model; }
set { model = value; }
}
}
class DefaultGameObjectModel { public Vector2 Position = new Vector2(){X=0}; }
class MissileModel : DefaultGameObjectModel { }
Missile m = new Missile();
m.Model.Position.X = 10;
This solution gives you access to your base model instance from the context of the base class, while giving you access to your concrete model instance from the inherited class.
There's no such thing as 'virtual fields'. Only properties and methods can be virtual.
In your Missle class, you appear to be using the new keyword as a modifier to hide the inherited member named Model.
When you hide an inherited member this way, you don't get polymorphic behavior. This is bad because the code in your base class (if it references the Model field) may not work as you expect.
Best bet: Use a property. Cast or generalize (move members to base class) as necessary.
If you used an interface, I believe you'd still be able to call:
IGameObjectModel.Position.X = 10;
As long as the object type you used for Position has a read/write property called X. Your interface would look something like:
public interface IGameObjectModel
{
Vector2 Position
{
get;
// only add set if you need to set the Position object outside of your class
// set;
}
// ...other properties
}
You said that if you used an interface with a property that you "can't do IGameObjectModel.Position.X=10". I assume this is because Vector2 is a struct and therefore has value-type semantics. If this is correct, you should simply assign the Position property to a new Vector2 calculated from the original value. For example:
Missile m = new Missile();
m.Model.Position = new Vector2()
{
X = m.Model.Position.X + 10,
Y = m.Model.Position.Y
};
Did you try using generics? Using generics you can separate your game object model from your game object. You can then instantiate your game object with any game object model. The game object can communicate with the game object model thru standard interfaces.
interface IGameObjectModel {
void Shoot();
:
}
class GameObject<TModel> where TModel:IGameObjectModel {
public TModel Model;
public GameObject(TModel model) {
Model = model;
}
public void Shoot() {
Model.Shoot();
}
:
}
class MissleModel : IGameObjectModel {
public void Shoot() {
:
}
}
With the above, you can then instantiate your game object with the missle model :-
MissleModel model = new MissleModel();
GameObject<MissleModel> obj =
new GameObject<MissleModel>(model);

Categories

Resources