Raw C# Objects vs. GameObjects - which is preferable?

This question might be too broad, but I think it’s worth a try.

In my RTS-type game, there will be an array of “tasks” that have to be completed by the player’s employees (tasks like construct a building, operate a machine, etc.) Each task has a location/position associated with it, along with some variables like required skill, percent completion, required resources, etc.

One approach would be to simply make a C# Task class, and then have a singleton TaskManager (not the Windows kind) with a variable holding a List of Tasks. Upside: It’s simple, maybe efficient. Downside: It’s harder to see that list in the Editor.

Another approach would be to have each Task as a GameObject with a Task script attached. Make them all children of a TaskManager parent, and use the GameObject’s Transform to track the actual position of the task (like, the position of the building that needs to be built). Upside: It feels more appropriate for Unity. Downside: Maybe having a lot of GameObjects will reduce performance.

Any thoughts?

One approach would be to simply make a C# Task class[…] Downside: It’s harder to see that list in the Editor.

Just add the [System.Serializable] tag above your Task class, and Unity will show the list of tasks in the editor.

[System.Serializable]
public class Task
{
    // ...
}


public class TaskManager : MonoBehaviour
{
     [SerializeField]
     private Task[] tasks ;
}

First thought is a response to “Downside: It’s harder to see that list in the Editor.” This isn’t much of a downside. I’ve observed that students of Unity tend to think of C# as a subordinate script language, which is prompted by Unity’s presentation of C#, even to the point of thinking scripts are the central programming concept instead of classes. While I have my own viewpoint about C# itself, I see no reason to view development from a Unity “script” viewpoint, but as the development of a C# application using the Unity framework. The editor’s ability to “see” and “edit” values from an attached object “script” isn’t all that advantageous given the viewpoint of C# as more central than merely “scripting”.


A concrete example is the potential conflict between default values assigned in code vs. that set within the editor. Although I know the object instantiates with the values from code, the Unity framework serializes the values from the editor, so the values used at runtime are those from the editor. While that can be a convenience for adjusting and debugging, it means that if one develops reusable code there can be confusion as to what initializing values are at work. If I reuse a class which relied upon the settings from the editor, the new instantiation will rely, at first, on the values from code. Now I have two places to edit, and that can be as much a detriment as a convenience.


More generally, most editor configuration of the code (and only the code) creates a dependent relationship upon the editor. This comes up in synchronization, for example. If code is built which demonstrates an issue at initialization, and the editor is used to control the order of “Start” or “Awake” functions to solve that, then while the problem is “patched” to work, the original problem is still in the code. If, instead, the problem is solved in the code, there’s no need to use the editor to “patch” it to work. To me, this is a fundamental argument between the convenience of Unity editor’s inspector and the habits of professional programmers.


In my view that extends to your larger question, about C# objects that aren’t derived from MonoBehaviour. More generally, the beginner and intermediate level thought process should be on the notion of what class should perform what methods. If a method is really a MonoBehaviour concept, it should be performed in a MonoBehaviour derivative. If it isn’t strictly a MonoBehaviour concept, there’s no good reason to put the method in a MonoBehaviour derivative beyond mere convenience (or mediocrity).


Theres no reason, for example, not to use List<> generics for storage as members of MonoBehaviour objects, but List<> is part of C#, not Unity. It is a primary clue to the real answer to your question. Any design pattern can and should be implemented as independently from MonoBehaviour as the concept being implemented indicates. If there is already a MonoBehaviour object acting as a singleton, perhaps there’s a reason to base other singleton pattern work on that class.


Your observation that a lot of GameObject or MonoBehaviour classes comes with weight is exactly on point. Unless there’s a reason to inherit form MonoBehaviour, or otherwise become a component, classes independent of Unity will benefit from their independence. Vector3 itself is an example. It would likely operate outside of Unity to good effect.


Tasks, threads and other paradigms will do well on their own, but the typical caveats of synchronization may apply, but are worth it. Think of code in Unity less from the viewpoint of the Unity editor created scripts and more like a C# application that uses Unity as a framework. There’s nothing but upside in reality, if you view the minor issue of inspector visibility as a non-issue (because, it’s code in this viewpoint, not a Unity editor controlled development processes).