Animating/ Transforming on Collision

Hello,

I’m new to Unity scripting and have finished the Roll-a-ball tutorial. Before I move on I’m extending the game to play around with some other functions, and thought it would be nice if the pickUp cubes would have a little animation when the player sphere collides with them. I’ve been trying to do this for the last few days though and can’t seem to get it right!

So firstly I created an animation whereby the cube’s scale goes momentarily from 0.5 to 0.75, then over a second shrinks to 0.1 (all in x, y and z). My intention was to play this in the onCollision method, just before the cube is deactivated. I’d added the animation component onto the ‘player’ sphere and the animation was associated with the pickUp prefab, but the animation didn’t play even though the cube was deactivated.

I then tried to do the same but through two Lerp’s, one to increase the scale and another to reduce it (full code below) with some delays in between. I’ve written this as a separate function and called it once the condition has been met on collision. The error I get in the console is:

So from this, I’m guessing I’m not passing the correct parameters into the function (when I have got the game to run, it doesn’t seem to run the changeScale function at all, any debug logs I put there do not show). So rather having exhausted trial and error I thought I’d ask where I’m going wrong? Also, is doing this via a Lerp the best way or should I stick with the animation?

using UnityEngine;
using System.Collections;

public class PlayerController : MonoBehaviour {

	public float speed;
	private int count;
	private float timeCount;
	private bool timer;

	public float smooth1;
	public float smooth2;

	public GUIText countText;
	public GUIText winText;
	public GUIText timerText;
	public GUIText finalTime;

	void Start()
	{
		timer = true;
		count = 0;
		timeCount = 0;
		SetCountText ();
		winText.text = "";
		finalTime.text = "";
		timerText.text = "Time: "+ 0;
		smooth1 = 0.1f;
		smooth2 = 1f;
	
	}

	void Update()
	{
		if (timer == true)
		{
		timeCount += Time.deltaTime;
		timerText.text = "Time: " + timeCount.ToString ("F2");
		}
	}

	void FixedUpdate()
	{
		float moveHorizontal = Input.GetAxis ("Horizontal");
		float moveVertical = Input.GetAxis ("Vertical");

		Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);

		rigidbody.AddForce(movement * speed * Time.deltaTime);
	}


	void OnTriggerEnter(Collider other) 
	{
		if (other.gameObject.tag == "PickUp") 
		{
            changeScale ();
			
			other.gameObject.SetActive(false);
			count = count +1;
			SetCountText ();
			endGame ();
		}
	}

	void SetCountText ()
	{
			countText.text = "Count: " + count.ToString ();
	}

	void endGame ()
	{
		if (count >= 16) 
		{
			timer = false;
			countText.gameObject.SetActive(false);
			timerText.gameObject.SetActive(false);
			winText.text = "You Win!!!";
			finalTime.text = "Final Time: "+timeCount.ToString ("F2");
		}
	}

	IEnumerator changeScale(Collider other)
	{
		Debug.Log ("Function has been called");
		Vector3 original = other.gameObject.transform.localScale;
		Vector3 bigger = new Vector3 (0.75f, 0.75f, 0.75f);
		Vector3 smaller = new Vector3 (0.1f, 0.1f, 0.1f);

		other.gameObject.transform.localScale = Vector3.Lerp(bigger,original,smooth1*Time.deltaTime);
		yield return new WaitForSeconds(0.2f);
		other.gameObject.transform.localScale = Vector3.Lerp(smaller,bigger,smooth2*Time.deltaTime);
		yield return new WaitForSeconds(1f);
	}

}

Any help and general coding advise on the way I’m going about this would be appreciated!

Welcome!

I’m not familiar with the tutorial you’re mentioning, but I can help and comment on what you’re trying to do.

You’d have to show more details of your attempt to clarify why this didn’t work, but if you were attempting to animate the colliding “PickUp” prefab, then adding the animation component to the player sphere makes no sense. You would have had to add the animation component to the prefab and played the animation on collision while delaying the deactivation of the colliding object until after the animation had completed. There are a few ways of handling that, but you might be most interested in looking at animation events for that.

Regarding the compiler error you are correct: you are defining a method changeScale that takes in a Collider as a parameter, but attempting to call it by passing no parameters (line 57 in your snippet). That’ll break your build and you shouldn’t even be able to run at all. You probably just intended to call changeScale in OnTriggerEnter by passing along the other Collider:

void OnTriggerEnter(Collider other)
{
    if(other.gameObject.tag == "PickUp")
    {
        changeScale(other);
               
        other.gameObject.SetActive(false);
        count = count +1;
        SetCountText ();
        endGame ();
    }
}

However, there are still 2 problems with your approach: First, you are immediately deactivating the object that you’ve collided with, anything you were planning to do with it is made irrelevant. Like I mentioned above, you’d have to delay its deactivation until after you are done animating.

But more importantly: Based on your changeScale method it looks like you were attempting to use co-routines, but you are not actually using them correctly. To actually fire off a co-routine correctly you have to pass its return value to the StartCoroutine method like this:

StartCoroutine(changeScale(other));

Then changeScale method will execute until the first yield return statement, after which control is passed back to the caller and Unity will later on continue the execution of the coroutine according the IEnumerator you’ve yielded (WaitForSeconds in this case). So if you trace the logic along you’ll see that you won’t actually get the intended result since you’re just setting the object’s scale once, waiting 0.2 seconds, setting the scale again, then waiting 1 second and then… not doing anything.

What you’ll want to do is something like this:

void OnTriggerEnter(Collider other)
{
    if(other.gameObject.tag == "PickUp")
    {
        StartCoroutine(PickUp(other));            
        count = count + 1;
        SetCountText();
    }
}

IEnumerator PickUp(Collider other)
{
    Debug.Log("Function has been called");

    other.collider.enabled = false; //to disable further collision for the remainder of the animation

    yield return null; //yielding null will defer execution until the next frame
        
    var original = other.gameObject.transform.localScale;
    var bigger = new Vector3(1.25f, 1.25f, 1.25f);
    var smaller = new Vector3(0.1f, 0.1f, 0.1f);

    var currentT = 0.0f;
    var speedT = 1.0f / 0.2f;
    while(currentT < 1.0f)
    {
        currentT += speedT * Time.deltaTime;
        other.gameObject.transform.localScale = Vector3.Lerp(original, bigger, currentT);
        yield return null;
    }

    currentT = 0.0f;
    speedT = 1.0f;
    while(currentT < 1.0f)
    {
        currentT += speedT * Time.deltaTime;
        other.gameObject.transform.localScale = Vector3.Lerp(bigger, smaller, currentT);
        yield return null;
    }

    other.gameObject.SetActive(false); //consider Destroy(other.gameObject) instead if you are done with the object for the rest of the game
    endGame(); //check endgame case once we're done animating
}

Regarding which approach is better (animation vs. co-routine lerping), that’ll really depend on your specific case and preferences. Personally, for something as direct as this I’d probably go with co-routines - it’ll be easier to maintain and will avoid the overhead if involving the animation system. But if you wanted a more intricate animation with different curves and events, then using the animation system would be easier.

If you have any questions just let me know.
Good coding to you!

Fantastic! That now works perfectly, thanks very much.