Search Unity

Question Problem using MVC + Command Pattern for a turn-based game for animations events

Discussion in 'Scripting' started by CalzoneAddict, Feb 27, 2024.

  1. CalzoneAddict

    CalzoneAddict

    Joined:
    Oct 29, 2021
    Posts:
    41
    Hi everyone,

    I'm working on a turn-based game tht plays on an isometric grid. Think Into the Breach :D

    I'm trying to structure the whole thing around a MVC framework, with commands.

    All the units in the game (allies and enemies) are instances of :
    Code (CSharp):
    1. public class Unit :
    2.     {
    3.         public UnitBrain Brain;
    4.         public int Health;
    5.         public Vector2Int Position;
    6.         public List<Vector2Int> AttackedPositions = new List<Vector2Int>();
    7.         [NonSerialized] public UnitController Controller;
    8.     }
    The Brain is what dictates the behaviour, and it is added via a factory. This is a sort of Strategy pattern, I suppose. The Brain has 3 major functions :

    Code (CSharp):
    1. public abstract void Move();
    2. public abstract void TelegraphNextAttack();
    3. public abstract void ExecuteTelegraphedAttack();
    Everything is managed by 2 commands buffers. One for the logic, one for the view. For example, say I have a brain with a movement that goes

     CombatCommandsBuffer.Add(new MoveCommand(this.Unit, GetFooPosition()) 

    The unit's component that is reponsible for movement is going to update its position, then call
    OnMoved?.Invoke
    and the view component responsible for updating the position will do it accordingly.

    Now, my question is about complex behaviours.

    Case 1 :
    For example, let's say I have a MonsterA : UnitBrain that attacks one cell for 1, and the cell behind for 2. I want those two steps to happen one after the other during the animation.
    How should I handle that ? Logicwise, it would be something like :
    Code (CSharp):
    1.         CombatCommandsBuffer.Add(new DamageCommand(this.Unit.DamagerComponent, m_FirstTarget, m_FirstDamage);
    2.         CombatCommandsBuffer.Add(new DamageCommand(this.Unit.DamagerComponent, m_SecondTarget, m_SecondDamage);
    I want to update my target's view at the impact frame, and to display the correct amount of damage, without my Brain's logic to know anything about this.
    Should the Attacker know about the Target and call its visual feedback during its animation, or should the Target listen to the Attacker's animation and do its thing on its own ?

    Case 2 :
    Now let's say I have a MonsterB : UnitBrain that burns its targets. Codewise, this is :
    Code (CSharp):
    1.         CombatCommandsBuffer.Add(new BurnCommand(this.Unit.Burner, target, 3);

    When the target gets burnt, I would like a VFX to play and an icon to show up aside the health bar. This means that a whole different logic should be able to operate at the impact frame. I mention this case because at some point I considered passing an instance of a DamageInfo class, that the view would enqueue and restitute, but this would mean an Info class per attack, etc.

    I'm really scratching my head on this. I hope I gave enough details. If not, of course feel free to ask some more, I can provide as much info as needed !

    Thank you everyone !
     
  2. Lekret

    Lekret

    Joined:
    Sep 10, 2020
    Posts:
    356
    Your command approach looks fine, just a bunch of reusable commands which are added to global queue, seems good to me.

    Since you are using MVC (this will work without it too) just hook-up some events to your game logic (Model). Some HealthChangedHandler or StatusEffectsListener will be subscribed to Model and react without Model knowing anything about visual representation. In MVC Controller manages such subscriptions and View don't know about Model directly either.
    UI, VFXes and Audio are usually done reactive like that.
    With Animations it is more tricky, because you probably care about animation events, it's not "one-way" as in previous cases, but you can do something like that, I hope you get the idea:
    Code (CSharp):
    1. Model.AttackPerformed<string> -> Controller.OnAttackPerformed(string key) -> View.PlayAnimation(key, () => Model.ProcessAttackImpact(key))
    If you care about "How can I react visually different to different attacks", then you no doubt need some container with attack data, and view should be able to analyze that state and react accordingly.

    "Who should listen for impact victim or attacker", you should draw the boundry of responsibilities here. I would no doubt say attacker should listen for his animations events and when they are triggered call victim.Attack() or something.
    When I find such questions really diffucult to answer, let's say you have victim who can parry or reflect the attack and do other complex stuff, then I usually use 3rd entity. Something like AttackService.PerformAttack(attacker, victim) which will take all that responsibility, but it depends on your game and how you structure you data/logic.
     
    Last edited: Feb 28, 2024
  3. CalzoneAddict

    CalzoneAddict

    Joined:
    Oct 29, 2021
    Posts:
    41
    Thanks a lot for your answer, this is very helpful.

    The 3rd entity sounds like a good option, but I think that it might be overkill and a waste of time on the long run if that becomes a heavy system.

    The attack data container sounds like a good plan. I'm thinking that they could be stored in a queue, and dequeued during the animation. How does that sound ?
     
  4. Lekret

    Lekret

    Joined:
    Sep 10, 2020
    Posts:
    356
    Well, quite the opposite. You will move a lot of logic from units and put into another class making code potentionally more maintainable. I don't find it particularly dififcult, to start you just need to move a couple of methods and do small refactoring, not a big deal imo.
    It make sense if you want to apply damage multiple times during single animation, in that case it's ok approach and would work. You can however just store them in a list or build on a fly, because you can track how many times the specific animation event fired and can provide specific attack data for 0, 1, 2 index or something like that. Anyway, whatever you find easier for yourself.