Prefab doesnot activate when collide with object - c#

I'm making a project and there is a problem that I am facing.
I have two gameObject with 2D colliders (coming from a prefab) which moves right to left. When they touch each other they deactivate.
I also have an empty game object in which i add a script Respawner which randomly generates obstacles.
The problem is when they touch each other once, they never get re-activated again.
Respawner Empty GameObject :
Border :
Prefabs :
Respawn Script:
public class Respawn : MonoBehaviour {
[SerializeField]
private GameObject[] obstacles;
private List<GameObject> listname = new List<GameObject>();
void Awake(){
InitilizeObstacle();
}
void Start() {
StartCoroutine(RandomObstacleSpawn());
}
void InitilizeObstacle(){
int index = 0;
for (int i=0; i<obstacles.Length * 3 ; i++) {
GameObject obj = Instantiate(obstacles[index],new Vector3(transform.position.x,transform.position.y,-2f),Quaternion.identity) as GameObject;
listname.Add(obj);
listname[i].SetActive(false);
index++;
if(index==obstacles.Length){
index =0;
}
}
}
void shuffle(){
for (int i=0; i<listname.Count; i++) {
GameObject temp = listname [i];
int random = Random.Range (i, listname.Count);
listname [i] = listname [random];
listname [random] = temp;
}
}
IEnumerator RandomObstacleSpawn(){
yield return new WaitForSeconds(Random.Range(1.5f,2.5f));
int index = Random.Range (0, listname.Count);
while (true) {
if(!listname[index].activeInHierarchy){
listname[index].SetActive(true);
listname[index].transform.position = new Vector3(transform.position.x,transform.position.y,-2f);
break;
} else {
index = Random.Range(0,listname.Count);
}
StartCoroutine(RandomObstacleSpawn());
}
}
}
Script attach to prefab for move:
public class ObstacleMove : MonoBehaviour {
private float speed = -1.25f;
void Start() { }
void Update() {
Vector3 pos = transform.position;
pos.x += speed * Time.deltaTime;
transform.position = pos;
}
}
Scripts attach to prefab for touch border:
public class BorderTouch : MonoBehaviour {
void OnTriggerEnter2D(Collider2D target){
if(target.tag=="Border"){
gameObject.SetActive(false);
}
}
}

New answer based on new question:
If you want to make things happen AFTER the collision. Put a script on Border:
using UnityEngine;
using System.Collections;
public class borderListener : MonoBehaviour {
public Respawn rS;
void OnTriggerEnter2D(Collider2D target){
rS.spawnIt ();
}
}
On Unity Editor, drag the Respawn object to the Border Script on hierarchy.
Do not skip this step or things won't work!
On Respawn script, remove the last StartCoroutine(RandomObstacleSpawn()); line on IEnumerator RandomObstacleSpawn() method. And create a public method (to access from other script) anywhere inside Respawn script:
public void spawnIt(){
StartCoroutine(RandomObstacleSpawn());
}
Old answer based on old code:
From what I see on your package:
while (true) { //A
if(!listname[index].activeInHierarchy){
//B
listname[index].SetActive(true);
listname[index].transform.position = new Vector3(transform.position.x,transform.position.y,-2f);
break; //C
} else {
index = Random.Range(0,listname.Count);
}
StartCoroutine(RandomObstacleSpawn()); //D
}
I am a lil noob, I will try my best to help. But this piece of code makes me wonder:
while(true) what? what is true? (EDIT: found some observation bellow)
The code seem to do this path:
Go inside the while loop (A)
Go at the first option in if statement (B)
Go to the line break; (C)
Never reaches the StartCoroutine (D) > that is why it does not activate again.
If you try and put a StartCoroutine(RandomObstacleSpawn()); before the break; you probably will get an Unity crash. What if you take off the while statement at all? You need to adjust time of yield tho.
This is the code I would use:
IEnumerator RandomObstacleSpawn(){
yield return new WaitForSeconds(Random.Range(3.5f,4.5f));
int index = Random.Range (0, listname.Count);
if(!listname[index].activeInHierarchy){
listname[index].SetActive(true);
listname[index].transform.position = new Vector3(transform.position.x,transform.position.y,-2f);
}else{
index = Random.Range(0,listname.Count);
}
StartCoroutine(RandomObstacleSpawn());
}
EDIT: about the while(true) I've manage to find more information about this concept here: R, How does while (TRUE) work?
But still... the break; on the code is really making the Access to StartCoroutine(RandomObstacleSpawn()); unreachable .

The problem you are seeing is fairly common, when the following code is executed gameObject.SetActive(false); it deactivate everything on that gameObject.
Thus, the next time they would have collided will never be triggered.
I'm not sure what you want to achieve for the behaviour, but if you just want to hide the gameObjects you could disable the Renderer component instead.
gameObject.GetComponent<SpriteRenderer>().enable = false;
And switch it to true when needed.
That's why we must be carefull when we use SetActive(false) on gameObject in Unity.

Related

Detecting Enemies inside Area,Detecting Enemies inside Circle UNITY2D

im doing my first game and im trying to detect the enemies inside an circle area around my player.
i have two problems right now:
-When a start the game, the circlecollider and player collider is detected as enemies even when i use the compare tag "Enemy"
-My corroutine dont refresh every 2s, and only detect colliders one time when the game start
public class ItemDamage : MonoBehaviour
{
[SerializeField] int damage;
[SerializeField] Collider2D[] objectsInsideArea;
Vector2 radiusOfDamage;
int radius;
public void Start()
{
radiusOfDamage = new Vector2(radius, 0f);
StartCoroutine(DamageEnemy());
}
bool IsEnemy(string tag)
{
for (int i = 0; i < objectsInsideArea.Length; i++)
if (objectsInsideArea[i].gameObject.CompareTag("Enemy"))
{
Debug.Log("object {i} is an Enemy");
return true;
} else
{
Debug.Log("object {i}");
}
return false;
}
IEnumerator DamageEnemy()
{
objectsInsideArea = Physics2D.OverlapAreaAll(Vector2.zero, radiusOfDamage);
foreach (bool IsEnemy in objectsInsideArea)
{
Debug.Log("You damage the enemy");
}
yield return new WaitForSeconds(2);
}
}
For coroutine to repeat itself, it has to start itself in the end again like this:
IEnumerator DamageEnemy()
{
objectsInsideArea = Physics2D.OverlapAreaAll(Vector2.zero, radiusOfDamage);
foreach (bool IsEnemy in objectsInsideArea)
{
Debug.Log("You damage the enemy");
}
yield return new WaitForSeconds(2);
StartCoroutine(DamageEnemy());
}
Also instead of coroutine for this, you can use InvokeRepeating method.
You don't have to use a predefined array of objects, but rather use SphereRaycast method where you specify layer of objects to look for.
And your enemies can be located in the specific layer.
A Caroutine does not start it self.
For the other problem. objectsInsideArea is a Collider array and not a bool array. You cannot check it this way. Your code must look like this:
IEnumerator DamageEnemy()
{
while(someBoolOrTrue) {
objectsInsideArea = Physics2D.OverlapAreaAll(Vector2.zero, radiusOfDamage);
foreach (var collider in objectsInsideArea)
{
if(collider.tag.Equals("Enemy")) {
Debug.Log("You damage the enemy");
}
}
yield return new WaitForSeconds(2);
}
}

Unity3D: How can I destroy instances of an obstacle prefab once the player passes them on the z-axis in an endless runner?

I've tried setting different names for the instantiations and Destroy(this), Destroy(this.gameObject), and just Destroy(gameObject) but none of them seem to work...
Here is the code for the instantiations attached to an empty game object:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Spawner : MonoBehaviour
{
public GameObject[] obstaclePatterns;
public Transform player;
private float timeBtwSpawn;
public float startTimeBtwSpawn;
public float obstacleSpawnDistance;
public float obstacleSpDMin;
public float obstacleSpDMax;
public float decreaseTime;
public float minTime = 0.65f;
public float obstacleHeightMin = 10f;
public float obstacleHeightMax = 15f;
// makes obstacles randomly ahead of player
private void FixedUpdate() {
if (timeBtwSpawn <= 0) {
obstacleSpawnDistance = Random.Range(obstacleSpDMin, obstacleSpDMax);
int rand = Random.Range(0, obstaclePatterns.Length);
Instantiate(obstaclePatterns[rand], new Vector3(obstaclePatterns[rand].transform.position.x, Random.Range(obstacleHeightMin, obstacleHeightMax), player.position.z + obstacleSpawnDistance), Quaternion.identity);
rand = Random.Range(0, obstaclePatterns.Length);
Instantiate(obstaclePatterns[rand], new Vector3(obstaclePatterns[rand].transform.position.x, Random.Range(obstacleHeightMin, obstacleHeightMax), player.position.z + obstacleSpawnDistance), Quaternion.identity);
rand = Random.Range(0, obstaclePatterns.Length);
Instantiate(obstaclePatterns[rand], new Vector3(obstaclePatterns[rand].transform.position.x, Random.Range(obstacleHeightMin, obstacleHeightMax), player.position.z + obstacleSpawnDistance), Quaternion.identity);
timeBtwSpawn = startTimeBtwSpawn;
if (startTimeBtwSpawn > minTime) {
startTimeBtwSpawn -= decreaseTime;
}
}
else {
timeBtwSpawn -= Time.deltaTime;
}
}
}
And here is the code for the destroy function attached to the obstacle prefab:
using UnityEngine;
public class ObjectDestruction : MonoBehaviour {
public Transform player;
// Update is called once per frame
void FixedUpdate()
{
if (transform.position.z < player.position.z) {
Destroy(gameObject);
Debug.Log("test");
}
}
}
The Debug.Log works fine, but the console keeps producing this error: MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it. Your script should either check if it is null or you should not destroy the object. Spawner.FixedUpdate () (at Assets/Scripts/Spawner.cs:26)
EDIT: Destroy(this) seems to get rid of the error message, but it does not destroy the objects.
Thank you so much in advance!
I am not familiar with unity3d, but I suspect that when you destroy an object you also need to remove references to it:
Currently, you have an array of gameObjects stored in obstaclePatterns, so when you destroy the object it becomes null, but remains in the obstaclePatterns array and your code keeps trying to use it.
There are two ways to fix this:
When you destroy a GameObject you could remove it from the obstaclePatterns array, for example int x = IndexOf(obstaclePatterns, gameObject); obstaclePatterns[x] = null; or
Make your program check if the GameObject in obstaclePatterns is null, and if so, then do not use it, for example if(obstaclePatterns[x] != null){do stuff here}.
Alternately:
Are you sure that you want to destroy the game object? Wouldn't it be better to just move the object ahead of the player again and re-use it?

Unity c# mario game

I am following this youtube series that creates a Unity 3D Mario game.
( https://www.youtube.com/watch?v=vjL3S5dKLN4&index=6&list=PLZ1b66Z1KFKgm4QrzZ11jfaeHVaWHSHW7 )
So I am trying to recreate the "going down the pipe" script like on the Mario game. I create an Animator and I am trying to trigger the animation using a C# script on key down press.
1. The problem:
This guys writes his scripts in Javascript and apparently my Unity version does no longer support js so I need to write it in C# ( and I prefer writing it in C#).
Basically I need to convert this js to c#:
var PipeEntry : GameObject;
var StoodOn : int;
function OnTriggerEnter (col : Collider) {
StoodOn = 1;
}
function OnTriggerExit (col : Collider) {
StoodOn = 0;
}
function Update () {
if (Input.GetButtonDown("GoDown")) {
if (StoodOn == 1) {
//GameObject.Find("FPSController").GetComponent("FirstPersonController").enabled=false;
transform.position = Vector3(0, -1000, 0);
WaitingForPipe();
}
}
}
function WaitingForPipe () {
PipeEntry.GetComponent("Animator").enabled=true;
yield WaitForSeconds(2);
PipeEntry.GetComponent("Animator").enabled=false;
//GameObject.Find("FPSController").GetComponent("FirstPersonController").enabled=true;
}
2. MY CODE
My script currently looks like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//
using UnityStandardAssets.Characters.FirstPerson;
public class PipeEntry : MonoBehaviour
{
public GameObject pipe_entry;
public int StoodOn;
public int has_run = 0;
public int waiting_pipe = 0;
public int waiting_pipe_animation = 0;
IEnumerator OnTriggerEnter(Collider col)
{
StoodOn = 1;
yield return StoodOn;
}
IEnumerator OnTriggerExit(Collider col)
{
StoodOn = 0;
yield return StoodOn;
}
IEnumerator WaitPipeAnimation()
{
waiting_pipe_animation = 1;
yield return new WaitForSeconds(2);
}
// Update is called once per frame
void Update()
{
if (Input.GetButtonDown("PipeDown"))
{
if (StoodOn == 1)
{
// Freeze player
//GameObject.Find("Player").GetComponent<FirstPersonController>().enabled = false;
transform.position = new Vector3(0, -1000, 0);
WaitingForPipe();
has_run = 1;
}
}
}
public void WaitingForPipe()
{
pipe_entry.GetComponent<Animator>().enabled = true;
WaitPipeAnimation();
pipe_entry.GetComponent<Animator>().enabled = false;
waiting_pipe = 1;
//GameObject.Find("Player").GetComponent<FirstPersonController>().enabled = true;
}
// Start is called before the first frame update
void Start()
{
}
}
3. Facts
The animation works good if triggered directly from Unity, so the script does not work.
I set some debug variables that are set to value 1 too where script breaks and waiting_pipe_animation is the only one that doesn't reach.
4. UPDATE
- So basically the main issue is with this function:
yield return new WaitForSeconds(2);
If I place it in a void function I get this:
The body cannot be a iterator block because void is not a iterator
interface type.
If I set animation to true and never set it back to false, it works - but not as expected because obviously animation goes forever.
1. The functions OnTriggerEnter and OnTriggerExit don't return IEnumerator and aren't coroutines.
private void OnTriggerEnter(Collider col)
{
StoodOn = 1;
}
private void OnTriggerExit(Collider col)
{
StoodOn = 0;
}
2. If the character is going to have more than one animation, I suggest you to create transitions between the animations and create variables to control it. There are some useful functions, like SetFloat, SetInteger, SetBool, SetTrigger and so on. If not, enabling and disabling the animator like you did will be fine.
3. As stated by Okeme Christian, it's required to use StartCoroutine("WaitPipeAnimation") to call your coroutine.
4. pipe_entry.GetComponent<Animator>().enabled = false; should be inside the WaitPipeAnimation function, because all the code ouside it will still be executed in the same frame. So basically you are activating and deactivating the animator in the same frame and won't be able to see the animation.
IEnumerator WaitPipeAnimation()
{
waiting_pipe_animation = 1;
yield return new WaitForSeconds(2);
pipe_entry.GetComponent<Animator>().enabled = false;
}
If you want to understand better how it works, please test this code below:
void Start()
{
StartCoroutine("CoroutineTest");
Debug.Log("3");
}
IEnumerator CoroutineTest()
{
Debug.Log("1");
yield return new WaitForSeconds(5f);
Debug.Log("2");
}
Note that the print order in the console will be "1", "3", "2".
You cant call a coroutine as a function. Instead you start a coroutine like this
StartCoroutine("Fade");
Where Fade is the coroutine name, so in your case you will start it like this
StartCoroutine("WaitPipeAnimation")

How do I carry over data between scenes in Unity?

For a game I am developing I have a part when if the user gets too close to an enemy it switches scene to a battle scene. However I have no clue how to load that enemy into the battle screen (given that a user can battle many different enemies). Below is my current cod for the enemy. I was wondering if I could carry over it's name into the next scene or something. I just want my enemy to go from one screen to another when the scene is changed. Code would be appreciated thankyou
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FolllowAndLoad : MonoBehaviour
{
public Transform target;
public Animator anim;
public Rigidbody2D myRigidBody;
public string levelToLoad;
private static string keyname; // value I want to carry over
public float MoveSpeed;
private bool checkTrigger;
public Rigidbody2D targetRigidBody;
void Start()
{
target = GameObject.FindGameObjectWithTag("Player").GetComponent<Transform>();//getting the position of our player
anim = GetComponent<Animator>();
myRigidBody = GetComponent<Rigidbody2D>(); //getting my components
targetRigidBody = GameObject.FindGameObjectWithTag("Player").GetComponent<Rigidbody2D>();
}
void Update()
{
float distance = Vector2.Distance(target.position, myRigidBody.transform.position); //getting the distance between our player and our enemy
if (distance < 5)
{
transform.position = Vector2.MoveTowards(transform.position, target.position, MoveSpeed * Time.deltaTime); //moving our enemy towards our player
anim.SetBool("checkTrigger", true);
anim.SetFloat("MoveX", moveXvalue()); //updating the animations for our enemy
anim.SetFloat("MoveY", moveYvalue());
}
else if (distance > 5) //if out of range stop walking
{
anim.SetBool("checkTrigger", false);
}
}
int moveXvalue()
{
int value;
if (myRigidBody.transform.position.x < target.transform.position.x && Mathf.Abs(target.position.y - myRigidBody.position.y) < Mathf.Abs(target.position.x - myRigidBody.position.x)) //these are saying if the enemy is closer in x than in y use x animations and vice versa
value = 1;
else if (myRigidBody.transform.position.x > target.transform.position.x && Mathf.Abs(target.position.y - myRigidBody.position.y) < Mathf.Abs(target.position.x - myRigidBody.position.x))
value = -1;
else
value = 0;
return value;
}
int moveYvalue()
{
int value;
if (myRigidBody.transform.position.y < target.transform.position.y && Mathf.Abs(target.position.y - myRigidBody.position.y) > Mathf.Abs(target.position.x - myRigidBody.position.x))
value = 1;
else if (myRigidBody.transform.position.x > target.transform.position.x && Mathf.Abs(target.position.y - myRigidBody.position.y) > Mathf.Abs(target.position.x - myRigidBody.position.x))
value = -1;
else
value = 0;
return value;
}
public void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.name == "Player")
{
Debug.Log(gameObject.name);
anim.SetBool("checkInContact", true);
Application.LoadLevel (levelToLoad); //loading our level
}
}
}
There are a lot of ways to do this, but the simplest way to get something working quickly just until you get more familiar with Unity it to use a simple static class in your project that you can access from any script in any scene.
So if you were to make a new script in your project right now called SharedResources.cs and then pasted this into the script and saved it....
public static class SharedResources
{
public const int kSceneIs_TitleScene = 0;
public const int kSceneIs_ActualGameScene = 1;
public const int kSceneIs_HighScoreScene = 2;
public static int highScore = 0;
public static int enemyID = 0;
public static void sampleFunction()
{
//this is a sample method you can call from any other script
}
}
You could now be in a script in one scene and do this
SharedResources.highScore=SharedResources.highScore+20;
SharedResources.enemyID=5;
You could then open up a new scene and a script in that scene could access the high score
Debug.Log(SharedResources.highScore)
Debug.Log(SharedResources.enemyID)
You can also access constant and run subroutines that are in the static class as shown above.
The correct way to do this is up for debate and really depends on what your ultimate goal is. I will reference another link to a post that goes into more detail....
https://gamedev.stackexchange.com/questions/110958/unity-5-what-is-the-proper-way-to-handle-data-between-scenes
Ideally, you should read and understand the difference between using a simple static class versus one that derives from MonoBehavior, and also the different between a static class and a Singleton, which in many ways is much more powerful (but can also cause issues if you don't code it correctly)
Last but not least, don't forget you can also use the built in PlayerPrefs function in Unity to store scores and other settings that need to carry over between launches of the game....
https://answers.unity.com/questions/1325056/how-to-use-playerprefs-2.html

Android Controller for Unity game

I was making game in Unity 3D
and used the one click converter of unity to convert it in Android .apk
The game is opening in Android phone
but the player is not moving
Player controller Script:
using UnityEngine;
using System.Collections;
public class PlayerController : MonoBehaviour {
public Vector2 moving = new Vector2();
public int Bulletlimit = 0;
public int MaxBulletlimit = 3;
public bool Gun;
private float lastShotTime ;
public float fireDelay = 0.2f;
public Transform BulletDirection;
public Bullet bullet;
// Use this for initialization
void Start () {
lastShotTime = Time.time;
}
// Update is called once per frame
void Update () {
moving.x = moving.y = 0;
if (Input.GetKey ("right")) {
moving.x = 1;
} else if (Input.GetKey ("left")) {
moving.x = -1;
}
if (Input.GetKey ("up")) {
moving.y = 1;
} else if (Input.GetKey ("down")) {
moving.y = -1;
}
if (Input.GetKey ("s")) {
if(Gun){
if(Bulletlimit < MaxBulletlimit)
{
if(Time.time > lastShotTime + fireDelay)
{
Bullet clone = Instantiate (bullet, BulletDirection.position, Quaternion.identity) as Bullet;
Bulletlimit = Bulletlimit + 1;
lastShotTime = Time.time;
}
}
}
}
}
public void BulletCount()
{
Bulletlimit = Bulletlimit - 1;
}
}
How do I make him move in touch screens?
Your code is based on keystrokes - that (probably) wont apply to your touch screen.
There are a few ways you could do this, for something so simple I would probably try adding a Canvas along with some buttons to act as controls.
From there you can use the OnMouseDown()/OnMouseUp()/OnClick() methods (This also converts to touchscreens) instead of keystrokes.
How you code from there is a choice you have to make but in this case I would likely use the buttons to turn on/off movement bools and check/apply them in the Update() method.
If you're unsure of how to use the new unity UI try this...
https://unity3d.com/learn/tutorials/modules/beginner/ui/ui-canvas
If you're running the new version of unity (currently 5.1 I believe), my editor looks different to the tutorials and won't stay as default for some reason. Simply set the editor/inspector view to default if some of the options appear to be missing.
There are more complex things you can do with the actual touch inputs but I don't think you need to worry about it in this particular case.
Hope this helps :)

Categories