Search Unity

Good scripting component design

Discussion in 'Getting Started' started by x6herbius, Nov 28, 2020.

  1. x6herbius

    x6herbius

    Joined:
    Nov 26, 2020
    Posts:
    2
    I'm a relatively experienced programmer, but am new to Unity and its particular design philosophy. I have been looking at how to write C# scripts for game objects and I think I'm getting the hang of it, but I wanted to ask some questions about best practices when doing this.

    As a personal exercise, I've been working on making a simple third person controller script, which manages the movement of a player character (a cube right now) and a camera in relation to it. At the moment, the script is attached to the cube object itself, and the camera is provided using a public
    Camera
    member on the script. I'd like the movement to be kinematic because it gives me more control, so I gather I'll shortly have to look into handling the resolving of collisions myself.

    This approach seems to work fine as far as my experiments have gone, but I would argue it has the following limitations:
    • It relies on the correct elements being plugged in together: a camera must be provided, and the object being controlled must have some collider as one of its components.
    • By virtue of the above, there are situations where the script would not be in a valid state (eg. if a camera is not provided, the script has no way to get the "forward" direction for motion). This means that the script has to contain extra logic to check these corner cases, and has to know how to gracefully handle when not all the required pieces are present.
    • If I were to make this system more complex, I might want to modularise it by isolating different functionalities into different script components. This would compound the above problems, as different scripts may have to look one another up (eg. by calling
      GetComponent()
      ) and handle when an expected script does not exist.
    I guess my questions come down to learning the differences between a stricter engine, where you might write a player class to have a specific known set of components that can be relied upon to exist, and an engine like Unity where objects are built of of looser sets of building blocks.

    What are the Unity best practices when writing scripts like this?
     
    Last edited: Nov 28, 2020
  2. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    I think there's a bit of a misunderstanding about Colliders here.
    You would have to handle collision events manually regardless of whether your Rigidbody is kinematic or not, but you don't have to manually handle collisions themselves - the engine takes care of that.

    With that in mind, you can probably remove the Collider dependency in your controller.

    As for handling component dependencies in general, I suppose you would have to decide on whether you want the script to work around the possibility of the references being unassigned, or if they should be mandatory.

    In the case of mandatory references, I typically just throw an exception in the script's
    Awake
    method if the component reference is unassigned.

    Unity does have a RequireComponent attribute that you can add to your class declaration and it will force Unity to add the required component(s) on the same GameObject that you add your script to. Attempting to remove a required component from the GameObject will display an alert window stating that it cannot be removed.

    I'm not really a fan of
    RequireComponent
    though for that reason, however. Sure, it makes sense to be unable to remove mandatory components, but I don't like how the components all have to be on the same GameObject. It can get cluttery, and there may be cases where you don't always want the component to be on the same GameObject that your script is on.

    What I tend to do instead is something like this:
    Code (CSharp):
    1. public class SomeClass : MonoBehavior {
    2.    public Rigidbody requiredBody;
    3.  
    4.    private void Awake() {
    5.       if(requiredBody == null) {
    6.          throw new UnassignedReferenceException("[SomeClass] requires a reference to its [requiredBody] field.");
    7.       }
    8.    }
    9.  
    10. //This pre-processor directive will exclude any code within from the standalone build of the application.
    11. //See: https://docs.unity3d.com/Manual/PlatformDependentCompilation.html
    12. #if UNITY_EDITOR
    13.  
    14.    //Reset is an editor-only callback that fires when a script is added and when the "Reset" context menu option is selected.
    15.    //See: https://docs.unity3d.com/ScriptReference/MonoBehaviour.Reset.html
    16.    private void Reset() {
    17.       //Try to assign an existing reference on this GameObject or its children.
    18.       requiredBody = GetComponentInChildren<Rigidbody>();
    19.  
    20.       //If a reference wasn't found, then add one to this GameObject.
    21.       if(requiredBody == null) {
    22.          requiredBody = gameObject.AddComponent<RequiredBody>();
    23.       }
    24.    }
    25. #endif
    26. }
    I've written some editor utility scripts just to do the above in a more abstracted way so that the same things don't have to be rewritten for every mandatory component.
     
    x6herbius likes this.
  3. x6herbius

    x6herbius

    Joined:
    Nov 26, 2020
    Posts:
    2
    Good point - I think I need to do some more research about how best to do player control.

    Throwing an exception in
    Awake()
    seems to be a good way of going about things. I take it that this means no other callbacks are called later, and so no other checks would need to be performed elsewhere?

    I also have another question: is it possible in Unity to create specific subclasses of
    GameObject
    and add these specifically into your levels? For instance, if I knew I wanted to have a player class which contained a model, a rigid body, and maybe some other stuff, could I create a new class called
    Player
    which inherited from
    GameObject
    and added all these components automatically? Would the editor let me place this in the same way as it does a normal
    GameObject
    ?
     
  4. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    Correct. A script will automatically disable upon throwing an exception.

    Nope,
    GameObject
    is a sealed class. It cannot be inherited.
     
    x6herbius likes this.