I'm making a 3D Unity game. The red cube deletes every element with tag='Enemy' to which it touches during the playtime. The problem occurs when the script tries to count the number of objects with this tag at first. How to fix this problem?
The error:
FindGameObjectsWithTag is not allowed to be called from a MonoBehaviour constructor (or instance field initializer), call it in Awake or Start instead. Called from MonoBehaviour 'Collide'.
See "Script Serialization" page in the Unity Manual for further details.
Collide..ctor () (at Assets/Scripts/Collide.cs:9)
The script Collide.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Collide : MonoBehaviour
{
public Text txt;
public int obji = GameObject.FindGameObjectsWithTag("Enemy").Length;
void OnCollisionEnter(Collision collision)
{
if (collision.collider.gameObject.tag == "Enemy")
{
transform.localScale -= new Vector3(0.03F, 0.03F, 0.03F);
Destroy(collision.collider.gameObject);
obji = obji - 1;
Debug.Log(obji);
if ((obji) > 0)
{
txt.text = (obji).ToString();
}
else {
txt.text = "You win!";
}
}
}
}
Like Piflik said try this
public int obji = -1; //this is an example, I always try to initialize my variables.
void Start(){ //or Awake
obji = GameObject.FindGameObjectsWithTag("Enemy").Length;
}
Related
I am working on a small Unity project and am relatively new. I am trying to have multiple objects be able to be selected by a click using raycasting. The objects are a prefab and have both circle collider2ds and rigidbody2ds. When There is one object on the field the code works fine. It detects that the object is there and "Selects" it. But when I try to run the same code with two objects it doesn't detect the colliders for either of the objects. I assume it is an issue with the editor and not the code but there is probably a better way of achieving what I want to have happen. Thanks for any help!
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class CharacterMovment : MonoBehaviour
{
GameObject selected = null;
Characteristics characteristics;
public GameObject screenText;
private TMP_Text textComponent;
bool characterSelected = false;
private void Awake()
{
textComponent = screenText.GetComponent<TMP_Text>();
}
// Update is called once per frame
void Update()
{
RaycastHit2D hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);
if (Input.GetMouseButtonDown(0)){
Debug.Log(hit.collider.gameObject.tag);
if (hit.collider.gameObject.tag == "Character") {
characterSelected = true;
selected = hit.collider.gameObject;
characteristics = selected.GetComponent<Characteristics>();
Debug.Log("Adding character to selected. Character name: " + characteristics.name);
}else{
characterSelected = false;
}
}
if (characterSelected) {
textComponent.text = "Selected Character: " + characteristics.name;
}
}
}```
If you want to get all objects in the mouse position , you can use RaycastAll.
Please clarify if I misunderstood.
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class CharacterMovment : MonoBehaviour
{
GameObject selected = null;
List <Characteristics> characteristics;
public GameObject screenText;
private TMP_Text textComponent;
bool characterSelected = false;
private void Awake()
{
textComponent = screenText.GetComponent<TMP_Text>();
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
RaycastHit2D[] hit = Physics2D.RaycastAll(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);
for (int i = 0; i < hit.Length; i++)
{
Debug.Log(hit[i].collider.gameObject.tag);
if (hit[i].collider.gameObject.tag == "Character")
{
characterSelected = true;
selected = hit[i].collider.gameObject;
characteristics.Add(selected.GetComponent<Characteristics>());
Debug.Log("Adding character to selected. Character name: " + characteristics.name);
}
else
{
characterSelected = false;
}
if (characterSelected)
{
textComponent.text += "Selected Character: " + characteristics[i].name;
}
}
}
}
}
When you click script runs on every character. So basically you need only one instance of the MonoBehaviour for registering clicks. Right now you have CharacterMovement script on each character and it runs on each character multiple times when you click. Try to move logic to separate single script.
The manager script is attached to empty gameobject :
I tried to use a loop in the coroutine but it's moving only one drone the rest are not moving.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class DronesManager : MonoBehaviour
{
private List<GameObject> drones = new List<GameObject>();
// Start is called before the first frame update
void Start()
{
drones = GameObject.FindGameObjectsWithTag("Drone").ToList();
StartCoroutine(MoveDrone());
}
// Update is called once per frame
void Update()
{
}
private IEnumerator MoveDrone()
{
for (int i = 0; i < drones.Count; i++)
{
var drone = drones[Random.Range(0, drones.Count)];
if (drone.GetComponent<DroneControl>().go == false)
{
drone.GetComponent<DroneControl>().movingSpeed = 0.5f;
drone.GetComponent<DroneControl>().go = true;
}
yield return new WaitForSeconds(300);
}
}
}
It's moving only one but in the drones list there are 18 drones.
I want to move random drone each time every 300ms.
The script DroneControl is attached to each drone :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DroneControl : MonoBehaviour
{
public float movingSpeed;
public bool go = false;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if(go)
{
transform.position -= transform.forward * movingSpeed * Time.deltaTime;
}
}
}
Your code is almost OK.
Problem is that you use WaitForSeconds(300).
300 means 300 seconds, i.e 5 minutes.
If you want to move a drone each 300ms use WaitForSeconds(0.3f)
Alright, so here's what's happening: When I hit play and left click to shoot, unity editor freezes and I have to do the old Ctrl + Alt + Del, now, I am almost certain this script is the source of the issue, because when a bullet is shot, this script is immediately added to it, so here's the script(It's called BulletLife.cs, just letting you know)
using System.Timers;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
public class BulletLife : MonoBehaviour
{
public GameObject bullet;
public double bulletLifeSpan = 3;
bool bulletLifeEnded;
public LayerMask targetMask;
bool hasHitTarget;
// Start is called before the first frame update
void Start()
{
var bulletAge = new System.Timers.Timer(bulletLifeSpan * 1000);
bulletAge.Elapsed += OnTimedEvent;
bulletAge.AutoReset = false;
while(hasHitTarget == false && bulletLifeEnded == false) {
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
Destroy(bullet);
Debug.Log("Finish");
}
private void OnTimedEvent(System.Object Source, ElapsedEventArgs e) {
bulletLifeEnded = true;
}
}
Also, here's the Shoot.cs script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shoot : MonoBehaviour
{
public Transform gun;
public GameObject bullet;
public LayerMask targetMask;
public float bulletSpeed = 1000f;
bool hasHitTarget = false;
// Update is called once per frame
void Update()
{
if(Input.GetButtonDown("LeftClick")) {
GameObject bulletInstance;
bulletInstance = Instantiate(bullet, gun.position, new Quaternion(gun.rotation.w, gun.rotation.x, gun.forward.y, gun.rotation.z));
bulletInstance.AddComponent<Rigidbody>();
bulletInstance.GetComponent<Rigidbody>().useGravity = false;
bulletInstance.GetComponent<Rigidbody>().AddForce(gun.up * bulletSpeed);
bulletInstance.AddComponent<BulletLife>();
bulletInstance.GetComponent<BulletLife>().bullet = bulletInstance;
}
}
}
NOTE: I am using Unity 2019.4.15f1
Well everytime you instantiate a bullet in Start you do
while(hasHitTarget == false && bulletLifeEnded == false)
{
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
this loop will never finish since none of the conditions is changed inside the loop. There either is a hit or not .. but then the parameters for the raycast are never changed, the position isn't updated since you are still in the same frame => endless loop => freeze the main thread completely.
What you rather wanted to do is move that thing to Update which is called once a frame like e.g.
//public GameObject bullet; // not needed
public double bulletLifeSpan = 3;
//bool bulletLifeEnded; // not needed
public LayerMask targetMask;
//bool hasHitTarget; // not needed
void Start()
{
var bulletAge = new System.Timers.Timer(bulletLifeSpan * 1000);
bulletAge.Elapsed += OnTimedEvent;
bulletAge.AutoReset = false;
}
private void Update()
{
if(Physics.CheckSphere(transform.position, transform.localScale.y, targetMask))
{
Destroy(gameObject);
Debug.Log("Finish");
}
}
private void OnTimedEvent(System.Object Source, ElapsedEventArgs e)
{
Destroy(gameObject);
Debug.Log("Finish");
}
Or make it a single Coroutine
// If Start returns IEnumerator it is automatically started as Coroutine
// So no need to start an extra routine
private IEnumerator Start()
{
// Keeps track of how long your bullet exists already
var bulletAge = 0f;
while(bulletAge < bulletLifeSpan && !Physics.CheckSphere(transform.position, transform.localScale.y, targetMask))
{
// Increase by the time passed since last frame
bulletAge += Time.deltaTime;
// "Pause" this routine, render this frame
// and continue from here in the next frame
yield return null;
}
Destroy(gameObject);
Debug.Log("Finish");
}
Btw note that in Shoot you can shorten this a lot
void Update()
{
if(Input.GetButtonDown("LeftClick"))
{
// Note that your quaternion made no sense -> simply pass in the gun.rotation
var bulletInstance = Instantiate(bullet, gun.position, gun.rotation);
var rb = bulletInstance.AddComponent<Rigidbody>();
rb.useGravity = false;
rb.AddForce(gun.up * bulletSpeed);
var life = bulletInstance.AddComponent<BulletLife>();
// Assigning the gameObject reference is completely unnecessary
// within BulletLife simply use "gameObject" as show before
}
}
You could shorten this even more by making sure these components already exist on your prefab object and are configured correctly. Then you wouldn't need any of these line but just Instantiate it.
And finally you shouldn't use thisCheckSphere at all but rather let Unity handle its Collision detection itself and use OnCollisionEnter and configure your Collision Layers according to your needs!
The issue with your solution is: If your bullet moves fast it might simply pass a target without your CheckSphere noting it namely if its velocity is higher then localScale.y * 2.
Your Start method is blocking, thus freezing your game.
You'll have to use Update or a Coroutine to make your hit tests.
public class BulletLife : MonoBehaviour
{
public GameObject bullet;
public double bulletLifeSpan = 3;
bool bulletLifeEnded;
public LayerMask targetMask;
bool hasHitTarget;
// Start is called before the first frame update
void Start()
{
StartCoroutine(CheckHit(0, bulletLifeSpan));
}
private IEnumerator CheckHit(float interval, float lifetime){
bool checkEveryFrame = interval <= 0;
WaitForSeconds wait = checkEveryFrame ? null : new WaitForSeconds(interval);
while(lifetime > 0){
yield return wait;
lifetime = lifetime - (checkEveryFrame ? Time.deltaTime : interval);
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
bulletLifeEnded = true;
Destroy(bullet);
Debug.Log("Finish");
}
}
I'm pretty new to unity, and I'm having issues making the player character be propelled upward after successful collision with an enemy's head. I've tried multiple types of getcomponent.gameObject with vector3.AddForce, but I'm having no luck whatsoever. Am I placing the code in the wrong line? Am I missing something trivial? I'll paste my code down below-- any help would be appreciated.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class PlayerHandlerImproved : MonoBehaviour {
public float forceApplied;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void FixedUpdate () {
}
void OnCollisionEnter(Collision collision)
{
if (collision.collider.gameObject.tag == "EnemyHead")
{
if (transform.position.y >= collision.collider.transform.position.y)
{
collision.gameObject.GetComponent<Rigidbody>().AddForce (0, forceApplied, 0);
Destroy(collision.collider.transform.parent.gameObject);
Destroy(collision.collider.gameObject);
GameManager.score += 100;
}
}
else if (collision.collider.gameObject.tag == "EnemyBody")
{
GameManager.lives -= 1;
Debug.Log(GameManager.lives);
if (GameManager.lives == 0)
{
GameManager.over = true;
Time.timeScale = 0;
}
else
{
SceneManager.LoadScene("game on level1");
}
//this part is just for game score purposes.
}
}
}
Your mistake is that you're applying the force to collision, which, in this case, is the enemy, not your player. Change:
collision.gameObject.GetComponent<Rigidbody>().AddForce(0, forceApplied, 0);
to
GetComponent<Rigidbody>().AddForce(0, forceApplied, 0);
More informations about OnCollisionEnter can be found here.
I'm making a script in Unity using C#. I'm trying to use the Update() method to detect once the Camera position is past a certain point and then Instantiate an object into the scene and overwrite the variable "x" to something else so this only happens once.
The problem is I cant overwrite this "x" variable.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour{
public GameObject GroundSprite;
public int x = 1;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (((Camera.main.transform.position.x) < -4) && ( x == 1))
{
Instantiate(GroundSprite, transform.position, Quaternion.identity);
int x = 2;
}
}
}
Please remove int from below code, your value will be overwritten.
// Update is called once per frame
void Update()
{
if (((Camera.main.transform.position.x) < -4) && ( x == 1))
{
Instantiate(GroundSprite, transform.position, Quaternion.identity);
//int x = 2;
x=2;
}
}