I'm trying to make the system work so that when a game starts and for each wall, it will either be in the game or not, randomly. Somehow, when I run it, it either keeps or destroys all the walls. How do I fix this?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WallGen : MonoBehaviour{
public GameObject wallObject;
System.Random rnd = new System.Random();
// Use this for initialization
void Start () {
generate();
}
void generate()
{
int number = rnd.Next(1, 5);
if (number == 2)
{
OnDestroy();
}
}
private void OnDestroy(){
Destroy(wallObject);
}
// Update is called once per frame
void Update () {
}
}
Every time you do new System.Random() it is initialised using the clock. Now as the Start function is called at the same time, you get the same value for all the gameobjects. You should keep a single Random instance for all gameobjects and keep using Next on the same instance.
Or the easy solution
just Use Random.Range (1,5) instead of rnd.Next(1, 5);
As a side note, don't use the function name OnDestroy, as it is one of the MonoBehaviour functions in Unity and is called automatically when the attached gameobject is destroyed.
Related
What I want:
I want the player to be able to click on instantiated objects and get points, then have those points show in the score-keeping text.
What I’ve done:
I’m currently using the following “FindGameObjectsWithTag” code to retrieve the buttons that are components of the instantiated prefab objects:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class CPointScore : MonoBehaviour
{
public TextMeshProUGUI CPointsText;
private float ScoreNum;
private GameObject[] CButtonGmeObjsHolder;
private void CTagFinder()
{
CButtonGmeObjsHolder = GameObject.FindGameObjectsWithTag("Ctag");
foreach (GameObject CButtonGmeObj in CButtonGmeObjsHolder)
{
Debug.Log("GmeObj Found");
Button CButton = CButtonGmeObj.GetComponent<Button>();
CButton.onClick.AddListener(AddScore);
}
}
public void AddScore()
{
ScoreNum += 1;
Debug.Log("Point Added # " + ScoreNum);
}
void Start()
{
InvokeRepeating("CTagFinder", 1f, 15.1f);
}
void Update()
{
CPointsText.text = ScoreNum.ToString();
}
}
Because FindGameObjectsWithTag only calls once I have the InvokeRepeating code in start. I have game objects spawning throughout the duration of the game so it needs to be constantly checking for tags.
Issue:
So the code finds the tags, the buttons are able to be clicked, and the score-keeping text updates which is great. The problem is that if I click one tagged button it will register a point for itself and every tagged button currently in the scene that spawned after it. For example, lets say I have 4 spawned objects currently on scene, when the first object spawned is clicked it will add 4 points instead of 1. If the second object spawned is clicked it will add 3 points instead of 1. I would like to have only the tagged button that is clicked register a point.
Question:
What can I change in my code so that only the tagged button that is clicked registers a point?
Thank you
I think there are two things here:
You repeatedly add the listener so you will end up with multiple callbacks when the button is finally clicked.
The repeated FindGameObjectsWithTag is also quite inefficient
Your main issue is the repeated calling.
For each repeated call of CTagFinder you go through all existing buttons and do
CButton.onClick.AddListener(AddScore);
so these existing buttons end up with multiple listeners attached!
You either want to make sure it is only called once per button, e.g. keeping track of those you already did this for:
private readonly HashSet<Button> alreadyRegisteredButtons = new HashSet<Button>();
and then
if(!alreadyRegisteredbuttons.Contains(CButton))
{
CButton.onClick.AddListener(AddScore);
alreadyRegisteredButtons.Add(CButton);
}
or alternatively make sure you remove the callback before you add it like
CButton.onClick.RemoveListener(AddScore);
CButton.onClick.AddListener(AddScore);
In general I would not use FindGameObjectWithTag an poll objects repeatedly. Rather make your code event driven. This would already avoid the issue at all since there would be no repeated attaching of the listener anyway.
I would simply have a dedicated component YourComponent attached to the same GameObject as the buttons and have a global
public static event Action<YourComponent> OnCTSButtonSpawned;
and in this dedicated component do
private void Start()
{
OnCTSButtonSpawned?.Invoke(this);
}
and in your CPointScore listen to this event like
private void Awake()
{
YourComponent.OnCTSButtonSpawned += AttachListener;
}
private void AttachListener(YourComponent component)
{
if(compoenent.TryGetComponent<Button>(out var button))
{
button.onClick.AddListener(AddScore);
}
}
private void AddScore()
{
ScoreNum++;
CPointsText.text = ScoreNum.ToString();
}
I'm trying to add a healthbar which is affected by the choices made and link the two but am unable to do so. Any help would be appreciated.
I will recommend you that before you go any further with your game development process, you research a bit more about general OOP programming (no offense or anything, it will just make things waaay easier, trust me).
That being said, here's an example to steer you to the right direction:
You can create a script with a global variable called health and subtract it every time the player makes a decision that would "punish" them, here's an example of how that could work:
PlayerManager.cs (put this script on your player)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerManager : MonoBehaviour {
public float health = 100f;
private void Update() {
if (health < 0)
Die();
}
private void Die () {
//Fancy animations and text can be added on this method
Destroy(gameObject);
}
}
And on your dialogue script, once they choose a wrong answer, then you can just decrease health on the proper player manager instance, like this:
DialogueSystem.cs
public PlayerManager playerManager;
public float damage = 10f;
private void Start() {
//You can initialize other stuff here as well
//This line is assuming you have the dialogue system script attach to your player as well
playerManager = GetComponent<PlayerManager>();
}
private void Update () {
//This is obviously going to change depending on how your system is built
if (wrongChoice) {
playerManager.health -= damage;
wrongChoice = false;
}
}
When I retrieve an object from a list of created objects and reactivate it, it destroys itself, but only if I have force applied to start it moving. If I never apply force, everything works as intended and I can keep activating the object over and over.
I've tried putting a debug.log in the OnCollision and it isn't colliding with anything.
As said above, if I never initiate speed, the rest works fine.
When speed is applied, the first projectile works, the second destroys itself.
I've tried manually activating them instead of by code. Same result.
Exception: If I manually activate/deactivate objects myself through the inspector, they work if I only have 1 shot in the pool.
The one shot rule doesn't work if I do it through code. It breaks even if I only have one shot.
All the code worked when I wasn't using pooling.
Spawn code:
void CreateBullet(int GunID)
{
GameObject ShotFired = Guns[GunID].GetComponent<Weapon>().FireWeapon();
if (ShotFired == null)
{
ShotFired = Instantiate(Guns[GunID].GetComponent<Weapon>().Projectile,Guns[GunID].gameObject.transform);
Physics.IgnoreCollision(ShotFired.GetComponent<SphereCollider>(), Guns[GunID].GetComponentInParent<CapsuleCollider>());
Guns[GunID].GetComponent<Weapon>().AmmoPool.Add(ShotFired);
}
ShotFired.transform.position = Guns[GunID].transform.position;
ShotFired.transform.rotation = Guns[GunID].transform.rotation;
ShotFired.SetActive(true);
}
Projectile Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mirror;
public class Projectile : NetworkBehaviour
{
public float Speed;
public float LifeTime;
public float DamagePower;
public GameObject ExplosionFX;
// Start is called before the first frame update
void Start()
{
GetComponent<Rigidbody>().AddRelativeForce(Vector3.up * Speed, ForceMode.VelocityChange);
StartCoroutine(DeactivateSelf(5.0f));
}
private void OnDestroy()
{
Debug.Log("WHY!");
}
IEnumerator DeactivateSelf(float Sec)
{
yield return new WaitForSeconds(Sec);
Explode();
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.GetComponentInChildren<Vehicle>())
{
collision.gameObject.GetComponentInChildren<Vehicle>().CmdTakeDamage(DamagePower);
}
Explode();
}
void Explode()
{
GameObject ExplosionEvent = Instantiate(ExplosionFX, this.transform.position, Quaternion.identity);
NetworkServer.Spawn(ExplosionEvent);
GetComponent<Rigidbody>().AddRelativeForce(Vector3.zero);
gameObject.SetActive(false);
}
}
Any thoughts welcome. Thanks in advance!
In case anyone else stumbles across this, here's what happened and why assigning a movement speed to the object was what caused the issue.
My projectiles had a 'trailrenderer' on them.
By default 'Autodestruct' is true.
Autodestruct does not destruct the trail, but the game object when the trail disappears. So if you disable anything that had a trail, and activated it through movement, when the object stops moving (say to reposition in an object pool) it will destroy the parent object.
If the object never moves, it never generates a trail, and never needs to be destroyed.
To fix, just uncheck autodestruct.
https://docs.unity3d.com/ScriptReference/TrailRenderer-autodestruct.html
I am Making a game like fire-fighter, i have a scenario that, machine is on fire due to electricity, first player have to switch off electricity, so i made button and show that press o to turn off electricity but i am unable to destroy electricity object, here is my code i do so far, but nothing goes good.
using UnityEngine;
using System.Collections;
public class SwitchONOFF : MonoBehaviour {
public Transform Player;
public Texture texture;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void OnGUI () {
float distance = Vector3.Distance(Player.position, transform.position);
if(distance<2 )
{
GUI.DrawTexture (new Rect (600, 150, 200, 100), texture, ScaleMode.StretchToFill, false);
if (Input.GetKey (KeyCode.O)) {
Destroy (gameObject.tag="chin");// here i want to destroy object with a tag of "chin", but how
}
}
}
You are already doing this wrong by using OnGUI and also searching GameObject to destroy by tag instead of caching the GameObject in the Start function. To get your current code to work, simply replace Destroy (gameObject.tag="chin"); with
Destroy(GameObject.FindWithTag("chin"));
or
Destroy(GameObject.FindGameObjectWithTag("chin"));
I am relatively new to unity and I'm trying to make collectibles in a game but i need to keep a tally of how many items have been collected but still have the collected item disappear. So far I have this. And yes the collectables are hairspray :p
Collection.cs
using UnityEngine;
using System.Collections;
public class Collection : MonoBehaviour {
public control controlSrc;
void OnTriggerEnter () {
controlSrc.AddScore();
killHairSpray();
}
void killHairSpray () {
Destroy(gameObject);
}
}
control.cs
using UnityEngine;
using System.Collections;
public class control : MonoBehaviour {
public int hcTot = 0;
public void AddScore () {
hcTot = hcTot + 1;
Debug.Log("Working");
}
}
I'm not sure why it isn't working but the console says;
NullReferenceException: Object reference not set to an instance of an object
Collection.OnTriggerEnter () (at Assets/Collection.cs:10)
Thanks:) this has been driving me crazy!
You most likely haven't connected anything to the controlSrc variable so it's empty. Hence the null reference exception.
In the Unity editor select the GameObject with the Collection.cs script, then in the Inspector set the controlSrc (most likely listed as "Control Src") by assigning the game object containing the control.cs script.