ScriptableObject resetting Array of PropertyDrawer to Length of Zero - c#

I have a simple class called Behaviour that saves a string and a string-array. It looks like this:
[System.Serializable]
public class Behaviour {
public string Methodname;
public string[] Parameters;
}
As you might already expect, this class is to save a method to make it possible to plug methods into the unity inspector. Methodname is the name of the method plugged in, while Parameters are all the parameters for that method formatted in a way that a static utility class can read (these string get converted to objects to then be used as parameters. so a string of "i25" would get converted to an integer parameter of 25. the array is a string as object[] cannot be serialized and thusly not saved.
I will strip out all unnecessary logic and focus on what's going wrong. Lets just assume that we want to save a single integer into the first index of the parameters array. The PropertyDrawer of Behaviour would then look like this:
[CustomPropertyDrawer(typeof(Behaviour))]
public class BehaviourEditor : PropertyDrawer {
private Behaviour propertyReference;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
propertyReference = fieldInfo.GetValue(property.serializedObject.targetObject) as Behaviour;
// Check if the string array is null. This happens the very first time the scriptable object is created
if(propertyReference.Parameters == null) propertyReference.Parameters = new string[1];
// Check if the first index is null. In this simplified example also only happening the very first time.
if(propertyReference.Parameters[0] == null) propertyReference.Parameters[0] = "i0";
int value = (int)DataConverter.StringToObject(propertyReference.Parameters[index]);
value = EditorGUILayout.IntField(value);
propertyReference.Parameters[0] = DataConverter.ObjectToString(value);
}
}
The DataConverter simply converts from object to string and vice versa (so int n = 9 would become "i9" or "i255" would become object x = 255). All of this logic works.
Again to clarify: This is the PropertyDrawer of Behaviour. Behaviour is a private [SerializedProperty] within a ScriptableObject.
If the array is null, the if == null triggers and puts the array Parameters to the length of one. The later logic all works, we can assign values to the int field from the EditorGUILayout and the value there gets correctly saved into the array. All of that logic works.
BUT: something is changing the Parameters-Array of Behaviour. I believe that something to be the ScriptableObject. The very next frame, Parameters is no longer null (obviously) and we try to thusly access index position 0. Which results in an index out of range exception because something changed Parameters to new string[0]. They didn't set it to null, they set it's length to 0.
Why? What could possible trigger this logic? If I make Parameters a property and set a breakpoint into the set-method, noone else calls it but my own code above, yet the array still becomes length of 0. Any ideas?

Unity's Serializer indeed auto-initializes any fields of a serializable type such as a list or array or string etc! -> They will never be null, worst case they will be empty.
If you simply want defau values for your fields you can simply assign them in your class itself, no drawer needed for this:
[Serializable]
public class Behaviour
{
public string Methodname = "ExampleMethod";
public string[] Parameters = new string[1] { "i0" };
}
In general do not directly change values via the target in editor scripts!
This will cause you a lot of headaches regarding marking changed objects as dirty, serialize/save values correctly persistent, handle undo/redo etc
Always rather go through the SerializedProperty and use e.g.
var methodName = property.FindPropertyRelative(nameof(Behavior.Methodname));
var parameters = property.FindPropertyRelative(nameof(Behavior.Parameters));
and then do e.g.
EditorGUILayout.PropertyField(methodName);
and access and assign e.g.
parameters.arraySize = 1;
var parameter = parameters.GetElementAtIndex(0);
parameter.stringValue = "i0";
but as said that's only an example, you really want to simply put the default values in the class, not the drawer.
Also putting
EditorGUI.BeginProperty(position, label, property);
...
EditorGUI.EndProperty();
around your property drawer content is essential for the dirty state handling! See Property Drawers

Related

Unnecessary assignment of a value to 'player"

I am having a problem assigning values in my function. Here is my code
//Player cents
private int add_cents = 3;
public int player_1, enemy_1, enemy_2, enemy_3;
public void players_ready()
{
add_cents_player(player_1, add_cents);
}
public void add_cents_player(int player, int cent_v)
{
player = player + cent_v;
}
I want to be able to call this function and input whoever is the active player (player) and increase their value by (cent_v). However, player = player + cent_v; is saying "Unnecessary assignment of a value to 'player" and I don't understand why. It wouldn't be possible to hard code, as it is dependant on what who is the active player.
One option is to change the method return type:
private int add_cents = 3;
public int player_1, enemy_1, enemy_2, enemy_3;
public void players_ready()
{
player_1 = add_cents_player(player_1, add_cents);
}
public int add_cents_player(int player, int cent_v)
{
return player + cent_v;
}
int is a value type. It is passed by value meaning the player will actually be a copy of player_1. If you then change the player inside your method this doesn't affect in any way the player_1 since it is no reference and no relationship between them.
It looks like what you wanted to do would be using ref in order to "force" the value to be passed by reference
public void players_ready()
{
add_cents_player(ref player_1, add_cents);
}
public void add_cents_player(ref int player, int cent_v)
{
player += cent_v;
}
thus that after calling players_ready the value player_1 is actually increased
There are 2 ways to pass a variable to a function. 1 is by reference, meaning you pass a reference to a variable into the function. This is what happens with variables of type object - not the whole object's memory is copied and supplied to the function, but only an address to the piece of memory where that object resides.
For int, float etc. this is different. The values are passed by value.
Also see the relevant msdn docs.
There is a fundamental difference between the two options: reference types are passed by reference and can be altered and the original object also gets altered. E.g. this works:
class MySimpleObject // an object is a reference type
{
public int someValueType; // int is a value type
}
...
var x = new MySimpleObject();
myFunc(x); // increment x.someValueType by 5
This does not count for objects passed by value, which is what happens with int, as its a value type. Therefore your function does nothing, because its only manpulating the local value, the reference is not passed.
var player_1 = 5;
add_cents_player(player_1, 15);
// Player_1 is still 5
add_cents_player(player_1, 15);
// Player_1 is still 5
...
And thats what the compiler is complaining about; you're adding a value to the local parameter in the function. But since you don't return this, or read the value at some point the compiler is like 'hey this code does nothing, and therefore its better to remove it.

String value not getting modified in a recursion

I am trying to modify a string inside a recursion . Here is the code :-
using System;
public class Program
{
public static void Main()
{
string s = "abcdc";
string added = "";
checkIfPalindrome(s , added);
Console.WriteLine( added);
}
public static void checkIfPalindrome(string s , string added)
{
//Console.WriteLine(added);
if(s.Length < 2)
{
return;
}
if(s[0] == s[s.Length -1])
{
checkIfPalindrome(s.Substring(1,s.Length-2) , added);
}
else
{
added = s[0] + added;
checkIfPalindrome(s.Substring(1,s.Length-1), added) ;
}
}
}
Here is the fiddle for the same :-
<iframe width="100%" height="475" src="https://dotnetfiddle.net/Widget/u4119h" frameborder="0"></iframe>
I am expecting added to be modified inside recursion and appear as a result outside function call and inside main method, since string is a reference type .But the value of added literal is an empty string . Although when recursion hits the base case , it has ba as value.
Also correct value is reflected when ref keyword is used for passing added
Is my understanding wrong ?
At the very top, you're assigning a value to the added local in your Main function. But you're never changing that value. You're only ever changing what the argument of checkIfPalindrome points to. This has no effect on the local added in Main, unless you pass it by reference - using ref.
Reference types have reference semantics, yes. But that doesn't mean that a reference to a reference type has the same property. If you modified the value of added, you would get the behaviour you expect - but you only ever replace the argument's reference - pointing it somewhere else. Of course, strings in .NET are immutable, so you can't actually change the value of added - your only option is to pass the string by reference (or better, make it a return value, which makes for much clearer recursion anyway).
string is a reference type but immutable which means it cannot be changed after it has been created. Every change to a string will create a new string.

Storing an object by reference or workarounds

I am building internal logic for a game in C# and coming from C++ this is something that might be lost in translation for me.
I have an object, Ability that calculates the bonus it provides and returns that as an integer value. The calculation is meant to be dynamic and can change depending on a variety of variables.
public class Ability: Buffable
{
public string abbr { get; private set; }
public Ability(string name, string abbr, uint score) : base(name, score)
{
this.abbr = abbr;
}
// Ability Modifier
// returns the ability modifier for the class.
public int Ability_modifier()
{
const double ARBITARY_MINUS_TEN = -10;
const double HALVE = 2;
double value = (double)this.Evaluate();
double result = (value + ARBITARY_MINUS_TEN) / HALVE;
// Round down in case of odd negative modifier
if (result < 0 && ((value % 2) != 0))
{
result--;
}
return (int)result;
}
I then have another object, Skill which should be aware of that bonus and add it into it's calculation. I wanted to pass an Ability into the constructor of Skill by reference and then store that reference so that if the Ability changed the calculation would as well. The obvious problem with this being that apparently storing references is taboo in C#.
Is there either a work around way to do this or an alternate way to approach this problem that my pointer infested mind isn't considering? I would greatly prefer not to have to pass the ability to the function that evaluates Skill every time, since the one referenced never changes after construction.
The obvious problem with this being that apparently storing references is taboo in C#.
Absolutely not. References are stored all over the place. You're doing it here, for example:
this.abbr = abbr;
System.String is a class, and therefore a reference type. And so the value of abbr is a reference.
I strongly suspect you've misunderstood how reference types work in C#. If you remember a reference to an object, then changes to the object will be visible via the reference. However, changes to the original expression you copied won't be.
For example, using StringBuilder as a handy mutable reference type:
StringBuilder x = new StringBuilder("abc");
// Copy the reference...
StringBuilder y = x;
// This changes data within the object that x's value refers to
x.Append("def");
// This changes the value of x to refer to a different StringBuilder
x = new StringBuilder("ghi");
Console.WriteLine(y); // abcdef
See my articles on references and values, and parameter passing in C# for much more detail.
I am not quite seing enough of your code to give a concrete example, but the way to do this is to pass in a lambda delegate such as () => object.property instead of this: object.property.
In C#, there are reference types and value types. All non-value-type objects are passed by reference, so there should be no issue with references. Just pass it, and it will be passed by reference.

XNA's Vector2 class, property returns an error when attempting to change its value

I will first illustrate my issue with some code:
class ExampleClass
{
private Vector2 _myVector;
public Vector2 MyVectorProperty { get { return _myVector; } set { _myVector = value; } }
private void MyMethod()
{
_myVector = Vector2.Zero; // Setting to zero
MyVectorProperty.X = 5; //Cannot modify the expression because it is not a variable (returns an error)
_myVector.X = 5; //Works fine!
}
}
As you can see, I am getting the error "Cannot modify the expression because it is not a variable" when trying to change the value of X and Y on the vector using the property. I am unsure why this happens and haven't had any luck looking on the net and i was wondering why this is and how (if) I can fix it?
Another sub question, is it good programming practice to use the public properties or the private/protected fields when working inside the class they belong to?
You should be happy compiler does not let you do so, otherwise you'll be really surprised with result of operation being lost.
MyVectorProperty is property - which means getting the value is call to a function returning the value (something like this.get_MyVectorProperty()).
Since type of the MyVectorProperty is Vector2 which is struct it means that value returned by the get_... function is a copy of value, not reference like it would be in case of normal class.
Changing field X of above copy would simply change X inside of copy of the value, and since that copy of the value is not assigned to anything it will be lost.
Vector2 is a struct (value type), so your property returns the value of _myVector (i.e. a copy) and you can't change that.

Unable to save a float value in to a variable (float) property

I am trying to save a floating point values (from a Float[] array) to a variable property (of type float), but I am not able to save it correctly. Everytime I want to save a new value, the variable property never accepts the new value and keep retaining the intialized value only. Here I am trying to save value,
CommandLineVariables.PiSenseResistor = tempFloatArray[0];
Where,
CommandLineVariables is the class name
PiSenseResistor is the variable property
tempFloatArray is the float array from which I want to save value
I tried using single stepping and watching the variable property value, but always after the execution of the above mentioned instruction, it shows the initialized value only.
The same thing I am doing with other variable properties as well and they are working correctly. I am wondering what I am doing wrong with this saving of floating point number into variable property.
Edited
Adding some extract of the code:
//variable initialization
private static float piOffsetPressure = 1.01295f;
//Property definition for the variable
public float PiOffsetPressure
{
get
{
return piOffsetPressure;
}
set
{
piOffsetPressure = value;
}
}
//Copy the parameter value into its corresponding property
if (!Convert.ToBoolean(ReturnCode))
{
CommandLineVariables.PiOffsetPressure = tempFloatArray[0];
CommandLineVariables.PdOffsetPressure = tempFloatArray[1];
}
You are setting PiOffsetPressure, but the property you posted is PiSenseResistor. Why would setting one affect the other?

Categories

Resources