Search code examples
c#unity-game-enginecollision

How to make a change on an object using OnCollisionEnter when there are multiple objects that are the same?


so I am practicing Unity's scripting and trying to build a prototype of Pachinko game. What I want to achieve is when the ball hit an obstacle, it would change the color (material) - I have achieve that. But I want the material to be changed back after two seconds, this is where I am confused on how to proceed.

The problem lays on when the ball hits another obstacle before the 2 second marks, the other in OnCollisionEnter(Collision other) is assigned to the new obstacle therefore changing the material of the new obstacle back.

Here's my simple code using a function to changeback the material, and Invoke it with 2 seconds.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CollisionBehavior : MonoBehaviour
{
    [SerializeField] Material mats;
    [SerializeField] Material defaultMats;
    Renderer meshRenderer;

    void OnCollisionEnter(Collision other) {
        if (other.gameObject.tag == "Obstacle") {
            meshRenderer = other.gameObject.GetComponent<Renderer>(); 
            meshRenderer.material = mats;

            Invoke("ChangeMatBack", 2);
        }
    }
    void ChangeMatBack() {
        meshRenderer.material = defaultMats;
    }

}

I also tried using Coroutine, but the problem is the same.

void OnCollisionEnter(Collision other) {
        if (other.gameObject.tag == "Obstacle") {
            meshRenderer = other.gameObject.GetComponent<Renderer>(); 
            meshRenderer.material = mats;

            // Invoke("ChangeMatBack", 2);
            StartCoroutine(waiting());
        }
    }
    // void ChangeMatBack() {
    //     meshRenderer.material = defaultMats;
    // }
    IEnumerator waiting() {
        yield return new WaitForSeconds(2f);
            meshRenderer.material = defaultMats;
    }

So TLDR, OnCollisionEnter function should make the material goes like Initial -> Changed, due to hits -> Wait two seconds -> Initial.


Solution

  • If I understand correctly all you would need to do is pass along the reference of the according Renderer you want to reset instead of overwriting a class field:

    [SerializeField] private Material mat;
    
    private void OnCollisionEnter(Collision other) 
    {
        if (!other.gameObject.CompareTag("Obstacle")) return;
        
        var renderer = other.gameObject.GetComponent<Renderer>(); 
        var defaultMaterial = renderer.material;
        renderer.material = mat;
    
        StartCoroutine(ResetRoutine(renderer, defaultMaterial)); 
    }
    
    private IEnumerator ResetRoutine(Renderer renderer, Material[] defaultMaterials)
    {
         yield return new WaitForSeconds(2f);
    
         renderer.material = defaultMaterial;
    }
    

    OnCollisionEnter can even be a Coroutine itself and you could even simply do:

    [SerializeField] private Material mat;
    
    private IEnumerator OnCollisionEnter(Collision other) 
    {
        if (!other.gameObject.CompareTag("Obstacle")) yield break;
        
        var renderer = other.gameObject.GetComponent<Renderer>(); 
        var defaultMaterial = renderer.material;
        renderer.material = mat;
    
        yield return new WaitForSeconds(2f);
    
        renderer.material = defaultMaterial;
    }