OnMouseDown() null reference exception - c#

public class TowerNode : MonoBehaviour {
public bool IsShopOpen=false;
//Z position
public Vector3 positionOffSet;
//colors
public Color hoverColor;
private Color startColor;
//GameObjects
public GameObject turret;
//shop
public Shop shop;
//Build Manager
BuildManager buildManager;
void Start()
{
rend=GetComponent<Renderer>();
startColor = rend.material.color;
buildManager = BuildManager.instance;
shop = GetComponent<Shop>();
}
//When mouse is on the turret node
public void OnMouseDown()
{
Debug.Log("Mouse is Down");
bool IsShopOpen = true;
if (IsShopOpen == true)
{
Debug.Log("shop is open");
shop.onEnable();
}
if (EventSystem.current.IsPointerOverGameObject())
{
return;
}
if (!buildManager.CanBuild)
{
return;
}
if (turret != null)
{
Debug.Log("Cant Build Here!!!");
return;
}
buildManager.BuildTurretOn(this);
}
the other script of the shop is this:
public class Shop : MonoBehaviour
{
public TurretBlueprint Archery;
public TurretBlueprint Treb;
public TurretBlueprint Workamp;
public TurretBlueprint Barracks;
public Button Archeryy;
public Button Trebb;
public Button WorkCampp;
public Button Barrackss;
BuildManager buildManager;
void Start()
{
buildManager = BuildManager.instance;
disableAllButtons();
//OnEnable();
}
public void SelectArchery()
{
buildManager.SelectTurretToBuild(Archery);
Debug.Log("archery!!!!");
}
public void SelectTreb()
{
buildManager.SelectTurretToBuild(Treb);
Debug.Log("Treb!!!!");
}
public void SelectWorkamp()
{
buildManager.SelectTurretToBuild(Workamp);
Debug.Log("Work Camp!!!!");
}
public void SelectBarracks()
{
buildManager.SelectTurretToBuild(Barracks);
Debug.Log("Barracks!!!!");
}
public void onEnable()
{
Archeryy.gameObject.SetActive(true);
Trebb.gameObject.SetActive(true);
WorkCampp.gameObject.SetActive(true);
Barrackss.gameObject.SetActive(true);
}
public void disableAllButtons()
{
Archeryy.gameObject.SetActive(false);
Trebb.gameObject.SetActive(false);
WorkCampp.gameObject.SetActive(false);
Barrackss.gameObject.SetActive(false);
}
}
I need when i click on the game object that the shop will open the sop got few buttons under it, but it gives me:
"NullReferenceException: Object reference not set to an instance of an object
TowerNode.OnMouseDown () (at Assets/Scripts/TowerNode.cs:51)
UnityEngine.SendMouseEvents:DoSendMouseEvents(Int32)"
and I don't understand why.

The error you are getting implies that an object in the OnMouseDown function is null when executed:
NullReferenceException: Object reference not set to an instance of an object TowerNode.OnMouseDown () (at Assets/Scripts/TowerNode.cs:51)
You should look at ine 51 of TowerNode.cs and ascertain what can be null on that line. Could shop or buildManager be null? Perhaps this event is firing before you have initialised your TowerNode object.
It seems you are trying to access a component (Shop) from a completely unrelated GameObject. If you want all of your TowerNode objects to reference the Shop then you could use GameObject.Find. This isn't really recommended though; it seems like there is a better way to link these two objects but I am unsure of your use case.

Related

Is there a better way than this to create a character selection and spawner?

This is the code I have for my spawner object on the level scene:
public float spawnShipID;
public GameObject basicShip;
void Start()
{
if (spawnShipID == 1)
{
Instantiate(basicShip, transform.position, Quaternion.identity);
}
}
And this is the code I have on a button on the start menu:
public float ShipID = 1f;
public void shipchoice()
{
SceneManager.LoadScene("watcherqueen");
GameObject.Find("playerspawner").GetComponent<spawnplayer>().spawnShipID = ShipID;
}
The reason I am using different floats on the button and the spawner is because I would like to have different buttons spawn different ships.
Using this code, the scene changes (only with the Play button, but not in the build, though that is a question for another time), and the player ship does not spawn. Are there any solutions or better ways I could have done this?
When you call LoadScene, the scene does not fully load until the next frame. Therefore, your GameObject.Find("playerspawner") doesn't even find the instance of playerspawner that is in that scene. (And, even if it were the same instance, it wouldn't run through its Start again.)
Instead, you should set the variable to something that already exists in the scene currently running, which also will not destroy on load. A Singleton is especially useful for this application. Starting from this example by PearsonArtPhoto:
public class ShipSingleton: MonoBehaviour {
private static ShipSingleton _instance;
public static ShipSingleton Instance { get { return _instance; } }
private void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(this.gameObject);
} else {
_instance = this;
DontDestroyOnLoad(this.gameObject);
}
}
public int spawnShipID;
}
Then in the start menu button:
public int ShipID = 1f;
public void shipchoice()
{
SceneManager.LoadScene("watcherqueen");
ShipSingleton.Instance.spawnShipID = ShipID;
}
and in the spawner:
public GameObject basicShip;
void Start()
{
if (ShipSingleton.Instance.spawnShipID == 1)
{
Instantiate(basicShip, transform.position, Quaternion.identity);
}
}
I used an int for spawnShipID for simplicity, but an enum would be better practice, including how it could help avoid entering invalid ids.
Using an enum could look something like this:
public class ShipSingleton: MonoBehaviour {
private static ShipSingleton _instance;
public static ShipSingleton Instance { get { return _instance; } }
private void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(this.gameObject);
} else {
_instance = this;
DontDestroyOnLoad(this.gameObject);
}
}
public enum ShipType
{
small,
medium,
large,
boss
};
public ShipType spawnShipID;
}
Then in the start menu button:
public ShipSingleton.ShipType ShipID = ShipSingleton.ShipType.small;
public void shipchoice()
{
SceneManager.LoadScene("watcherqueen");
ShipSingleton.Instance.spawnShipID = ShipID;
}
and in the spawner:
public GameObject basicShip;
void Start()
{
if (ShipSingleton.Instance.spawnShipID == ShipSingleton.ShipType.small)
{
Instantiate(basicShip, transform.position, Quaternion.identity);
}
}

GameObject won't change after updating it with a Method (object reference not set)

Unity newb here :)
I have a button which calls this function which basicly sets a prefab to instantiate.
public void setTurret()
{
towerNode.setTurretType(turret)
Debug.Log("selected shop turret:" + turret.name);
}
My Tower_Node class handles the actual instantiate
public class Tower_Node : MonoBehaviour
{
private GameObject turret;
public void setTurretType(TowerType _turret)
{
turret= _turret.prefab;
}
private void OnMouseDown()
{
Instantiate(turret,GetBuildPosition(),Quaternion.identity);
}
...}
EDIT: What I also tried
public class Tower_Node : MonoBehaviour
{
private TowerType turret;
public void setTurretType(TowerType _turret)
{
turret = _turret;
}
private void OnMouseDown()
{
Instantiate(turret.prefab,GetBuildPosition(),Quaternion.identity);
}
...}
EDIT:
This is how the references in the inspector look. The shop script is the script with the setTurret() method
--
Setting the turret with the setTurretType method works. If i check it with Debug.Log() i get the right TowerType but outside of the function the gameobject is still =null and when i try to instantiate it gives me an NullReferenceException (because Gameobject is null obviously)
What am i missing here?
Thank you for your answers.
switch OnMouseDown() for
if (Input.GetMouseButtonDown(0))
in your update loop
just make sure that turret.prefab is a prefab
Instantiate(turret.prefab, GetBuildPosition(),Quaternion.identity);
I would say that if your TowerType class has a GameObject var named prefab, you should either assign the prefab to the prefab, or assign the turret to the _turret and the access the prefab.
Either this:
public void setTurretType(TowerType _turret) {
turret = _turret;
hoverTurret = _turret.hoverPrefab;
}
private void OnMouseDown()
{
Instantiate(turret.prefab, GetBuildPosition(),Quaternion.identity);
}
or
public void setTurretType(TowerType _turret) {
turret.prefab = _turret.prefab;
hoverTurret = _turret.hoverPrefab;
}
private void OnMouseDown()
{
Instantiate(turret.prefab, GetBuildPosition(),Quaternion.identity);
}
should work.
Edit:
You could set the turret while instiantiating:
public void setTurretType(TowerType _turret) {
turret.prefab = _turret.prefab;
hoverTurret = _turret.hoverPrefab;
}
private void OnMouseDown()
{
turret = Instantiate(turret.prefab, GetBuildPosition(),Quaternion.identity);
}

Unity 3d pass bool variable between two objects

how i can pass a simple boolean variable between two different object?
I can try this but didn't work...
First script:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
public bool active = false;
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.tag == "Obstacle")
{
active = true;
}
}
}
Second script (that read the boolean variable "active")
public class EmptyControllerColl : MonoBehaviour
{
public CollisionController controller;
public PlayerMovement movement;
public bool activeLocal = false;
private void Start()
{
GetComponentInChildren<CollisionController>();
}
void Update()
{
activeLocal = controller.active;
if(activeLocal == false)
{
Debug.Log("Nothing...");
}
if(activeLocal == true)
{
Debug.Log("Game over");
}
}
}
When the variable bool "Active" change its status, the variable "activeLocal" don't change status.. How can I resolve this problem?
Collision Controller is "connect" to Cube Object.
EmptyControllerColl is "connect" to emptyGameObject (parent of Cube).
This line
_ = GameObject.Find("cubo Variant").GetComponent<CollisionController>().active;
makes no sense. First of all there is no field or variable declared with the name _ so this shouldn't even compile at all. And secondly what do you need this for? Rather store the according reference once in the controller field and reuse it later.
Then for your usecase there is no need at all to store the value in a local variable ... this makes things only more complicated. Simply where you need it get the value from controller.active.
Also do not use == for tags. Rather check via CompareTag. The problem is that == silently fails if you have any typo or the tag doesn't exist at all. CompareTag rather throws an error that the given tag is not valid.
public class EmptyControllerColl : MonoBehaviour
{
// Best already drag this in via the Inspector in Unity
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
// As fallback get it ONCE on runtime
private void Awake()
{
// since you say the cube is a child of this empty object you do not use
// Find at all but can simply use GetComponentInChildren
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
}
void Update()
{
// No need to store this in a local field at all
if(!controller.active)
{
Debug.Log("Nothing...");
}
// use if else since both cases are exclusive and you don't even need to check the value twice
else
{
Debug.Log("Game over");
}
}
}
Event Driven - part A
In general you should avoid poll checks for a bool value in Update and rather come up with a more event driven solution! An example could look like:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
// Here everyone who wants can add listeners that get called as soon as
// we invoke this event. We will do it everytime the 'active' value is changed
public event Action<bool> OnActiveStateChanged;
// Backing field for 'active'
private bool _active;
// Property that reads and writes '_active'
// Everytime it is assigned it also invokes 'OnActiveStateChanged'
private bool active
{
get { return _active; }
set
{
_active = value;
OnActiveStateChanged?.Invoke(_active);
}
}
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.CompareTag("Obstacle"))
{
active = true;
}
}
}
Now you would register a listener for this event like
public class EmptyControllerColl : MonoBehaviour
{
// Best already drag this in via the Inspector in Unity
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
// As fallback get it ONCE on runtime
private void Awake()
{
// since you say the cube is a child of this empty object you do not use
// Find at all but can simply use GetComponentInChildren
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
// register a callback. It is allowed an save to unregister first
// which makes sure this is only registered exactly once
controller.OnActiveStateChanged -= HandleControlerActiveStateChanged;
controller.OnActiveStateChanged += HandleControlerActiveStateChanged;
}
private void HandleGameOver()
{
Debug.Log("Game over");
}
private void HandleControlerActiveStateChanged(bool value)
{
if(!value)
{
Debug.Log("Nothing...");
}
else
{
Debug.Log("Game over");
}
}
private void OnDestroy()
{
// always clean up listeners
controller.OnActiveStateChanged -= HandleControlerActiveStateChanged;
}
}
This now is way more efficient since you don't all time run an Update method. Instead the HandleControlerActiveStateChanged is only called when the value of active is actually changed.
Event Driven - part B
And then actually in your case there is need to use a bool at all you could use a simple event Action instead and remove all the bools entirely:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
public event Action OnGameOver;
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.CompareTag("Obstacle"))
{
OnGameOver?.Invoke();
}
}
}
Now you would register a listener for this event like
public class EmptyControllerColl : MonoBehaviour
{
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
private void Awake()
{
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
controller.OnGameOver -= HandleGameOver;
controller.OnGameOver += HandleGameOver;
}
private void HandleGameOver()
{
Debug.Log("Game over");
}
private void OnDestroy()
{
controller.OnGameOver -= HandleGameOver;
}
}
using UnityEngine;
public class CollisionController : MonoBehaviour
{
void Start()
{
// Calls the function ApplyDamage with a value of 5
// Every script attached to the game object
// that has an ApplyDamage function will be called.
gameObject.SendMessage("ApplyDamage", 5.0);
}
}
public class EmptyControllerColl : MonoBehaviour
{
public void ApplyDamage(float damage)
{
print(damage);
}
}
https://docs.unity3d.com/ScriptReference/GameObject.SendMessage.html

Item/Ability system using scriptableobjects in Unity

I'm new to Unity and I'm having some trouble creating scriptable objects for items to pick up and use in my project. I've created the initial set-up to make the items with variables that I can plug-and-play with.
What I'm trying to do is:
1 - plug in a monobehavior script for each new item's behavior when used.
2 - Allow the player to pick up the item on collision and use the item on key command. The player should only carry one item at a time.
Here is what I have so far.
the scriptable object (itemObject.cs)
[CreateAssetMenu(fileName = "New Item", menuName = "Item")]
public abstract class Item : ScriptableObject {
public new string name;
public string description;
public int damageAmount;
public int affectDelay;
public int affectDuration;
public int affectRange;
public float dropChance;
public float itemLife;
}
I have a script that will trigger if the player collides with the object (itemDisplay.cs)
Currently, this doesn't do anything but destroys the item.
public class itemDisplay : MonoBehaviour {
public Item item;
public static bool isActive = false;
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player")
{
var playerPickUp = other.gameObject;
var playerScript = playerPickUp.GetComponent<Player>();
var playerItem = playerScript.playerItem;
playerScript.pickUpItem();
bool isActive = true;
Object.Destroy(gameObject);
}
}
void Start () {
}
void Update(){
}
}
Currently, I have a pickup function and a use item function that's being handled by my player script(Player.cs). Currently, this just toggles a boolean saying that it did something when the item is collided with. My big question is how/where should I create and reference the item ability and how do I pass it to the player script here?
public void pickUpItem()
{
playerItem = true;
//itemInHand = itemInHand;
Debug.Log("You Picked Up An Item!");
// Debug.Log(playerItem);
if(playerItem == true){
Debug.Log("This is an item in your hands!");
}
}
public void useItem()
{
//use item and set
//playerItem to false
playerItem = false;
}
If your question is "how do I create an instance of an Item in my project", you do that by right clicking in your Project view, then selecting Create -> Item. Then drag the Item asset that you created from your Project view into the ItemDisplay scene object's item field in the Inspector.
Regarding how to pick up the item, you would pass the item to your Player script from your ItemDisplay script. You've got most of that already wired up.
public class ItemDisplay : MonoBehaviour
{
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player")
{
var playerPickUp = other.gameObject;
var playerScript = playerPickUp.GetComponent<Player>();
var playerItem = playerScript.playerItem;
// This is where you would pass the item to the Player script
playerScript.pickUpItem(this.item);
bool isActive = true;
Object.Destroy(gameObject);
}
}
}
Then in your Player script...
public class Player : MonoBehaviour
{
public void PickUpItem(Item item)
{
this.playerItem = true;
this.itemInHand = item;
Debug.Log(string.Format("You picked up a {0}.", item.name));
}
public void UseItem()
{
if (this.itemInHand != null)
{
Debug.Log(string.Format("You used a {0}.", this.itemInHand.name));
}
}
}
You should also make your Item class non-abstract, or leave it abstract and create the appropriate non-abstract derived classes (Weapon, Potion, etc.). I put an example of that below.
You can't attach a MonoBehaviour to a ScriptableObject if that's what you're trying to do. A ScriptableObject is basically just a data/method container that isn't tied to a scene. A MonoBehaviour has to live on an object within a scene. What you could do however, is add a "use" method to your Item class, and call that from a MonoBehaviour within the scene when the player uses an item.
public abstract class Item : ScriptableObject
{
// Properties that are common for all types of items
public int weight;
public Sprite sprite;
public virtual void UseItem(Player player)
{
throw new NotImplementedException();
}
}
[CreateAssetMenu(menuName = "Items/Create Potion")]
public class Potion : Item
{
public int healingPower;
public int manaPower;
public override void UseItem(Player player)
{
Debug.Log(string.Format("You drank a {0}.", this.name));
player.health += this.healingPower;
player.mana += this.manaPower;
}
}
[CreateAssetMenu(menuName = "Items/Create Summon Orb")]
public class SummonOrb : Item
{
public GameObject summonedCreaturePrefab;
public override void UseItem(Player player)
{
Debug.Log(string.Format("You summoned a {0}", this.summonedCreaturePrefab.name));
Instantiate(this.summonedCreaturePrefab);
}
}
Then change your UseItem method in Player:
public class Player : MonoBehaviour
{
public void UseItem()
{
if (this.itemInHand != null)
{
this.itemInHand.UseItem(this);
}
}
}

Access Serialized List Object for Custom Unity Editor Window

I am trying to create a Custom EditorWindow (TransporterToolKit.cs) with buttons of my game's fast-travel points.
I have a GameObject (TransporterSystem.cs, a singleton, the manager):
that has a LIST of child GameObjects that are Nodes (The GameObjects fast travel points). Each node has a Serializable TransporterLocation that holds details about the actual location.
I get null object error:
NullReferenceException: Object reference not set to an instance of an
object
whether I am running the game or not using my current TransporterToolKit.cs file
How do I access the list of nodes so I can get their Serializable TransporterLocation?
EDITOW WINDOW:
What my problem question is about.
TransporterToolKit.cs
public class TransporterToolKit : EditorWindow {
[MenuItem("Project ToolKits/Transporter ToolKit")]
public static void ShowWindow() {
GetWindow<TransporterToolKit>("Transporter ToolKit");
}
public List<TransporterNode> nodes;
private void OnEnable() {
//ERROR COMES FROM THIS LINE
nodes = TransporterSystem.s_Instance.GetAllTransporterNodes();
}
private void OnGUI() {
GUILayout.Label("Transporter System", EditorStyles.boldLabel);
//Create a list of buttons with the location name
foreach (TransporterNode node in nodes) {
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.BeginHorizontal();
if (GUILayout.Button(node.ThisLocation.locationName)) {
//Do something
}
GUILayout.EndHorizontal();
EditorGUILayout.EndVertical(); //end section outline
}
}
}
The other classes.
TransporterSystem.cs
public class TransporterSystem : Singleton<TransporterSystem> {
[Header("Transporter Nodes")]
[SerializeField]
private List<TransporterNode> nodeList;
public List<TransporterNode> GetAllTransporterNodes() {
return nodeList;
}
}
TransporterNode.cs
public class TransporterNode : MonoBehaviour {
[SerializeField]
private TransporterLocation thisLocation;
public TransporterLocation ThisLocation {
get {
return thisLocation;
}
set {
thisLocation = value;
}
void Awake() {
ThisLocation.locationName = gameObject.name;
ThisLocation.transporterLocation = transform.position;
}
}
TransporterNode.cs
public enum TRANSPORTER_NODE_STATE {
OFFLINE,
ONLINE
}
[Serializable]
public class TransporterLocation {
public Vector3 transporterLocation;
public TRANSPORTER_NODE_STATE locationState;
public string locationName;
public TransporterLocation() {
transporterLocation = Vector3.zero;
locationName = "NOT SET";
locationState = TRANSPORTER_NODE_STATE.OFFLINE;
}
}
It looks like s_Instance is null. The problem is, that you are asking for the TransporterSystem static instance in OnEnable of the EditorWindow, however, the static instance will only be set in Awake during play mode. (At least this is what I assume after testing your code in my project).
I would fix this by actively searching for the TransporterSystem from within the editor window:
TransporterToolKit.cs
private void OnEnable()
{
TransporterSystem system = FindObjectOfType<TransporterSystem>();
if (system == null)
{
Debug.LogError("No TransporterSystem in scene.");
}
else
{
nodes = system.GetAllTransporterNodes();
}
}
Alternatively, you could also make your singleton implementation lazy, similar to this:
public class MonoSingleton
{
public static TransporterSystem instance
{
get
{
if (m_Instance == null)
m_Instance = Object.FindObjectOfType<TransporterSystem>();
if (m_Instance == null)
Debug.LogError("Unable to find TransporterSystem instance in scene.");
return m_Instance;
}
}
private static TransporterSystem m_Instance;
}
To fix the problem, that the Node is only updated in play mode:
// Reset is called when the component is added to a GameObject or when Reset is selected from the inspector.
void Reset() {
#if UNITY_EDITOR
UnityEditor.Undo.RecordObject(gameObject, "Update LocationNode");
#endif
ThisLocation.locationName = gameObject.name;
ThisLocation.transporterLocation = transform.position;
}
Instead of assigning the values in Awake, you need to do it at edit time. The simplest example would be via the Reset callback (but you could trigger it from your editor window or a special button as well). The important part is, that you want to not only set the values, but actually serialize the data to disk. This means, marking the scene as dirty. Unity recommends, to use the Undo class, to not only record an undoable action, but also set the scene as dirty. Alternatively, you can just do:
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(gameObject.scene);
Beware, that the editor code needs to be either within a file that is placed in the Editor folder or surrounded by the compilation symbols UNITY_EDITOR.

Categories