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

Day 95 of 100 Days of VR: Endless Flyer – Implementing The Invincible Power-Up Effect

0.00/5 (No votes)
6 Feb 2019CPOL6 min read 1.9K  
How to implement the invincible boost power-up

Today’s the day! We’re going to implement the our invincible boost power-up!

To implement this power-up, there will be 2 things that we will need to implement:

  • Making our plane move faster.
  • Making our plane invincible so that we won’t die if we collide against anything.

Making the plane move faster isn’t hard, however the big problem is implementing invincibility. We want to prevent game over when we collide against an obstacle, however if we were to do that, we’re going to have a problem with being able to go off stage.

Either way it’s going to be an interesting problem, but let’s tackle them one at a time!

Step 1: Make the Plane Move Faster

Step 1.1: Change the plane speed in PlaneController

Introduction

It’s been a long time since we last looked at this script, but if we’re looking at changing movement speed, the script that does that is PlaneController located in the Player game object (and sibling of our favorite PlaneCollider script).

We’re adding a very simple change to our script:

C#
using System;
using UnityEngine;

public class PlaneController : MonoBehaviour
{
    private Camera _mainCamera;

	void Start () {
        _mainCamera = Camera.main;
	}
	
	void Update ()
	{
	    switch (PlayerManager.Instance.CurrentState)
	    {
            case PlayerManager.PlayerState.Alive:
                MovePlayer();
                break;
	    }
	}

    /// <summary>
    /// Moves the player forward and to the side based off of where they're looking at 
    /// with the cardboard.
    /// </summary>

    private void MovePlayer()
    {
        Vector3 movement = GetMoveSpeed(_mainCamera.transform.rotation.x, 
                                        _mainCamera.transform.rotation.y);
        Vector3 forward = transform.forward;
        if (PlayerManager.Instance.ContainsPowerUp(PlayerManager.PowerUpType.Invincible))
        {
            forward *= 2;
        }
        transform.position += (forward + movement) / 2;
    }

    /// <summary>
    /// Creates and returns a Vector3 using rotation values from the camera 
    /// that will be used for this game objects 
    /// vertical/horizontal movement
    /// </summary>

    /// <param name="x">The X rotation value of our camera, 
    /// used to calculate our vertical movement (up and down)</param>
    /// <param name="y">The Y rotation value of our camera, 
    /// used to calculate our horizontal movement (left and right)</param>
    /// <returns>A Vector3 that has the horizontal and vertical direction 
    /// the plane should be moving to</returns>
    private Vector3 GetMoveSpeed(float x, float y)
    {
        // create our movement vector value based off of where we're looking at with a cap 
        float xMove = Mathf.Min(Mathf.Abs(y * 10), 3);
        float yMove = Mathf.Min(Mathf.Abs(x * 10), 3);

        // Figure out which direction our plane should be turning to
        if (x >= 0)
           yMove *= -1;
        if (y < 0)
            xMove *= -1;

        return new Vector3(xMove, yMove, 0f);
    }
}

Walking Through the Code

  • Like what we did with the score multiplier, in MovePlayer() if our player has the invincible power-up, we take our base movement (forward), double it, and then re-apply it back to our existing code.

That’s it! Now whenever we pick up the invincible power-up, we will move twice as fast as we do.

Step 2: Make the Player Invincible

Step 2.1: Adding the Invincibility Effect in PlaneCollider

Now that we have the speed boost, the next thing we need to add is the ability to not die in the game when we collide against anything.

Accomplishing this isn’t difficult, as we will soon see, the real problem will come after this!

We only get a game over whenever we collide against the walls in our path and that code is located in PlaneCollider!

Here’s what it looks like now:

C#
using UnityEngine;

public class PlaneCollider : MonoBehaviour 
{
    …

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

Walking Through the Code

  1. In EnemyCollision() (which gets called when we collide against cube), we add an if check to see if we have picked up an invincible power-up and if we haven’t, then we will crash and get the game over screen like normal. If we have picked up the power-up, then we won’t go into our if statement and live.

With that, if we have the invincible power-up, we’ll never be able to die. Which is great, except for this:

Image 1

As you can see, we can fly off the stage…

Step 2.2: Preventing the Player From Flying Out of the Path, Fixing Our Trigger Collision

So how are we going to fix this? I thought about it for a while, but in the end, the solution to this problem was simple. We need to flip the way we’ve been handling our collision logic.

Currently in Plane Collider, we have a capsule collider with the Is Trigger option checked. When this is checked, it means that our plane will ignore any physics in the game and continue to fly through everything.

Before we didn’t have any problems, because the moment we hit a wall, we died, and we would never go through anything, however now that we’re invincible, we can no longer say that’s the case.

However, we can’t simply just convert everything to be a collision (non-trigger), because we don’t want to bump into a power-up/coin. Instead, we must separate the logic.

We must separate our collision logic to:

  • Use triggers (what we already have) to pick up coins and power-ups
  • Use collisions for bumping against walls

Currently, our logic works because our plane is set to be a trigger, however if we were to change that, most of our code won’t work anymore.

Here’s what we must do to solve this:

  1. In Capsule Collider component in Player, uncheck the Is Trigger, this will make enable us to bump into objects and prevent us from moving, however this will break our original coin/power-up code as nothing is a trigger anymore. Let’s fix that.
  2. Go to our Magnet prefab (drag it into the scene if it’s not there) and in the Box Collider component, check Is Trigger. Make sure to hit Apply to save our changes to all our prefabs.
  3. Go to the 2x > 2x Model prefab and in the Box Collider component, check Is Trigger. Make sure to hit Apply to save our changes to all our prefabs.
  4. Go to the Rocket > rocket prefab and in the Box Collider component, check Is Trigger. Make sure to hit Apply to save our changes to all our prefabs.
  5. Go to the Coin Container prefab and in the Box Collider component, check Is Trigger. Make sure to hit Apply to save our changes to all our prefabs.

With these changes in now, we have the trigger collision functionality.

However, we have one more thing that we should change. This will become obvious the moment we make our changes below, but when we collide against something, our plane will rotate because of the collision (and cause massive dizziness!).

We don’t want that, so to fix that, we’re going to fix our rotation in our RigidBody component.

  1. In the RigidBody component in Player, under Constraints > Freeze Rotation check and freeze X, Y, and Z so the plane’s rotation can never be changed.

Step 2.2: Preventing the Player From Flying Out of the Path, Fixing Our Collision

The next thing we have to do now is move our wall collision to be in the OnCollisionEnter() entry point.

The difference between a collision and a trigger is based off the object that you’re colliding against. A trigger isn’t affected by physics and a collision is.

What we need to do to make this work is that we need to move our Wall collision logic from the OnTriggerEnter() to OnCollisionEnter(). Here’s our change to the PlaneCollider script:

C#
using UnityEngine;

public class PlaneCollider : MonoBehaviour 
{
	public GameObject PlaneObject;
    public GameObject Explosion;
    public AudioClip MagnetSFX;
    public GameObject MagnetParticleEffect;
    public AudioClip MultiplierSFX;
    public GameObject MultiplierParticleEffect;

    private SoundManager _soundManager;
    private GameObject _currentParticleEffect;

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

    void OnCollisionEnter(Collision other)
    {
        CheckUnTaggedCollision(other.gameObject);
    }

    void OnTriggerEnter(Collider other)
    {
		switch (other.tag) {
			case "Coin":
				CoinCollision(other);
				break;
            case "Magnet":
                MagnetCollision(other);
                break;
            case "Score":
                ScoreColllision(other);
                break;
            case "Invincible":
                InvincibleCollision(other);
                break;
			// default:
				// CheckUnTaggedCollision(other);
				// break;
		}
    }

    // 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(GameObject other) {
		if (other.name.Contains("Cube")) {
			EnemyCollision();
		}
	}

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

Walking Through the Code

It feels like it’s been a while since we had a more complex code change, huh? Here’s what our changes do:

  1. In OnTriggerEnter(), we removed the case where we bump into the walls (an untagged collision), but we kept everything else the same.
  2. To replace our wall collision, I added OnCollisionEnter() and call CheckUnTaggedCollision() which will handle the object the same way. One thing to note that is different, in OnCollisionEnter, we receive a parameter that is typed Collision while the OnTriggerEnter receives a type Collider. The documentation on Collision explains the difference, but the main difference is that Collision contains more information about the physics of the game object we collided against. However, because we have a different type, I changed CheckUnTaggedCollision() to take in a game object instead of a
  3. In CheckUnTaggedCollision(), I change the passed in type to be a GameObject which coincidentally has the same API calls that we were using for when we were passing in a Collider so we don’t have to change much of anything.

With these changes in, we have everything we need now to have our invincible power-up work!

Check it out! Note that we picked up the invincible power-up.

End of Day 95

And that’s it! It turns out that implementing the invincible power-up took more work than we thought, but overall it wasn’t too bad!

We now have a fully functional invincible power-up in our game!

The next and final thing we need to do now is to add sound and special effects for our power-up. I know what you’re thinking, this is going to be super repetitive, but don’t worry, there’s a special effect that I want to implement that’s completely different than what we have done in the past!

However until then, I’ll see you later!

License

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