Acessing custom property with AddComponent - c#

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.

Related

Using the same boolean in two different scripts in Unity

I'm a total beginner at Unity and I don't understand how to use a variable in two different scripts.
For example, let's say that I have a script called "player" and another called "logic".
In the player script, I create the boolean "IsAlive", and when I collide with something, I die :
public class PlayerScript : MonoBehaviour
{
public bool IsAlive = True;
}
private void OnCollisionEnter2D(Collision2D collision)
{
IsAlive = False;
}
Now, I want to do something in the "Logic" script and have to check if the player is alive or not before. How do I do that ? I tried something like :
public class LogicScript : MonoBehaviour
{
public PlayerScript PlayerScript_logic;
private bool IsAlive_logic = PlayerScript_logic.IsAlive;
}
so that I could use "IsAlive_logic" which would be the same as "IsAlive".
But that is apparently not how if works.
If somebody could help me, please, I'm so lost.
Simply instead of IsAlive_logic in all places use PlayerScript_logic.IsAlive. Despite the fact that you can't access a non-constant field (PlayerScript_logic) when declaring your other fields - it is bad practice to store (and maintain) the same value in multiple places anyway ;)
Alternatively if you really for some reason want/need to you could have a property
private bool IsAlive_logic => PlayerScript_logic.IsAlive;
// or also
//private bool IsAlive_logic { get => PlayerScript_logic.IsAlive; }
// or also
//private bool IsAlive_logic { get { return PlayerScript_logic.IsAlive; } }
which basically simply returns PlayerScript_logic.IsAlive everytime you access it. In general this adds some tiny overhead though and it would be better to go through the PlayerScript_logic.IsAlive directly...

Can't figure out what i've done wrong

I'm new to programming and I'm currently trying to do a simple game in Unity.
The code makes some things dissapear when they touch the ground and it works well but the "Evaporated" variable does not update in the Unity Inspector. When something touch the ground, evaporated should be incremented, but it stays at 0.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
public class EnemyEvaporate : MonoBehaviour
{
public GameObject Enemy;
public int Evaporated;
void OnCollisionEnter(Collision CollisionInfo)
{
if (CollisionInfo.gameObject.name == "Map" || CollisionInfo.gameObject.name == "Player")
{
Thread.Sleep(15);
gameObject.SetActive(false);
Evaporated++;
}
}
}
I'm not really sure what's the behavior you're trying to get in this script. If Evaporated is some kind of 'Global counter' for score, it need to be written different and not store inside script for this gameObject.
It might be related to your gameObject.SetActive(false) which will deactivate your object, so in fact it will disables also the scripts connected with it (and especially this one. SetActive documentation) and removes them from Update() for a given gameObject (so given enemy. If you have 4 Enemy objects on the Scene, in fact you have 4 instances of this script and each has own Evaporated variable)
Why won't you move the void OnCollisionEnter(Collision CollisionInfo) to your Enemy scipt? I think Enemy should now if it collided with anything, not some external script.
You can also use tags instead of names when detecting Collisions.
Also, gameObject.SetActive(false) - if you set your gameObject to disabled, nothing more from its script will happen unless you set it to active again. It e.g. cancels all Coroutine. The gameObject is just "sleeping", I would say. It's not destroyed - you still can see it in your Hierarchy window - but it can't do anything. (By the way, in this case you're setting EnemyEvaporate gameObject as disabled, not Enemy gameObject.)
Also, you can use Coroutine instead of Thread.Sleep(15). It is more common to use Coroutines than Threads in Unity.
Now, Evaporation. If you want to count how many Enemies evaporated (I suppose, looking at Evaporated++;) you should have some external object to count it. The simplest way I can propose fow now is creating a GameObject with EvaporationCounter : MonoBehaviour script attached to it. You can also use Singleton pattern to be sure there's only one object of this type.
public class EvaporationCounter : MonoBehaviour
{
private int evaporates;
public int Evaporates {
get { return evaporates; }
private set
{
if (value >= 0) evaporates = value;
}
}
public void AddEvaporation()
{
Evaporates++;
}
}
Your Enemy class could look like:
public class Enemy : MonoBehaviour
{
private IEnumerator coroutine;
private EvaporationCounter evaporationCounter;
private void Start()
{
evaporationCounter = FindObjectOfType<EvaporationCounter>();
}
private IEnumerator WaitAndSetInactive(float waitTime)
{
yield return new WaitForSeconds(waitTime);
gameObject.SetActive(false);
}
private void OnCollisionEnter(Collision CollisionInfo)
{
if (!CollisionInfo.gameObject.CompareTag("Map") && !CollisionInfo.gameObject.CompareTag("Player")) return;
if (evaporationCounter != null)
evaporationCounter.AddEvaporation();
StartCoroutine(WaitAndSetInactive(15f));
}
}
Calling evaporationCounter.AddEvaporation() from the Enemy script is not the best solution, since it does not apply to the Dependency Inversion principle but I would say it's good for the beginning with Unity.

Unity communication between scripts

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.

Go to next level when Key is grabbed by Player Unity 2d

I need some help with a feature I'm implementing for a game made in Unity 2D.
The player must take a key in order to unlock a door (maybe showing an animation) and when the player go in front of that door if he has the key, he will automatically go to the next level.
I need help, cause the door is not letting go to the next level.
Here is the KEY code/script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class KeyScript : MonoBehaviour {
//public AudioSource coinSoundEffect;
public AudioClip key1;
void Awake () {
//source = GetComponent<AudioSource>();
}
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
void OnCollisionEnter2D (Collision2D other) {
Debug.Log("Chiave Presa");
if(other.gameObject.tag =="Player")
GameObject.Find("KeyDoor").SendMessage("HitKey");
SoundManager2D.playOneShotSound(key1);
}
}
Here is the DOOR code/script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class DoorScript : MonoBehaviour {
public bool key = false;
public string nextLevelName;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
void HitKey (){
Debug.Log("La porta รจ sbloccata");
key = true;
Destroy (GameObject.Find ("Key"));
}
void OnCollisionEnter2D (Collision2D other){
if(other.gameObject.tag == "Player"){
if (key == true){
Application.LoadLevel(nextLevelName);
//Destroy(gameObject);
//GameObject.Find("Key").SendMessage("DestroyKey"); //If you also want to destroy the key
//GoToNextLevel ();
}
}
}
void OnTriggerEnter2D (Collision2D other)
{
Application.LoadLevel(nextLevelName);
}
public virtual void GoToNextLevel()
{
//loadingImage.SetActive(true);
Application.LoadLevel(nextLevelName);
}
}
The code works but when the player goes in front of the door, he is not passing to the next level.
Any help or hint appreciated.
Cheers
First of all, you're missing one thing:
unity is Component based engine
This means you should make interactions between Components, not GameObjects.
In your script there's a public field public bool key which should not be accessible from the outside. But if you're looking for simple and wrong answer that will work then you can just replace this line :
GameObject.Find("KeyDoor").SendMessage("HitKey");
Into this one :
GameObject.Find("KeyDoor").GetComponent<DoorScript>().key = true;
In that case you'll end up with messy code and unstable game. What I can recommend to you as an good alternative is to rewrite your logic a bit.
Instead of making a MonoBehaviour which is not needed you can just create a Component :
public class KeyComponent : Component
{
[SerializeField]
AudioClip m_KeyPickupSound;
[SerializeField]
bool m_IsPickedUp;
public bool PickedUp
{
get { return m_IsPickedUp; }
}
public void PickUp()
{
m_IsPickedUp = true;
SoundManager2D.playOneShotSound(m_KeyPickupSound);
}
}
Now attach this into your Player's Components list and in your door script do:
void OnCollisionEnter2D (Collision2D other)
{
if(other.GetComponent<KeyComponent>() != null && other.GetComponent<KeyComponent>().PickedUp)
{
SceneManager.LoadScene(2);
}
}
Now only thing left is to update your Player's MonoBehaviour and add simple collision check :
void OnCollisionEnter2D(Collision2D other)
{
if(other.tag == "TAG_FOR_KEY")
GetComponent<DoorScript>().PickUp();
}
Now you're interacting with Components and not GameObject which then require less effort changing some scripts.
First off, you should change Application.LoadLevel to SceneManager.LoadScene. The first is obsolete in the newer versions of unity.
Second you need to make sure that your scenes are actually registered in File -> BuildSettings and the parameter you pass to LoadScene matches either the index in that list or the name as string.
Make sure your player needs to have a Rigidbody2D and a Collider2D and the door has a Collider2D component
If your doors Collider2D is a trigger you must use void OnTriggerEnter2D(Collider2D other).
Another problem may be that "Level 2" isn't added to your build settings, make sure that it is added to the level's list.
Answering your last comment as it seemed the problem was that the Collider2D on the door, you can make this in a lot of ways, the easiest way is this:
Add a public string variable called nextLevelName in your Door's script, then when calling LoadLevel, use this variable. You can change the value from the inspector in each level. The problem with this is that if you rearrange the levels then you need to change the strings in each level.
The best solution in that case is this one:
int currentLevelNum = SceneManager.GetActiveScene().buildIndex;
SceneManager.LoadScene(currentLevelNum+1);
This way if you have your levels in order in your build settings you don't need to do anything else.

Enable/disable a GameObject component from a script [Unity3D]

I need to take the value of a boolean (put in a variable called "bouclier") set in one script to enable or disable a GameObject.
The variable is in game object Player (bottom right here):
And I need to enable of disable this game object ("Bouclier01"):
To do this, I attached a script to game object "Bouclier01". Here it is:
using UnityEngine;
using System.Collections;
public class ShowBouclier : MonoBehaviour {
public GameObject Bouclier01;
public bool bouclier;
// Use this for initialization
void Start () {
Bouclier01 = Bouclier01.GetComponent<GameObject>();
}
// Update is called once per frame
void Update () {
Bouclier01.enabled = false;
if (bouclier == true) {
Bouclier01.enabled = true;
}
}
}
I must be missing something, because this comes up with this error message:
Any idea how to properly accomplish this?
You can use GameObject.SetActive() function to activate or deactivate a GameObject (I think GameObject.enabled was in the old API):
Bouclier.SetActive(false);
By the way, if you want to know the current activation state of a GameObject, use GameObject.activeSelf, which is a read only variable:
Debug.Log(Bouclier.activeSelf);
it will works
public GameObject otherobj;//your other object
public string scr;// your secound script name
void Start () {
(otherobj. GetComponent(scr) as MonoBehaviour).enabled = false;
}

Categories