I'm making a modular attack system, where I have AttackData scriptable objects, that hold attack's parameters, and among those, I have a dictionary list of prefabs the attack uses for FX (Particle systems, lights, etc). I am currently thinking on how I can best retrieve those objects in code with most readability, because seeing something like GameObject fx = Instantiate(attack.fxList[0]) ain't very informative, plus you'll be unable to see in the editor which slot of the array needed for which FX reference.
So far I considered two alternatives - strings and enums.
downside of strings is that they can be mistyped, and there's no direct coupling between a string in the code and a string in the dictionary list (the "was it 'attack_muzzleflash' or 'muzzleflash_attack'?" problem), but the upside is that they're very flexible and you do not need to edit the code if you need to add a new reference to the list.
With enums its the exact opposite - you can't mess them up since it's a predefined list of options, but you need to edit the definition of the enum each time you want to reference a new type of element in the dictionary.
Ideal solution would be to construct enum dynamically based on the contents of keys of fxList across the created scriptable objects, but as far as I can tell that's not possible.
So far for my purposes I feel like using strings is a better option for my case, but the threat of mistyped names remains (even if I sanitize them by forcing lowercase during OnValidate()). Are there any alternatives that at least wouldn't have downsides of either?
#Alternative to strings or enums as readable keys?
1 messages · Page 1 of 1 (latest)
You could use SOs as well.
Also code Gen to create enums.
Maybe generics.
It's not entirely clear what part exactly you're looking to fix.
Sharing some code where you access the strings would be helpful.
Well, in my current setup, I use enums to determine the FX prefab in the Scriptable Object, like in the image.
I get this prefab via the following function in the attack logic script:
protected bool TryAttachFx<T>(string fxType, Transform point, out T fxObject) where T : Component
{
fxType = fxType.ToLower().Replace(" ", "_");
fxObject = null;
if (!attackDef.attackFX.ContainsKey(fxType) || !attackDef.attackFX[fxType] || !point)
{
DevLog.LogWarning("Attack definition " +attackDef.name + " doesn't have '" +fxType+ "' FX effect prefab.",attackDef);
return false;
}
GameObject o = GameObject.Instantiate(attackDef.attackFX[fxType],point);
o.transform.localScale = Vector3.one;
o.transform.localPosition = Vector3.zero;
o.name = attackDef.attackFX[fxType].name + " (Instance)";
fxObject = o.GetComponent<T>();
if (!fxObject)
{
DevLog.LogError("Attack Fx '"+attackDef.attackFX[fxType].name+"' is used by "+
this.GetType().Name+" Attack Logic, but doesn't have required "+typeof(T)+" component!",attackDef);
Object.Destroy(o);
return false;
}
return true;
}
It has some error reporting code in case the necessary FX key string fxType is missing from the attackFX dictionary, but I wonder if it is the best solution.
In the code of the attack logic, this function is used like this:
if(TryAttachFx("beam", attachmentPoint, out Beam fx))
{
laser = fx;
laser.SetBeamColor(attackDef.attackDataReadonly.projectile.color);
laser.width = attackDef.attackDataReadonly.projectile.size;
laser.Init();
laser.gameObject.SetActive(false);
}
So can you have several Beam type fx assigned?
Well, if needs be, yes.
How are you gonna tell which one you need then?
What am getting to is perhaps use references directly? Or get the object by type(actual type, not a string or enum)
What do you mean?
Well, if you have 2 beams in the fx, how are you gonna get the correct one?
I assume that by the string ID/Name(so, like Beam0, Beam1) . And where are you gonna define these strings? Hardcode them in the logic? That's a pretty bad thing on its own. If you're gonna define them in the inspector somewhere, then you might as well reference the fx directly.
Using strings as IDs, usually means you have more problems than just the string IDs.
Well, if you have 2 beams in the fx, how are you gonna get the correct one?
Oh, I thought you meant "instantiate more than one instance of the prefab" when you said "So can you have several Beam type fx assigned?"
but, yeah, hardcoding in the logic was the idea.
Each attack logic does its own thing, but the idea of the system as a whole is that any attack logic can use any attack definiton SO.
(Well, since the attack logic is defined in the attack definition, it be the reverse actually, come to think about it ^^")
Well, first I'd definitely avoid hardcoding the IDs. If the attack itself is an SO, then you can serialize the IDs in the inspector. At which point using an SO as an ID could be nice.
Attack logic aren't SOs, they're pure classes
Well, what's prevents you from making them SOs?
Since every NPC would need their own copy of that logic to run independently
Scriptable Objects are singletons IIRC
You can also separate it into a plain class for logic only and a pair SO for data, like the IDs.
That's how it is set up now.
No, they're not. And even if they were, I don't see how that's related.
Yes, but you're still hardcoding IDs in the logic. That's a pretty bad idea.
Well the only other option is to ditch the strings, use simple list, and select FX in the code by index. Which is bad for clarity.
No. That's not the only way.
- You can define the IDs in the paired data SO.
- You can get it by type and order. This would be easier if you want to keep hardcoding it.
Could you elaborate please? Since it sounds like option 1 is just moving the list to an additional different SO, and option 2 is unclear. You reference prefabs, so all types would be GameObject
id = attackData.Beam0;.id = attackData. GetFx(Beam, 0);- Beam here would be a C# type and the attack data would need a more sophisticated storage and retrieval system.
Regarding one refer to the previous message.
Regarding 2: I can see that you already have actual C# types for different FX, like Beam in the code you shared here: #1414749339208908867 message
Yeah but I expect my FX would contain a lot of, say, particle systems too.
Each FX prefab can have an orchestrator class at the root like Beam. So it wouldn't matter what else it has.
What I mean is that Beam is not a class unique to that specific effect used in that specific circumstance.
Yes, but it's unique in your list.
Or dictionary I guess.
And if you have several, you can distinguish by order.
Hm... I think I get it. Using Type's instead of Strings? Does Unity editor supports displaying those?
No, but you don't need to. That's why you need a more sophisticated storage system.
Distinguishing by order has its own problems though... I'll need to remember the order or even insert null references to not mess up the order (if say one attack doesn't use muzzleflash particles but other does, and they both must spawn two additional particle emitters when attack is happening)
There are several ways here too:
- Add the fx to a list in the inspector. Then at runtime create a dictionary of lists with Type - gameobject pairs and populate from the serialized list. Or loop the list each time you need to retrieve one of the fx.
Yep.
Which is why paired SOs approach would be best IMHO.
This would take some effort to refactor though, so it's up to you.
Plainly speaking, your current approach is fine. Many games do it like that. Even AAA. It's not ideal. But better approaches require time and effort.
Whether it's worth it, is up to you as a dev to decide.
Just to make the SO approach clear: you would have a unique SO class for each attack logic/type that exposes exactly the FX type references that your logic expects.
Well, yeah, having a buttload of SO classes is what I was trying to evade, actually.
Well, think of it as an extension to your attack type. They can even be defined in the same file.
It would also make it easier to configure characters/skills/mobs with different attack types, as you would just need to assign the specific SOs to them in the inspector.
Just read the conversation here and I think, one main culprit is, that you are trying to bake all events happening in a logic "chain" into one class. Which can easily result in a bunch of classes (or SOs) worst case. Did you think about creating a generic chain list that can fire events tied to specific classes? You can retrieve methods from your classes with reflection at edit mode and then try to fire them on the actual runtime reference for example.
Not really, but I do want to make attacks as isolated as possible, and they should be concerned with basically one thing: what happens during an attack.
So that I can freely swap and move them between NPCs or weapons.
SOs
asset bloat is expected, that's just how they are designed
editing enums could lead to breaking serialization
Can you elaborate more on what attacks you might have. I am just wondering, if you are trying to be too dynamic in this situation. Like, for example, a humanoid character should be able to attack with laser beam as well as a sentinel turret? Sounds to me like it might be over the top to be able to swap ANY attack type with ANY character.
Yeah, it might be over the top, but when all attack logic is contained in the class and all NPC does is to trigger it to begin and then waiting for it to be finished - it ain't that hard or complicated, actually. Basically very similar in execution to a state machine.
Mainly I wanted that since majority of my enemies are going to be humans that can and should use shared set of moves, plus the ability to use items.