I have been working on a building game and whenever I try to destroy a block all of the blocks that contain the script, DestroyBlock.cs are destroyed. I know why the problem is happening, but I don’t know how to fix it. Could anyone please help me? I am trying to get a development build out by this Saturday and I need a quick fix to this.
using UnityEngine;
using System.Collections;
public class DestroyBlock : MonoBehaviour
{
public static bool IsDestroyable = false;
void Update ()
{
if (Input.GetMouseButtonDown(1) && IsDestroyable == true)
{
Destroy(gameObject);
}
}
void OnMouseEnter()
{
renderer.material.color = Color.black;
IsDestroyable = true;
}
void OnMouseExit()
{
renderer.material.color = Color.white;
IsDestroyable = false;
}
}
Okay, so I've looked over the code more, and I'm almost positive the reason this is happening is because your IsDestroyable variable is static. This means a single instance of this is shared between every DestroyBlock object ever created. Any time an instance of DestroyBlock sees that the mouse has entered it, it sets IsDestroyable to true, which means that for every single block in existence IsDestroyable == true. Update is called, and, because there hasn't been a MouseExit yet, all the blocks get destroyed. As it stands, either every block is going to get destroyed or none of them will.
The easiest way to fix this would just be to change IsDestroyable to a non-static property.
Related
I'm pretty new to unity and I've been trying to get this scene switch to work however the trigger doesn't seem to be activating when the player hits it. The debug.Log is doing nothing so I'm stumped. I know my terminology may make no sense so let me show some pictures. If youre able to help it would be extremely helpful. Thank you!
This is the inspector panel for the object I want to trigger
This is the inspector the player
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneChangeScript : MonoBehaviour
{
public int iLevelToLoad;
public string sLevelToLoad;
public bool useIntegerToLoadLevel = false;
void start()
{
}
void update()
{
}
private void onTriggerEnter2D(Collider2D other)
{
if(other.CompareTag("Player"))
{
Debug.Log("Somethings Being Triggered!");
LoadScene();
}
}
void LoadScene()
{
if(useIntegerToLoadLevel)
{
SceneManager.LoadScene(iLevelToLoad);
}
else
{
SceneManager.LoadScene(sLevelToLoad);
}
}
}
Unity Monobehavior Lifecycle methods start with a capital and C# methods are case-sensitive. Therefore, the following methods need to be corrected to be used by Unity:
start => Start
update => Update
onTriggerEnter2D => OnTriggerEnter2D
Since the C# convention is that methods start with uppercase you'll less likely encounter this issue if you assume it starts with an uppercase letter rather than lower case. But it is better to confirm! Also if you are using Visual Studio, you can avoid much of these pains by investing the time to learn some shortcuts.
Hey I just spotted that you initiated the bool useIntegerToLoadLevel to be false and you are using this condition to load the scene in the if statement and the bool is never being set to true.
Try setting this bool to true when you enter the trigger, that is in the OnTriggerEnter2D method, should definitely work. It is a minor bug that anyone can make.
How would you find the time since a certain variable was changed? Take for example a boolean variable, how would you find the time since it was last changed? I want to use the boolean variable as a trigger (activating the trigger when it's true), but only after an exact, constant time (such as 0.5s) has passed since it was changed to true (it can only be changed from false to true).
Here is the code I have:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class hitRegistration : MonoBehaviour
{
AudioSource hitSound;
private bool hitState = false;
// Use this for initialization
void Start()
{
hitSound = gameObject.GetComponent<AudioSource>();
}
void OnMouseOver()
{
Debug.Log("Mouse is over game object.");
if (Input.GetKey(KeyCode.X) && hitState == false)
{
hitSound.Play();
hitState = true;
}
}
private void OnMouseExit()
{
Debug.Log("Mouse is no longer over game object.");
if (hitState == true)
{
// sound clip gets cut if the cursor leaves before its finished.
Destroy(gameObject);
}
}
// Update is called once per frame
void Update()
{
}
}
"OnMouseOver()" Is simply a function that is called when the mouse is placed over the game object in question. I want to delay destroying the game object until a certain time has passed.
First off, as noted in a comment, you are probably trying to solve this problem the wrong way and you are probably asking an "XY" question -- a question where you are asking a question about a proposed bad solution instead of asking a question about the actual problem you face.
To answer the question you actually asked, for better or worse: there is no way to associate behaviours with reading or writing a variable in C#, but you can associate behaviours with a property:
private bool hitState; // The "backing store".
private bool HitState
{
get
{
return hitState;
}
set
{
hitState = value;
}
}
You would then use HitState rather than hitState throughout the rest of your class.
Now you can add whatever logic you want that happens when the property is read or written:
private DateTime hitStateTime = default(DateTime);
private bool hitState; // The "backing store".
private bool HitState
{
get
{
return hitState;
}
set
{
hitState = value;
hitStateSet = DateTime.Now;
}
}
Now you know when it was set. And so on.
Unless you really need to keep track of how much time has passed on each single frame, one way to do what you are asking for is to use Unity Coroutines.
A coroutine is a method that runs in parallel with the main thread. To solve your question, you can first create a coroutine in the same script, that waits and then does the thing you want to have delayed. A couroutine in Unity is a method that takes up to one parameter and has an IEnumerator return type. You use yield return WaitForSeconds(t); inside the coroutine to have it delay for t seconds.
Then, once it's time to die, check if the mouse is currently hovering over the object with isHovered (set in your OnMouseOver/OnMouseExit methods). If it is, keep a note that it's time to die. If it isn't, then it can die immediately.
IEnumerator WaitToDie(float delaySeconds)
{
yield return new WaitForSeconds(delaySeconds);
// If the mouse is on the object, let OnMouseExit know we're ready to die
if (isHovered)
{
readyToDie = true;
}
// Otherwise, just die
else
{
Destroy(gameObject)
}
}
And then inside your OnMouseOver code, run the coroutine after starting the sound
void OnMouseOver()
{
isHovered = true;
Debug.Log("Mouse is over game object.");
if (Input.GetKey(KeyCode.X) && !hitState)
{
hitState = true;
hitSound.Play();
// we want to delay for half a second before processing the hit.
float delaySeconds = 0.5;
IEnumerator coroutine = WaitToDie(delaySeconds);
StartCoroutine(coroutine);
}
}
And in your OnMouseExit, let everything know that you're done hovering and check if it's past time to die or not.
private void OnMouseExit()
{
isHovered = false;
Debug.Log("Mouse is no longer over game object.");
if (readyToDie) {
Destroy(gameObject);
}
}
Altogether this code will have the object die when both the mouse is off the object AND the time has elapsed.
As a sidenote, I think you might want to revisit how you are checking for a hit, unless your really want to trigger from the player holding X and then moving the mouse over the object. If you intend to trigger any time X is pressed down while the mouse is on top, you might want to put the check in Update and check Input.GetKey(KeyCode.X) && !hitState && isHovered
keep a seperate variable(DateTime) and call it lastUpdate. then be sure to set it to DateTime.Now, each time the bool you're tracking is updated. then when you need to see how long its been you can just subtract:
DateTime lengthOfTime = DateTime.Now-lastUpdate;
from lengthOfTime you can now access how many days, hours, minutes, and/or seconds have passed.
im on my phone so take it easy on my pseudo-code.
good luck
So I'm basically brand new to unity and C# and have been following along with tutorials online (N3K 2D Platformer, YouTube), I'm trying to create a basic UI to display score etc and I seem to have come across a null pointer exception which I can't seem to figure out as I have referenced the two objects that are causing this error, namely my scoreText object and my hitPointText object.
As I've said I did reference those very objects by dragging them from my hierarchy and into the fields I had created in my level manager script in the inspector and further to that I am simply following along with a tutorial and have done exactly as the video has instructed, but yet on the video it seems to work fine.
The offending lines of code are:
scoreText.text = score.ToString();
hitPointText.text = hitPoints.ToString();
This tutorial is now over 1 year old, is it possible that a unity update has changed that way things NEED to be referenced?
I'll post my level manager code below in the hopes that someone may be able to point out the error that I seem to be missing.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LevelManager : MonoBehaviour
{
public static LevelManager Instance { set; get; }
public Transform spawnPosition;
public Transform playerTransform;
private int hitPoints = 3;
private int score = 0;
public Text scoreText;
public Text hitPointText;
private void Awake()
{
Instance = this;
scoreText.text = score.ToString();
hitPointText.text = hitPoints.ToString();
}
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
private void Update ()
{
if(playerTransform.position.y < (-10))
{
playerTransform.position = spawnPosition.position;
hitPoints--;
if(hitPoints <= 0)
{
Debug.Log("Your Dead!");
}
}
}
public void Win()
{
Debug.Log("Victory");
}
}
Snippets of screens below:
Scene view of unity engine
Game view of unity engine, with game running
So here is a snippet of code from my player class which uses Instance on the LevelManager script in order that it can have access to the win() method as can be seen in the last case of the switch "WinPost", not sure if that is what you are referring to when your mentioning singleton, other than that never is the term singleton used in any of the scripts I have.
switch (hit.gameObject.tag)
{
case "Coin":
Destroy(hit.gameObject);
break;
case "JumpPad":
verticalVelocity = jumpForce * 2;
break;
case "Teleporter_1":
controller.enabled = false;
transform.position = hit.transform.GetChild(0).position;
controller.enabled = true;
Debug.Log("This works!");
break;
case "Teleporter_2":
controller.enabled = false;
transform.position = hit.transform.GetChild(0).position;
controller.enabled = true;
Debug.Log("This works!");
break;
case "WinPost":
LevelManager.Instance.Win();
break;
default:
break;
}
My guess would be that the components aren't initialized when you call Awake. Awake gets called as a constructor-kind-of method as soon as the object is created. When it is called, you can't be sure if the other components got initialized already.
I would suggest you copy the assignments you make in Awake into Start and come back to see if it works. Start gets called after the GameObjects have their components initialized.
private void Awake()
{
Instance = this;
}
// Use this for initialization
void Start ()
{
scoreText.text = score.ToString();
hitPointText.text = hitPoints.ToString();
}
Thanks to everyone for trying to help and all the great suggestions.
Ultimately I ended up breaking my game in the process of trying to recreate the same UI in a new blank scene, I did manage to recreate the same error before breaking my game which at the time then left me none the wiser. However due to the fact I broke my game I had to step back at least two tutorials and recreate the level manager object and the empty child spawnPosition object, (the level manager script was ok, it was just the level manager object and its child that I broke), anyway in having to recreate both of those objects again everything now seems to work as intended and so this leads me to the conclusion that the problem was not the code but the objects themselves???
Thanks again to everyone that tried to help, another day another learning experience.
D.
I have my own dialog system which is basically a gameobject with a collider. After triggering collider, Canvas with a Text component should show up. So far, this works. Now, I want to make it happen only once. Here how its work: I start level trigger showing dialog. Game is pretty hardcore so player probably will die. When player dies, I call Application.LoadLevel(Application.loadedLevel); (so basically I restart level)
If I use something like this in scrip with collider
private static bool firstTimeText = true;
void OnTriggerEnter2D(Collider2D coll)
{
if (coll.tag == "Player" && firstTimeText)
{
GetComponent<BoxCollider2D>().enabled = false;
firstTimeText = false;
}
}
everything works like a charm. EXCEPT if I make copy of this gameobject, static variable in script will "handle" that every instantion of this object will have firstTimeText on false after trigger dialog first time. So basically I can use it only once.
So is there any sollution for making trigger which run only once and not reset after Application.LoadLevel?
Also this doesn't work
void Awake()
{
DontDestroyOnLoad(transform.gameObject);
}
Static variables are globally identical - therefore if you need to have multiple gameobjects exhibiting this behavior, they'll need to hook into different static values in some way.
One way to do this would be to have a key, and then keeping a static list of all "keys" already displayed. My recommendation would be a HashSet - thus
private static HashSet<string> firstTimeSet = new HashSet<string>();
public string singleRunKey;
void OnTriggerEnter2D(Collider2D coll)
{
if (coll.tag == "Player"
&& firstTimeSet.Add(singleRunKey))
{ GetComponent<BoxCollider2D>().enabled = false; }
}
Note that .Add returns true if the item wasn't in the collection (and adds it), but false if it already was (and does not add it, because no duplicates). All you have to do now is assign singleRunKey a unique value per object via the Inspector, and each one will run exactly once
Consider just having a static class that will set a flag to indicate if the dialog should appear or not. Note that I am not inheriting from MonoBehaviour here.
public static class ApplicationData {
public static bool ShowDialog = true;
}
Usage:
if (ApplicationData.ShowDialog)
{
ShowDialog();
ApplicationData.ShowDialog = false;
}
The static class will retain its values during the lifetime of your application. So, it will retain the FALSE value even if you reload your scene.
Thanks for help in advance. Here is a short snippet of the code that I am having an issue with.
GameObject[] allMotor_array;
public List<GameObject> BrokenMotor_list = new List<GameObject>();
void Start()
{
allMotor_array = GameObject.FindGameObjectsWithTag ("Motors");
}
void Update()
{
foreach (GameObject motor in allMotor_array)
{
if(motor.GetComponent<Pump_event>().damaged)
{
BrokenMotor_list.Add(motor);
}
}
}
I have an array of Gameobjects that is created on Start, each of the gameobjects in the array have a script called Pump_event. What I want to do is add the gameobject with a true boolean (damaged) to the list so that I can create a GUI list of all the motors that are damaged (and then take further action on those motors).
With the current code it instantiates the array fine, but when One of the motors boolean changes to true the list tends to continuously add the motor gameobject to the list on each update cycle. So what I want is to figure out a way of adding the gameobject to the list ONCE.
Having it in the update() is probably not the best method but I really am stuck on how to approach this.
G
The Solution to my problem
Thanks for your answers, you all had well thought out responses. I appreciate it. I didn't go with 1 persons method but instead adapted logical approaches found here to work with my script/s.
Here is what I did.
In my pump_event script the events are sorted in a Case and switch as damage increased on the pump the event would escalate. So I added in a section to that script to include "reporting" the damage.
public class Pump_event : MonoBehaviour
//The damage has taken place and event_category=0\\
switch (event_category)
{
case 0:
Master_script.GetComponent<Control_room>().AddtoList (gameObject);
event_category = 1;
break;
I took advice not to insert these types of programing and placed it into its separate class which works out well.
public class Master_script: MonoBehaviour
public void AddtoList(GameObject motor_tobadded)
{
BrokenMotor_list.Add(motor_tobadded);
}
This also eliminated the need on having an array holding all of the pump event controllers as well.
Now the script all works fine. It may not be most efficient but it is doing its job.
Thank you again to all that helped.
In your Pump_event Script you can have a event Action which you register in this snippet and whenever damaged is set true you need to fire the event.
Example:
// in Pump_event Class
public static event Action<GameObject> OnDamagedValueChanged;
private bool _damaged;
public bool Damaged
{
get { return _damaged;}
set
{
_damaged = value;
if(_damaged)
{
if(OnDamagedValueChanged != null)
OnDamagedValueChanged(gameObject);
}
}
}
In your Current Class where you have array of GameObjects:
void OnEnable()
{
Pump_event.OnDamagedValueChanged += HandleOnDamagedValueChanged;
}
void OnDisable()
{
Pump_event.OnDamagedValueChanged -= HandleOnDamagedValueChanged;
}
void HandleOnDamagedValueChanged(GameObject obj)
{
if (!BrokenMotor_list.Contains (obj))
{
BrokenMotor_list.Add (obj);
}
}
Using Actions is a better approach than doing it in Update Method. It is not good for performance to keep checking for a bool in iteration in update method. and try to avoid GetComponent and Find/FindObjectWithTag Methods in Update. It is not good practice. I hope this is helpful.
According to the code you have posted, the problem lies within the fact that the damaged property is never reset. One solution would be to reset this property once you add it to the list, like so:
if(motor.GetComponent<Pump_event>().damaged)
{
motor.GetComponent<Pump_event>().damaged = false;
BrokenMotor_list.Add(motor);
}
However, multiple copies of the same object could still be added to your list if the motor is damaged again.
To go around this, you could use a HashSet. The hash set will allow only one copy of an object to exist within it, thus, if an object is already present is will not be added again.
The catch is that you will need to override the GetHashCode and Equals methods for your GameObject class since these will be used internally by the hash set to place items within itself and identify duplicates.
check if list already contains motor.
if(motor.GetComponent<Pump_event>().damaged)
{
if(BrokenMotor_list.Contains(motor))
{
BrokenMotor_list.Add(motor);
}
}
although on msdn describes how to implement IEquatable in case if you want compare different objects(with different references) https://msdn.microsoft.com/ru-ru/library/vstudio/bhkz42b3%28v=vs.100%29.aspx
if (!BrokenMotor_list.Contains (motor)) {
BrokenMotor_list.Add (motor);
}
You'd better do this after damage event occur by add a delegate.