1
\$\begingroup\$

I was wondering if anyone would mind explaining a little about how a composition based spell system should work.

I've been working on this for a few days now, I think I understand the composite pattern enough for what I want to do as my entity's follow the same pattern. (Everything has a single purpose)

But I am really having trouble connecting everything together.

For example my Entity has a Entity Caster Component and Entity SpellList Component and I believe following a pattern like this the Entity Caster Component shoudn't need to know anything about the spell other than it's just been asked to cast the one given, correct?

Which leads me to the problem of actually casting the spell as each spell is currently an empty object with 4 SpellComponents on it one of each type (SpellTargeting, SpellEffect, SpellLocomotion, SpellModifier) which all extend from SpellComp so I should in theory be able to treat them all the same.

But in practice I can't as targeting, modifier and effect mostly only need to run once where as locomotion needs to be called every frame.

I haven't figured out how to still call one inherited base method but have 1 component fire a coroutine and the others work as normal while still treating them the same.

Currently when I "cast" a spell I spawn an empty gameobject with the comps and enable the first component which does it's job and fires an event to start the next component until the last.

TargetComp > EffectComp (VFX) > LocomotionComp > ModifierComp (Heal/Hurt etc)

which has the obvious effect of leaving a empty object in my scene that responds to target input even after the spell is over.

I feel like at some point I've started mixing two separate systems together and made an unholy mess I no longer understand.

Every other part of the project has gone and is going fine but just thinking about taking another look at the spell system makes me lose a few brain cells.

I linked an empty project I created purely for testing this with my "oh god its so awful but it sort of works" implementation attempt.

I would add the code for each file but there is over 10 and most of them are just extending from a base abstract class so I can treat them the same and this post is already very long.

https://www.dropbox.com/s/ft6awn4v36a0pu0/Modular%20Spell%20Test.zip?dl=0

Any help appreciated, thanks!

Note: Also I use "Entity" as a base class for anything that moves and would have health, I am not trying to implement ECS, I think.. it was just the easiest way to categorize it in my head.

\$\endgroup\$
  • 2
    \$\begingroup\$ I'm too new to Unity to post an answer here, but I am a software developer, and your description leaves me wondering if you're looking for an inversion of control approach. Instead of giving your Caster a Cast() method to cast the Spell, make it so that every Spell has a Cast() method which takes in a Caster, e.g. myFireSpell.Cast(myGenericSpellCaster);. You retain the "spell agnostic" caster class, but every spell (class) can work with this caster the way they need to. \$\endgroup\$ – Flater May 29 '18 at 11:38
  • \$\begingroup\$ How do you iterate over your entities? Or is it that everything you do creates an event and you iterate over the events? Im currently at work, so i cant check out your dropbox. \$\endgroup\$ – PSquall Aug 9 '18 at 8:13
1
\$\begingroup\$

Component-based architecture can be a nice solution, but they are not the right solution for every architecture problem. When a component doesn't actually do anything but is just holding a piece of information required exactly once then it shouldn't really be a component.

But note that an Entity with a list of Components is not the only form of composition. The classic Composite object, which is an object with other objects in its attributes, is also an option.

In your case, Spell could be such a composite class, with SpellTargeting, SpellEffect, SpellLocomotion and SpellModifier all being attributes or lists of attributes. The types might be full-fledged objects (not MonoBehaviours - plain old C# classes), or they could just be a delegate method or even just a value.

Example:

class Spell: MonoBehaviour {

     private SpellEffect[] effects; // an array of effects which happen to whatever gets hit
     private Action<Transform> locomotionFunction; // a delegate-method manipulating a transform
     private SpellTargeting targetMode; // might be an enum
     private SpellModifier modifier;

     void Start() {
         targetMode.doWhateverThisDoes();
     }

     void Update() {
         // move the spell using the locomotion function
         locomotionFunction(transform);
     }

     void OnCollision(other) {
          foreach(SpellEffect effect in effects) {
              effect.applyOn(other, modifier);
          }
     }
}
\$\endgroup\$
  • \$\begingroup\$ That's what I attempted the first time in my main project and I think where the confusion first came in, for example my spell class had SpellComp targetComp; SpellComp effectComp; SpellComp locoComp; SpellComp modifierComp; and the idea was to use the builder pattern to create each spell with it's components in the SpellManager then the spell cast method would invoke each in turn until it completed. But that lead to the "3 Components do a single action and another does a single action over and over until X" and being unable to structure the components properly. \$\endgroup\$ – Pheonix2105 May 28 '18 at 14:58
  • 2
    \$\begingroup\$ @Pheonix2105 I think you misunderstood something. There is no reason why any of those parts of the composit should inherit from SpellComp. I added a code example (untested) to better illustrate what I mean. \$\endgroup\$ – Philipp May 28 '18 at 15:20
  • \$\begingroup\$ That example definitely helps and yes I was certainly misunderstanding and I think I still am, for example my ideal version of the flow of execution for this is: ActionBarButton 1 is pressed > ActionBarButton 1 > assignedSpell.Cast() > Spell.Cast() > Call TargetComp > Got Target Event > Call Effect Comp > Model Spawned Event > Call LocoComp > Move however > Target reached Event> Call Modifier Comp > Apply Damage/heal/whatever to Target. I can't see how the example would help either, though it has given me another way to look at the problem. \$\endgroup\$ – Pheonix2105 May 28 '18 at 15:36
1
\$\begingroup\$

Beware of thinking of a solution, then making that the new problem. You're basically asking "how do I use the Composite pattern to make flexible spells?" when you could also be asking "how do I build flexible spells?".

A pattern that's well-worth looking into is Bytecode/Interpreter and Gameprogrammingpatterns has a page exactly for this.

You'd basically be creating a tiny language inside your game for wildly expansive flexibility when building new behaviour, such as "select nearest enemy, damage by 10, knockback by 20, select player, heal by 10".

\$\endgroup\$
  • 2
    \$\begingroup\$ First of all, I didn`t downvote you, because its a valid answer. But to be honest, its a terrible idea so implement an own basic language and a fitting vm just to archiv what OP wants, even for your example it would arguably be a bit to much. \$\endgroup\$ – PSquall Aug 9 '18 at 8:01
  • \$\begingroup\$ That's fair, but if you want infinite flexibility, you're going to have to use code, and if you want data to describe the flexible elements, you're going to need some code/data overlap... Another option could be passing in function(caster, target) chunks, but I'm not sure how that works in C#. \$\endgroup\$ – Alex Mitan Aug 10 '18 at 9:18
  • \$\begingroup\$ @AlexMitan Yes, you can pass functions around in C#, including anonymous functions and lambda expressions. It's called a delegate. \$\endgroup\$ – Philipp Oct 18 '18 at 8:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.