#De-spaghettiing power-ups

1 messages · Page 1 of 1 (latest)

tribal crow
#

Hang on this is gonna get long let's move to a thread

#

Instead of saying things like "the instantiated object" and "on key press" please be specific. Use game words, not code words. Things like "When the player touches a power-up, their gun can shoot a different kind of bullet"

#

Saying things like "The instance of the prefab" is ambiguous. There are many prefabs involved in this situation

#

Use nouns, not descriptions

foggy rover
#

when the player presses a key, an effect is applied that makes it so the gun can shoot a different kind of bullet until the effect expires

tribal crow
#

Okay, so these are more like League of Legends-style active abilities? You hit a button and it applies for a while?

foggy rover
#

yes exactly

tribal crow
#

So then what was this about colliding with something in the world?

foggy rover
#

the bullet that gets spawned when the effect is active applies a slow effect when it hits other players

tribal crow
#

Okay, so earlier when I asked what the effects were and you said they changed what attack was used, that wasn't actually what they were for

#

The effects are what the bullets do to the things they hit

foggy rover
#

ah, I have called buffs and debuffs "effects" as a general term

tribal crow
#

As I said, right now, pick one thing

#

Just pick your favorite one

#

And then we go through the process on that one

foggy rover
#

The effect that changes the bullet is a buff

tribal crow
#

Okay, so, you want to discuss the buff here?

#

Which is basically "For the next N frames, whenever you spawn a bullet, spawn this other one instead"

foggy rover
#

yes

tribal crow
#

Since you refuse to give me concrete examples like I asked for, we are going to assume that this buff is called BeegBullet and it simply a larger prefab

foggy rover
#

wdym

#

I did post the concrete buff

tribal crow
#

So, let's talk through how a player might enable BeegBullet and how it uses code

flint dust
tribal crow
#

and you never did

#

so now we're using the ability BeegBullet

#

which makes the bullet very large

foggy rover
#

I named the effect I want to discuss, I explained what I wanted it to do and I posted the code in the channel

#

I'm not sure what I'm doing wrong to not be concrete here

tribal crow
#

Let's discuss the lifetime of BeegBullet.
Let's say it's a buff that is activated by pressing R.
Let's say it lasts five seconds, and has a cooldown of ten seconds after that.
Its effect is that when the player attacks, instead of normalBulletPrefab, they spawn a beegBulletPrefab

foggy rover
#

yes

tribal crow
#

So, here's what we need.

We need some sort of data structure that can hold the information that we need.
We need a way to "apply" the data structure and keep track of time so we know when to stop

foggy rover
#

yes I have a list for that, that part already works

tribal crow
#

In the first case, this seems kind of like we want a "Database" of variables that are tied to a specific effect. This sounds, to me, like a use case for ScriptableObjects

This will let us define a file in the project that contains some data. Something like this:

[CreateAssetMenu(menuName = "Buffs/BulletChangeBuff")]
public class BulletChangeBuff: ScriptableObject {
  public float activeDuration;
  public float cooldownDuration;
  public Bullet newBulletPrefab;
}
#

You will then be able to create an asset in your project from the right click menu. You can create one called BeegBullet, and set its values to 5, 10, and your beegBulletPrefab respectively.

#

On the player, you could have a field akin to:

public BulletChangeBuff abilityR;

And you could drag in that BeegBullet.asset you just made

#

Your player now has a reference to this database that tells it which ability is equipped to that button.

You follow me so far?

foggy rover
#

yes, that sounds a lot like what I already have, which is separate classes like that for each buff and debuff

flint dust
#

now imagine you have 100 "buffs", so you want to have a separate class for each?

#

that's sounds like a terrible architecture to maintain

foggy rover
tribal crow
flint dust
glass patrol
#

(tbh i thought there was a good reason for multiple types of an Effect)

tribal crow
tribal crow
foggy rover
glass patrol
tribal crow
foggy rover
#

ok

tribal crow
#

The SO I defined above can handle any effect that involves changing out the bullet prefab, with just one class. Is there any other properties you can think of that might be useful to have on the file that defines one of these buffs?

foggy rover
#

by one of these do you mean bullet changing buffs?

tribal crow
#

Yes, any effect that involves changing out the bullet prefab

foggy rover
#

well currently I have this abstract class

public abstract class EffectObject
{
    public abstract EffectType EffectType { get; }
    public abstract EffectAlignment EffectAlignment { get; }
    public abstract int EffectAuthorityLevel { get; }
    public abstract float EffectDuration { get; set; }


    public virtual void StartEffect(MonoBehaviour mono) { return; }
    public virtual void StopEffect() { return; }
}```
that all effects inherit from so those properties and methods should probably exist if they go in the SO
tribal crow
#

Okay, so, we have duration already. EffectAuthorityLevel seems like a priority sytem, where a conflicting effect of higher priority just kicks the old one off and takes its place, right?

#

StartEffect(MonoBehaviour mono) is blasphemy and should be purged.

foggy rover
#

the authority level is meant to be the difficulty of dispelling the effect

glass patrol
foggy rover
#

multiple effects of the same kind can apply in parallel with their own timers

tribal crow
#

Yeah but why does the effect need to start a coroutine

#

but we'll get to timing later. Spoiler: It's gonna be on the thing using the effect

foggy rover
#

or rather, it deals x damage every y seconds

#

hang on I'll show you the burn effect

tribal crow
#

I think this seems like a different kind of thing than the one we're working with at the moment

foggy rover
#

yes it does

#

but thats the reason why I did the mono thing with the coroutine

tribal crow
#

Yeah we're separating the responsibilities of "Define a thing" from "Do a thing"

#

The general principle is that your effects should have a file that defines what they are, and the MonoBehaviours in your game actually apply the effects.

#

For practical examples, we return to BeegBullet

#

If your player has a set of fields like:

private BulletChangeBuff activeBulletBuff;
private float bulletBuffDuration;

Along with the above abilityR field:

void OnPressRButton(){
  activeBulletBuff = abilityR;
  bulletBuffDuration = activeBulletBuff.activeDuration;
}

The duration can tick down in Update:

void Update(){
  ...
  if (activeBulletBuff != null){
    bulletBuffDuration -= Time.deltaTime;
    if (bulletBuffDuration <= 0) {
      activeBulletBuff = null;
    }
  }
  ...
#

And when you shoot your bullet:

if (activeBulletBuff != null){
  bulletFired = Instantiate(activeBulletBuff.newBulletPrefab, ... );
else
  bulletFired = Instantiate(normalBulletPrefab, ... );
#

Does this make sense

foggy rover
#

yeah

#

I'm already doing that too actually

#

I think, anyway

#

just not in the SO way

tribal crow
#

With this you can have an infinite number of bullet buffs without ever having to write new code

#

You just make a new SO instance

#

The if-else chain you had before is no longer a thing

foggy rover
#

well ok but that chain is not just for bullet buffs

#

Instead, if looks like now I will have to do this private BulletChangeBuff activeBulletBuff; but for every possible kind of effect?

tribal crow
#

You'll need to do a bit of thinking and find ways to group things.

#

For example, the burn effect you mentioned, could be something like a PeriodicEffect that has a duration, a frequency, and the thing it does. Like a healOverTime would add health, while burn would decrease it

#

The important thing is to try to keep data flowing one way. You have a read-only effect instance that defines some data. The thing that triggers the effect reads from it and sets up its own fields to handle the timing, you don't put that object back into the effect data

foggy rover
#

so basically, I would group them by the arguments in the constructors used here?

tribal crow
#

And you can make a shared parent class since they all seem to have effectDuration in common

#

And make each of these sub-classes of that

foggy rover
#

also a SO?

tribal crow
#

Yes, SOs can have inheritence just like a normal class

#

The core idea is that instead of having UnityEvents and invokes, you'd have an object simply hold a reference to one of these things and modify itself. Like, Slow and Quick for example, could be something along the lines of:

if (movementAlteringEffect != null) {
  movementVector = movementAlteringEffect.GetNewMovement(movementVector);
}
#

But it could also be something like this instead:

void OnGainEffect(StatAlteringEffect eff){
  currentStr = originalStr * eff.strMult;
  currentDef = originalDef * eff.defMult;
  currentSpd = originalSpd * eff.spdMult;
  ...
}
#

and your slow effect would just have 1s for everything and a 0.8 for speed or something

#

There are multiple avenues, some more general than others, but the important thing is to consider what kinds of things you want to apply. It's okay to have a catch-all "Effect" term, but really drill down into each category of things. Categories can have sub-categories that can have even more sub-categories

foggy rover
#

well I'm handling different stats in different controllers so I might have to group by affected controllers too

#

but also how do I actually apply these effects then

tribal crow
#

What are all the ways an effect can be applied? In gameplay terms.

#

We've seen "Hit the ability button" and "get hit by a bullet"

#

what others are there

foggy rover
#

it could also be "get hit by a skill" which I guess is the same as "get hit by a bullet" practically but it could also be something like "picking up the flag" and possibly "when game timer is low" but I'm not sure about that yet tbh

#

I haven't thought about how to handle those last parts yet, but it might make sense to handle it like a debuff instead of creating a new system

#

Currently, to apply an effect, I have the EffectController instantiate the effect, call the StartEffect method on it and put it in a list to keep a reference and count down the timers

foggy rover
#

I think I still need to keep that list centrally for purposes like the dispelling

#

and I need to keep effect alignment for the same reason, to do for example "remove all negative effects (ie. debuffs)"