Best practice: writing a power-up system with OnTriggerEnter() | OnCollisionEnter() ?

I’m just looking for some advice as I can’t yet decide how I want to set this up :slight_smile:

Basically, I’m writing a power-up system for the player. I use triggers (or colliders in some places) to enable/disable power-ups as the player moves through the level.

My object is a bike moving only on X and Y - sidescrolling racing game. The bike as multiple power-ups like boost, high jump, boost ramps, ect…

Right now I have a “PowerUps.cs” script for the power-ups which controls boost, boost ramps, special boost etc… Each power-up has its own function within the script, and I setup event listeners in Awake().

To trigger the power-ups, I have a trigger/collider gameObject with a “BoostRamp.cs” | “SpecialBoost.cs” etc…script attached to it. When the player enters the trigger/collider object, I send an event to the “PowerUps.cs” script, with the correct identifier:

void OnTriggerEnter ( Collider collider )
{
    Messenger<float>.Broadcast( "Power Boost", 300f );
}

But I was thinking, it might be cleaner/easier to keep each power-up code inside the power-up script which detects collision (the one attached to a collider object), and doing it this way instead:

public Transform bike;

void OnTriggerEnter ( Collider collider )
{
    // Apply power boost (boost ramps, rockets, etc...), but only if moving forward
    if( bike.rigidbody.velocity.x > 0 )
    {
        bike.rigidbody.AddRelativeForce( bike.right * boostPower, ForceMode.Impulse );
        Debug.LogWarning("POWER BOOST", this);
    }
}

Now my power-up specific code is only in my power-up specific script, instead of being in another script, and I don’t have to use events at all.

Is there a best practice when it comes to stuff like this? Which way is better and why? I realize it also depends on the game, and on user preference, but I also know that I have only been programming for 1 year(ish) and I have a lot to learn when it comes to writing smart code and having a nice structure which makes sense - when you come back to it a year from now :stuck_out_tongue:

Stephane

What you need is a simple class/subclass system which adds them as components to your character. That way, it can listen for it’s own keys or operate and do things as it needs, even destroy it’s self when its done.

public class PowerUp : MonoBehaviour{}

public class PowerBoost : PowerUp{// code to make it work}

public class SpringPeds : PowerUp{// code to make it work}


// find all powerups on me:

PowerUp[] myPowerUps = (PowerUp[])gameObject.GetComponents<PowerUp>();

// find only the PowerBoost on me:
PowerBoost = gameObject.GetComponent<PowerBoost>();

// find the power of up of an incoming powerup:
public void PickUpPowerUp(PowerUp power){
PowerUp current = gameObject.GetComponent<typeof(power)>();
if(!current) gameObject.AddComponent<typeof(power)>();
}
1 Like

I really like that solution, but I have to admit, I’m not familiar with this kind of class/subclass system and I have a few questions, if you don’t mind?

public class PowerUp : MonoBehaviour{}  // Is this basically an abstract class?

And about the ‘PickUpPowerUp(PowerUp power)’ function, I would call it when the player collides with a power-up trigger, to add the correct power-up type, which means I would still need to:

A: Attach a PowerUpType script to each trigger, which would either send a message to PickUpPowerUp(PowerUp power) or access it using ‘myClassInstance.PickUpPowerUp(ThisPowerUpType)’.

B: Check for each trigger’s name, tag or layer within the same script as the PickUpPowerUp(PowerUp power) function, like so:

void OnTriggerEnter(Collision collision)
{
    if( collision.gameObject.name == "PowerBoost" ) 
    {
        PickUpPowerUp(PowerBoost); 
    }

    if( collision.gameObject.name == "SpringPeds " ) 
    {
        PickUpPowerUp(SpringPeds ); 
    }
}

public void PickUpPowerUp(PowerUp power)
{
    PowerUp current = gameObject.GetComponent<typeof(power)>();
    if(!current) gameObject.AddComponent<typeof(power)>();
}

I don’t like checking for names, tags or layers, so I would probably end-up creating a class for each power-up type for the collision detection only, attached to each trigger, which is kind of what I’m doing right now.

Am I missing something about how to implement your solution? Like I said, I’m not familiar with this setup so it’s very possible that I’m completely missing the point of doing it this way hehe…

Thanks a lot for your time btw!
Stephane

Lets say you have a “power up” ready to be picked up, (a nifty sparkling power up that everyone wants). It has a trigger so that when someone enters it, it does some things. (lets also say that only the player (tagged “Player”) can pick them up)

public PowerUp powerUp;

void OnTriggerEnter(Collider other){
if(other.tag == "Player"){
other.gameObject.SendMessage("PickUpPowerUp", powerUp, SendMessageOptions.DontRequireReceiver);
Destroy(gameObject);
}
}

Now what this does is when the player hits the trigger it says, Oh, your a player, here, have this… The don’t require reciever meanst that it doesnt really care if the player heard him.

What the player does is adds the powerup to himself and goes on.

We can make a case that we should destroy the powerup if it exists and add the new one.

public void PickUpPowerUp(PowerUp power)
{
    PowerUp current = gameObject.GetComponent<typeof(power)>();
    if(current) DestroyImmediate(current);
    gameObject.AddComponent<typeof(power)>();
}

Now, onto your questions…

A. Yes, as the example above (quick and easy)

B. No, quite the opposite, the powerup checks to see if the thing that ran into it can pick it up and forces it on him. Then destroys it’s self, no keeping up with 500 powerups in script, just make what you want and let it work its self out.

C. I dont really like tags/layers or names, I generally check to see if a component is there. (health or something like that) The above example is very easily adaptable to that theory.

D. It is just about simple enough to slap you in the face… lol. Implementing it is as above. :wink:

Thanks for the example and explanation BigMisterB! It was very helpful and yes indeed, very simple to implement. What I am already doing is similar, except I don’t “let it work its self out” like in your example, which I like much better, so I’m gonna go implement it right now :slight_smile:

Your example(s) reinforced my first idea on implementing this, so thanks - again - for helping me feel good about my first choice, and improving the implementation in the process :slight_smile:

Stephane

I can’t get the code above to work, and I have tried multiple different ways:

public void PickUpPowerUp ( PowerUp power )
{
    PowerUp current = gameObject.GetComponent<typeof(power)>();    // error CS1525: Unexpected symbol `)'
    PowerUp current = gameObject.GetComponent<power>();    // error CS0246: The type or namespace name `power' could not be found. Are you missing a using directive or an assembly reference?

    PowerUp current = gameObject.GetComponent<PowerUp>();    // Works fine...is that what you meant to use there?
	
}

I have never seen ‘GetComponent()’ used as: GetComponent<typeof(type)>(); before, is it supposed to work in Unity? Also, is doing ‘gameObject.GetComponent();’ instead of ‘gameObject.GetComponent();’ return the same result, since the base type is always ‘PowerUp’ anyways?

Thanks for your time, and sorry for any stupid questions…when something confuses me, whether it is supposed to be dumb easy or not, I always ask instead of mindlessly looking for a solution…:wink:

Well that just sucks… It should be just a Type, but evidently you can’t replace the type with anything.

So the solution is actually simple. Rather than doing a generic replacement, do a specific one.

3 scripts: The generic PowerUp, a Pedestrian Spring and one called IronHide. Ped spring is a usage based power up, and IronHide is a timed one. All tested and work perfectly.

To use it, just dump the power up you want onto an empty game object, or an object you can see, it will do all the collider stuff and look for a script called “Health” (forgot to include)

using UnityEngine;
using System.Collections;

public class PowerUp : MonoBehaviour {
	[HideInInspector]
	public string toolTip = "Power Up";
	
	public bool displayIcon = true;
	public Texture2D icon;
	public Vector2 iconPosition = Vector3.zero;
	public int uses = 5;
	
	[HideInInspector]
	public bool isActive = false;
	[HideInInspector]
	public bool isPassive = false;
	public float passiveLifetime = 20;
	[HideInInspector]
	public float dieAt = 0;
	
	
	public virtual void Init(){
		if(isPassive){
			Destroy(this, passiveLifetime);
			dieAt = Time.time + passiveLifetime;
		} else {
			isActive = true;
		}
	}
	public void Dectivate(){isActive = false;}
	
	void OnGUI(){
		if(!displayIcon) return;
		if(!icon) return;
		if(!GetComponent<Health>()) return;
		
		Color c = Color.white;
		if(!isActive || isPassive) c.a = 0.5f;
		GUI.color = c;
		
		string s = "";
		if(uses > 1) s = uses.ToString();
		GUIContent content = new GUIContent(icon, toolTip);
			
		
		Rect rect = new Rect(iconPosition.x, iconPosition.y, icon.width, icon.height);
		GUI.Box(rect, content);
		s=""; // uses or seconds remaining
		if(uses > 1) s = uses.ToString();
		if(isPassive){
			float t = dieAt - Time.time;
			s = (Mathf.Round((dieAt - Time.time) * 10.0f) / 10.0f).ToString();
			if(t > 3)  s = (Mathf.Floor(dieAt - Time.time)).ToString();
		}
		if(s != ""){
			GUI.color = Color.white;
			GUI.Label (rect, s);
		}
	}
}
using UnityEngine;
using System.Collections;

public class PedSpring : PowerUp {
	
	public string displayName = "Pedestrian Spring";
	
	
	// Use this for initialization
	void Start(){
		if( ! GetComponent<Health>()){
			SphereCollider col = GetComponent<SphereCollider>();
			if(!collider){
				col = gameObject.AddComponent<SphereCollider>();
			}
			col.radius = 0.5f;
			col.isTrigger = true;
			isActive = false;
		}
	}
	
	public override void Init () {
		toolTip = displayName;
		
		if(GetComponent<Health>()){
			// make this the current active powerup
			PowerUp[] pups = (PowerUp[])GetComponents<PowerUp>();
			foreach(PowerUp pup in pups) pup.isActive = false;
			isActive = true;
		}
	}
	
	// Update is called once per frame
	void Update () {
		if( ! GetComponent<Health>()){
			// do animation effects here.
			return;
		}
		
		if(isPassive){
			DoPowerUp();
		} else if(isActive){
			if(Input.GetButtonDown("Jump")){
				DoPowerUp ();
			}
		}
	}
	
	// handle the trigger enter stuff and update the player
	void OnTriggerEnter(Collider other){
		GameObject go = other.gameObject;
		if(go.GetComponent<Health>()){
			if(go.GetComponent<PedSpring>()){
				Destroy(go.GetComponent<PedSpring>());
			}
			// create the new PowerUp
			PowerUp po = go.AddComponent<PedSpring>();
			po.icon = icon;
			
			// if it is the only PowerUp then it should be active
			PowerUp[] pups = (PowerUp[])go.GetComponents<PowerUp>();
			po.Init();
			
			Destroy(gameObject);
		}
	}
	
	void DoPowerUp(){
		// do the actual powerup stuff here.
	}
}
using UnityEngine;
using System.Collections;

public class IronHide : PowerUp {
	
	public string displayName = "Hide of Iron";
	
	// Use this for initialization
	void Start(){
		if( ! GetComponent<Health>()){
			SphereCollider col = GetComponent<SphereCollider>();
			if(!collider){
				col = gameObject.AddComponent<SphereCollider>();
			}
			col.radius = 0.5f;
			col.isTrigger = true;
			isActive = false;
		}
	}
	
	public override void Init () {
		toolTip = displayName;
		isPassive = true; // is a passive power up
		iconPosition = Vector2.right * 128;
		
		if(GetComponent<Health>()){
			Destroy(this, passiveLifetime);
		dieAt = Time.time + passiveLifetime;
		}
	}
	
	// Update is called once per frame
	void Update () {
		if( ! GetComponent<Health>()){
			// do animation effects here.
			return;
		}
		
		if(isPassive){
			DoPowerUp();
		} else if(isActive){
			if(Input.GetButtonDown("Jump")){
				DoPowerUp ();
			}
		}
	}
	
	// handle the trigger enter stuff and update the player
	void OnTriggerEnter(Collider other){
		GameObject go = other.gameObject;
		if(go.GetComponent<Health>()){
			if(go.GetComponent<IronHide>()){
				Destroy(go.GetComponent<IronHide>());
			}
			// create the new PowerUp
			PowerUp po = go.AddComponent<IronHide>();
			po.icon = icon;
			Debug.Log (po);
			
			po.Init();
			
			Destroy(gameObject);
		}
	}
	
	void DoPowerUp(){
		// do the actual powerup stuff here.
	}
}
1 Like

Well man, you rock! Thanks so much for taking the time to put this together and test it, I couldn’t have asked for anything better :slight_smile:

Looking at your classes and how everything is working together teaches me a lot of things about creating systems likes this, it’s very clean and easy to understand, and is imo perfect for a power-up system.

I’m still learning inheritance/polymorphism and this is a great working example I can play with and get more comfortable with this concept. Again, thanks a bunch!

Stephane