Hierarchy issue Unity - c#

Im making a simple game in Unity 3d, basically you have a backdround object with more gameobjects on top, what you have to do is drag those game objects into some trash cans, as soon as you hit the correct trash can with the correct object said object gets deleted, the idea of the game is that when you put all the objects in the corect cans you win the game
I have the drag and acceptance all made but i cant figure out how to make the script so that when theres no more objects, you win the game.
I've been trying to use Hierarchy on an empty game object thats the parent for the objects that you have to destroy, but i dont know how to actually delete the objects so that the code can register it, any help?
The code for the drag and the code im using for the trash cans:
using UnityEngine;
using UnityEngine.EventSystems;
public class arrastrar : MonoBehaviour, IDragHandler
{
public float z = 0.0f;
public void OnDrag(PointerEventData eventData)
{
Vector3 mousePosition = Input.mousePosition;
mousePosition.z = z;
transform.position = Camera.main.ScreenToWorldPoint(mousePosition);
}
}
And the code inside the trashcans
using System.Collections;
using UnityEngine;
using System.Collections.Generic;
public class botarBasura: MonoBehaviour
{
void OnCollisionEnter(Collision other)
{
if (other.gameObject.name == "cubo")
Destroy(other.gameObject);
} else if (other.gameObject.name == "escombros")
{
other.gameObject.transform.position = new Vector3(-1, 2.5f, -2); //If its not the correct trash can it returns the object to the starting position
}
} else if ( other.gameObject.name == "botellas")
{
other.gameObject.transform.position = new Vector3(0, 2.5f, -2);
}
}
}

You can use a scriptable object to keep track of your object. Objects just need to add and remove themselves to a list in the scriptable object in their OnEnable() and OnDisable() callbacks. When an object removes itself you can have code check to see if there are no more objects left and trigger an event that something in your scene will react to.
(Dragable Object)
public class DragableObject : MonoBehaviour{
//reference to the scriptable object
public DragableObjectSet RuntimeSet;
void OnEnable(){
RuntimeSet.Add(this);
}
void OnDisable(){
RuntimeSet.Remove(this);
//you can put code checking if the runtime set is empty here
//or you can put it in the DragableObjectSet Remove method
}
}
Scriptable object example code
public DragableObjectSet : ScriptableObject
{
public List<DragableObject> RuntimeSet = new List<DragableObject>();
public void Add(DragableObject obj){
RuntimeSet.Add(obj);
}
public void Remove(DragableObject obj){
RuntimeSet.Remove(obj);
if(RuntimeSet.Count() == 0)
//some code here raising an event (if desired)
}
}
This type of object is called a runtime set and is detailed in this talk around the 40 minute mark.
https://www.youtube.com/watch?v=raQ3iHhE_Kk

A solution could possibly be using a singleton:
Create a new empty GameObject and add this code:
using UnityEngine;
public class TrashCounter : MonoBehaviour {
public static TrashCounter instance = null;
public int counter = 0;
void Awake(){ instance = this; }
}
Now, wherever you create an object (or in the Start() method of any object code), you just need to do TrashCounter.instance.counter++ and you will have your trash count.
After that, you just need to update this counter everytime you delete an object by doing TrashCounter.instance.counter-- and after you ask if(TrashCounter.instance.counter == 0) WinGame().

You can get an array of all active & not-yet-destroyed GameObjects with some enabled component with Object.FindObjectsOfType<ComponentType>();. Once you have that, you could then look at the length of that array.
So, you could do something like this in the code that destroys/deactivates the draggable gameobject:
GameObject otherObject = /* get draggable object to possibly destroy */;
if ( /* test to remove otherObject */ )
{
// deactivate the draggable object
otherObject.setActive = false;
// get all other draggable objects
DraggableObject[] remainingDraggableObjects = Object.FindObjectsOfType<DraggableObject>();
if (remainingDraggableObjects.Length == 0)
{
// there are no more active gameobjects with enabled DraggableObject component
// handle win condition here.
}
}
In your specific example, it could look like this:
void OnCollisionEnter(Collision other)
{
if (other.gameObject.name == "cubo")
{
other.gameObject.setActive = false; // better than destroying in most cases
// get all other draggable objects
arrastrar[] remainingObjects = Object.FindObjectsOfType<arrastrar>();
if (remainingObjects.Length == 0)
{
// there are no more active gameobjects with enabled arrastrar component
// handle win condition here.
}
}
// ...

Related

Destroying clone of prefab with a button which is spawned by a different button

I'm working on a Unity Project in which 3D objects like Cube, Sphere & Cylinder spawn at the center of the screen when selected from a dropdown button list. The instantiate script is working fine but all 3 objects spawn and merge with each other. It is expected to destroy the other 2 when one of them is instantiated. The issue I'm facing is the object spawned is a clone and I can't get it to be destroyed. I'm a beginner to Unity and trying to learn. I've pasted the code below, I've created 3 of these for each button and changed the parameters as per the object.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Cube_Inst : MonoBehaviour
{
public GameObject box;
public Transform pos;
public bool trigger;
public Button yourButton;
public GameObject Sphere_Destroy;
public GameObject Cylinder_Destroy;
void Start()
{
Button btn = yourButton.GetComponent<Button>();
btn.onClick.AddListener(TaskOnClick);
}
void TaskOnClick()
{
trigger = true;
Destroy(Sphere_Destroy);
Destroy(Cylinder_Destroy);
}
public void Update()
{
if (trigger == true)
{
Instantiate(box, pos.position, pos.rotation);
trigger = false;
}
}
}
You should rather implement the entire thing centralized ("Single responsibility pattern").
Don't let the individual buttons have individual scripts with a ton of cross references between them.
Rather have one central controller component and let the buttons pass in different parameters to a general spawn method.
Also you should do it event driven - you already know the moment a button is clicked - instead of poll checking a flag in the Update method.
I would use something like
// Put this on ONE object that is always active in the Scene
public class SpawnController : MonoBehaviour
{
private GameObject _currentClone;
private GameObject _currentPrefab;
public void SetInstance(GameObject prefab, Vector3 position, Quaternion rotation)
{
// Was the same prefab passed again? -> Ignore
if(prefab == _currentPrefab) return;
_currentPrefab = prefab;
// Already exists a clone? -> Destroy it
if(_currentClone) Destroy(_currentClone);
// Create and store the new clone
_currentClone = Instantiate (prefab, position, rotation);
}
}
So if SetInstance is called it automatically destroys any already existing instance.
And then have the same component on each button but only configure them accordingly
public class SpawnButton : MonoBehaviour
{
// Already reference this via the Inspector if possible
// If not we will get it on runtime as fallback
[SerializeField] private Button _button;
// For each different button reference a different prefab
[SerializeField] private GameObject _prefab;
// Reference the spawn point
[SerializeField] private Transform _spawnPoint;
// Here drag in the SpawnController from the scene if possible
// if not, we will find it on runtime as fallback
[SerializeField] private SpawnController _spawnController;
private void Awake()
{
if(!_button) _button = Get component<Button>();
_button.onClick.AddListener(DoSpawn());
if(!_spawnController) _spawnController = FindObjectOfType<SpawnController>();
}
private void DoSpawn()
{
// Only tell the SpawnController to do its thing
// This button doesn't have to know or care what that means for the Scene
_spawnController.SetInstance(_prefab, _spawnPoint.position, _spawnPoint.rotation);
}
}
You could even take a step beyond and don't let the buttons know the SpawnController and its secret SetInstance method at all and rather do something like
public class SpawnController : MonoBehaviour
{
private GameObject _currentClone;
private Button _lastClickedButton;
private void Awake()
{
SpawnButton.OnSpawnButtonClicked += SetInstance;
}
private void SetInstance(Button button, GameObject prefab, Vector3 position, Quaternion rotation)
{
// Was the same button pressed again? -> Ignore
if(button == _lastClickedButton) return;
_lastClickedButton = button;
// Already exists a clone? -> Destroy it
if(_currentClone) Destroy(_currentClone);
// Create and store the new clone
_currentClone = Instantiate (prefab, position, rotation);
}
}
and then
public class SpawnButton : MonoBehaviour
{
public static event Action<Button, GameObject, Vector3, Quaternion> OnSpawnButtonClicked;
// Already reference this via the Inspector if possible
// If not we will get it on runtime as fallback
[SerializeField] private Button _button;
// For each different button reference a different prefab
[SerializeField] private GameObject _prefab;
// Reference the spawn point
[SerializeField] private Transform _spawnPoint;
private void Awake()
{
if(!_button) _button = Get component<Button>();
_button.onClick.AddListener(DoSpawn());
}
private void DoSpawn()
{
// Only Invoke the event
// You don't care if or who is listening
OnSpawnButtonClicked?.Invoke(this, _prefab, _spawnPoint.position, _spawnPoint.rotation);
}
}

Unity3D Shooter: Using tags to switch level after killing all enemies

I am new to Unity and was trying, after some suggestions, to use tags to know the number of enemies i have in each level and move to the next scene right after eliminating all enemies. This is the script i use on enemy gameobjects. I've also tagged each of them with the "enemy" tag in unity inspector but it still doesn't work when i run the game. After killing all the enemies, it didnĀ“t change to next scene (Success!). Any ideas on what I'm doing wrong? Any other suggestions?
Thanks a lot for the help.
Enemies Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class BadguyScript : MonoBehaviour
{
public GameObject[] enemies;
public int maxHealth;
public int curHealth;
private Animator myAnimator;
private bool isDead;
[SerializeField]
private float DespawnTime = 2.5f;
[SerializeField]
private string DeathAnimHash = "isDead";
void Start()
{
myAnimator = GetComponent<Animator>();
myAnimator.enabled =true;
myAnimator.SetBool (DeathAnimHash ,isDead);
maxHealth = 1;
curHealth = maxHealth;
}
void Update()
{
if (curHealth < 1)
{
isDead = true;
myAnimator.SetBool (DeathAnimHash ,isDead);
Destroy(gameObject,DespawnTime);
}
enemies = GameObject.FindGameObjectsWithTag("enemy"); // Checks if enemies are available with tag "Enemy".
if (enemies.Length == 0)
{
SceneManager.LoadScene("SucessScene"); // Load the scene with name "SucessScene"
}
}
void OnTriggerEnter2D(Collider2D col)
{
if (isDead)
return;
if (col.tag == "bullet")
{
curHealth -= 1;
Destroy(col.gameObject);
}
}
}
I would create a script holder gameobject for this and put a GameManager script inside it. And inside GameManager.cs which should be a singleton class you can have a property like this:
int _enemyNumber;
public int EnemyNumber{
get{
return _enemyNumber;
}
set{
_enemyNumber = value;
}
}
And when you need to change these values, use some functions you will create inside this game controller such as:
public void DecreaseEnemyCount(){
//do the logic here
}
public void SetEnemyCount(){
//do the logic here
}
Also you can find information about creating a singleton class here
You create a list with all enemies, its a good practice, cause you'll gain performance. But you're verifing if enemies.Lenght == 0, what will never occur, because before you are adding the gameObject in the list enemies = GameObject.FindGameObjectsWithTag("enemy");
In the start method, you can search for all enemies and add then in your array, and in the update or onTriggerEnter you remove it from your array and validate the array lenght. I think it'll be more easy.
Instead of adding the script to a new gameManager script attached to an empty game object cause now once all enemies are killed the script will not be in work but if added to an empty gameobject it will be working always.

Activate and deactivate an object not work

i try to make a fire place were i need to collect wood and use it on fire place but nothing happen, i have used the debug.log to see if the variable are correct receive the changes and its work perfect but the if i have fireonplace variable = 1 then activate the fireplace object the wood its inserted on the variable but the trigger for activate the fire not happen.
This is my code
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class enablefire : MonoBehaviour {
public GameObject Enable_Disable;
public static int woodonfire = 0;
public void Enable ()
{
Enable_Disable.SetActive (true);
}
public void Disable()
{
Enable_Disable.SetActive (false);
}
// Use this for initialization
void Start ()
{
Enable ();
//Enable_Disable.SetActive (false);
}
void Update()
{
if (woodonfire >= 1)
{
Enable_Disable.SetActive (true);
}
if (woodonfire == 0)
{
Enable_Disable.SetActive (false);
//Enable_Disable.SetActive (false);
}
}
}
change the name of the game object, as a rule the name has to begin with lowercase, try names like enableDisable;
also you already created a method to enable/disable the GameObject why don't you just change this code...
void Update()
{
if (woodonfire >= 1)
{
Enable();
} else if (woodonfire == 0)
{
Disable();
}
}
You have to move the script onto another object.
Since an inactive gameobject has all its components inactive, the script on it will be inactive too and you won't be able to active the object again. It may be a better option to make the particle system in another object which contains the script. ( You will basicly activate and de-activate your child object (fire) ).

how can assign a gameObject from code using c#

I'm working on random coin generator. I have included the code below:
timer -= Time.deltaTime;
if (timer <= 0)
{
Vector3 position = new Vector3(Random.Range(11f, 14f), Random.Range(-0.7f, 4.5f), 0);
Instantiate(coins, position, transform.rotation);
timer = delayTimer;
}
and I add a collision to pick the coin and make a score text:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class Collisions : MonoBehaviour {
public Text scoreText;
private float score;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
scoreText.text = "Score : " + score;
}
private void OnCollisionEnter2D(Collision2D col)
{
if (col.gameObject.tag == "Player")
{
Destroy(gameObject);
scoreText.text = "Score : " + score;
score++;
}
}
}
so now when I need to add the Text to the prefab I can't
and if I assign the text in somehow to the prefab it doesn't count any score
You need a script or a prefab to know about a certain object in the scene. right?
Whenever you find yourself in this situation you must revise your approach. Keep in mind:
You can't assign a reference of an object from a scene to a prefab. That's basically impossible. prefabs aren't supposed to know about the scene.
You can assign a reference of an object from a scene to another object from the same scene.
You can assign a reference of an object from (or within) a prefab to any object (either to another prefab or same prefab or within a prefab or to an object in the scene)
In other words:
you can drag and drop anything anywhere except for from scene to prefab
Alternative?
There are many.
Why not try singleton pattern? It's easy to learn and easy to use. It's impeccable with objects which only have one instance. Like a GameController or GuiController or a UserProfileController etc.
Having a singleton GuiController:
public class GuiController : MonoBehaviour
{
//singleton setup
static GuiController instance;
void Awake() { instance = this; }
//now it's ready for static calls
public UnityEngine.UI.Text MyText;
//static method which you can call from anywhere
public static void SetMyText(string txt) { instance.MyText.text = txt; }
}
Have an object in the scene (better be the canvas). attach this script to it. Set the reference of My Text in it. In your collisions script just call GuiController.SetMyText("Score : " + score)
Edit
There is actually a slight mistake with your Collision script.
the field score which is defined in Collisions resets to 0 in the new object every time a game object with this script is being instantiated. This happens because score is not defined as a static member of Collisions and any new object naturally has its own members i.e. a new score. In fact, it shouldn't be in Collisions it is better to have a GameController script handling these kinds of data.
Again with the singleton pattern:
public class GameController : MonoBehaviour
{
static GameController instance;
void Awake() { instance = this; }
float score;
public static float Score { get { return instance.score; } set { instance.score = value; } }
}
Attach the script to a game object and instead of score++ you can write GameController.Score++;

animation.Play() doesn't work

I'm pretty sure that
animation.Play("DoorOpen");
Would play the animation "DoorOpen", but when i'm trying to put it in my code, it just giving me an error message:
The Animation attached to this GameObject (null if there is none attached).
using UnityEngine;
using System.Collections;
public class DoorPhysics : MonoBehaviour {
int Open = 0;
// Update is called once per frame
void Update() {
if (Open == 0) {
if (Input.GetKeyDown("e")) {
animation.Play("DoorOpen");
}
}
}
}
You need to show location of gameobjects in unity, they do not know eachother, you have to always use :
GameObject.GetComponent<T>()
GetComponentInParent<T>()
GetComponentInChildren<T>()
best practice is to get object references at Start()
also you should attach IMPORTANT!!! Animation component to the object this script it attached to
public class DoorPhysics : MonoBehaviour {
public Animation animation;
int Open = 0;
void Start()
{
animation=GameObject.GetComponent<Animation>(); //if your have derived type change Animation to good class DoorAnimation for example
}
void Update()
{
if (Open == 0) {
if (Input.GetKeyDown("e")) {
this.animation.Play("DoorOpen");
}
}
}
}
if that code wont work, you will need show me your GameObject hierarchy
And if your just start your trip with it learn MonoBehaviour call order
and life cycles of events

Categories