Unity3D - unable to respawn an object after it has been destroyed - c#

I am having an issue respawning a prefab after it has been destroyed. I can't seem to get it to respawn back at its original start position after a second of being destroyed. I have created an empty game object and attached the SpawnTargets.cs script to it. I'm not sure of what the best methodology to approach this situation. Another object with a script attached to it does the actual destroy of the prefab. BulletCollisionHandler.cs works fine though. Thanks for any help. Code is below:
SpawnTargets.cs:
using UnityEngine;
using System.Collections;
public class SpawnTargets : MonoBehaviour
{
public GameObject targetCircle;
public GameObject targetSquare;
public GameObject targetStar;
private Vector3 circleSpawnPosition = new Vector3(0.0f, 1.227389f, -7.5f);
private Vector3 squareSpawnPosition = new Vector3(0.0f, 1.027975f, -7.993299f);
private Vector3 starSpawnPosition = new Vector3(0.0f, 1.8f, -7f);
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
void Update ()
{
SpawnTarget ();
}
void SpawnTarget()
{
}
}
BulletCollisionHandler.cs:
using UnityEngine;
using System.Collections;
public class BulletCollisionHandler : MonoBehaviour
{
public GameObject targetCircle;
// Use this for initialization
void Start ()
{
Destroy (gameObject, 2);
}
// Update is called once per frame
void Update ()
{
}
void OnCollisionEnter(Collision other)
{
if(other.gameObject.name == "TargetSquare")
{
other.gameObject.rigidbody.isKinematic = false;
((TargetMovementHorizontal)other.gameObject.GetComponent<TargetMovementHorizontal>()).enabled = false;
Destroy (other.gameObject, 1);
Debug.Log("Hit square");
}
else if(other.gameObject.name == "TargetCircle")
{
other.gameObject.rigidbody.isKinematic = false;
((TargetMovementHorizontal)other.gameObject.GetComponent<TargetMovementHorizontal>()).enabled = false;
Destroy (other.gameObject, 1);
Debug.Log("Hit circle");
}
else if(other.gameObject.name == "TargetStar")
{
other.gameObject.rigidbody.isKinematic = false;
((TargetMovementHorizontal)other.gameObject.GetComponent<TargetMovementHorizontal>()).enabled = false;
((TargetMovementVertical)other.gameObject.GetComponent<TargetMovementVertical>()).enabled = false;
Destroy (other.gameObject, 1);
Debug.Log("Hit star");
}
}
}

You're not calling Instantiate() anywhere, so it's hard to see where the new object would come from in the code you've supplied.
In any case, it might be better not to use Destroy. If you want to immediately reset the object, why not simply recycle it back to the start position? It's a good idea to avoid instantiating and destroying lots of objects, it's better to hide/disable the ones your don't need and unhide/re-enable them.
Here's a tutorial on the general idea. The tutorial is about groups of objects but the same trick would work for recycling single objects too.

You are better of using gameObject.SetActive( true/false ); for activating / deactivating the gameObject instead of just using Destroy.
Then if you are using Destroy you have 3 options that comes to mind for getting it into the desire position before the Player sees it.
1) You enable the game object after disabling its Renderer component. Then you equalize the transform's position / rotation the one you need. After this you re-enable the Renderer component. It should be placed where you want it.
2) You Instantiate the gameObject, but first making sure the Renderer component is disabled on its Prefab, by default, so you can re-assign its Transform values then - re-enable the Renderer again.
3) You make an invisible gameObject (an Empty gameObject) and Instantiate the wanted gameObject, you then make the Empty to be the parent of the newly created gameObject.. Provided that the parent Empty is exactly where you want it to be, when you instantiate and reset the child's position it should jump off right on top the the Empty parent.
I'm not giving code since you haven't and I don't have no idea of which method you might end up liking more. In terms of performance the Enable/Disable are the best option.
And as theodox says Object Pooling is your best friend for things like bullets, although it might be applied to many other gameObjects that might work as 'collections of objects' on your game's logic. It's totally worth learning.

Related

How to make Instantiated Game Objects script act separately for every object. Unity2D

I have a script for that Instantiates 2 game object but when something happens to one of them it also happens to the another one even when the conditions are not met for it. How can I make the script act separately for every Game Object?
GO script:
private Transform target;
public float speed = 2f;
private Animator anim;
public float H2Damage = 5f;
private healthBar Hp;
void Start()
{
target = GameObject.FindGameObjectWithTag("enemy").GetComponent<Transform>();
anim = gameObject.GetComponent<Animator>();
Hp = GameObject.FindGameObjectWithTag("enemy").GetComponentInChildren<healthBar>();
}
void Update()
{
target = GameObject.FindGameObjectWithTag("enemy").GetComponent<Transform>();
if (Hp.Died == true)
{
Hp.Died = false;
anim.SetBool("Hero2Attack", false);
anim.SetBool("Hero2Move", true);
}
if (!target || this.anim.GetCurrentAnimatorStateInfo(0).IsName("Hero2ATTACK"))
return;
transform.position = Vector2.MoveTowards(transform.position, target.position, speed * Time.deltaTime);
anim.SetBool("Hero2Move", true);
}
private void OnTriggerEnter2D(Collider2D col)
{
if (col.gameObject.CompareTag("enemy"))
{
anim.SetBool("Hero2Attack", true);
healthBar hp = col.transform.GetComponent<healthBar>();
hp.GiveDamage(H2Damage);
}
}
I believe they act the same way because they are getting the same GetComponentWithTag(), so they will Get the same objects. You also instantiate the animator, which is the exact same one, so they will do the same things. --> If it was as simple as changing the position by 1 meter per second, they would have different behavior (ie. be at different positions) But.... If you instantiate it, the current position is also instantiated, so you are going to have to get the current postion and set it to what you want, so both of these moving boxes aren't at the exact same position. You will have to do something similar here, you are going to want to either (create different scripts for the objects, or set the script values to different things)
TL;DR: You are copying the exact same script with the exact same components, so they will act similarly.
Fix: The only way to fix this is by setting their values after you instantiate them using GetComponent<>()or changing the script you assign them. (or any components for that matter)
Let me know if you have any problems or questions in the comments! :)

Unity - What is OOP - Null Reference Exception

im kind of newbie to unity and object oriented programming. Recently im trying to clone Cube Surfer mobile game. Basic idea from my view is this ;
-When we triggered to collactable cubes which consist script will be duplicated and it will be belong the main cube parent as child object then triggered cube will be destroyed.(After positioning)
-Later this duplicate child objects(cubes) will do the same when they enter trigger area of other collectable cubes(those will be the same prefab but did not yet create a prefab)
Im trying to collect(create a clone of it position and destroy the object) cubes. For first cube, I added some code to my movement script which is below.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Movement : MonoBehaviour
{
public GameObject addup;
Rigidbody rb;
float controlSpeed = 1.25f;
float forwardMovementSpeed = 10f;
private Vector3 axisGet;
float deathTime;
public int collected;
// Start is called before the first frame update
void Start()
{
rb = gameObject.GetComponent<Rigidbody>();
collected = 0;
}
// Update is called once per frame
void FixedUpdate()
{
axisGet = new Vector3(0, 0, Input.GetAxis("Horizontal"));
rb.MovePosition(transform.position + Vector3.left * forwardMovementSpeed * Time.deltaTime + axisGet * controlSpeed * Time.deltaTime);
}
private void OnTriggerEnter(Collider other)
{
if(other.tag=="add up")
{
gameObject.transform.position += Vector3.up;
var newObject = Instantiate(addup.gameObject, Vector3.zero, Quaternion.identity);
newObject.transform.parent = transform;
newObject.transform.position = gameObject.transform.position + Vector3.down;
Destroy(other.gameObject);
newObject.GetComponent<BoxCollider>().isTrigger = false;
collected++;
}
}
}
WORKED WITHOUT ERROR BUT THEN, I applied the same method to collectable cubes scripts.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UpNdown : MonoBehaviour
{
void Start()
{
}
// Update is called once per frame
void Update()
{
}
private void OnTriggerEnter(Collider other)
{
if (other.tag == "add up")
{
GameObject parentTransform;//?????????
parentTransform = gameObject.GetComponentInParent<GameObject>(); //get first cube component
parentTransform.transform.position += Vector3.up; //first cube one unit up
GameObject newObject; // ?????????
newObject = Instantiate(other.gameObject, Vector3.zero, Quaternion.identity) as GameObject; //???????????
Debug.Log(newObject);
var collect = parentTransform.GetComponent<Movement>().collected;
if (other != null)
{
Destroy(other.gameObject); //destroy triggered collactable
}
newObject.transform.parent = parentTransform.transform; //setting parent to new cube
newObject.transform.position = parentTransform.transform.position + Vector3.down * (collect + 1); //setting position of new cube
newObject.GetComponent<BoxCollider>().isTrigger = false; //prepare the below cubes(new cubes) for trigger with other collactable cubes
collect++;
}
}
}
And, I had nullexception error in every line in ontriggerenter method then, I changed(added) the lines with question marks. So, I get
ArgumentException: GetComponent requires that the requested component 'GameObject' derives from MonoBehaviour or Component or is an interface.
UnityEngine.GameObject.GetComponentInParent[T] (System.Boolean includeInactive) (at :0)
UnityEngine.GameObject.GetComponentInParent[T] () (at :0)
UpNdown.OnTriggerEnter (UnityEngine.Collider other)
I thought, I understood the OOP instance idea which objects in the scenes are instances scripts has their own value... but i dont understand that while I was operating on a instance why it is null in the memory :((((((((((((( if PC can't access how instantiates the object ?
SORRY I WRITE THIS LONG BUT IM ABOUT THE EDGE AGAIN I DON'T WANT TO QUIT BECAUSE OF FACING THIS PROBLEM AGAIN
TY FOR YOUR ANSWERS, ALREADY APPRECIATED :)
GameObject is no component (it is rather a container of all components attached to it!)
=> you can't get it using GetComponent or GetComponentInParent at all. (Btw Note that GetComponentInParent starts the search on this object itself first before bubling up the hierarchy so either way this isn't what you want to use).
What you want is simply transform.parent to get the Transform component of the parent object of the object this script is attached to (assuming the rest of your code does what it should)
private void OnTriggerEnter(Collider other)
{
// Rather use CompareTag instead of string ==
// The latter silently fails in case of typos making your debugging life miserabel
// it is also slightly less efficient
if (!other.CompareTag("add up")) return;
// Get the parent of this object
var parentTransform = transform.parent;
// Cache this you will need it later see below
var parentMovement = parentTransform.GetComponent<Movement>();
var collect = parentMovement.collected;
parentTransform.position += Vector3.up;
// By using the correct type you want later you can skip GetComponent
var newObjectCollider = Instantiate(other, Vector3.zero, Quaternion.identity);
Debug.Log(newObjectCollider);
Destroy(other.gameObject);
newObjectCollider.transform.parent = parentTransform;
newObjectCollider.transform.position = parentTransform.position + Vector3.down * (collect + 1);
newObjectCollider.isTrigger = false;
// This does absolutely nothing. Numeric values are passed by value and there is no connection
// between your local variable and the component you got it from
//collect++;
// you probably rather want to increase
parentMovement.collected++;
}
Or alternatively since you anyway have a specific component on your parent object you could also do
// Instead directly get this component
var parentMovement = GetComponentInParent<Movement>();
// Then wherever needed access the transform through it
var parentTransform = parentMovement.transform;
...
I'm quite sure though that the other way round it is more efficient since you already know exactly which parent you are searching the component on.
Or - and this would probably be the best option - cache that parent information once right away:
// If possible already reference this vis the Inspector
[SerializeField] private Movement parentMovement;
private Transform parentTransform;
private void Awake ()
{
if(! parentMovement) parentMovement = GetComponentInParent<Movement>();
parentTransform = parentMovement.transform;
}
Ty sir my first code was nearly the same of your first answer but didn't work again at least for error.
private Transform parentTransform;
private void Awake ()
{
if(! parentMovement) parentMovement = GetComponentInParent<Movement>();
parentTransform = parentMovement.transform;
}
But this worked, I guess the problem I need to define instances to class so they don't disappear instantly on the trigger function or direct I need to define them to class.
Anyway, thank you derHugo now need to solve different problems :D

How can I execute code if something is true, continue executing the code indefinitely, but stop evaluating the if statement?

I'm just starting out please excuse vast ignorance.
I'm writing a c# script in unity as part of the essentials training. I'm doing the 3d audio module and I thought I'd try and get a little bit fancier than the scope of this particular lesson which is supposed to be having an object fly through a window in a pre-built scene and make a 3d sound as it moves.
I wanted to make the movement of the object conditional upon a player moving close to it in 3d space. I figured out how to trigger the movement of an object in a script with an if statement that changes the transform parameters of the object the script is attached to when a 'distanceFromObject' variable is < 2. It works, however the script runs in the update section of the script which runs once every frame. This means that the object's transform parameters are changed every frame as expected but of course stops doing so when the distance between the object that's moving and the player exceeds 2.
I see the mistake I've made because if the object moves away when the player gets close then it will inevitably eventually move far enough away that the distanceFromObject variable will grow bigger than 2 whereupon it stops and just hovers in place. I don't know how to fix it though.
I need the script to check the distance between the object and the player every frame so that it will trigger the instance the player gets close enough, and when they get close enough, I need the object to move away, however once it has been triggered to move, I need the object to continue moving, but the script to stop checking what the distance is anymore.
The script looks like this
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FlyOff : MonoBehaviour
{
public Vector3 rotateChange;
public Vector3 positionChange;
public float distanceFromObject;
public GameObject character;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
distanceFromObject = Vector3.Distance(character.transform.position, this.gameObject.transform.position);
print (distanceFromObject);
if (distanceFromObject < 2)
{
transform.Rotate (rotateChange);
transform.position += positionChange;
}
}
}
Use flags instead of writing your logic in the if statement :
public class FlyOff : MonoBehaviour
{
// fields removed for more readability
// use a flag that's set to true/false
private bool isCloseEnough = false;
void Update()
{
distanceFromObject = Vector3.Distance(character.transform.position, this.gameObject.transform.position);
print (distanceFromObject);
// set the flag to true when player is close enough
if (distanceFromObject < 2)
{
isCloseEnough = true;
}
// even if the player gets far, the flag will remain true
if (isCloseEnough)
{
transform.Rotate (rotateChange);
transform.position += positionChange;
}
}
}
You can even apply the opposite logic to stop the object to move away when it has reach a certain distance :
if (distanceFromObject < 2)
{
isCloseEnough = true;
}
else if (distanceFromObject > SomeValue)
{
isCloseEnough = false;
}
If I understand correctly you could just add a bool flag and set it once you are close enough. Then you can start moving and skip further distance checks but keep moving forever.
private bool flyAway;
void Update()
{
if(!flyAway)
{
distanceFromObject = Vector3.Distance(character.transform.position, transform.position);
print (distanceFromObject);
if (distanceFromObject < 2)
{
flyAway = true;
}
}
else
{
transform.Rotate (rotateChange);
transform.position += positionChange;
}
}
In general: Avoid using print every frame! Even if you user doesn't see the log in a built app it is still causing overhead!

Delete spawning list objects consistently

Already 2 days I'm trying to solve this problem but I can't.I'm trying to delete the unlimited spawning barriers consistently when they are collide with the boundary.The barriers are made from 5 cubes.Any ideas?
public List<GameObject> spawning=new List<GameObject>();
public Vector3[] positions = new Vector3[5];
public GameObject barrier;
public GameObject boundary;
void Start()
{
StartCoroutine (SpawnBarrier());
}
void Update()
{
if(true)
{
foreach (GameObject move in spawning)
move.transform.Translate (0f, 0f, -0.1f);
}
}
IEnumerator SpawnBarrier(){
yield return new WaitForSeconds (3f);
while (true) {
for(int i=0;i<=4;i++)
{
spawning.Add (Instantiate (barrier, positions [i], Quaternion.identity)as GameObject);
}
yield return new WaitForSeconds (3f);
}
}
Your barriers are marked as isTrigger on their colliders therefore you can use OnTriggerEnter to detect when any of the barriers collides with the boundary.
You need to create new script, lets call that BoundaryDetector and attach it to the barrier prefab so that every instance of the barrier will have this script attached to it.
When OnTriggerEnter is called, check if the trigger is made by the boundary. This can be done by checking for the "Player" tag since boundary is tagged as Player in your screenshot. If the detected trigger tag is "Player", first remove the spawning from the spawning List then Destroy it.
The BoundaryDetector script is as below (Must be attached to the barrier prefab):
ScriptFromYourQuestion yourInstancingSript;
void Start()
{
GameObject obj = GameObject.Find("NameOfObjectScriptInYourQuestionIsAttachedTo");
yourInstancingSript = obj.GetComponent<ScriptFromYourQuestion>();
}
void OnTriggerEnter(Collider other)
{
//Detect if we collided with the boundary
if (other.CompareTag("Player"))
{
//Remove Self/barrier from the List
yourInstancingSript.spawning.Remove(this.gameObject);
//Delete Self/barrier
Destroy(this.gameObject);
}
}
Note: The ScriptFromYourQuestion should be replaced with the name of the script in your question.
MUST DO:
To get the code above working, the following changes must be made in the Update function of the script from your question:
1.You must attach Rigidbody to at-least one of the Objects (boundary or barrier). In this case, I suggest you do so to the barrier prefab.
2.You must remove move.transform.Translate (0f, 0f, -0.1f); and replace it with Rigidbody.MovePosition and use that to move your barries because you have now attached Rigidbody to your barries and this is the proper way to move a Rigidbody Object.
Maybe something like this:
Your Update function in the code form your quesion should looks something like this:
public float speed = 100.0f;
void Update()
{
if (true)
{
foreach (GameObject move in spawning)
{
//Get Rigidbody component
Rigidbody rb = move.GetComponent<Rigidbody>();
//Calculate Z-axis pos to move to
Vector3 pos = new Vector3(0, 0, 1);
pos = pos.normalized * speed * Time.deltaTime;
//Move with Rigidbody
rb.MovePosition(rb.transform.position + pos);
}
}
}
Your problem is that you have an infinite loop while true that you're not breaking out of. Instead you should add a counter. You haven't explained what you want to do but my example is that it will breakout once it completes 10 instantiations of 5 blocks.
IEnumerator SpawnBarrier(){
spawnCount=0
yield return new WaitForSeconds (3f);
while (true) {
for(int i=0;i<=4;i++)
{
spawning.Add (Instantiate (barrier, positions [i], Quaternion.identity)as GameObject);
}
if (++spawnCount==10)
{
break;
}
yield return new WaitForSeconds (3f);
}
}
If I understood corrently, you want to destroy barriers that collided with some kind of boundary?
If the 'boundary' is the visible screen space
In this case, we can assume that, once the object is invisible to MainCamera it can be counted as out of the boundary. Keeping that in mind, we can use OnBecameInvisible() method, which is method (or message, as stated in Unity Scripting API) called by MonoBehaviour class:
// A piece of barrier that gets destroyed once out of screen
public sealed class BarrierPiece : MonoBehaviour
{
// Called once the object is no longer visible to any (scene editor included) camera
private void OnBecameInvisible()
{
Destroy(gameObject);
}
}
NOTE: if you have more than one Camera in your scene, the object must be invisible to all of the cameras in order for the OnBecameInvisible() to be called.
If the 'boundary' is other object
In this case, there are many approaches based on your game:
Create GameObject with Rigidbody component and add a script which would have OnTriggerEnter(...) method and destroy colliding barriers in it.
(Inefficient) have two static Vector2 variables, one for pivotPosition, other for maximumOffset (this one should be squared for faster calculations). Then, have script on each of the barriers and in Update() checks whether the object's squared distance to the pivotPosition is greater than maximumOffset and call Destroy(gameObject) if the condition is true.
(others) ...
Those are two that came up quickly in my mind.
Hope this helps!
EDIT
Didn't look at the images...
So for your solution, you would need to assign Rigidbody component to the Boundary and add BoxCollider component on all of the barriers. Now, create a tag, called Barrier and tag all the barriers with it. Create a script:
// Destroys barriers on collision
public sealed class BarrierBoundary : MonoBehaviour
{
// Called once the script is created
// Checks if the object has Rigidbody component attached
private void Awake()
{
Debug.Assert(GetComponent<Rigidbody>() != null);
}
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.tag == "Barrier")
Destroy(collision.collider.gameObject);
}
}
(The code wasn't tested, so it can have some typos.)
Now, assign the script to the Boundary. And voila! Barriers are destroyed once hitting the boundary.
Hope (again) this helps!

How do I Transfer Instantiate put inside OnCollisionEnter() after created on another function OnCollisionExit() in Unity 3D?

I'm creating my first game "Endless Runner" and my problem is in the code, I don’t need to destroy the new object, I need to transfer them to the old place.
public class GenerateEnv1 : MonoBehaviour {
public GameObject []EnvTile;
float tileZ = 29.31f;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
void OnCollisionEnter (Collision col) {
if(col.gameObject.tag =="ground"){
GameObject o = Instantiate (EnvTile[Random.Range(0,4)], new Vector3(0f,0f,tileZ), Quaternion.EulerAngles(0,0,0));
tileZ += 2.96f;
}
}
void OnCollisionExit (Collision col) {
if(col.gameObject.tag =="ground"){
Destroy (col.gameObject,3); // this's i need to replace that in the new object
}
}
}
Since instantiating and destroying a tile is more costly, every time you leave a tile OnCollisionExit will fire and you will move that tile right after the next tile and so on and so forth, also instantiating two tiles onStart is enough since we're reusing them
public class GenerateEnv1 : MonoBehaviour
{
public GameObject[] EnvTile;
float tileZ = 29.31f;
// Use this for initialization
void Start()
{
Instantiate(EnvTile[Random.Range(0, 4)], new Vector3(0f, 0f, tileZ), Quaternion.EulerAngles(0, 0, 0));
Instantiate(EnvTile[Random.Range(0, 4)], new Vector3(0f, 0f, tileZ * 2), Quaternion.EulerAngles(0, 0, 0));
}
void OnCollisionExit(Collision col)
{
if (col.gameObject.tag == "ground")
{
col.gameObject.transform.position = new Vector3(col.gameObject.transform.position.x , col.gameObject.transform.position.y, col.gameObject.transform.position.z + (tileZ * 2));
}
}
}
The solution to your problem is called Object Pooling.
There's a tiny bit too little detail to provide a full code as an answer, but in general it works like that: Instantiate all the objects you'll need when the game loads, this is farily expensive and is best done at front.
Than, everything you do is do some juggling with gameObject.SetActive(true/false).
Generic implementation will contain a list of gameobjects (pool), you deactivate (instead of destroing) an object on OnCollisionExit, than you take new object from the pool (pick one that is not active), and activate it (and place it).
It is not clear however how the colliders are placed, obviously collision will not be triggered by an object that is not yet active on scene.
Is your script meant to go on the player?

Categories

Resources