Get variable from OnTriggerEnter class to another class - c#

I have a really simple question, where I can't understand what I am missing.
I am getting error:
NullReferenceException: Object reference not set to an instance of an object
test.Update () (at Assets/Scripts/test.cs:13)
So, I have one class "DissableInput", where there is a simple trigger that changes whenever it triggered by another object. It returns true/false, whenever it changes.
Another class has is named "test", which basically should print out when it's true/false depending on trigger input.
//Test Class
public bool touchedCollision;
// Update is called once per frame
void Update()
{
if (this.GetComponent<DissableInput>().touchedCollision)
{
Debug.Log("IN COLL");
}
if(!this.GetComponent<DissableInput>().touchedCollision)
{
Debug.Log("NOT IN COLL");
}
}
// DisableInput Class
public bool touchedCollision;
void OnTriggerEnter (Collider other)
{
Debug.Log("In Triger");
touchedCollision = true;
}
public void OnTriggerExit(Collider other)
{
Debug.Log("Out Triger");
touchedCollision = false;
}
I am expecting that true/false will go into the test class, but instead it gives NullReferenceException error.

This part this.GetComponent<DisableInput>().touchedCollision will try to get a component of the specified type, in this case your "DisableInput" class, in the same gameObject that its attached. If you wish to have the DisableInputClass script in another gameObject you need to reference in some other way.
Using a public variable would look like that
//TestClass
public DisableInput disableInput;
// Update is called once per frame
void Update()
{
if (disableInput.touchedCollision)
{
Debug.Log("IN COLL");
}
else //you don`t need to specify the condition again, you can do it with and else
{
Debug.Log("NOT IN COLL");
}
}
you can look how GetComponent works here
An alternative is also to use FindObjectOfType<DisableInput>()
//TestClass
private DisableInput disableInput;
void Awake(){
disableInput = FindObjectOfType<DisableInput>();
// you only need to get the
//class refence one time, no need for it to be in the update,
//it gains a lot of performance
}
// Update is called once per frame
void Update()
{
if (disableInput.touchedCollision)
{
Debug.Log("IN COLL");
}
else //you don`t need to specify the condition again, you can do it with and else
{
Debug.Log("NOT IN COLL");
}
}
More about FindObjectOfType here, but to use this you must understand that
it will return the first object that he finds that has DisableInput attached to it. If you have multiple gameObjects with DisableInput you can`t be sure which one it will get.

Your scripts are on different objects, therefor this.GetComponent won't work
You want to know when ObjA touches ObjB, so you have one script on ObjA that wants to know and a script on ObjB that is run when the two touch. However, this.GetComponent can only see scripts attached to the same object. So you can't run this code from ObjA (because it doesn't, and can't, know about ObjB!)
I'm going to make two small changes to your scripts:
//Test Class
public bool touchedCollision; //you already had this, but weren't using it
// Update is called once per frame
void Update()
{
if (touchedCollision)
{
Debug.Log("IN COLL");
}
if(!touchedCollision) //can be changed to just `else`
{
Debug.Log("NOT IN COLL");
}
}
// DisableInput Class
//removed touchedCollision from here
void OnTriggerEnter (Collider other)
{
Debug.Log("In Triger");
other.GetComponent<TestClass>().touchedCollision = true;
}
public void OnTriggerExit(Collider other)
{
Debug.Log("Out Triger");
other.GetComponent<TestClass>().touchedCollision = false;
}
This works because it is assumes that:
You have more than one DisableInput instance for each volume that you wish to disable input
You may have more than one TestClass (or whatever you're going to call it) instance (for example, multiplayer or AI controlled objects).
The DisableInput class finds the TestClass of the object that caused the physics event and changes its value.
Addendum:
You should check for nulls. This is the simplest way (glory be to C# 6!)
other.GetComponent<TestClass>()?.touchedCollision = ...
Using a null conditional operator.

Related

I can't change the value of a serialized variable in Unity's custom inspector

I just learned how to (partially) use unity's custom inspector. I mainly want to use this to serialize specific variables if a bool or enum or whatever is true.
Anyway, in a test project I made a bool called usesSpeed. Then I made a float called speed. But I only want to serialize speed whenever usesSpeed is true. That works well, however the problem is I can't change the value of speed through the inspector at all. I can't "drag" the value or input anything. It's always just 0.
Here's my code.
public class script : MonoBehaviour
{
[HideInInspector] public float speed;
public bool usesSpeed;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
Debug.Log(speed);
}
[CustomEditor(typeof(script))]
public class edtr : Editor
{
public override void OnInspectorGUI()
{
script scrpt = (script)target;
base.OnInspectorGUI();
if (scrpt.usesSpeed)
{
EditorGUILayout.LabelField("Speed stuff");
EditorGUILayout.PropertyField(serializedObject.FindProperty("speed"));
}
}
}
}
All I had to do was just add this at the end and everything works great.
serializedObject.ApplyModifiedProperties();

Variable not being set in class using inheritance

Edit: It turns out it was how I was getting the ProjectileWeapon component. I was getting the one that was on the non-instantiated prefab instead of getting the one on weapon gameobject. I changed it so that the code instantiates the game object first (or gets the existing one if we've already picked it up), and then get the component from that. So the rest of the code works fine. Now I can move on and improve it!
I have an issue with the class below called ProjectileWeapon. It is based on an abstract class called Weapon, and that class inherits MonoBehaviour.
Weapon has two abstract functions called BeginCycle and EndCycle which are implemented in the ProjectileWeapon class. Those functions set a variable called "firing".
The problem is, "firing" doesn't ever seem to be set despite the functions being called correctly. I know the functions are called because I can see the prints in the console.
Also, when I use that variable in the update function, it doesn't do anything because the variable never changes.
The OnGUI function is working and is displaying text on screen, however the "firing" variable is never updated.
Am I misunderstanding how to use inheritance?
This class is on the weapon prefab which is then instantiated during the equipping function in the game
public class ProjectileWeapon : Weapon
{
private bool firing;
private float firingTimer;
void Start()
{
print("ProjectileWeapon start");
}
void OnGUI()
{
GUI.Label(new Rect(0,100,100,100), "ProjectileWeapon firing: " + firing);
}
void Update()
{
// this function is called but "firing" is not updated
}
public override void BeginCycle()
{
print("projectile begin cycle");
firing = true;
}
public override void EndCycle()
{
print("projectile end cycle");
firing = false;
}
}
Here's the base class:
public abstract class Weapon : MonoBehaviour
{
public abstract void BeginCycle();
public abstract void EndCycle();
}
EDIT: Here is the code that calls the above
This component is added to the player game object
public class WeaponHandler : MonoBehaviour
{
public bool FireInput { get; set; } // set to true when user holds the mouse button down, and false when let go
public Weapon WeaponBehaviour; // this is the script that does the weapon functionalility. Any subclass of Weapon can be put here e.g. ProjectileWeapon, MeleeWeapon
private bool isFiring = false;
void OnGUI()
{
GUI.Label(new Rect(0,0,100,100), "fire input:" + FireInput + ", isFiring:" + isFiring);
}
void Update()
{
// fire weapon
if (WeaponBehaviour && FireInput && !isFiring)
{
ActivateWeapon();
}
else if (!FireInput && isFiring)
{
DeActivateWeapon();
}
}
private void ActivateWeapon()
{
print("activate weapon");
isFiring = true;
WeaponBehaviour.BeginCycle();
}
private void DeActivateWeapon()
{
print("deactivate weapon");
isFiring = false;
WeaponBehaviour.EndCycle();
}
}
Based on the code you've provided, there are three possible scenarios:
You're not calling the functions. However, if the print functions are called, then you must be calling them.
You're calling both functions, which sets the variable to true, and then to false.
You're overriding the variable with a local variable with the same name. Visual Studio will warn you if that's the case.
It's hard to tell without knowing where the variables are supposed to be called. If you upload the rest of the code, I'm sure the answer will be clear.
It turns out it was how I was getting the ProjectileWeapon component. I was getting the one that was on the non-instantiated prefab instead of getting the one on weapon gameobject. I changed it so that the code instantiates the game object first (or gets the existing one if we've already picked it up), and then get the component from that. So the rest of the code works fine. Now I can move on and improve it!

What do I do to solve an enclosing type error?

I have trying to create this code so that my player in Unity walks through a trigger box and sets off specific audio but when I put in this code it comes up with saying there's an Enclosing Error:
[10:51:37] Assets/DoorFall.cs(7,24): error CS0542: 'DoorFall': member names cannot be the same as their enclosing type
I have tried changing different names and moved different nodes around but nothing is working.
public class DoorFall : MonoBehaviour {
public AudioSource DoorFall;
// Use this for initializatoin
void Start() {
DoorFall = GetComponent<AudioSource>();
}
// Update is called once per frame
void Update() {
}
void OnCollisionEnter (Collision collision) {
if (collision.gameObject.tag == "player")
{
DoorFall.play();
Destroy(collision.gameObject);
}
}
}
Your class name DoorFall is the same as a member of the class public AudioSource DoorFall; name one differently.
Chances are you dont need the member to be public either.
public class DoorFall : MonoBehaviour {
private AudioSource doorFallAudioSource;
// Use this for initializatoin
void Start() {
doorFallAudioSource = GetComponent<AudioSource>();
}
// Update is called once per frame
void Update() {
}
void OnCollisionEnter (Collision collision) {
if (collision.gameObject.tag == "player")
{
doorFallAudioSource.play();
Destroy(collision.gameObject);
}
}
}
In your case Compiler error CS0542 is being caused because the type name DoorFall is the same as the name of one of it's members (the public field). You should rename either the member or the type.
This generates a compile time error because the names conflict. Inside a method body in your type if you write or DoorFall.[memberHere] the compiler does not know whether to resolve the type or the member. Here is a more succinct example illustrating why this should generate a compile time error.
public class DoorFall {
private static string ToString() { return "A"; }
public AudioSource DoorFall = "B";
void Start() {
// what value would be assigned here?
var result = DoorFall.ToString();
}
}
On a side note it is almost never acceptable to declare a public field. Instead you should use properties to expose field values or to set them. If you want the value to be set/get publicly you could use an auto getter/setter.
public AudioSource DoorFall { get; set; }
If it never accessed from outside the type then declare it as private instead.

Detect collision from different objects?

I couldn't come up with a suitable title for this question but I'll explain.
I have a collision box, which holds a script. This script has an if statement that detects collision from object "Cube001" and sends a Debug.Log to console.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class cubeDetect : MonoBehaviour {
void OnCollisionEnter(Collision collision) {
if (collision.gameObject.name == "Cube001")
{
Debug.Log("Cube001 hit!");
}
}
}
With this method, the box collider knows what cube has touched it, as I have instructed so with
collision.gameObject.name == "Cube001"
But say if I have 10 cubes colliding with the collision box, how can I change the if statement so instead of writing another 9 if statements that check if it touches the collision box, I can just have 1 if statement that just first detects a collision from another cube, knows what cube hit the box, and with this knowledge, is able to do a Debug.Log to display the name of the cube that hit the box.
I've tried going through the documentation for OnCollisionEnter but couldn't find anything to help with this.
One option would be to tag all of your similar objects you want to collide with, with the same name. Say we give them the tag "Cubicle". Then we can do the following:
Gameobject myCube;
void OnCollisionEnter(Collision collision) {
if (collision.collider.tag == "Cubicle")
{
Debug.Log(collision.gameObject.name + " hit!");
myCube = collision.gameObject; // store colliding gameobject info for use elsewhere
}
}
You need to use Dictionary. This eliminates the need for all the if statements.
This is what the Dictionary should look like:
public static Dictionary<GameObject, System.Action> objToAction = new Dictionary<GameObject, Action>();
Then a function to add objects to the dictionary when they are instantiated or in the Start function if they already exist
public void registerObject(GameObject obj, System.Action action)
{
objToAction.Add(obj, action);
}
The key in the Dictionary is the GameObject(Cube), you can also use string(name of the GameObject) but using the GameObject is better. The value in the Dictionary stores what you want to to do when OnCollisionEnter is called. So the code that should've been inside that if statement should be placed here. This is done with Action and delegate. You can add as many GameObjects (Cubes) as you wish.
You must add those Cube Objects to the Dictionary with the function above:
public GameObject prefab;
void Start()
{
//Create new Cube
GameObject obj = Instantiate(prefab);
obj.name = "Cube001";
//Add it to the Dictionary
registerObject(obj, delegate { Debug.Log("Hit: " + obj.name); });
}
Your new OnCollisionEnter code is as below. No if statement required. If the Object exist in the Dictionary, execute that code we stored in the value of that Dictionary for each key GameObject.
void OnCollisionEnter(Collision collision)
{
Action action;
if (objToAction.TryGetValue(collision.gameObject, out action))
{
//Execute the approprite code
action();
}
}
Note that the objToAction variabel should either be made static or placed in another script attached to an empty GameObject so that you can access it. There should only be one instance of it.
What was working best for me was to use interfaces and components and check for that. This is working great if you have certain logic on collision but when you don't you can just use tag and set it to something like "collidable".
interfaces solution:
public interface ICollidableObject
{
void CollidedWith(ICollidableObject other);
}
public class CollidableBlock : MonoBehaviour, ICollidableObject
{
public void CollidedWith(ICollidableObject other)
{
Debug.Log((other as MonoBehaviour).gameObject.name);
}
}
public class CollidableSphere : MonoBehaviour, ICollidableObject
{
public void CollidedWith(ICollidableObject other)
{
Debug.Log("sphere001");
}
}
And just use it like such :
void OnCollisionEnter(Collision collision)
{
ICollidableObject collidedWith = collision.gameObject.GetComponent<ICollidableObject>();
if ( collidedWith != null )
collidedWith.CollidedWith(this);
}

Cannot access class variable in member function

In Unity C# script I have a singleton game controller object in which the game variables are stored but I am getting odd behaviour when accessing them in a member function. It prints the initialized value, not the current one. In the update function however it prints the correct value each frame. I summarized the class below. The controller class has a static reference to itself. If you need to know additional details you can ask. I am new to C# and Unity so I might be lacking some obvious answer.
Thanks
public class controller : MonoBehaviour {
public int[] star = new int[64];
void Start(){ /* calls another function to set 0 for each star index */ }
void Update(){ // during gameplay star[0] gets a value of 1
print(star[0]); // prints correct value which is 1
}
public void checkValue(){
print(star[0]); // prints 0 incorrectly which should be 1
}
}
I've set up a little example. I created a button and an empty gameobject GameController. I added this code to the GameController:
using UnityEngine;
using System.Collections;
public class GameController : MonoBehaviour {
public static GameController instance;
[System.NonSerialized] public int[] star = new int[64];
private void Awake()
{
if(instance == null)
instance = this;
else if(instance != this)
Destroy(gameObject);
DontDestroyOnLoad(gameObject);
}
private void Start()
{
StartCoroutine(SetAllTo(1));
}
// for simulating some changes during the game
private IEnumerator SetAllTo(int value)
{
yield return new WaitForSeconds(2.0f);
for(int i = 0; i < star.Length; i++)
star[i] = value;
Debug.Log("Setting done");
}
public void PrintFirst()
{
Debug.Log(star[0]);
}
}
Now I added an OnClick event to the button, dragged the GameController gameobject into the slot and picked PrintFirst.
I start the game and click the button once before the Coroutine log and once after and the console gives the following:
0
Setting Done
1
Edit:
The gameobject for the OnClick event must be in the scene, it can't be a prefab in the assets folder.
It seems that's the call to the start function may also occurs in your external calls before the call for ckeckValue
Try debugging by putting breakpoint in each of these functions and you can understand what is going on.
The start function runs once in the beginning of the game and not again.
For that you have pattern - Singleton
The second thing why to call external function to initialize class member? do it in your default contractor.
- C# default constractor already initialize 0 in int[].
if you still what to run Start from other scope in your code make it public (c# makes variable/methods as private by default)

Categories

Resources