I am new in C# and Unity3D and I'm struggle using events so I need HELP. So I'm making hypercasual game to improve my skills. In the game I want if player hits an item to different things happened. For example: triggering bomb collider will destroy the bomb and play explosive animation, triggering dollar — destroy it, play dollar animation, add score. PROBLEM !!! When Player hits bomb ALL bombs and money destroying and playing animation. I want to affect on every prafab independetly. I DON"T HAVE ANY STATIC FIELDS.
A SHORT VIDEO OF BEHAVIOR IN THE EDITOR
https://gfycat.com/farawaynegativedore
I have classes Bomb and Money that are inherited from abstract class Item. (Bomb and Money differs only with methods, so I include only Bomb).
In Item.cs I get refference to an event script.
public abstract class Item : MonoBehaviour
{
protected OnCollisionEvent _onCollisionEventScript;
private void Awake()
{
_onCollisionEventScript = FindObjectOfType<OnCollisionEvent>();
}
protected virtual void HandleAnimation() { }
protected virtual void DestroyOnHitting() { }
In Bomb.cs I subscribe it to an event.
public class Bomb : Item
{
private void OnEnable()
{
_onCollisionEventScript.InteractOnCollision += HandleAnimation;
_onCollisionEventScript.InteractOnCollision += DestroyOnHitting;
}
private void OnDisable()
{
_onCollisionEventScript.InteractOnCollision -= HandleAnimation;
_onCollisionEventScript.InteractOnCollision -= DestroyOnHitting;
}
protected override void HandleAnimation()
{
Debug.Log("EXPLOSION");
}
protected override void DestroyOnHitting()
{
Destroy(gameObject);
}
}
In OnCollisionEvent.cs, which is attached to Player, I invoke the event in "voidOnTriggerEnter()"
public class OnCollisionEvent : MonoBehaviour
{
public event UnityAction InteractOnCollision;
private void OnTriggerEnter(Collider other)
{
InteractOnCollision?.Invoke();
}
}
In SpawManager.cs I spawn random prefab from List of prefabs at fixed position along Xaxis and at random position along Yaxis
public class SpawnManager : MonoBehaviour
{
[SerializeField] private List<Item> ItemPrefabs;
[SerializeField] private float SpawnRepeatRate;
private readonly List<Item> _items = new();
private float _time = 0f;
private void Start()
{
foreach (var itemPrefab in ItemPrefabs)
_items.Add(itemPrefab);
}
private void Update()
{
_time += Time.deltaTime;
Item randomItem = _items[Random.Range(0, _items.Count)];
if (_time >= SpawnRepeatRate)
{
Instantiate(randomItem, GetRandomSpawnPosition(), Quaternion.identity);
_time = 0f;
}
}
private Vector3 GetRandomSpawnPosition()
{
int[] CoordinatesOnY = new int[11];
CoordinatesOnY[0] = 2;
for (int i = 1; i < CoordinatesOnY.Length; i++)
CoordinatesOnY[i] = CoordinatesOnY[i - 1] + 2;
int randomSpawnPositionAlongY = Random.Range(CoordinatesOnY[0], CoordinatesOnY.Length);
Vector3 spawnPosition = new Vector3(24f, randomSpawnPositionAlongY, 0f);
return spawnPosition;
}
}
Is my mistake on using events and passing references or in implementing SpawnManager and passing references?
I don't have any static fields. Maybe I use event wrong. I thought that in SpawnManager.cs I should create a list/array of references to prefabs, but I don't know how to implement it.
So your problem is that all your bombs and money listen to the same single event. That FindObjectOfType<OnCollisionEvent>();
basically almost equals having it static
in your case.
You should rather invert this logic and either let the trigger on the player handle what should happen
public class OnCollisionEvent : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
if(other.TryGetComponent<Item>(out var item))
{
// only trigger the callback/method on the specific item you currently collide with
// I would pass on the reference so you can use GetComponent to access other
// components on the player e.g. for damage and money management
item.TriggerEvent(this);
}
}
}
then on your item class you can simply have a
public abstract class Item : MonoBehaviour
{
public void TriggerEvent(OnCollisionEvent player)
{
HandleAnimation(player);
DestroyOnHitting();
}
protected virtual void HandleAnimation(OnCollisionEvent player) {
// inheritors can use the player reference to access other components and
// e.g. inflict damage or increase money etc
}
protected virtual void DestroyOnHitting() { }
}
The alternative would be to completely invert the logic flow and rather handle the event on the item
public abstract class Item : MonoBehaviour
{
protected void OnTriggerEnter(Collider other)
{
// `Player` would be a central component on your player object
// that can have further references like e.g. to the damage and money management etc
if(other.TryGetComponent<Player>(out var player))
{
HandleAnimation(player);
DestroyOnHitting();
}
}
protected virtual void HandleAnimation(Player player) { }
protected virtual void DestroyOnHitting() { }
}
and get rid of the OnCollisionEvent
at all.