I am new to C# and Unity, but slowly learning. That said, please explain to me like I’m a 5 year old.
I have a script on my player game object that is tied to the UI. My “int health” relates to how many hearts are shown in the UI.
I would like to access this from my GameController script, wherein enemies can damage and items can heal the player. How can I access “health” from my GameController script?
class Player : MonoBehaviour
{
public int Health;
}
class GameController : MonoBehaviour
{
void Update()
{
//Ding the Health
}
}
And you want to change or view health from GameController.
Simply add a public variable in GameController.
class GameController : MonoBehaviour
{
public Player MyPlayer;
void Update()
{
//Ding the Health
MyPlayer.Heath--;
}
}
Now go to the editor, click on the GameObject with the ‘GameController’ class, and you’ll see in the Inspector that there’s now a property called ‘MyPlayer’.
Drag-and-drop your GameObject with the Player component into this MyPlayer box.
There - GameController can now directly interact with your player health - or anything else you want, including the Animator class (through MyPlayer.GetComponent())
You access it by having health be either a public variable or through a public method on the player script. Examples:
public int Health;
or
private int health = 5;
public void CauseDamage(int damageAmount)
{
health -= damageAmount;
}
public int GetHealth()
{
return health;
}
Another way is to use getters and setters, but some new users get confused by them and they don’t seem as popular today as when I first was learning C# back in the day.
Now how to actually access this player script from the GameController script is done through a reference to the player script. How to get that reference can be a little tricky, depending on if you are instantiating the player object or if it is part of the scene. If part of the scene you can just establish the reference by dragging it to a public field on the GameController script. Otherwise you can use a singleton, or one of the find methods.
For example, lets say the player object is an instantiated prefab, and the GameController is a part of the scene (pretty common way of doing things). The GameController script is on a GameObject which has been tagged “GameControllerObject”. You can do something like below:
Player script:
private int health = 5;
void Start()
{
GameObject gameControllerObject = GameObject.FindWithTag("GameControllerObject");
if (gameControllerObject != null)
{
GameController gameControllerScript = gameControllerObject.GetComponent<GameController>();
if (gameControllerScript != null)
{
gameControllerScript.PlayerScript = this;
}
else
{
Debug.Log("GameControllerObject is missing the GameController component");
}
}
else
{
Debug.Log("Unable to find GameControllerObject in scene");
}
}
public void CauseDamage(int damageAmount)
{
health -= damageAmount;
}
public int GetHealth()
{
return health;
}
GameController script:
public Player PlayerScript;
//Damage the player
public void DamagePlayer(int amountDamage)
{
if (PlayerScript != null)
{
PlayerScript.CauseDamage(amountDamage);
}
}
//Get the player's health, returns -1 if we are missing a reference to the player
public int GetPlayerHealth()
{
int returnValue = -1;
if (PlayerScript != null)
{
returnValue = PlayerScript.GetHealth();
}
return returnValue;
}
So after reading through this, I think it makes more sense that I build my health into my player script, then use the Game Controller object to cause damage or healing.
So here’s what I’m looking to do:
Have a player int health = 3
Have the Health script (tied to UI) applied to Player game object, then get the int health from the Player script
Manipulate the player int health using Game Controller object & script.
Here’s what I’ve got so far:
Player Script:
public class BitchFaceController : MonoBehaviour
{
//Combat System
public static int health;
private void Start()
{
health = 3;
}
Health Script:
public class Health : MonoBehaviour
{
public BitchFaceController healthScript;
private int health;
public int numOfHearts;
public Image[] hearts;
public Sprite fullHeart;
public Sprite emptyHeart;
// Start is called before the first frame update
void Start()
{
healthScript.Health.health = 3;
}
// Update is called once per frame
void Update()
{
if(health > numOfHearts)
{
health = numOfHearts;
}
for (int i = 0; i < hearts.Length; i++)
{
if (i < health)
{
hearts[i].sprite = fullHeart;
}
else
{
hearts[i].sprite = emptyHeart;
}
{
}
if(i < numOfHearts)
{
hearts[i].enabled = true;
}
else
{
hearts[i].enabled = false;
}
}
}
}
I’m getting this error:
Assets\Scripts\Health.cs(20,9): error CS0176: Member ‘BitchFaceController.health’ cannot be accessed with an instance reference; qualify it with a type name instead
I’m so out of my league here, and I really feel like it shouldn’t be this complicated right?
The issue is that you’ve made the health variable static. A static variable is a variable that belongs to the type, not to instances of the type. An analogy here is that the number of corners a rectangle has is not a property of any particular rectangle, but something that all rectangles have in common. A static variable says that all instances of that type share the same value and the value cannot be different from instance to instance. You can’t have a rectangle with 3 corners.
So, change public static int health to public int health
However, that change won’t solve all of your problems here. Maybe you left out some code, but these two class don’t look like they do anything together. Line 15 of the Health script looks like it is trying to assign a value but then that value is never referenced anywhere else. Also, I think that line should read healthScript.health = 3;, you have an extra Health in there that will cause a compiler error.
tbh, you probably should delete the first class altogether and handle it through the Health script.
public class Health : MonoBehaviour
{
// If you are new to C#, you may not have seen these before. This is called an auto-property. A property is
// a shortcut for making a variable and a pair of methods that get and set the variable's value. In this case,
// we let anyone get Health's value, but only we are allowed to set it.
public int Health {get; private set;}
public int numOfHearts;
public Image[] hearts;
public Sprite fullHeart;
public Sprite emptyHeart;
public void TakeDamage(int damage)
{
Health -= damage; // We make this a different method so that you could do other effects in here if you want to, like play and 'oof' sound or blood animation
}
// Start is called before the first frame update
void Start()
{
Health = numOfHearts;
}
// Update is called once per frame
void Update()
{
if(Health > numOfHearts)
{
Health = numOfHearts;
}
for (int i = 0; i < hearts.Length; i++)
{
if (i < Health)
{
hearts[i].sprite = fullHeart;
}
else
{
hearts[i].sprite = emptyHeart;
}
if(i < numOfHearts)
{
hearts[i].enabled = true;
}
else
{
hearts[i].enabled = false;
}
}
}
}
// This is in a different file, and is an example for a GameManager
public class GameManager : MonoBehaviour
{
private Health playerHealth; // This is not visible in the inspector, or to other scripts because it is private
void Start()
{
// This searches the scene for a Health object, which should be your player's health script
// If you add more than one health script to the scene, this will stop working correctly and might
// grab a different health script, which is almost definitely not what you want
playerHealth = FindObjectOfType<Health>();
}
// This method lets any other script that knows about the GameManager damage the player if it wants to by calling gameManager.DamagePlayer(1);
public void DamagePlayer(int damage)
{
playerHealth.TakeDamage(damage);
}
}
Nothing wrong with this as such, but you should only use statics like this if you only have one instance of whatever it is (ie. player) in a scene. If you decided to support two or more players, you’d need to rebuild everything, and it would be awkward for enemy health as you’d have plenty of enemies on screen at once.
You should use a static instance like this for anything in the scene that is purely management - scripts that find out if the level has been completed, spawn enemies etc. They are fantastic for that, since a number of different scene objects will need to interact with them.
private static PlayerHealthManager _Instance;
public static PlayerHealthManager Instance
{
get
{
if (_Instance == null)
{
GameObject go = new GameObject("PlayerHealthManager");
go.AddComponent<PlayerHealthManager>();
}
return _Instance;
}
}
Quick comment about this though…
While this is a cute little “Oops, I forgot to add the behaviour to a GameObject!” trick, there’s also no specific need to create a new game object at runtime - I prefer to keep all of these sorts of ‘management’ behaviours on a single GameObject, rather than dozens of different GameObjects, which your solution would most likely lead to if you forgot to put the PlayerHealthManager on something in your scene. Worse, there’s a chance that your script execution order might mean that PlayerHealthManager ended up being in the scene twice in certain scenarios, and you won’t be able to tell which one is ‘active’. However, there’s almost no performance difference, it’s just neater :p.
Yup your right, I fell into the trap when i first started Unity and c# a few months ago “what is the easiest way to manipulate variables from any script with ease” sort of thing and found the above. Knowing what I know now, yeah i’d much rather call scripts via “public MyScript _myscript” and getcomponent etc. But I must admit it is nice and easy to manipulate variables whilst running when all of the Managers are visible on one GameObject
The game I am working on only has 3 scenes and 2 of them are pre-initialisation and main menu so all the little Gamemanagers scripts are attached to one gameobject so I have no trouble now, but I can see it happening soon if I decide to change current game or do a new one
Do you want to change the speed for just one platform, or change for all platforms?
If you want the same speed for all platforms, you should make speed static.
in class Car declare a variable as static
public static float speed;
and class from you want to update value Car.speed = 15.0f;
If you want to change the speed for a specific platform, get the Platform component from a game object, and modify speed.
// assuming “platform” is of type gameObject platform.GetComponent().speed = 15.0f; // replace 15.0f with your desired speed.