Unity communication between scripts - c#

I'm making a Unity3D game. I want to implement a connection between the script Timer.cs and Collide.cs, by which they exchange the variable obji. And before you mark this question as a duplicate I want to mention that have already read this tutorial. As a result of the solution provided I get the error
A namespace cannot directly contain members such as fields or methods
Can you provide a solution for exchanging information between scripts that have no element in common. I want Timer.cs to get the variable obji from Collide.cs
Timer.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Timer : MonoBehaviour
{
public ScoresManager ScoresManager;
Text instruction;
// Start is called before the first frame update
void Start()
{
instruction = GetComponent<Text>();
InvokeRepeating("time", 0, 1);
}
void time() {
if (timeLeft <= 0){
/* if(move.obji() <= 0){
instruction.text = "You win!";
}else{
instruction.text = "You lost!";
}*/
} else {
timeLeft = timeLeft - 1;
instruction.text = (timeLeft).ToString();
}
}
// Update is called once per frame
int timeLeft = 30;
void Update()
{
}
}
Collide.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Collide : MonoBehaviour
{
public Text txt;
public int obji = -1; //this is an example, I always try to initialize my variables.
void Start()
{ //or Awake
obji = GameObject.FindGameObjectsWithTag("Enemy").Length;
}
void OnCollisionEnter(Collision collision)
{
if (collision.collider.gameObject.tag == "Enemy")
{
transform.localScale -= new Vector3(0.03F, 0.03F, 0.03F);
Destroy(collision.collider.gameObject);
obji = obji - 1;
Debug.Log(obji);
if ((obji) > 0)
{
txt.text = (obji).ToString();
}
else {
txt.text = "You win!";
}
}
}
}

Communication between scripts like this (sharing properties of one class with another class) is a very common task in Unity. The script that needs the value of a property of another class should get a reference to that other class.
In your example, since Timer needs to access the obji property from the Collide class, you need to add a reference to the Collide class to the Timer class:
public class Timer : MonoBehaviour
{
public Collide _collide;
// The rest of the script...
}
Then, in the Inspector in Unity, you need to drag a GameObject that has the Collide script attached to the _collide property of the GameObject with the Timer script attached.
Finally, you can access the obji property through your newly created reference:
if (_collide.obji > 0)
See this tutorial from Unity which covers this topic in depth.

The error you've once received:
A namespace cannot directly contain members such as fields or methods,
tells you that in a namespace cannot be placed any methods or fields (i.e. variables) directly. A namespace can only contain
classes,
interfaces,
enums,
delegates,
structs
namespaces.
Generally speaking, a namespace is used to provide certain scope and organize entities.
There are many ways you can get access to another class's member fields. The cleanest and simplest way is through a so-called Getter method (also through get properties). You should avoid using and referencing public fields. For example, in your Collide class
// You don't have to always initialize your fields: they have default values.
// Initialize only when you need to.
private int obji;
...
public int GetObji() {
return obji;
}
Now, to call that method you need a proper reference to it. For that you can simply add that as a parameter in your Timer class:
public Collide CollideRef;
...
// Get the field
CollideRef.GetObji();
And then just drag and drop the GameObject, having the Collide component onto it.

Related

Why do my variables keep going back to 0 and why do my variables look different on the screen then in reality?

So for some reason when I try to access variables from another script the text is showing what I put but when I change things in the editor the accrual values don't change. even if in the code I put playerPoints to 9 when I run it its 0 during a collision. I have all the scripts and objects connected up, and when I try to flip where the variables are, make them in the destroy script, for some reason, it doesn't work. it wont let me use public BallsText bt; it will return an error even though it works fine for the other. I'm sorry if all of these are really basic questions but I've looked as far as I can on the internet and I cant find anything, so any help would be appreciated.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class BallsText : MonoBehaviour
{
public Destroy destroy;
// public int playerPoints = 0;
//public int enemyPoints = 0;
//int playerPoints = 0;
//int enemyPoints = 0;
public Text playerPointsText;
public Text enemyPointsText;
void Update()
{
playerPointsText.text = destroy.playerPoints.ToString();
enemyPointsText.text = destroy.enemyPoints.ToString();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Destroy : MonoBehaviour
{
public int playerPoints = 9;
public int enemyPoints = 0;
void OnCollisionEnter2D(Collision2D col)
{
if (this.name == "Destroy.cs")
{
Debug.Log("i have a doughnut");
}
if (col.gameObject.name == "Player")
{
Debug.Log(playerPoints);
playerPoints++;
Debug.Log(playerPoints);
Debug.Log("at least im a dounut");
Destroy(this.gameObject);
}
else if (col.gameObject.name == "Enemy Zone")
{
enemyPoints++;
Destroy(this.gameObject);
}
}
}
You can hide them from the inspector's view with HideInInspector while still being public.
[HideInInspector] public int playerPoints = 9;
You can also use the reset script to return the setting of public numbers to the first state.
Like Everts said, you had the value at zero and created an instance of the script. Now when you change the script Unity will serialize (save) all the public/serialized fields, then reload the script, then deserialize (load) all the public/serialized fields it previously saved. This means your values are locked into the instance that you previously used. If you make a new instance, you get the current values, and if you reset the script you'll get the current values.
This has also bitten me enough times that I don't set default values when variables are declared anymore. If you have a value that you want to use, set those values in Start() or Awake().
The advantage here is that whatever is in the script will get overwritten when play mode starts. The disadvantage is that you can't customize those values on a per-instance basis anymore, because all instances of the script will all load the same default values when play mode starts. If this matters to you, and you want to be able to customize those values, then unfortunately you'll need to go to each script and change those values manually.
If you use a property with an automatic backing field then you won't be able to see it in the editor
public class Destroy : MonoBehaviour
{
public int playerPoints{get; set;} = 9; // Can't see this in the editor
public int enemyPoints{get; set;} = 0; // Can't see this in the editor
If you use a property with an explicit backing field then you can expose the backing field to the editor with the [SerializeField] tag, but then you've got the same problem you've got now - the editor will serialize that field and subsequent changes to the script won't affect instances:
public class Destroy : MonoBehaviour
{
[SerializeField]
private int playerPoints_ = 9; // Instances will "lock in" values and later changes to the script here won't take effect
[SerializeField]
private int enemyPoints_ = 0; // Instances will "lock in" values and later changes to the script here won't take effect
public int playerPoints
{
get=>playerPoints_;
set{playerPoints_ = value;}
}
public int enemyPoints
{
get=>enemyPoints_;
set{enemyPoints_ = value;}
}
If you keep the fields public (and thus exposed to the editor) but set the values at runtime in Awake() or Start() then you can see the values in the editor but the editor values for all instances will be overridden when play mode starts:
public class Destroy : MonoBehaviour
{
public int playerPoints; // Doesn't matter what the value is here on instantiation becuase you'll override it on Awake()
public int enemyPoints; // Doesn't matter what the value is here on instantiation becuase you'll override it on Awake()
public void Awake()
{
playerPoints = 9; // Will override *every* instance's values with this.
enemyPoints = 0; // Will override *every* instance's values with this.
}
:EDIT:
I'll add too that repeatedly polling is wasteful. If you use events then you can subscribe and get notifications when there's something to see. Consider instead:
public class Destroy : MonoBehaviour
{
private int playerPoints = 9;
private int enemyPoints = 0;
public System.EventHandler<int> OnPlayerPointsChanged;
public System.EventHandler<int> OnEnemyPointsChanged;
void OnCollisionEnter2D(Collision2D col)
{
if (this.name == "Destroy.cs")
{
Debug.Log("i have a doughnut");
}
if (col.gameObject.name == "Player")
{
Debug.Log(playerPoints);
playerPoints++;
OnPlayerPointsChanged?.Invoke(this, playerPoints);
Debug.Log(playerPoints);
Debug.Log("at least im a dounut");
Destroy(this.gameObject);
}
else if (col.gameObject.name == "Enemy Zone")
{
enemyPoints++;
OnEnemyPointsChanged?.Invoke(this, enemyPoints);
Destroy(this.gameObject);
}
}
}
Now Destroy has two public events that fire when the public or enemy points change. Anyone subscribing to those events will get notified when the points change, and part of the event notification is the current point value.
Then your other script subscribes to the events and does whatever they need to when they receive that event. Here they'll convert the points .ToString() and update the Text values:
public class BallsText : MonoBehaviour
{
public Destroy destroy;
public Text playerPointsText;
public Text enemyPointsText;
private void Start()
{
destroy.OnPlayerPointsChanged += PlayerPointsChanged;
destroy.OnEnemyPointsChanged += EnemyPointsChanged;
}
public void PlayerPointsChanged(object sender, int points)
{
playerPointsText.text = points.ToString;
}
public void EnemyPointsChanged(object sender, int points)
{
enemyPointsText.text = points.ToString();
}
}
Last note here is that your Destroy script increments enemyPoints but then also immediately destroys the gameObject, so I don't see the point in incrementing enemyPoints unless there's something else accumulating enemy points. With the subscription model that's totally doable - you could have something else subscribing to the Destroy script models and they'll get notifications before the script self-destructs.

How to change the value of field simultaneously in two classes without using "static"? C#. Unity

I have two classes Player and PlayerUI in Player class I have its 'health' value and in PlayerUI class I have a Slider which value updates each frame according to 'health' value in Player class. I also have a Damage method that changes 'health' value. How can I change the value of field 'health' simultaneously in PlayerUI class and in Player class without using static?
Player class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public static float health = 150;
private void Update()
{
if (Input.GetKeyDown(KeyCode.Q))
{
Damage();
}
}
void Damage()
{
health -= 10;
}
}
PlayerUI class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerUI : MonoBehaviour
{
public Slider hpBar;
private void Awake()
{
hpBar.maxValue = Player.health;
}
private void Update()
{
hpBar.value = Player.health;
}
}
In general you don't want to poll and set values in Update every frame but rather make your code more event driven => Only change the UI slider in the moment the value is actually changed.
I would use a property to make it event driven.
The main questions with this kind of constructs are: "Who shall be responsible for what?" and "Who shall know who?"
Option A
For example: Shall the player actively control the UI, without the UI knowing that a Player even exists?
The player "knows" the PlayerUI => it has its reference
The player actively informs the UI and updated the slider value
like e.g.
public class Player : MonoBehaviour
{
// In this case the Player needs to know the UI
public PlayerUI ui;
private float health = 150;
public float Health
{
get => health;
set
{
health = value;
// Whenever the value of Health is changed actively update the UI
ui.hpBar.value = value;
}
}
private void Start ()
{
// initially inform the UI
Health = health;
}
...
void Damage()
{
// Important: don't change the field anymore but rather the property!
Health -= 10;
}
}
Advantage
Player gains more power and responsibility and possibly becomes the core component that ensures a clean interoperability between multiple subcomponents like the UI and others. It would also be the central API point for accessing these sub components from the outside
Disadvantage
As the player needs to know all the subcomponents, everytime you add one you have to hard code it into the class and configure the references and connections between the subcomponents
Option 2
Or should it rather be the other way round and the Player doesn't even know that a UI exists?
-> Instead of knowing the UI you could add a UnityEvent (just like the button onClick) in befores code example where you can attach callbacks via the Inspector or on runtime to react to every change of the health property.
The Player simply invokes his event, not knowing/caring who is listening to it
The UI hooks up to the event -> The UI knows the player
like e.g.
public class Player : MonoBehaviour
{
// In this case the Player doesn't know anyone
// attach listeners via the Inspector or on runtime via code
public UnityEvent<float> OnHealthChanged;
private float health = 150;
public float Health
{
get => health;
set
{
health = value;
// Whenever the value of Health is changed just raise the event
// you don't care who is listening or not, but whoever is will get informed
OnHealthChanged.Invoke(value);
}
}
private void Start ()
{
// initially inform all listeners
// Note that for timing reasons it is essential that the listeners are attached in "Awake"
// so before this "Start" is called
// Alternatively the listeners can of course also ONCE poll the value directly -> Up to you
Health = health;
}
...
void Damage()
{
// Important: don't change the field anymore but rather the property!
Health -= 10;
}
}
The UI can hook up to the player and listen to the event and react to it like e.g.
public class PlayerUI : MonoBehaviour
{
...
// In this case the UI needs to know the player
public Player player;
private void Awake ()
{
player.OnHealthChanged.AddListener(UpdateSlider);
}
private void OnDestroy ()
{
if(player) player.OnHealthChanged.RemoveListener(UpdateSlider);
}
private void UpdateSlider (float value)
{
hpBar.value = value;
}
}
Advantage
This solves exactly the Disadvantage of Option A and allows to extremely flexible add and remove listeners without the Player having to care at all.
Disadvantage
With a lot of subcomponents and events and listeners it can quick get out of hand and become hard to debug and maintain. Also as noted this is more open for race conditions and timing and order issues.
Imo the best approach would be to create an event like "OnPlayerDamaged" in Player script that is raised when player gets any damage. Then create a method in PlayerUI that is subscribed to OnPlayerDamaged event and changes your healthbar.
Player script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public static event Action<float> OnPlayerDamaged;
private float health = 150;
private void Update()
{
if (Input.GetKeyDown(KeyCode.Q))
{
Damage();
}
}
void Damage()
{
health -= 10;
OnPlayerDamaged.Invoke(health)
}
public static float GetHealth()
{
return health;
}
}
PlayerUI
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerUI : MonoBehaviour
{
public Slider hpBar;
private void Awake()
{
hpBar.maxValue = Player.GetHealth();
}
private void OnEnable()
{
Player.OnPlayerDamaged += ChangeHealth;
}
private void OnDisable()
{
Player.OnPlayerDamaged -= ChangeHealth;
}
private void Update()
{
}
public void ChangeHealth(float currentHealth)
{
hpBar.value = currentHealth;
}
}
With this approach you can later add anything you want that uses the information about player getting damaged like screen effects or audio change. Just add the method to the event just like the ChangeHealth() method in our case. Remember to remove the method from the event on script disable to avoid multiple subscription of the same method.
GetHealth() method is a quick hack there but you should use ScriptableObject for player statistics like health and reference it where you want to use it. Then you don't need to pass currentHealth in the event and when it is raised just get value from scriptable object.

Heritage, child variable not working

This problem is on Unity, but I think I am just doing the c# side wrong.
EDIT
It looks like by doing the code and storing a child Class B of parent Class A, and by saying is a Class A type, by modifing my variable of type Class A containing Class B I modify some sort of hybrid Class A/B that doesn't represent my real Class B Script
What I do is, having multiple script on different prefabs. Each of those script represent an item and all have has parent Usable which is a class that I actually use like an interface, but that will in the future get some stuff.
The full WeaponLaser and Usable script is below
When the player go over a drop, I instantiate the gameObject containing the script like this (using prefab)
GameObject Item = Instantiate(droppedItem, transform.position, Quaternion.identity);
Item.transform.parent = transform;
usableItem = droppedItem.GetComponent<Usable>();
usableItem.OnUsed += ReleaseItem;
and Use the item like this
if (usableItem != null)
usableItem.Use(firePoint.position);
The thing is, it looks like the script I call when I do Use() is another version.
I mean, If I set int fireCurrentShoot = 10; on top of the script WeaponLaser and then trought code in the Start for exemple I do fireCurrentShoot = 2;
It will work on the inside the script WeaponLaser, but when I call it using the above code
if (usableItem != null)
usableItem.Use(firePoint.position);
It will show fireCurrentShoot = 10 so without the modification
END EDIT
Hello,
I have a problem with heritage I don't understand, I cleaned all my class, and still I can't find why.
I have a class A :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Usable : MonoBehaviour
{
protected virtual void Start()
{
}
protected virtual void Update()
{
}
public virtual void Use(Vector3 pos)
{
}
protected virtual void Used()
{
}
}
and a class B
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WeaponLaser : Usable
{
const int SHOOT_AVAILABLE = 5;
const float FIRE_COOLDOWN = 1;
float fireCurrentCooldown = 0.0f;
int fireCurrentShoot = 0;
protected override void Start()
{
base.Start();
Debug.Log("start");
fireCurrentShoot = SHOOT_AVAILABLE;
Debug.Log("fireCurrentShoot" + fireCurrentShoot);
}
protected override void Update()
{
Debug.Log(fireCurrentShoot); // value is = 5
base.Update();
}
public override void Use(Vector3 shootPosition)
{
Debug.Log(fireCurrentShoot);// value is = 0
base.Use(shootPosition);
base.Used();
}
void FireCooldown()
{
}
}
when I call Use, my Debug.Log of booth value give 0... but I am expecting to have fireCurrentShoot = 5
I call it like this *:
usableItem = droppedItem.GetComponent<Usable>();
usableItem.Use(firePoint.position);
why he is equal to 0?
Your inheritance seems fine, so the problem might be in the way you use these objects in Unity. This makes it a bit more difficult to be sure what the solution is, but here is what you could try:
Be sure that Start is called: I think it is the case for you since
you say you see a debug print, but remember that Start is not called
on objects that are deactivated in the Editor, so some values that
you set in start will not be initialized.
Name your objects in unity editor with unique names and add the name in the debug
Debug.Log(fireCurrentShoot + ", " + name);
This should help you sure you see the values for the object that interests you
You can also replace
int fireCurrentShoot = 0;
by a property:
int _fireCurrentShoot = 0;
private int fireCurrentShoot
{
get{ return _fireCurrentShoot;}
set{ _fireCurrentShoot = value; Debug.log("fireCurrentShot set to " + _fireCurrentShoot);}
}
this will allow you to see a message when the value is modified. You
can also set a debug point on the setter to see the callstack
EDIT:
I think I got it: you don't register to the instanciated object, but to your prefab
replace
usableItem = droppedItem.GetComponent<Usable>();
by
usableItem = Item.GetComponent<Usable>();

Acessing custom property with AddComponent

In Unity3D I've got a script that adds the variable 'eaten' as a component.
using UnityEngine;
[AddComponentMenu("My game/IsEaten")]
public class IsEaten : MonoBehaviour
{
public bool Eaten;
}
Yay! I can then add another script to access 'Eaten'
using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour
{
private Eaten someScript;
// Use this for initialization
void Start ()
{
someScript = GetComponent<IsEaten>();
bool temp = someScript.Eaten;
print(temp); // false
}
}
Which works fine. What do I have to do to access the variable with dot notation from another script? ie
if (myCube.eaten == true)
{
// do something
}
You know, in Unity one does rarely create the whole script to add a single property to some object. The common approach is to think of scripts as 'components' (which they are, in fact). Let me explain this, a component is a single piece of code that add certain functionality to your GameObject, like ability to animate, or to behave the laws of physics. So, maybe, it would be better to reform your IsEaten class to form a true component, like Pickup (I'm assuming that you need Eaten property for the pickup of some sort) that will have functionality to be eaten by a player, or something.
// You will actually need to attach a collider (and check the 'IsTrigger' checkbox on it) to your GameObject
// Then this method will be called whenewer some game object with a collider (e. g. player, npc's, monsters) will enter the trigger of this object
void OnTriggerEnter(Collider other)
{
// Check whether this object was eaten before and if the actor entering our trigger is actually the player
if (!Eaten && other.tag == "Player")
{
// Make shure that we will not get eaten twice
Eaten = true;
// Apply some effect to the player that has devoured us
other.GetComponent<Player>().AddHp(25);
}
}
Other than that, I'm personally thinking, that getting out of your way to simply enable a little sweeter syntax is not worth the hassle, but, if you provide some insight of what are you actually trying to implement, I may try to help you with it :)
One way to do it might be using Get/Set:
using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour
{
private Eaten someScript;
// start new!
public bool eaten
{
get
{
return someScript.Eaten;
}
set
{
someScript.Eaten = value;
}
}
// end new!
// Use this for initialization
void Start ()
{
someScript = GetComponent<IsEaten>();
bool temp = someScript.Eaten;
print(temp); // false
}
}
Then you can access the Test class property:
Test t = GetComponent<Test>();
t.eaten = true;
ath.

Unity C# Null Reference Exception

I am trying to get data from an int variable in Unity using C# code.
Below is the C# code I am using to get the int.
using UnityEngine;
using System.Collections;
public class endGameMessage : MonoBehaviour {
public static int score2;
void Start () {
GameObject thePlayer = GameObject.FindWithTag("Player");
gameScript game = thePlayer.GetComponent<gameScript>();
score2 = game.score;
}
// Update is called once per frame
void Update () {
Debug.Log (score2);
}
}
Below is the code from the other script I am trying to pull the data from.
using UnityEngine;
using System.Collections;
public class gameScript : MonoBehaviour {
//score
public int score = 0;
void OnTriggerEnter(Collider other) {
if(other.gameObject.tag =="hammer"){
GameObject.FindGameObjectWithTag("pickUpMessage").guiText.text = ("Picked Up A Hammer");
Destroy(other.gameObject);
Debug.Log("collision detected hammer");
audio.PlayOneShot(gotHit);
score = score+10;
}
}
}
I can get the the int value to come across to the other script but its always 0 even if the int was meant to be 10.
My question is how would i keep the value across the scripts? Any help is appreciated.
try this
public static int score2
{
get
{
return GameObject.FindWithTag("Player").GetComponent<gameScript>().score;
}
}
You have a lot of possibilities.
The first one is to set your Score as a static argument for you gameScript.
So you can access it anywhere just like that :
int myScore = gameScript.Score ;
And the declaration should be :
public static int score;
The second possibilities is far better if you want to save a lot of differents values from differents script.
In this case, you need to define a gameContext singleton.
If you don't know what is this, you should take a look at singleton in C# :
[https://msdn.microsoft.com/en-us/library/ff650316.aspx]
Singleton will allow you to have a single instance of your gameContext.
In your case, your singleton will have a Score attribute.
And you will be able to get the value from any scene and any scripts.
This is the best way so far.
score2 is read once at start and then never again. int is an integral type in C# and thus passed by value i.e. it receives a copy. There several ways to solve this problem.
The easiest solution is to access the gameScript.score directly - it provides read/write access to everyone anyway. To encapsulate it you may choose to define a property.
A better way could be to define a new class GameStatus which holds all relevant things. This can be implemented as singleton for example.

Categories