Search Unity

Question VirtualControllers & Boolean flags

Discussion in 'Scripting' started by heerok7, May 9, 2022.

  1. heerok7

    heerok7

    Joined:
    May 26, 2015
    Posts:
    15
    Hey everyone ! Hoping I could get some help and new ideas on something I've been scratching my head about for a while now.

    I've been researching the idea of being able to have my game "play itself" / control all entities in my game from the player or A.I. I've seen you post a lot about this idea and I think I've got something working for the most part.

    While trying to unit test, I found that I've had trouble trying to back my implementation with tests. I'm very early stages in my project, but don't want to code myself into a corner and not be able to write tests in the future. I'll try to be as breif as possible, but I want to give you as much of the details as possible.

    I have a class called CharacterBrain. Which is essentially the VirtualController layer, entities (player or AI) will tell CharacterBrain what it is requesting to do. I'm using the new Unity Input System for player Input. I listen to events to tell the CharacterBrain what it wants to do. Eventually the ai will do the same.

    Something caught my eye when doing jump actions. I created a boolean flag, that is set to true when the entity wants to jump. This works fine, but since it's a bool that is set when the jump event is raised (opposed to polling every frame [although I think this would still have its issues]), it will stay that way until it's set to false.

    So I created a method within the CharacterBrain to "reset" the cached value. This method is called any time the ability/action being requested to do right now is read true and starting to do its action. In a nutshell, it sets the bool back to false to prevent the character repeating the action over and over.

    This allowed my game to work just fine from a gameplay and functionality perspective. From a testing perspective it has made development very difficult.

    Now since CharacterBrain has some actual logic in it, it is an abstract class with some implementation. This makes testing difficult because I can't inject an ICharacterBrain because interfaces can't have logic. Well, they can have some logic. But then I break a lot of rules. Properties in my interface are now public getters and setters and anything that gets a hold of them could theoretically set them.

    I have a feeling I'm doing something wrong here with my approach. This could be a limitation with the new input system, but I don't believe that. It could be my lack of understanding with it. Or maybe testing is too difficult with this approach, but I do find value in testing as we use it in my enterprise day job and it really does help.

    I'm curious your thoughts about this situation; Buttons / Actions that are just one time executions instead of continuous checks. Movement is something that I check constantly, but I can't wrap my head around bool flags in a Virtual Controller. Ideally, I'd like to be able to have this virtual controller be a player based one or a ai based one. Then in testing I could just inject a mocked version to be able to run tests.

    Left some code if that might make more sense.

    I appreciate your time if and any input you all might have. :)

    Code (CSharp):
    1. public abstract class CharacterBrain : MonoBehaviour
    2. {
    3.         public Vector2 MovementDirection { get; protected set; }
    4.         public bool JumpRequested { get; protected set; }
    5.         public void ResetJumpRequestCache() => JumpRequested = false;
    6. }
    Code (CSharp):
    1.  public class JumpAbility
    2. {
    3.     public void Jump()
    4.     {
    5.          if (Brain.JumpRequested)
    6.          {
    7.               Brain.ResetJumpRequestCache()
    8.               PerformJumpThings();
    9.          }
    10. }
    11.  
     
  2. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,688
    What if you separate the VirtualController from the CharacterBrain? You can keep your extra logic in CharacterBrain, and your VirtualController can be a direct 1:1 mapping to inputs from your physical controller or from a AI script(s) that simulate the physical controller.
     
  3. heerok7

    heerok7

    Joined:
    May 26, 2015
    Posts:
    15
    Oh that's an interesting idea :eek:o_O. Let me see if I am understanding this idea correctly. Now the VirtualController, is something on the CharacterBrain. The CharacterBrain has more implementation that can do the "clean up" (aka reset the boolean flags). This will allow the CharacterBrain to be injected with AI as the one setting the VirtualController values.

    Further, this would allow the VirtualController to be JUST inputs related to the game and not concerned with resetting flags when being used. Testing would be much easier because then we can Substitute the IVirtualController in testing.

    Here's my quick attempt at implementing that idea.

    Code (CSharp):
    1. public interface IVirtualController
    2. {
    3.     Vector2 MovementDirection { get; set; }
    4.     Jump { get; set; }
    5. }
    6.  
    7. public class CharacterBrain
    8. {
    9.     public IVirtualController vc;
    10.  
    11.     public void Bind(IVirtualController vc)
    12.     {
    13.           this.vc = vc;
    14.     }
    15.  
    16.     // Logic to essentially use set flags
    17.     public void ResetJumpRequestCache() => vc.Jump = false;
    18. }
    A few things that catch my eye.
    • I don't see a way to keep the VirtualControllers set methods private to only the CharacterBrain. But I can't really think of a way to just keep those setters private.
    • This somewhat concerns me as anything that gets the VirtualController can set its values it it got a hold of it. But that might just be a "make a method for doing anything and limit the things that do set it" moment.
    I'm going to turn this rough psuedocode into actual code and see if I can at least get some unit tests written.
     
  4. heerok7

    heerok7

    Joined:
    May 26, 2015
    Posts:
    15
    @TonyLi I was able to test using this method! Thanks so much for your suggestion, I hadn't even thought about that. I was able to Substitute the VirtualController in my tests as well no problem. No need to build too many methods to inject monobehaviours, the implementation is done at a different level. I wasn't able to directly test for the case of AI, but I created another more point and click controller and I think I'm on the right track. The above code works pretty well for my situation.

    I still wonder now what the CharacterBrain does. Is it's sole job to do the clearing logic? It seems like an extra step to have per say a CharacterBrain that all Characters have, but then a controller that lives in the brain that drives it. But I guess that's kind of what one the points in the MARPO architecture.

    The player's brain's decisions are implemented via device input. While AI is implemented via FSM, Behavior Tree, or GOAP.

    Curious if you have any more closing thoughts.

    Thanks again.
     
  5. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,688
    If it works, go with it. Then again, maybe you could look at the abstraction differently. Maybe instead of a brain it's a body or something like that. Sometimes changing the abstraction gives you a different perspective on what it should implement and how.
     
  6. heerok7

    heerok7

    Joined:
    May 26, 2015
    Posts:
    15
    I think you're right, I think I can ride with this solution as it answers my question. Interesting idea with the idea of moving the brain to a part of the body. I haven't thought about how to blend actions past arms, legs or anything. I'll share if I get to that point. Thanks a lot @TonyLi
     
    TonyLi likes this.