- Home /
Design pattern for tracking unit experience
Hi,
I have a simple side-based shooter. Units fire projectiles; the projectiles hit opponents; opponents die if health is less than zero. I'd like that to extend further so that opponent dies and the launcher of the projectile gains experience. However, how do I handle or store the connection between projectile and its original owner? What is the best way to do this? I don't want to have to keep a reference to the game object that fired it, or is that the actual solution? Again, I'm not a C# or Unity expert and I'm not sure of any memory implications for storing things like that, especially as I intend on running the game on a mobile platform.
If there is a link to help with this elsewhere, that'd be appreciated too.
EDIT: I've found this post here (http://answers.unity3d.com/questions/25451/how-can-i-make-it-give-u-a-certain-xp-everytime-u.html), which mentions searching for a particular game object to assign experience too when required. The issue I have is that there will be many user and enemy game objects that will have to call this fairly repeatedly, which could slow things down tremendously and doesn't seem sensible.
I have another idea. Is it possible to store a reference to a script attached to the original shooter game object in the projectile gameobject itself that, if it kills the enemy gameobject, will first update the referenced script, then destroy itself?
Thanks!
Answer by Bunny83 · Aug 17, 2016 at 11:36 PM
Is it possible to store a reference to a script attached to the original shooter game object in the projectile gameobject itself that, if it kills the enemy gameobject, will first update the referenced script, then destroy itself?
Yes! ^^
That's the usual solution. In most cases referencing gameobjects is pretty pointless. It's always better to reference a script component that you actually need. Even when you don't have a script referencing the Transform usually makes more sense.
Imagine this script on a projectile:
public class Projectile : MonoBehaviour
{
public PlayerScript owner;
// call this from your hit detection code
void GiveXPToOwner(float aXP)
{
if (owner != null)
owner.AddXP(aXP);
}
}
And this would be the script on the Player:
public class PlayerScript : MonoBehaviour
{
// this is the prefab reference that the player uses to fire a projectile
public Projectile prefab;
public float XP = 0;
public void AddXP(float aXP)
{
XP += aXP;
}
void Fire()
{
Projectile p = Instantiate(prefab, yourSpawnPos, yourRotation);
p.owner = this; // set the owner to this script instance.
// do your other firing here.
}
}
Note: The amount of XP might depend on what the projectile hit actually hit. This could be handled in your hit detection code if needed, An enemy could have a script attached that holds a variable with the XP that this enemy gives when killed / hit.
Answer by sbsmith · Aug 18, 2016 at 05:47 AM
I think @Bunny83's answer works and gets straight to the point, and since you asked for a design pattern, I'll only add this one which you may find handy as your game gets bigger: The Observer Pattern.
Imagine that the player is shooting at an enemy. They want to know when the enemy is killed so they can gain XP. It's simple enough for the enemy to get a reference to the player (in the above example, through the projectile) and then call a function to tell the player that they killed the enemy.
Now let's add a scoreboard that updates a counter every time an enemy is killed. I suppose you could attach the scoreboard to the player or the enemy, but that means their code needs to know and care about scoreboards. Now add achievement tracking. Again, if we're holding references to scripts and calling functions, either our enemy or player code needs to care about achievement tracking too.
This can get messy very quickly.
The Observer Pattern tells us that an enemy shouldn't care about telling the player, scoreboard and achievement system that it has died. It should just make an announcement, and anyone who cares can listen to it and react accordingly. It can go about its business of being an enemy without needing to specifically handle the needs of other game objects.
This pattern may take a bit of time to wrap your head around, but it won't take you very much time to write the code because C# delegates and events make it easy. The MSDN calls it the Event Pattern which is pretty much the same principle.
Good luck!
I wasn't aware unity had a similar system to android broadcasts and broadcast receivers. This is probably more what I'm after long-term and what I first thought of doing. I'll still use bunny's solution for my prototype, but the issue I have is I plan to have maximum 100 units firing at once on screen with potentially over a thousand projectiles being present (again worst case scenario). I'm therefore concerned that have so many events broadcast and having to listen for them would add a massive overhead too (lag in receiving broadcasts is an issue on mobiles from experience) . It would work well for single item event receivers like the scoreboard or achievement system. Again keeping references to lots of scripts may also cause an issue.
I guess I'll try both and see the benefits of each. Thanks for highlighting this though.
You are welcome. I just wanted to toss this up for anyone else who comes along looking for design patterns. Also, I agree with you. For prototyping, the fastest implementation to write is good enough.
With the scenario you describe, a safe, computationally-inexpensive method would be to assign handles or IDs (rather than references) to your projectiles, and then have the enemies do the throwing of events. A player manager, rather than the player instance, could do the listening and be able to dereference the ids/handles. This would simplify life-cycle management of the game entities on screen.
For example: You fire a bullet and the player is killed before the bullet hits and kills a target. If you destroy the player on death, then the reference in the bullet will become null. However, since you're on mobile, you may use object pooling and the reference may be pointing to a different thing. You can code around all these problems but the handles mean less refactoring if you change your design.
Cheers!
That's an excellent example that I never thought of, but guaranteed I'd hit at some point. Thanks for your help!