How can I enable one by one an array element(C#) Unity - c#

This code is C# and I'm using the Unity game engine.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class CollisionDetectionForQuest : MonoBehaviour {
public GameObject[] Garbages;
public GameObject[] GarbagesDummy;
public bool activateInactive;
//public GameObject[] Counter;
void Start(){
Garbages = GameObject.FindGameObjectsWithTag("Trash");
foreach (GameObject garb in Garbages) {
}
GarbagesDummy = GameObject.FindGameObjectsWithTag ("TrashDummy");
foreach(GameObject dummy in GarbagesDummy){
dummy.gameObject.SetActive (false);
}
}
void OnCollisionEnter(Collision obj){
if (obj.gameObject.CompareTag ("Trash")) {
obj.gameObject.SetActive (false);
activateInactive = true;
//in this line i want to activate my GarbageDummy 1 by 1 base on
//every Trash i put in my collider.
foreach(GameObject a in GarbagesDummy){
a.gameObject.SetActive (true);
}
}
So basically I want to activate the garbagedummy gameobject 1 by 1 on every trash I put on my collider. But I tried this and it doesn't work:
foreach(GameObject dummy in GarbageDummy){
for(int i = 0; i < GarbageDummy.Length; i++){
dummy.gameobject.setActive(true);
}
}
anyone help please.
Here is my screenshot:

If you would like to activate a single garbage dummy object (any of them in the array), then you don't want to use a loop there. Instead, keep track of which one you've activated up to as a field like this:
public int ActivatedDummyIndex; // Add this!
public GameObject[] Garbages;
public GameObject[] GarbagesDummy;
...
Then whenever a trash object is added, increase that number and use it as an index in your GarbagesDummy array:
void OnCollisionEnter(Collision obj){
if (obj.gameObject.CompareTag ("Trash")) {
obj.gameObject.SetActive (false);
activateInactive = true;
// Get the next dummy:
GameObject dummy = GarbagesDummy[ActivatedDummyIndex];
// Increase the index for next time:
ActivatedDummyIndex++;
// Activate that dummy:
dummy.SetActive(true);
}
}
This assumes that the number of dummy objects is the same as the number of trash objects. If you've got more trash objects and add them all, you'll eventually get an index out of range exception (as it would "run out" of dummy objects to use).
Whilst that should answer your question, it's worth mentioning that this isn't the typical approach. Rather than having a fixed number of hidden dummy objects and showing one when needed, you'd normally instance a new dummy object instead (using GameObject.Instantiate).

Related

How to create a winning condition when a certain number of game objects have spawned

I am trying to get my 3-D Tic-Tac-Toe game project to work, I have game objects which are named cells that are instantitated I press OnMouseDown() click it makes a cell object spawn in its grid space. I don't want to use UI with the basic prefabs I created. Is there a way to get my game objects instantiated and once it reaches a certain number as a winning condition? I have considered using pathfinding but I am not certain if that would be the correct approach. I have looked every where to find a solution that is unique to my problem but could not find a solution. Perhaps, I am asking the wrong questions but I am desperate so that is why I came her to see if I could get input on how to approach this issue.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine.SceneManagement;
public class Cell : MonoBehaviour
{
public GameObject cell;
public GameObject negCell;
public GameObject alter;
public Transform transPos;
[SerializeField]
private bool isTapped = false;
private int counted;
public int gameObjectCount;
void Start()
{
gameObjectCount = GameObject.FindGameObjectsWithTag("Cell1").Length;
}
void Update()
{
}
public void OnMouseDown(int counted) //click and point to create and deestroy objects
{
counted = gameObjectCount;
isTapped = true;
transPos = alter.transform;
Instantiate(cell, transform.position, Quaternion.identity);
StartCoroutine(WaitForObject());
Debug.Log("Alter Destroyed!");
gameObjectCount++;
DestroyGameObject();
return;
}
IEnumerator WaitForObject()
{
if (isTapped == true)
{
Instantiate(negCell, -transform.position, Quaternion.identity);
isTapped = false;
}
yield return new WaitForSeconds(3f);
DestroyGameObject();
}
void DestroyGameObject()
{
if(gameObject == alter)
{
DestroyImmediate(alter, true);
}
else
{
DestroyImmediate(cell, true);
}
}
}
There are two easy ways to achieve this.
The first one would be to add a static member in your class, let's say :
private static int _instanceCounter = 0;
This will act as a class instances counter.
All you have to do is to increment this variable every time you instantiate a new game object.
Finally, base your win condition on the number of instances of the class you want.
You can also decrement this variable if for some reason at a moment you call the Destroy method on a specific game object.
The other way would be to use the FindObjectsOfType method from Unity which returns an array of all instances in your current scene.
By accessing the length of this array, you'll have the number of instances.
However, this only count for the current number of instances when this method is called. Note that you can also include the inactive game objects from the scene (those which are in grey within your hierarchy panel).
You now have two ways to do it, depending on how you want to achieve your win condition, i.e. the total of game objects instantiated OR a specific number of game objects at a given time.

Cloning too many of the same object when pressing an object to replace instead of the other

I want to replace the broken table with a fixed table but when I press the button it places the table multiple times around the background. This is the script for the replacement.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ReplaceObjects : MonoBehaviour
{
public GameObject Broken_Table;
public GameObject Table;
private bool used = false;
void Start()
{
}
void Update()
{
if (Input.touchCount > 0)
{
GameObject broken_Table = Instantiate(Broken_Table) as GameObject;
GameObject table = Instantiate(Table) as GameObject;
Replace(broken_Table, table);
used = true;
}
}
void Replace(GameObject obj1, GameObject obj2)
{
Instantiate(obj2, obj1.transform.position, obj1.transform.rotation);
Destroy(obj1);
}
}
You are instantiating 3 objects for every frame you have input.
GameObject broken_Table = Instantiate(Broken_Table) as GameObject;
GameObject table = Instantiate(Table) as GameObject;
and
Instantiate(obj2, obj1.transform.position, obj1.transform.rotation);
All of the above statements clone objects into your scene. You're then destroying the broken_Table right after creating it, meaning you're creating 2 fixed tables for every frame your finger is held down.
To fix this, you can do both of the following things:
Only enact logic on the first frame your player "touches" the object, rather than every frame. The best way to do this is to utilize the GetTouch method and only act during TouchPhase.Began.
Do not instantiate more than 1 "fixed table" in this code.
Your code will be called every frame while your touchcount is superior than zero.
So as long as you hold your finger on the screen it will call the Replace() method.
You can add your "used" bool to the previous if to avoid that, like so:
void Update()
{
if (Input.touchCount > 0 && !used)
{
Replace(broken_Table, table);
used = true;
}
}

How do I properly detect game objects?

My goal is to write one script that I can use on different game objects and it should have specific variables tied to it on that game object only without affecting other scripts in the process.
For example, if I take this script and put it on two game objects each game object should have their own unique variable value in that same script.
If my question is not clear enough, I'm more than happy to elaborate further.
I have a good understanding of the Unity Editor, however, I'm pretty new to C# so I don't think it's unreasonable that I made a rookie mistake somewhere in my code.
The way I've got things setup is that I have two separate scripts:
Fighting controls the values like the Team, Health, Attack Damage, Cool Down, Cooling down and Snap
TrigDetect controls the detection of a trigger being activated as a result of an enemy entering the trigger radius.
The problem I'm currently having lies in the TrigDetect script I guess.
It should also be noted that an empty attached to each game object in question contains both of these scripts and is tagged as "Troop".
TrigDetect
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TrigDetect : MonoBehaviour
{
//public GameObject[] Enemy;
bool once = false;
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Troop"))
{
//Debug.Log("Entered");
}
}
void OnTriggerExit(Collider other)
{
if (other.CompareTag("Troop"))
{
//Debug.Log("Exitted");
}
}
void OnTriggerStay(Collider other)
{
if (other.CompareTag("Troop"))
{
Fighting self = GetComponent<Fighting>();
GameObject g = GameObject.Find("Detection");
Fighting fScript = g.GetComponent<Fighting>();
//Enemy = GameObject.FindGameObjectsWithTag("Troop");
//Debug.Log("Staying");
//Debug.Log(Enemy);
//Debug.Log(self.Health);
//Debug.Log(fScript.Health);
if (once == false)
{
Debug.Log("I am the team:" + self.Team);
Debug.Log("I have detected the team:" + fScript.Team);
once = true;
}
if (self.Team != fScript.Team)
{
if (self.CoolingDown == false)
{
self.CoolingDown = true;
fScript.Health -= self.AttackDamage;
}
else
{
self.CoolDown -= Time.deltaTime;
if (self.CoolDown <= 0)
{
self.CoolingDown = false;
self.CoolDown = self.original;
}
}
}
}
}
}
Fighting
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Fighting : MonoBehaviour
{
public int Team = 1;
public int Health = 100;
public int AttackDamage = 10;
public float CoolDown = 2;
public float original = 2;
public bool CoolingDown = false;
public bool Snap = false;
// Update is called once per frame
void Update () {
if (Snap == true || Health <= 0)
{
//Destroy(gameObject, .5f);
Destroy(transform.parent.gameObject);
}
if (Input.GetKey(KeyCode.N)) Instantiate(transform.parent.gameObject);
}
}
The expected result when I move one game object into the trigger radius of the other is that they should both start subtracting Health from each other based on the AttackDamage value. They should do this every time the CoolingDown value is false. When an attack is executed, it's flipped to true and a timer starts, when the timer is done it's flipped back to false.
However, upon moving the two objects into each other's radius', the first object has its health taken away as expected and then proceeds to do nothing until it's health reaches 0 then it dies because of the object attacking it. The object attacking is successfully attacking the other object but, is still not being affected by the object it's attacking.
Basically, Find(name) only returns the first instance of anything by that name, thus your g = Find(name) is almost guaranteed to never be the object related to your trigger/collision condition. The OnTriggerStay(Collider other) already gives you the 'other' collider that's in your trigger zone, so use it. :)
Replace this:
GameObject g = GameObject.Find("Detection");
Fighting fScript = g.GetComponent<Fighting>();
with this:
Fighting fScript = other.GetComponent<Fighting>();
To your question header:
Every instaced (non-static) value is allways unique to the according component and thereby to the according GameObject it is attached to. You might want to refrase the question because this is actually not your issue.
The problem is that when you do
GameObject.Find("Detection");
it actually finds the same object both times: Namely the first one in the hierarchy. So in one of of the two components you find your own empty object and skip the rest in
if(self.Team != FScript.Team)
.. you could try to use
other.Find("Detection");
instead to only search in the according context .. However, you should not use Find at all!
It is very performance intense
You should allways reuse references and not search them over and over again
You don't need it in your case
Since you say both scripts are attached to the same object you can simply use
GetComponent<Fighting>();
and you can do so already in Awake and reuse the reference instead:
private Fighting myFighting;
private void Awake()
{
myFighting = GetComponent<Fighting>();
}
Than for the collision you don't have to use Find either because you already have the reference of the object you collide with: other.gameObject. I don't know your entire setup but you can search for the component either downwards in the hierachy
// the flag true is sued to also find inactive gameObjects and components
// leave it without parameters if you don't want this
var otherFighting = other.GetComponentInChildren<Fighting>(true);
or searcg upwards in the hierachy
var otherFighting = other.GetComponentInParent<Fighting>(true);
or if you already know you collide exactly with the correct GameObject anyway simply use
var otherFighting = other.GetComponent<Fighting>();
I will use the latter in my example.
Than cheking the health all the time in Update is a huge perfomance issue. You should rather have a method e.g. TakeDamage and do your check only if your health is actually changed:
Fighting
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Fighting : MonoBehaviour
{
public int Team = 1;
public int Health = 100;
public int AttackDamage = 10;
public float CoolDown = 2;
public float original = 2;
// you don't need that flag see below
//public bool CoolingDown = false;
public bool Snap = false;
private void Update()
{
// you might also put this in a callback instead of update at some point later
if(Snap == true)
{
Destroy(transform.parent.gameObject);
}
// Note: this also makes not muh sense because if you destroyed
// the parent than you cannot instantiate it again!
// use a prefab instead
if (Input.GetKey(KeyCode.N)) Instantiate(transform.parent.gameObject);
}
public void TakeDamge(int DamageAmount)
{
Health -= DamageAmount;
if (Health > 0) return;
Destroy(transform.parent.gameObject);
}
}
Another performance issue in general: Even if Start, Update etc are empty, if they are present in your script Unity will call them. So if you don't use them then completely remove them to avoid that useless overhead.
So I would have
TrigDetect
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TrigDetect : MonoBehaviour
{
bool once = false;
private Fighting myFighting;
private void Awake()
{
myFighting = GetComponent<Fighting>();
}
void OnTriggerStay(Collider other)
{
// if wrong tag do nothing
if (!other.CompareTag("Troop")) return;
Fighting fScript = other.GetComponent<Fighting>();
// here you should add a check
if(!fScript)
{
// By using the other.gameObject as context you can find with which object
// you collided exactly by clicking on the Log
Debug.LogError("Something went wrong: Could not get the Fighting component of other", other.gameObject);
}
if (!once)
{
Debug.Log("I am the team:" + self.Team);
Debug.Log("I have detected the team:" + fScript.Team);
once = true;
}
// if same team do nothing
if (self.Team == fScript.Team) return;
// you don't need the CoolingDown bool at all:
self.CoolDown -= Time.deltaTime;
// if still cooling down do nothing
if(self.CoolDown > 0) return;
fScript.TakeDamage(self.AttackDamage);
self.CoolDown = self.original;
}
}

spawn obstacles c# and accessing from another script

I'm Getting a error when i try to instantiate in the manager class. saying
Error CS1061: Type UnityEngine.Object' does not contain a definition forGetComponent' and no extension method GetComponent' of typeUnityEngine.Object' could be found. Are you missing an assembly reference? (CS1061) (Assembly-CSharp)
Add this to your Obstacle class:
void Start()
{
manager = GameObject.FindWithTag("ObstacleManager").GetComponent<ObstacleManager>();
}
The tag obviously has to be the tag of the gameobject the manager is attached to.
Also: Always start class names with a capital letter (I did that in the snippet, keep that in mind, you will get an error right know with that).
Maybe you want to actually change your spawning a bit though. Have two lists, one for free spawnpoints and one for occupied. When you destroy an obstacle, pass the position to the spawning function to move the position to the free list.
Edit:
Another option to create the reference is to set it in your ObstacleManager on spawning. You need to grab a reference to the instantiated obstacle for this. I believe this should work without actually grabbing the obstacle gameobject, but you could do that too.
Obstacle obs = ((GameObject)Instantiate(TypeOfObstacles[j], pointsAvailiable[pointsIndex].position, Quaternion.identity)).GetComponent<Obstacle>();
obs.SetManagerReference(this);
And in Obstacle add
public void SetManagerReference(ObstacleManager obsManager)
{
manager = obsManager;
}
For the free position you can do something like this:
// in Obstacle.cs
public void OnMouseDown()
{
manager.SpawnNewObstacle(transform.position); // you might be able to actually pass the transform, but I'm not sure if it will get destroyed before used in the other function
Destroy(gameObject);
}
In the Manager:
public int noOfObsacles;
public float[] xPercent;
public GameObject[] TypeOfObstacles;
float y;
// to keep track of which spawn points are free and which aren't use these lists
private List<Transform> freePositions;
private List<Transform> occupiedPositions;
private void Start()
{
freePositions = new List<Transform>(spawnPoints);
occupiedPositions = new List<Transform>();
SpawnObstacles();
}
private void SpawnObstacles()
{
// just use this for initial obstacles
// call Spawn as often as needed
for(int i = 0; i < noOfObstacles; i++)
{
Spawn();
}
}
// you call this function from the obstacle that gets destroyed
public void SpawnNewObstacle(Vector3 freePos)
{
// find the spawnpoint in the occupied points
// and move it to the free ones since the obstacle got destroyed
for(int i = 0; i < occupiedPositions.Count; i++)
{
if(occupiedPositions[i].position == freePos)
{
freePositions.Add(occupiedPositions[i]);
occupiedPositions.RemoveAt(i);
break;
}
}
// and call Spawn
Spawn();
}
private void Spawn()
{
y = Random.value;
int pointsIndex = Random.Range (0, freePositions.Count);
for (int j =0; j<xPercent.Length; j++)
{
if ( y < xPercent[j])
{
// these 4 lines are essential for the spawning
Obstacle obs = ((GameObject)Instantiate(TypeOfObstacles[j], freePositions[pointsIndex], Quaternion.identity).GetComponent<Obstacle>();
obs.SetManagerReference(this);
occupiedPositions.Add(freePositions[pointsIndex]);
freePositions.RemoveAt(pointsIndex);
break;
}
}
}
had a bracket issue! my bad
obstacle obs = ((GameObject)Instantiate(TypeOfObstacles[j], freePositions[pointsIndex].position, Quaternion.identity)).GetComponent();

Unity3D spawning 2d objects at fixed locations

I have an object that when clicked it is destroyed and spawns randomly somewhere else on a timer. I'm trying to make it so instead of random spots it shows up at fixed locations.
I also want them to randomly spawn at those fixed locations on a timed interval, one at a time.(so if it appears in one location for lets say 5 seconds, it will be destroyed and the next one will appear in a different location.)
I attempted to do fixed spawn locations, but the void spawner doesn't want to work.
I get a "The object of type "GameObject" has been destroyed but you are still trying to access it".
I can fix this by commenting out the On_TouchStart destroy line, but I need it.
Here is my code:
using UnityEngine;
using System.Collections;
public class Spawner : MonoBehaviour {
public float AppearTime = 0f;
public Transform[] teleport;
public GameObject[] prefab;
void Spawner(){
int tele_num = Random.Range(0,5);
int prefab_num = Random.Range(0,3);
if (prefab !=null){
Instantiate(prefab[prefab_num], teleport[tele_num].position, teleport[tele_num].rotation );
}
}
void StartTime()
{
StartCoroutine(DoTime());
}
void OnEnable(){
EasyTouch.On_TouchStart += On_TouchStart;
}
IEnumerator DoTime()
{
yield return new WaitForSeconds(AppearTime);
Spawner();
}
void On_TouchStart (Gesture gesture){
if (gesture.pickObject != null){
Destroy(gesture.pickObject);
StartTime();
}
}
If anyone could lead me on the right track I'd appreciate it.
Thanks.
I figured it out. Turns out the prefabs I were using were incorrect, so I needed to swap them out for ones that did.
Thanks to the ones who helped.

Categories