Troubles with FindObjectOfType - c#

I'm trying to make a simple damage boost I have 2 scripts, one attached to the projectile and one to the booster
Projectile:
public class DamageDealer : MonoBehaviour
{
[SerializeField] public int damage = 25;
public int GetDamage()
{
return damage;
}
public void Hit()
{
Destroy(gameObject);
}
public void IncreaseDamage(int value)
{
damage += value;
Mathf.Clamp(damage, 0, int.MaxValue);
}
}
Booster:
public class DamageUp : MonoBehaviour
{
int damageBoost = 50;
DamageDealer damageDealer;
void Awake()
{
damageDealer = FindObjectOfType<DamageDealer>();
Debug.Log(damageDealer); // this returns as null
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
damageDealer.IncreaseDamage(damageBoost);
}
}
}
As you can see in the Booster I've already identified what's returning as null, but I'm quite new to unity and C# so I don't know what I'm doing wrong
Some more info the Projectile is a prefab while the Booster is not (yet)
scripts are attached and enabled, objects are enabled too.
Hit() and GetDamage() are for another script I have no issue with.
Thanks in advance.

You're probably trying to find the object before it's instantiated, change the Awake to Start or try to change Script Execution Order settings
Check Also; Order of execution for event functions

Related

How to find a prefab by name

I have a problem, I need to block the creation of a new object (prefab) if there is already one prefab on the stage.I solved it with GameObject.FindWithTag, but maybe there is some other way
using UnityEngine;
public class CreateBullet : MonoBehaviour
{
public Transform firePoint;
public GameObject ballPrefab;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
if (GameObject.FindWithTag("ballBullet") == null)
{
CreatingBulletBall();
}
}
}
void CreatingBulletBall()
{
Instantiate(ballPrefab, firePoint.position, firePoint.rotation);
}
}
Here is what you should do:
Create script Bullet.cs with code from below;
Add it as component to the bullet prefab;
Change the CreateBullet class as shown below;
Assign bullet prefab to the ballPrefab field of the CreateBullet component (if unassigned after script change).
How it works: if _bullet field is null, new bullet is created and stored there. When bullet is destroyed, it fires event Destroyed. Callback OnBulletDestroyed will assign null to _bullet, so it can be created again.
File CreateBullet.cs:
public class CreateBullet : MonoBehaviour
{
public Transform firePoint;
public Bullet ballPrefab;
private void Update()
{
if (Input.GetMouseButtonDown(0))
TryCreateBulletBall();
}
private Bullet? _bullet = null;
private void TryCreateBulletBall()
{
if (this._bullet != null)
return;
this._bullet = Instantiate(ballPrefab, firePoint.position, firePoint.rotation);
this._bullet.Destroyed += this.OnBulletDestroyed;
}
private void OnBulletDestroyed()
{
this._bullet.Destroyed -= this.OnBulletDestroyed;
this._bullet = null;
}
}
File Bullet.cs:
public class Bullet : MonoBehaviour
{
public event Action? Destroyed;
private void OnDestroy()
{
this.Destroyed?.Invoke();
}
}
Not tested, but it should work. It works.
Update:
About performance:
In terms of performance it's perfectly fine. It is idiomatic to store the reference to the object once (through Inspector, in Start/Awake hooks, when object is instantiated, etc), instead of calling any of Find...() or GetComponent() methods in the Update hook. In fact, docs for Find() method state that you should avoid calling it every frame.
Simplified version of code.
As #derHugo noted in comment, it's sufficient to have this code only:
File CreateBullet.cs:
public class CreateBullet : MonoBehaviour
{
public Transform firePoint;
public GameObject ballPrefab;
private void Update()
{
if (Input.GetMouseButtonDown(0))
TryCreateBulletBall();
}
private GameObject _bullet;
private void TryCreateBulletBall()
{
if (this._bullet)
return;
this._bullet = Instantiate(ballPrefab, firePoint.position, firePoint.rotation);
}
}

Unity AudioSource.Play() does not work when the AudioSource component is added from script

The title pretty much sums up my problem. I am using an AudioManager script to play one of the sounds from an array. It seems that all variables have been assigned correctly and I am able to access all the properties of AudioSource. However, when I call Play(), no sound is heard in the editor. I have also tested this by manually adding a single AudioSource component and calling AudioSource.Play() from my play method (commented line in Play()), and it works just fine. Below are the two scripts in question. I am getting no errors or warnings.
I'm using Unity 2019.3.3f
Update: So I have found the problem, the pitch variable of Sound class was uninitialized so it was defaulting to 0.
public class AudioManager : MonoBehaviour
{
public Sound[] sounds;
public static AudioManager instance;
void Awake()
{
DontDestroyOnLoad(gameObject);
if (instance == null) instance = this;
else
{
Destroy(gameObject);
return;
}
foreach (Sound s in sounds)
{
s.audioSource = gameObject.AddComponent<AudioSource>();
s.audioSource.clip = s.clip;
s.audioSource.volume = s.volume;
s.audioSource.pitch = s.pitch;
s.audioSource.playOnAwake = s.playOnAwake;
}
}
public void Play(string name)
{
Sound s = Array.Find(sounds, sound => sound.name == name);
s.audioSource.Play();
// gameObject.GetComponent<AudioSource>().Play();
}
}
[System.Serializable]
public class Sound
{
public string name;
public AudioClip clip;
[Range(0f, 1f)] public float volume;
[Range(-3f, 3f)] public float pitch;
public bool playOnAwake = false;
[HideInInspector] public AudioSource audioSource;
}
When the object is a DontDestroyOnLoad object, it loses all of its variables.
Your Solution:
(This is also a whole lot simpler and easy to understand and you also don't need another script)
public class AudioManager : MonoBehaviour
{
public static AudioSource;
public static AudioClip clip1, clip2;
void Awake()
{
GameObject[] obj = GameObject.FindGameObjectsWithTag(gameobject.tag);
if (obj.length > 1)
Destroy(gameobject);
DontDestroyOnLoad(gameObject);
//Make sure your audioClips are in a folder called "Resources"!!!
//Put the name of the audio clip in the ""
clip1 = Resources.Load<AudioClip>("clip1");
clip2 = Resources.Load<AudioClip>("clip2");
}
void Update()
{
//Make sure that the gameObject with this script has an audioSource component!
if (!audioSource)
audioSource = GetComponent<AudioSource>();
}
//now you can simply call this function from another script using AudioManager.Play("name")
public static void Play(string name)
{
switch (name)
{
case "clip1":
audioSource.PlayOneShot(clip1);
break;
case "clip2":
audioSource.PlayOneShot(clip2);
break;
}
}
}

Singleton – extra script not destroyed?

I want to have a singleton which will store, update and show player score through all levels (scenes), but something works wrong.
This my singletone script GameStatus.cs:
using UnityEngine;
using TMPro;
public class GameStatus : MonoBehaviour
{
public static GameStatus instance;
[SerializeField] int pointsPerBlock = 50;
[SerializeField] TextMeshProUGUI scoreText;
[SerializeField] public static int currentScore = 0;
private void Awake()
{
if (instance != null)
{
Destroy(gameObject);
}
else
{
instance = this;
DontDestroyOnLoad(gameObject);
}
}
public void AddToScore()
{
currentScore += pointsPerBlock;
scoreText.text = currentScore.ToString();
}
}
I also have another script for objects player need to destroy, called Block.cs. Here it is:
using UnityEngine;
public class Block : MonoBehaviour
{
Level level;
GameStatus gameStatus;
private void Start()
{
level = FindObjectOfType<Level>();
gameStatus = FindObjectOfType<GameStatus>();
level.CountBreakableBlocks();
}
private void OnCollisionEnter2D(Collision2D collision)
{
DestroyBlock();
}
private void DestroyBlock()
{
level.BlockDestroyed();
gameStatus.AddToScore();
Destroy(gameObject);
}
}
And on level 1 everything works fine, but when the game goes to the next level this happens:
Player score stops updating.
If I use Debug.Log(currentScore); in GameStatus.cs I can see that this variable doesn't change when player breaks blocks, but if use Debug.Log(gameStatus.currentScore); in Block.cs then I can see that this variable is getting updated.
Debug.Log(FindObjectsOfType().Length); shows that there is one GameStatus object in the first level and two objects in the next levels, although I can't see the second one GameStatus in hierarchy.
So my question is - what's wrong and how to fix it?
If you use singleton, there is no point to make
currentScore
static variable, just make it
public int currentScore;
Also in your block cs you can just call
GameStatus.instance.AddToScore();
No need to make reference at start

Instances of prefab aren't being controlled separately

I have multiple enemies set up in a level, all using the same behaviour and animator scripts. When I hit or kill one of them, all of them get hit or killed. I need them to function as separate instances.
I have tried referencing only an instance of the script:
private GoblinBehaviour goblin;
goblin = GetComponent<GoblinBehaviour>();
goblin.IncrementHits(1);
But that doesn't work. An error arises that says that script can't be accessed with an instance and needs a type instead.
code for hit detection script:
public class RangedDetection : MonoBehaviour
{
private GoblinBehaviour goblin;
void OnTriggerEnter(Collider other)
{
//on colliding destroy rocks after its life time
Destroy(gameObject, rockLifeTime);
if (other.gameObject.tag.Equals("Enemy"))
//if (other.tag == "Enemy")
{
Destroy(other.gameObject);
}
else if (other.gameObject.tag.Equals("Goblin"))//goblin should play
death animation
{
goblin = GetComponent<GoblinBehaviour>();
goblin.IncrementHits(1);
GetComponent<BoxCollider>().enabled = false; //Removing hit
collider so it only hits target once.
}
}
}
Simplified Code for goblin script:
public class GoblinBehaviour : MonoBehaviour
{
Transform player;
public static bool isDead;
public static bool isPunching;
public static bool isHit;
public GameObject particles;
public Animator anim;
public void IncrementHits(int hitCount)
{
isHit = true;
hits += hitCount;
if (hits >= 2) isDead = true;
}
void Die()
{
Instantiate(particles, transform.position, transform.rotation);
Destroy(gameObject);
}
void updateAnim()
{
anim.SetBool("Punch_b", isPunching);
anim.SetBool("hit", isHit);
}
}
Things should animate and act separately, I'm not sure how to reference only the current instance of the script.
While your code is incomplete and the problem cannot be said for sure, it looks like you are using statics improperly.
Static properties are instance analagous. In other words, all of your goblin instances share any static properties (isPunching, isHit, isDead, etc.). Making these values static allows you to reference them directly without having to obtain the instance you're affecting, but results in you updating all goblins at once.
Your solution will involve removing the static modifier from your GoblinBehaviour properties unless the properties are meant to be shared across all instances.

Error on setting a Pooled GameObject's parent back to pooled container

I have a GameObject that I am spawning from a pool, that when I try to return it to the pool I get the error:
Setting the parent of a transform which resides in a prefab is
disabled to prevent data corruption.
I have used my ObjectPooler in many projects and have never once encountered this error.
I have created simple test GameObjects and it still throws this error, using the below code.
I have a Bomb abstract class. I have a PowerBomb class that inherits from Bomb. Bomb conforms to an interface IExplodable.
When my player spawns a bomb, I put it as the child of the player object so the player can move around with the bomb.
My ObjectPool has the correct bomb gameobject prefab ( a PowerBomb ). and my player has the same prefab as a reference to spawn. They player also holds a reference so it knows what it is holding.
I have spent the better part of 2 days trying to figure out why my PowerBomb will not return to the object pool.
Any help?
Bomb.cs
public abstract class Bomb : MonoBehaviour, IExplodable {
//Interface
public virtual void OnDetonate() {
Die();
}
public virtual void Die() {
Debug.Log("Bomb Die()");
ObjectPool.instance.PoolObject(gameObject);
}
}
PowerBomb.cs
public class PowerBomb : Bomb {
[SerializeField]
private AudioClip sfxSpawn;
}
BombManager.cs
public GameObject bomb;
public void SpawnBomb(){
GameObject obj = ObjectPool.instance.GetObject(bomb);
if (obj == null) return;
obj.SetActive(true);
PlayerInstance.HoldObject(obj);
}
public void DetonateBomb(){
bomb.GetComponent<PowerBomb>().OnDetonate();
}
Player.cs
[SerializeField]
private bool isHolding = false;
public bool IsHolding {
get { return isHolding; }
set { isHolding = value; }
}
private GameObject holdingObject;
public GameObject HoldingObject {
get { return holdingObject; }
set { holdingObject = value;}
}
public void HoldObject(GameObject o) {
HoldingObject = o;
HoldingObject.transform.SetParent(transform);
HoldingObject.transform.position = holdingArea.transform.position;
IsHolding = true;
}
ObjectPool.cs
private GameObject containerObject;
private List<GameObject>[] pooledObjects;
//Code removed for brevity. Getting from the pool is fine.
public void PoolObject(GameObject obj) {
for (int i = 0; i < objectsToPool.Count; i++) {
if (objectsToPool[i].objectToPool.name.Equals(obj.name)) {
obj.SetActive(false);
//THIS LINE IS THROWING THE ERROR
obj.transform.SetParent(containerObject.transform);
pooledObjects[i].Add(obj);
return;
}
}
}
You don't and can't modify a prefab. It might work in the Editor if you use some Editor API to modify it but it won't during run-time and it won't change the changes. You are getting this error because obj is a prefab which you are trying to modify at obj.transform.SetParent(containerObject.transform);.
You don't even use prefabs in a pool. That's not normal. What you do do is use the Instantiate function to instantiate the prefab then put the instantiated object in a pool.
GameObject obj = Instantiate(prefab, new Vector3(i * 2.0F, 0, 0), Quaternion.identity);
//Add instantiated Object to your pool List
pooledObjects[i].Add(obj);

Categories

Resources