At the beginning of Winter Term, my introduction to Ragball development, we came to see that we had one class really handling a lot of game logic simultaneously, some of which it had. The Player class, which should only handle the player's physical interaction with the environment relative to game logic, was handling physics collisions, identification, and setting other players' physical status. The behavior was quite bloated, and lacked any concept of Single Responsibility. The immediate task for the devs was to strip this behavior down into several different, parallel components that work together without living together.
So, as all devs do, we tried the simplest way to accomplish this strip down: create a new script for everything that didn't fall into the hands of the Player. For some things, this worked(such as our stamina system).
However, the Ragdoll system in Unity doesn't allow us to easily strip down these behaviors. The only way to program ragdolls is via the center of the rig, which we call "hips". This object is constantly referenced in our scripts because, to our dismay, it essentially controls all of what the player does.
The problem that we needed to work around came down to this: how can we have parallel working parts, that intersect when needed, and are all dependent on the hips of the ragdoll?
Solving this dependency problem requires that any collision logic live somewhere that isn't the Player class. Additionally, the logic should directly interact with the Player, as collisions need to know about the Player in physical space. However, the player in physical space does not need to know about collisions. In software development, this is called decoupling: ensuring that two components do not have direct control of one another.
To decouple collision and player logic while satisfying the hips dependency, we created a Collision Trigger that only lives under the hips. All this Collision Trigger does is return the Player instance through a Base Object script component. So, when a player should be staggered, the hips will check to see if all conditions are met to be staggered(the enemy has dashed/been thrown and is of the opposing color), get a reference to the player instance that is to be staggered, and then tell that instance to be staggered. The Player has no reference to the script that staggers, nor to the scripts that manages collisions, nor the Base Object script component. All pieces work parallel, and only know what they need to.
private void OnTriggerEnter(Collider other) { if(Game.Instance == null) return; Debug.Log("player collision event trigger"); BaseObject coll = other.GetComponent<BaseObject>(); if (coll == null) return; Player player = coll.player; if (player == null) return; OnStaggerSelf((player.dashing || player.isThrown), player.color); }}Without this Collision Trigger, we would not be able to invoke stagger on the player script, because the hips have no reference to the player script.