Get the changed value from OnValidate - c#

Say I have a class with multiple variables that I want to check via OnValidate, it doesn't seem to make much sense to check every variable in said class every time I change one variable through the Unity Inspector. So is there a way to just check the variable that was changed?
[System.Serializable]
public class Stat
{
[SerializeField]
private float _value;
Value { get; set; }
}
public class TestClass : MonoBehaviour
{
public Stat stat1;
public Stat stat2;
public Stat stat3;
void Start(){}
private void OnValidate()
{
if (stat1.Value < 0)
{
throw new ArgumentException("Error with stat1");
}
else if (stat2.Value < 0)
{
throw new ArgumentException("Error with stat2");
}
else if (stat3.Value < 0)
{
throw new ArgumentException("Error with stat3");
}
}
}
So in this case, everytime I change either stat1, stat2, or stat3 in Unity Inspector. OnValidate will go through and check every variable which doesn't seem very efficient. I'm wondering if there is a way to only get the value that was changed so my code can look something like this:
public class TestClass : MonoBehaviour
{
public Stat stat1;
public Stat stat2;
public Stat stat3;
void Start(){}
private void OnValidate()
{
if (value < 0) // value being the value that was changed
{
throw new ArgumentException("Error with {value}");
}
}
}
This shouldn't be too relevant but in case anyone is wondering why I have a Stat class, I am following a tutorial on creating character stats that have a SerializeField.
Edit: Added code examples for clarification

Related

Can I create array of Scenes unity?

I want to create public Scene[] levels; and manually assign my levels to the array, then loop through it and generate level selection buttons, but it won't show up in the inspector.
Is there any workaround?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class True : MonoBehaviour
{
// Start is called before the first frame update
public static int money;
[SerializeField]
public SceneManager[] scenes;
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void nextscencefirst()
{
SceneManager.LoadScene("Level2");
money++;
}
}
SceneManager is a built-in static class to control scenes during runtime. We can go to any scene by calling LoadScene method from that class. Also SceneManager cannot be seen in Inspector since it cannot be serialized class.
A reminder: Your scenes should be listed on build settings for these to work.
There are three ways to do what you want to do:
Method 1. Create a string list that holds names of scenes
public List<string> sceneNameList = new List<string>;
Method 2. Create a list of indices of scenes that added in build settings.
public List<int> sceneBuildIndexList = new List<int>
If the names of scenes are long or somehow difficult or you want to use sceneBuildIndex value it would be good to create a class and reach scenes from it
[System.Serializable]
public class SceneData
{
public int sceneBuildIndex;
public string sceneKey;
//public void LoadScene() //Maybe
}
public List<SceneData> sceneDataList = new List<SceneData>();
public SceneData GetSceneData(string key)
{
for (int i = 0; i < sceneDataList.Count; i++)
{
if (sceneDataList[i].sceneKey == key)
{
return sceneDataList[i];
}
}
return null;
}
Method 3: In Unity most classes are created from UnityEngine.Object, so it may let you assign scene from the Inspector.
[System.Serializable]
public class SceneData
{
public UnityEngine.Object scene;
public string key;
public void LoadScene()
{
if (scene == null)
{
Debug.LogError("Scene is not assigned");
return;
}
string pathToScene = AssetDatabase.GetAssetPath(scene);
SceneManager.LoadScene(pathToScene); //If the scene is not set in BuildSettings it will throw an error.
}
}
public List<SceneData> sceneDataList = new List<SceneData>();
public SceneData GetSceneData(string key)
{
for (int i = 0; i < sceneDataList.Count; i++)
{
if (sceneDataList[i].key == key)
{
return sceneDataList[i];
}
}
return null;
}
private void Awake()
{
SceneData data = GetSceneData("Level1");
if (data != null)
{
data.LoadScene();
}
}

Why isn't my variable from another class method being registered?

My variable in my parameter method is not being called in another class.
I tried telling it that we are getting the PlayerdmgAmount from the playerlivesdisplayed class. I get an error that says that PlayerLivesDisplay can not be converted to an int.
So I comment that out and wrote in the int value again. The code runs, but it is not doing what I want it to do.
public class PlayerLivesDisplay : MonoBehaviour
{
public void takeLives(int PlayerdmgAmount)
{
playerLives -= PlayerdmgAmount;
displayUpdate();
if (playerLives <= 0)
{
//TODO load mainMenu
}
}
}//playerLives
public class DamgePlayer : MonoBehaviour
{
private void OnTriggerEnter2D(Collider2D othercollision)
{
//PlayerLivesDisplay PlayerdmgAmount = GetComponent<PlayerLivesDisplay>()
int PlayerdmgAmount = 1;
FindObjectOfType<PlayerLivesDisplay>().takeLives(PlayerdmgAmount);
}
}
public class Attacker : MonoBehaviour
{
[Range(0f, 10f)] [SerializeField] float walkSpeed = 1f;
[SerializeField] int PlayerdmgAmount = 1;
GameObject currentTarget;
public void hurtplayer(int PlayerdmgAmount)
{
FindObjectOfType<PlayerLivesDisplay>().takeLives(PlayerdmgAmount);
}
}
What I am trying to achieve:
Have the attacker script have Player dmg amount on them.
Golem1 = take 5 lives away
Fox: takes 2 lives away
Pass these variables (when collided) to the players health damage (DamagePlayer script)
Then go to the player lives display class takeLives method and input the damage variables into the parameters that was initiated from the attackers script.
If your takeLives method takes an int variable as argument, you cannot pass your PlayerLivesDisplay object, you need to pass an int instead (that's what the error is about). PlayerLivesDisplay may contain the PlayerdmgAmount (so the int) but is not in itself the PlayerdmgAmount.
Depending on what you are trying to accomplish, you could do something like:
In your PlayerLivesDisplay add a property and use it to store the value that you need to get later on:
public class PlayerLivesDisplay : MonoBehaviour
{
...
public int PlayerdmgAmount { get; set; }
...
public void takeLives(int playerdmgAmount)
{
...
this.PlayerdmgAmount = playerdmgAmount;
}
}
Now you can access the value in other classes:
public class DamgePlayer : MonoBehaviour
{
private void OnTriggerEnter2D(Collider2D othercollision)
{
int playerdmgAmount = GetComponent<PlayerLivesDisplay>().PlayerdmgAmount;
...
}
}

Why isn't the value of class member changing?

Im fairly new to programming and am making a rpg battle simulator for practice. My problem is that I can't seem to make my attack method work. Heres the classes I have:
class Person
{
protected int attack;
protected int health;
public Person(int _attack, int _health)
{
attack = _attack;
health = _health;
}
public int GetAttack()
{
return attack;
}
public int GetHealth()
{
return health;
}
public int Attack(int _health)
{
_health -= attack;
return _health;
}
}
class Hero : Person
{
public Hero(int _attack, int _health)
:base (_attack , _health)
{
}
}
class Enemy : Person
{
public Enemy(int _attack, int _health)
:base (_attack , _health)
{
}
}
and heres the main:
class Program
{
static void Main(string[] args)
{
Hero Joe = new Hero(4, 10);
Enemy Tim = new Enemy(5, 20);
Joe.Attack(Tim.GetHealth());
Console.WriteLine(Tim.GetHealth());
Console.WriteLine(Tim.GetAttack());
Console.ReadLine();
}
}
My guess is that the attack method is doing the math, but is never changing the health that is passed into it. Or maybe it has something to do with the fact that their protected. Another thought of mine is that it doesn't need to return anything.
How would I go about making my attack method work? I just want it to take in somethings health value, subtract the attacking things attack value, and save the calculated value as the health? Thank you for reading this !
When you pass around an int, you are making copies of the number, not passing references to the same number in memory.
When you pass around an instance of a class, you are passing around references to the same object in memory.
Therefore, I suggest changing your design to something like this:
public void Attack(Person target)
{
target.health -= this.attack;
}
...
Joe.Attack(Jim);
You got a couple things you can improve here. First thing is naming conventions, I recommend reading the design guidelines.
First, If you change your Attack and Health to properties instead of protected fields, you expose getter and setter methods for it. Obviously you only want to set form the controller so make the set a private set:
public class Person
{
public int Attack { get; private set; }
public int Health { get; private set; }
public Person(int attack, int health)
{
Attack = attack;
Health = health;
}
// Rest of code
}
When you do it like this you eliminate the need for your individual GetAttack() and GetHealth() methods.
Next, the names of your parameter in Attack() is misleading. I assume you want the parameter to be "attack" and not "health" right? Since our setter is private this method allows us to only access health modifications inside the class. Since we already changed Health to be a property, we don't need to return it anymore so this method can now be void:
//Our property is named Attack so this has to be AttackAction or something different
public void AttackAction(int attack)
{
Health -= attack;
}
And if we put it all together:
public class Person
{
public int Attack { get; private set; }
public int Health { get; private set; }
public Person(int attack, int health)
{
Attack = attack;
Health = health;
}
public void AttackAction(int attack)
{
Health -= attack;
}
}
public class Hero : Person
{
public Hero(int attack, int health)
:base (attack , health)
{
}
}
public class Enemy : Person
{
public Enemy(int attack, int health)
:base (attack , health)
{
}
}
I made a fiddle here that shows this new code in action.
You are calling Attack() but are never saving the value returned by that method. You need to add a Setter for the health field, then set that value to the method's returned value. Something like
Health Property
public int Health
{
get { return health; }
set { health = value; }
}
Setting the Value
Tim.Health = Joe.Attack(Tim.Health);
If you want to keep the design pattern the same (you don't, see Blorgbeard's answer) you could add a SetHealth() method to Person and do something like this:
Tim.SetHealth(Joe.Attack(Tim.GetHealth());
This gets Tim's health total, passes that to Joe's attack method, which returns a value (what Tim's new health total should be) and then Tim's health is set to this value.

how to get an enum from another script in unity

I am trying to create a win condition script pulling the status of an enum from a different script and then do stuff with it.
crowd.cs
public enum crowdOptions {None, TeamA, TeamB};
public crowdOptions Crowd;
Crowd = crowdOption.None;
I have the crowd doing a bunch of stuff, but lets say it is set to none.
winning.cs
if (Crowd = crowdOption.None){
do something
} else if (Crowd = crowdOption.TeamA){
do something
} else {
do something
}
I tried a GetComponent and set the result of of Crowd to a newvariable, but I don't think I did that right
public CrowdSway = GameObject.Find("crowdManager").GetComponent<CrowdManager>();
I also tried
if (CrowdManager.Crowd = crowdOptions.None) {
print("none");
} else {
print("hmmmmmm");
}
that didn't work either.
In order to access the Crowd enum variable in your crowd.cs class from another script, that script needs to have an instance of a Crowd object. For example:
public class Crowd : MonoBehaviour
{
public enum crowdOptions {None, TeamA, TeamB};
public crowdOptions crowdOpts;
}
public class Winning : MonoBehaviour
{
void Start()
{
Crowd myCrowd = new Crowd();
if(myCrowd.crowdOpts == crowdOptions.None)
{
//do something
}
}
}
Alternatively, you could also make your crowdOptions enum variable static. Then you can access it from any script by name.
public class Crowd : MonoBehaviour
{
public enum crowdOptions {None, TeamA, TeamB};
public static crowdOptions CrowdOptions;
}
public class Winning : MonoBehaviour
{
void Start()
{
if(CrowdOptions == Crowd.crowdOptions.None)
{
//do something
}
}
}

Show variable herited from interface on Unity inspector

I want to create some game in Unity and I've started by creating a class hierarchy in order to be able to use polymorphism. So I've created some interfaces with both methods and also variables.
As said in the C# interfaces documentation, I've created my interface with variables like this
public interface IUnit : ISelectable {
int healthPoint { get; set; }
bool isIndestructible { get; set; }
/******************************/
void takeDamage(int dmg);
void die();
}
Now, I'm implementing my interface in a class:
[System.Serializable]
public class BasicUnit : MonoBehaviour, IUnit {
private int _healthPoint;
public int HealthPoint { get { return (_healthPoint); } set { _healthPoint = value; } }
private bool _isIndestructible;
public bool isIndestructible { get { return (_isIndestructible); } set { _isIndestructible = value; } }
public void takeDamage (int dmg)
{
if (this.isIndestructible == false) {
this.HealthPoint -= dmg;
if (this.HealthPoint <= 0) {
die();
}
}
}
public void die()
{
Destroy(gameObject);
}
}
My problem is that my variables, healthPoint and isIndestructible are not shown in Unity's inspector despite being public variables. I've tried using [System.Serializable] but it doesn't work.
Well my question is quite simple, how should I do to show my inherited variables in Unity's inspector ?
Note: I'm trying to have nice and readable code, so if possible I would like to keep my IUnit class as an interface and my variables inside my IUnit.
It's not your inherited code that's hidden. Inheritance has no effect whatsoever in the display of the fields.
Rather, what's really happening is that you have two fields that are serializeable, but marked private (_healthPoint & _isIndestructible),
and two public Properties. Unfortunately, Unity can't process and display properties out of the box.
Fortunately, here's a simple solution. I found this on Unity Wiki, and saved it for a situation like this :)
Expose Proerties in Inspector from Unity Wiki
How it works
Basically, any Monobehavior that you want the properties exposed on should inherit from ExposableMonoBehaviour and also
1. private fields (like your _healthPoint) should have the [SerializeField, HideInInspector] attributes
2. public properties (like HealthPoint) should have the [ExposeProperty] attribute
Partial example
public class BasicUnit : ExposableMonoBehaviour, IUnit {
[SerializeField, HideInInspector]
private int _healthPoint;
[ExposeProperty]
public int HealthPoint {
get { return (_healthPoint); }
set { _healthPoint = value; }
}
}
If still anyone have problem with setting value of the variables inherited from an interface in inspector, Thanks to Jetbrains Rider , I found a solution. just use [field: SerializeField] before introducing the variable in the child script.
example :
public interface IAlive
{
float HealthPoint { get; set;}
}
public class Cat : MonoBehaviour , IAlive
{
[field: SerializeField] float HealthPoint { get; set;}
}
I found it, you can use [SerializeField] on any field you want to show in the inspector. However, it have to be used on the private variable you want to serialize, not the public one.
public class BasicUnit : MonoBehaviour, IUnit {
[SerializeField]
private int _healthPoint;
public int HealthPoint { get { return (_healthPoint); } set { _healthPoint = value; } }
[SerializeField]
private bool _isIndestructible;
public bool isIndestructible { get { return (_isIndestructible); } set { _isIndestructible = value; } }
}

Categories

Resources