Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / game / Unity

Day 86 of 100 Days of VR: Endless Flyer – Implementing Magnet Effect in Unity

0.00/5 (No votes)
6 Feb 2019CPOL7 min read 2K  
Infrastructure to write the magnet power up
In this post, we will add the Magnet Power-Up when collected and implement the magnet power-up.

Welcome to Day 86! After two or so posts talking about making the power-up magnet, we’re finally at the point where we have the infrastructure to write the magnet power up. I Promise!

For our plan of action today, we only have one goal and that is to:

  1. Add the Magnet Power-Up when collected
  2. Implement the magnet power-up

This might look like a short list, but we’re still going to need to make quite a bit of script changes, but it’s nothing that we can’t handle!

Let’s get started!

Step 1: Add the Magnet Power-Up to our Player State

Step 1.1: Add our Power-Up to the Plane Collider Script

Like how we collect a coin on collision, we need to have some logic that handles colliding against the magnet power-up.

We need to go to our Plane Collider script and add that logic in.

Here’s what we have now:

C#
using UnityEngine;

public class PlaneCollider : MonoBehaviour 
{
	public GameObject PlaneObject;
    public GameObject Explosion;
	void OnTriggerEnter(Collider other)
    {
		print(other + " name " + other.name);
		switch (other.tag) {
			case "Coin":
				CoinCollision(other);
				break;
            case "Magnet":
                MagnetCollision(other);
                break;
			default:
				CheckUnTaggedCollision(other);
				break;
		}
    }

	// Collides with the coin, we get the script that controls 
	// the logic for the coin and call collect so it will
	// know what to do after we collide into it.
	private void CoinCollision(Collider other) {
		Coin coin = other.GetComponent<Coin>();
		coin.Collect();
		GameManager.Instance.CollectCoin();
	}

    // Collides with the magnet, we add the power up to our list of power-ups
    // and let the power-up destroy itself from the game.
    private void MagnetCollision(Collider other)
    {
        PlayerManager.Instance.AddPowerUp(PlayerManager.PowerUpType.Magnet);
        Magnet magnet = other.GetComponent<Magnet>();
        magnet.Collect();
    }

    // Check the collided object if it doesn't have a tag to see if it's
    // something we're also looking for.
    private void CheckUnTaggedCollision(Collider other) {
		if (other.name.Contains("Cube")) {
			EnemyCollision();
		}
	}

    /// Destroy the player and set the current state to the dead state.
    private void EnemyCollision()
    {
        Instantiate(Explosion, PlaneObject.transform.position, Quaternion.identity);
        Destroy(PlaneObject);
        PlayerManager.Instance.GameOver();
        GameUIManager.Instance.GameOver(gameObject);
        CameraManager.Instance.GameOver();
    }
}

Walking Through the Code

There’s not much difference from what we do when we collide against a magnet power-up vs when we collide against a coin.

  1. We detect if we collided against the magnet power-up by checking the tag that we set in the previous day to see if it’s “Magnet”.
  2. If it is Magnet, we call MagnetCollision(), which we add a Magnet Power-up to our PlayerManager and get rid of the magnet game object from the game. At this point, we don’t have a Magnet script like we do in a Coin script. We need to make one, as it stands now, our game won’t build.

Alternatively, we could have just removed the object inside the Plane Collider script, however if we want to play sound effects like what we do with coins, it would make sense to make another script that would take care of it.

Step 1.2: Create the Magnet Script

The next thing we need to do is make the magnet script.

  1. Put our magnet prefab back into the game.
  2. In the inspector, add a new script called Magnet.

Here’s what our Magnet script will look like. It looks very similar to the Coin script, because I mostly copied and pasted it:

C#
using UnityEngine;
using System.Collections;

public class Magnet : MonoBehaviour
{
    public AudioClip CollectMagnetSFX;

    private SoundManager _soundManager;

    void Start()
    {
        _soundManager = GetComponent<SoundManager>();
    }

    public void Collect()
    {
        // _soundManager.PlaySFXClip(CollectCoinSFX);
        StartCoroutine(RemoveGameObject());
    }

    private IEnumerator RemoveGameObject()
    {
        yield return new WaitForSeconds(0.1f);
        Destroy(gameObject);
    }
}

I won’t comment too much on it since it’s almost an exact copy of the coin script, however I will point out that I renamed the sound effect that we give it and I made sure we don’t play any sound effects when we collect the magnet.

We will investigate this some other day.

With this, our game will now builds and if we run into a Magnet power-up, we will collect it!

Pro tip: Instead of playing the game to find one, just drag and drop the prefab to be in front of the plane and collect it there.

Step 2: Implementing the Magnet Effect

We are finally here!

We:

  1. Added a magnet power-up asset
  2. Make it spawn in our game
  3. Added the necessary code to support getting a power-up

Now it’s time to finally code up a magnet effect!

How do we do it? It’s not actually too hard to do! Here’s our action plan to make it:

  1. We need to add a collider that will detect if we’re within a certain distance to coins.
  2. We need to modify our coin script to follow the player until the player collects the coin.

Once we have these two small changes, we should be able to get exactly what we are looking for.

Step 2.1: Detecting a Coin to Pickup

To detect coins for our magnet, we need to use a collider, we already have a Capsule Collider on our plane, which will make this confusing when trying to figure out if we are colliding against

  1. Create a new Game Object called Magnet Collider game object.
  2. Add a Sphere Collider to it.
  3. For the Sphere Collider make it a Trigger, set the Center to be (0, -1, 0) and change the Radius to be
  4. Add a Rigid Body component to the game object and disable gravity and enable Is Kinematic. This is necessary for us to be able to detect collision.
  5. Add a script called Magnet Collider to our game object.

What we’re going to do is we’re going to create a Magnet Collider game object that we will attach to our plane whenever we have the magnet power-up.

This game object will be the code that we’ll use to bring the coin towards us.

Here’s what the script looks like:

C#
using UnityEngine;

public class MagnetCollider : MonoBehaviour {

    void OnTriggerEnter(Collider other)
    {
        switch (other.tag)
        {
            case "Coin":
                Coin coin = other.GetComponent<Coin>(); 
                coin.Follow(gameObject.transform.parent.gameObject);
                break;
        }
    }
}

Walking Through the Code

This is like the Plane collider script we have. We use OnTriggerEnter() to detect when we run into a coin. With the coin script, we will call Follow() (which we haven’t implemented yet) and pass in the parent game object, specifically this will be the Player game object that we see in our project hierarchy. In theory, after this, the coin will start moving towards the player.

Step 2.2: Add the Magnet Collider Game Object to our Plane.

Now that we have a way for us to keep track of our magnet power-up, we need to add it our plane game object.

The best place to add the game object is when we collect the game object in the first place. Specifically in the PlayerManager code where we add our power up.

Here’s what it looks like now:

C#
using UnityEngine;
using System.Collections.Generic;

public class PlayerManager : MonoBehaviour
{
    public static PlayerManager Instance;
    public enum PlayerState { Alive, Dead }
    public GameObject Player;
    public GameObject MagnetCollider;

    public enum PowerUpType { Magnet }
    private Dictionary<PowerUpType, PowerUp> powerUpDictionary;
    private float powerUpDuration = 15f;
    private List<PowerUpType> itemsToRemove;

    public PlayerState CurrentState
    {
        // I chose to be explicit, but we could also have done:
        // get; private set;
        // read more: https://stackoverflow.com/questions/3847832/understanding-private-setters
        get { return _currentState; }
        private set { _currentState = value; }
    }

    private PlayerState _currentState;

    void Start ()
	{
	    if (Instance != null)
	    {
	        // If Instance already exists, we should get rid of this game object
	        // and use the original game object that set Instance   
	        Destroy(gameObject);
	        return;
	    }

	    // If Instance doesn't exist, we initialize the Player Manager
	    Init();
	}

    void Update()
    {
        foreach (KeyValuePair<PowerUpType, PowerUp> entry in powerUpDictionary)
        {
            entry.Value.Duration -= Time.deltaTime;

            // We can't remove an item from a dictionary if we're iterating through it.
            // Instead we have to keep track of it in a list and 
            // then remove the items from the dictionary later.
            if (entry.Value.Duration <= 0)
            {
                itemsToRemove.Add(entry.Key);
            }
        }

        // Go through all of the power ups that need to be removed and remove it from the 
        // dictionary.
        foreach (PowerUpType powerUpType in itemsToRemove)
        {
            switch (powerUpType)
            {
                case PowerUpType.Magnet:
                    Transform magnetCollider = Player.transform.Find
                                               ("Magnet Collider(Clone)");
                    print(magnetCollider);
                    Destroy(magnetCollider.gameObject);
                    magnetCollider = null;
                    break;
            }
            powerUpDictionary.Remove(powerUpType);
        }

        // We've removed everything, let's clear our list.
        itemsToRemove.Clear();
    }

    private void Init()
    {
        Instance = this;
        CurrentState = PlayerState.Alive;

        // Create an empty dictionary and list, otherwise they'll be null later when we
        // try to access them and crash.
        powerUpDictionary = new Dictionary<PowerUpType, PowerUp>();
        itemsToRemove = new List<PowerUpType>();
    }

    /// <summary>
    /// Sets the PlayerManager to be in the game over state.
    /// </summary>

    public void GameOver()
    {
        CurrentState = PlayerState.Dead;
    }

    public void AddPowerUp(PowerUpType powerUpType)
    {
        switch (powerUpType)
        {
            case PowerUpType.Magnet:
                // if we already have the MagnetCollider, don't add it again.
                if (powerUpDictionary.ContainsKey(powerUpType))
                {
                    break;
                }
                // We add the Magnet Collider to our player.
                Instantiate(MagnetCollider, Player.transform.position, 
                            Quaternion.identity, Player.transform);
                break;
        }
        // An interesting part of this is that if we get another power up that if we
        // get a duplicate power up, we will replace it with the new one.
        powerUpDictionary[powerUpType] = new PowerUp(powerUpDuration);
    }

    /// <summary>
    /// See if the player currently have the power up that we pass in.
    /// </summary>

    public bool ContainsPowerUp(PowerUpType powerUpType)
    {
        return powerUpDictionary.ContainsKey(powerUpType);
    }
}

Looking at the Fields

We introduced two game objects that we will use in our script:

  • public GameObject Player – This is a reference to our Player game object that we control
  • public GameObject MagnetCollider – This is the MagnetCollider prefab that we’re going to create and remove in our Player game object.

Walking Through the Code

The change we added in are at Update() in AddPowerUp():

  1. In AddPowerUp(), if we add a Magnet Power-Up, we add the Magnet Collider to our Player game object.
  2. In Update(), when our power up is over, if we’re moving a Magnet Power-Up, we would find and remove the Magnet Collider that the Player game object contains.

Setting Up the Script

The only thing we need to make this script workable is to:

  1. Drag the Player game object in the hierarchy into the Player slot in the PlayerManager script in the Manager object in the game hierarchy.
  2. Drag the MagnetCollider prefab we made into the Magnet Collider slot in the PlayerManager script in the Manager object in the game hierarchy.

With this, we will now add the MagnetCollider whenever we get our power-up and it will go away whenever the time passes!

Step 2.3 Get the Coin to Follow the Player

We’re almost there! Currently, our code doesn’t build and that’s because we’re trying to call Follow() on our Coin script, which doesn’t have that function.

We’re going to go and implement this function and then we’ll finally have implemented our magnet follow functionality.

Here’s our Coin script:

C#
using UnityEngine;
using System.Collections;

public class Coin : MonoBehaviour 
{
    public AudioClip CollectCoinSFX;

    private SoundManager _soundManager;
    private GameObject _player;
    private readonly int _speed = 30;

    void Start()
    {
        _soundManager = GetComponent<SoundManager>();
    }

    void Update()
    {
        if (_player != null)
        {
            print("coin moving");
            float step = _speed * Time.deltaTime;
            transform.position = Vector3.MoveTowards
                      (transform.position, _player.transform.position, step);
        }    
    }

    public void Collect() {
        _soundManager.PlaySFXClip(CollectCoinSFX);
        StartCoroutine(RemoveGameObject());
	}

    public void Follow(GameObject player)
    {
        _player = player;
    }

    private IEnumerator RemoveGameObject()
    {
        yield return new WaitForSeconds(0.1f);
        Destroy(gameObject);
    }
}

Looking at the Fields

  • private GameObject _player – This field will hold a reference to the player that we will be given from Follow(). It will be used to move the coin towards the player.
  • private readonly int _speed = 30 – This field decides how fast the coin will move towards the player.

Walking Through the Code

  1. We implement Follow() which we previously called. All it does is that it sets _player to be player, which means now in Update() we have some direction we can go towards.
  2. In Update(), after we have a player, we can start moving our coin towards the player. There’s a helpful function from the Vector3 game object that I found from this forum thread that makes moving towards the player really easy. Similar to what we have done before, we get our current position, our destination, and then frame by frame, we slowly move our current game object closer to the destination.

End of Day 86

Once we’re finished, we’ll finally have some form of Magnet Power-Up available to be used.

Here’s a simple gameplay of it. Notice that the coins are moving towards you? Neat!

At this point, we have our first power-up implemented, however there’s so much more that needs to be done. For one, we have no indication that we even grabbed a power-up and we have no idea when the power-up ends!

That’s why in the next post, we’re going to go and finish implementing the remaining sound and UI to help us make a more complete power-up experience. I’ll see you then!

Day 85 | 100 Days of VR | Day 87

Home

 

The post Day 86 of 100 Days of VR: Endless Flyer – Implementing Magnet Effect in Unity appeared first on Coding Chronicles.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)