I want simply to destroy a deactivated instance of a Quad prefab (hp bar) , am able to destroy activated ones with :
private GameObject correspondingHpBar;
private string correspondingHpBarName;
void Start()
{
correspondingHpBarName = "hpBar1"
}
void Update()
{
correspondingHpBar = GameObject.Find (correspondingHpBarName);
if (shipHp <= 0)
{
Destroy (correspondingHpBar);
Destroy (gameObject);
}
}
This doesn't work with the deactivated objects, i googled hard but failed to find an answer.
Deactivated object don't have their Start or Update method called (nor any coroutine for that matter). In fact when an object is deactivated it is like its own time is frozen.
What you could do is create a method that does the destruction and find a way to call it from another script (for example a kind of controller that keeps reference to all HP bars in your scene).
The following is some pseudo-code (didn't check if it compiles, but you should adapt it anyway):
// in script for HP bar
public Boolean TryDestroy()
{
if (shipHp <= 0)
{
Destroy (correspondingHpBar);
Destroy (gameObject);
return true;
}
return false;
}
// in another script
private List<HPBar> _allHPBars;
void Awake()
{
_allHPBars = new List<HPBar>(FindObjectsOfType(typeof(HPBar)));
}
void Update()
{
var destroyedHPBars = new List<HPBar>();
foreach (var hpBar in _allHPBars)
{
if (hpBar.TryDestroy())
{
destroyedHPBars .Add(hpBar);
}
}
foreach (var destroyedBar in destroyedHPBars)
{
_allHPBars.Remove(destroyedBar);
}
}
Related
I am making a multiplayer game in which the characters can change scenes. I have items scattered in different scenes which the player can pick up and add to his inventory. The items have an 'AddItem' script attached to them. If the player is in the first scene and picks up the item, it is added to the inventory, however when the player changes the scene and picks up an item in that scene (which is using same script), it throws null reference error. Here is the script:
public class AddItem : MonoBehaviour
{
public Item item;
DialogueConditions dc;
private GameObject player;
private void Start()
{
player = GameObject.Find("Engineer");
dc = player.GetComponent<DialogueConditions>();
}
void pickUp()
{
Debug.Log("Picking up : " + item.name);
HasBriefDoc();
bool itemPickedUp = Inventory.instance.Add(item);
if(itemPickedUp == true)
{
Destroy(gameObject);
}
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
pickUp();
}
}
public void HasBriefDoc()
{
if (item == null)
{
dc.hasBrief = false;
}
else if (item.name == "Brief Doc")
{
dc.hasBrief = true;
}
else
{
dc.hasBrief = false; //throws null reference
}
}
}
After debugging, I found out that after scene change, the object 'dc' is assigned with null reference in the Start() method. I also tried initializing it with FindObjectOfType<DialogueConditions>(); and adding the start code to the Awake() method, but the problem still persists. The only way it is working is when I drop the 'dc' object and directly use static variables, which is something I do not prefer.
To anyone who is facing this issue, my problem was occurring due to wrong execution order of functions. The 'AddItem' script was running before my player had even instantiated, on which this DialogueConditions script was attached. That is why it was returning null reference. I changed the order of execution by instantiating my player in the Awake() method, as it will then run before the Start() method of AddItem, hence the null reference exception was removed.
preface: i am very new to c#, and this is an object grabbing script i have taken from a prefab and edited.
script goal: detect if a game object is in pickup distance. if it is, when the grip trigger is pressed, parent the object to the virtual hand. when the trigger is released, remove the parent relationship. Also, while the object is parented to the controller, if the B button is held, scale the object up, and if the A button is held, scale the object down, and reset the scale upon thumbstick click.
I can see when my controller collides with the game object, but when i press the designated button to parent the object, nothing seems to happen.
I'd really appreciate any help I could get with this script, as there might be a significant amount i'm doing wrong.
I should mention that I don't really need any sort of physics simulations, all i need is the ability to manipulate an object floating in space for viewing purposes.
Thanks in advance!
Here is my script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Grabber : MonoBehaviour
{
// Update is called once per frame
void Update()
{
OVRInput.Update();
if ((OVRInput.Get(OVRInput.Axis1D.SecondaryHandTrigger)) > 0.2f && CollidingObject)
{
GrabObject();
}
if ((OVRInput.Get(OVRInput.Axis1D.SecondaryHandTrigger)) < 0.2f && ObjectInHand)
{
ReleaseObject();
}
if (OVRInput.Get(OVRInput.Button.Two) && ObjectInHand)
{
ScaleUp();
}
if (OVRInput.Get(OVRInput.Button.One) && ObjectInHand)
{
ScaleDown();
}
if (OVRInput.Get(OVRInput.Button.SecondaryThumbstick) && ObjectInHand)
{
ScaleReset();
}
}
public GameObject CollidingObject;
public GameObject ObjectInHand;
public void OnTriggerEnter(Collider other)
{
if (other.gameObject.GetComponent<Rigidbody>())
{
CollidingObject = other.gameObject;
}
}
public void OnTriggerExit(Collider other)
{
CollidingObject = null;
}
private void GrabObject()
{
ObjectInHand = CollidingObject;
ObjectInHand.transform.SetParent(this.transform);
ObjectInHand.GetComponent<Rigidbody>().isKinematic = true;
}
private void ReleaseObject()
{
ObjectInHand.GetComponent<Rigidbody>().isKinematic = false;
ObjectInHand.transform.SetParent(null);
ObjectInHand = null;
}
Vector3 scaleChangeUp = new Vector3(0.01f, 0.01f, 0.01f);
Vector3 scaleChangeDown = new Vector3(-0.01f, -0.01f, -0.01f);
public void ScaleUp()
{
ObjectInHand.transform.localScale += scaleChangeUp;
}
public void ScaleDown()
{
ObjectInHand.transform.localScale += scaleChangeDown;
}
private void ScaleReset()
{
ObjectInHand.transform.localScale = Vector3.one;
}
}
Hi I'm currently developing a game in Unity and I run into a small problem for some reason the fourth scene I'm creating(Level 3) gets stuck and doesnt finish loading preventing my game from transitioning from scene 2(Level 2) to scene 3(Level 3). I have a scenemanager script that manages these transitions and it works fine for all other scene transitions except in the case explained above. Does anyone know what I'm doing wrong? Below you will see the the code responsible for handeling the scene transitions:
This is my scenemanager script responsible for additively loading and unloading scenes:
public static class MySceneManager
{
private static int lastLoadedScene = 0;
public static void LoadScene(int index, MonoBehaviour caller)
{
ObjectPooler objP = new ObjectPooler();
objP.ReleaseAll();
caller.StartCoroutine(loadNextScene(index));
}
private static IEnumerator loadNextScene(int index)
{
var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);
_async.allowSceneActivation = false;
while (_async.progress < 0.9f)
{
yield return null;
}
_async.allowSceneActivation = true;
while (!_async.isDone)
{
yield return null;
}
var newScene = SceneManager.GetSceneByBuildIndex(index);
if (!newScene.IsValid()) yield break;
SceneManager.SetActiveScene(newScene);
if (lastLoadedScene >= 0) SceneManager.UnloadSceneAsync(lastLoadedScene);
lastLoadedScene = index;
}
}
This is the script from where I call the scene transition:
public class HeartScript : MonoBehaviour
{
int HeartEnter = 1;
void Start()
{
DontDestroyOnLoad(this.gameObject);
}
void OnTriggerEnter2D(Collider2D other)
{
Scene scene = SceneManager.GetActiveScene();
if (other.gameObject.CompareTag("White Ball"))
{
if (HeartIndicator.numOfHearts < 3)
{
Debug.Log("Entered COLLIDER");
HeartIndicator.numOfHearts += 1;
}
if(scene.name == "Level 2" && HeartEnter == 1)
{
MySceneManager.LoadScene(3, this);
HeartEnter++;
}
this.gameObject.SetActive(false);
}
}
}
This may possibly happen due to:
Scene 3 is not added to "Scenes in build".
Or the game object holding co-routine script is not active.
After closing the game and reopening it, I want to load the last scene, so the player can continue from the level he reached. I've tried using PlayerPrefs as you see in the code bellow, but the game crash on startup.
GameManager Script:
bool GameHasEnded = false;
public float RestartDelay = 2f;
public float NextLevelDelay = 5f;
int level_index;
private void Start()
{
level_index = PlayerPrefs.GetInt("Last_Level");
SceneManager.LoadScene(level_index);
}
public void CompleteLevel()
{
Invoke("NextLevel", NextLevelDelay);
level_index = level_index++;
PlayerPrefs.SetInt("Last_Level", level_index);
}
public void EndGame()
{
if (GameHasEnded == false)
{
GameHasEnded = true;
Invoke("Restart", RestartDelay);
}
}
void NextLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex +1);
}
void Restart()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().path);
}
}
All scenes are linked with GameManager, they all have the same code that load the next scene:
FindObjectOfType<GameManager>().CompleteLevel();
You need to specify a defaultValue for GetInt so that the first time it starts up, it can pick the appropriate scene to start at.
Also, you need to track whether or not you have already loaded at scene start, and only do this load if it hasn't yet happened. GameManager seems like it should be made into a singleton but since it's not, you can just make this loaded flag static, so that is common among all GameManager instances.
Altogether, this might look like this:
public readonly int defaultLastLevel = 1; // Set as appropriate
private static bool loaded = false;
void Start()
{
if (!loaded) {
loaded = true;
level_index = PlayerPrefs.GetInt("Last_Level", defaultLastLevel);
SceneManager.LoadScene(level_index);
}
}
Also, your CompleteLevel has some very strange code where you post-increment and assign in the same line:
level_index = level_index++;
This is extremely hard to read and actually doesn't do what you want it to do. You can do level_index = SceneManager.GetActiveScene().buildIndex + 1; instead.
Also, you need to Save changes you make to PlayerPrefs with PlayerPrefs.Save();
Altogether, this makes your CompleteLevel look like this:
public void CompleteLevel()
{
Invoke("NextLevel", NextLevelDelay);
level_index = SceneManager.GetActiveScene().buildIndex + 1; // use this instead
PlayerPrefs.SetInt("Last_Level", level_index);
PlayerPrefs.Save();
}
hey does anyone knows how to make a C# script for object to re spawn after getting destroyed by a bullet
There are multible ways to archive this.
Note that you can also work completely without Coroutine but this usually sucks
private float timer;
private bool isRespawning;
private GameObject clone;
private void DestroyObject(GameObject obj)
{
isRespawning = true;
timer = 3f;
// first make a clone of the current Object
var clone = Instantiate(obj, obj.transform.position, obj.transform.rotation, objtransform.parent);
// disable the clone
clone.SetActive(false);
// Destroy the original
Destroy(obj);
}
private void Update()
{
if(Mathf.Approximately(timer,0f)) return;
timer += Time.deltaTime;
if(timer > 0) return;
// Set the clone active
clone.SetActive(true);
timer = 0;
}
Simplier would be a Coroutine:
private void DestroyAndRespawn(GameObject obj)
{
StartCoroutine(DestroyAndRespawnRoutine(obj));
}
private IEnumerator DestroyAndRespawnRoutine(GameObject obj)
{
// first make a clone of the current Object
var clone = Instantiate(obj, obj.transform.position, obj.transform.rotation, objtransform.parent);
// disable the clone
clone.SetActive(false);
// Destroy the original
Destroy(obj);
// Wait 3 seconds
yield return new WaitForSeconds(3f);
// Set the clone active
clone.SetActive(true);
}
But why should you destroy the object and respawn it? You can simply only disable and later re enable it
private void DestroyAndRespawn(GameObject obj)
{
StartCoroutine(DestroyAndRespawnRoutine(obj));
}
private IEnumerator DestroyAndRespawnRoutine(GameObject obj)
{
// disable the obj
obj.SetActive(false);
// Wait 3 seconds
yield return new WaitForSeconds(3f);
// Set the objactive
obj.SetActive(true);
}
Update
Since you want to call this on collision:
You will need another GameObject that works as a Manager for the respawning / enabling, disabling since you can not just attach that script to the object you disable -> would also disable the script and never re-enable the object.
Create a new empty GameObject e.g. called "RespawnManager"
Create a new Script RespawnManager.cs:
public class RespawnManager : MonoBehaviour
{
public static RespawnManager Singleton;
private void Awake()
{
if(Singleton)
{
enabled = false;
return;
}
Singleton = this;
}
private void DestroyAndRespawn(GameObject obj)
{
StartCoroutine(DestroyAndRespawnRoutine(obj));
}
private IEnumerator DestroyAndRespawnRoutine(GameObject obj)
{
// disable the obj
obj.SetActive(false);
// Wait 3 seconds
yield return new WaitForSeconds(3f);
// Set the objactive
obj.SetActive(true);
}
}
Than you can call it on collision like
OnCollisionEnter(Collision obj)
{
if (obj.gameObject.name != "bullet") return;
// pass this GameObject to the manager
RespawnManager.Singleton.DestroyAndRespawn(gameObject);
}
Set object active to False. using
gameobject.SetActive(false);
And you can use Coroutine to do this.
If you use Destroy(yourObject);, the object does get removed and you loose access.
You could make your Object into a prefab and spawn it where you want by using GameObject obj = Instantiate(...);
Obj contains the reference to the new spawned object and not to the prefab, which means you can later again use Destroy(obj);