- Home /
How can I improve the organization of my character controller classes?
Earlier this year I spent a long time building my own custom character controller for 2D games. It works pretty well, but I've run into an issue with how to organize its classes, particularly when it comes to creating MonoBehaviours for different uses.
For example, my controller can be broken up into a class that handles movement, MovementController2D
, and another class that handles collisions with raycasting, CollisionController2D
. Not every object that moves needs to collide with the game geometry, so I wanted to keep these two classes separated, as I could then simply attach MovementController2D
to those objects. This works fine for some objects, but things get tricky on objects that want to collide, as the two classes must then work together. My collision execution works like this:
1. The MovementController2D
receives input from the player or AI, storing the desired input in a Vector2 velocity
variable
2. The CollisionController2D
reads the incoming velocity
and raycasts for collisions, using the movement this frame, velocity * Time.deltaTime
, as the length of the raycasts
3. The CollisionController2D
checks if the raycasts hit anything, and then updates the velocity
this frame accordingly
4. MovementController2D
moves the character using the final, constrained velocity
from step 3
Here's an outline of the two classes in code:
public class MovementController2D : MonoBehaviour
{
public Vector2 Velocity { get { return velocity; } }
[SerializeField]
private float gravity = -20f;
private Vector2 velocity;
private void Update()
{
velocity.y += gravity * Time.deltaTime;
}
// LateUpdate is used to ensure that inputs are handled
// before the controller tries to move
private void LateUpdate()
{
transform.Translate(velocity * Time.deltaTime);
}
// The player or AI controllers call this method
public void SetVelocity(Vector2 velocity)
{
this.velocity = velocity;
}
}
public class CollisionController2D : MonoBehaviour
{
private MovementController2D moveController;
private void Awake()
{
moveController = GetComponent<MovementController2D>();
}
private void Update()
{
Vector2 velocityThisFrame = moveController.Velocity * Time.deltaTime;
CollideHorizontally(ref velocityThisFrame);
// This presents an issue because MovementController2D is
// multiplying the final velocity by Time.deltaTime in LateUpdate() as well
moveController.SetVelocity(velocityThisFrame);
}
private void CollideHorizontally(ref Vector2 deltaMovement)
{
// Check for collisions and set deltaMovement to the appropriate value
}
}
This code does work, but it doesn't feel particularly clean. My goal is to be able to have different controller "pieces" that I can add to game objects depending on necessity; I don't want a floating balloon to needlessly be raycasting, for example.
The problem with what I currently have is that I have to "hack" the execution order by using Unity's Script Execution Order to get any of this to work, and my code falls apart without LateUpdate()
in the MovementController2D
class. It's like I'm trying to sandwich pieces of code together, and I feel like that is going pose a big maintenance issue later.
The execution order looks like:
Update() from Player input --> Update() from move controller --> Update() from collision controller --> LateUpdate() from move controller
Before trying to separate my controller logic, I put everything in one big 1,000 line script. This worked, but then I ran into the aforementioned issue of needless raycasting. I also considered having CollisionController2D
inherit from MovementController2D
, but isn't it frowned upon to have a (non-abstract) base class that can be added as its own component?
By the way, please do not recommend a different collision handling technique, such as moving first and then resolving collisions; after many months of testing, I have found this approach (read input, raycast, update velocity, then move) to be more robust in how it handles complicated collisions, such as with slopes, rotations and circular geometry.
Yes, I know this is sort of a picky post, but I'm trying to learn how to better organize my code.
Answer by Mouton · Sep 26, 2019 at 07:59 AM
It looks like you want to use inheritance to split your class in different behaviours: make your MovementController2D
more generic by removing specific code like the computation of the gravity, then for each specific controller inherit from it to extend the functionalities.
To solve the execution order issue, consider using FixedUpdate
(https://docs.unity3d.com/ScriptReference/MonoBehaviour.FixedUpdate.html) for physic related stuff, it is executed after Update and before LateUpdate.
It is not triggered as often as Update
does, there is a setting in Edit > Settings > Time > Fixed Timestep
to control the call rate (but you should probably don't need to change it).
Thanks for the reply. I was hesitant to use inheritance because I can't really think of much else to put in another child class other than the gravity calculation.
Well, you have at least 2 behaviors: with gravity, and without (for the floating balloon). There is no problem to subclass once, it splits the logic. It does not harm performances of your game.
It took me a little while to wrap my head around the design using inheritance, but ultimately this was the best solution for me. It kept things clean and organized.
Your answer
Follow this Question
Related Questions
Project Classes Organization - General Question 0 Answers
How to select a class instance from the inspector? 1 Answer
How to get a reference to the owner-Script from inside a Class which is being used as field/property 1 Answer
Show a dropdown for C# classes in inspector. 2 Answers
Make custom editor for generic class to apply to all current and future child classes 1 Answer