The GabUnity library is a C#/Unity library with C# files and assets that are reusable across most if not all Unity Projects.
The philosophy of the library is this:
Compartmentalization over optimization.
When browsing through the C# files of the library, you can see that most of these classes and components are as stand-alone as they can be. They never try to do anything more than the bare minimum of their purpose (usually self-explanatory with their class name). To connect events and behavior together, UnityEvents are exposed in the inspector. These UnityEvents are editable by both developers, designers, and artists. They are also mostly self-explanatory.
| Purpose: To avoid constant recompilation from simply changing or connecting references. |
|---|
There is a few things that should be set-up in the scene to make use of the library's architecture.
The "ManagerManager" class should exist in the scene. This class will automatically initialize anything that inherits off of "Base_Manager" in the C# assembly.
| Purpose: avoid manual set-up of most managers that need to be instantiated anyway. |
|---|
Managers are always singletons. Only one of them should exist in the scene, and most of their utility methods are static for everything to access. If you need to create a manager that you do not want to automatically instantiate, you should use the "MonoSingleton" class as a base class.
One annoying part about Unity is the manual assignment of assets to scene references or prefabs.
So, the Library gives you a base class you can inherit off of that allows you to create an asset dictionary that is automatically loaded in memory.
For example, if you want to create a prefab dictionary follow these steps:
- Create a C# script.
- Inherit off of SingletonDictionaryAsset<AssetType>
- Define your dictionary type (prefabs are gameobjects)
- Use [CreateAssetMenu(menuName = "namehere")] so you can create this as an asset in the project window, Resources folder.
- Access this dictionary with your other scripts using the C# class name you created.
Note that any singleton dictionary created should be put in the [UnityProject]/Assets/Resources folder
| Purpose: Unity can prevent these files from being turned into encrypted binary so they can be loaded at runtime. |
|---|
The features subfolder is full of utility functions centered around a small component—the UnitIdentifier. (But there are also some that don't rely on this, like VFX and Audio).
The unit identifier has information about a unit's team. This team value may control whether or not this unit is hitable by a projectile from a unit from the same team. This team system is very similar to Unity's tag system. Except this is programmatic and relies less on the engine. Most features (trigger colliders, unit detectors, projectile, health, etc.) interact with the team system so you can control and organize your game's behavior.
Use components as you will, and use the UnityEvents to link these behaviors with external code (or with other GabUnity components) (sample usage below).
In the GabUnity library, there are some scripts for audio automation. One script includes an automatic anti-spam PlayOneShotSafe function that automatically attaches to any AudioSource object if GabUnity is included (usage image below).
There is also already a definition of an AudioDictionary script which you can create an instance of in your own Resources folder. Be careful of accessing this AudioDictionary without creating the actual asset (this will trigger an asset missing error).
Artists can use these dictionaries to change files used throughout the game with one simple assignment.
The GabUnity automation for vfx is like the audio automation.
Like audio, vfx has a dictionary that can be accessible by all scripts (this means if you change a vfx, you only need to reassign it in the dictionary).
Since vfx requires gameobjects in the scene, there are two classifications of vfx: global, and instanced.
Global vfx is any particle system that is world-space, and you can use Unity's built-in EmitParams.position on. If the particle system is local, and does not work with EmitParams (maybe because it is a system that relies on duration and looping), then it is instanced.
See the root gameobject of an example Global Effect prefab below for reference:
Meanwhile, Instanced vfx is just that: vfx that is instantiated per requestor. Usually, you don't really need instanced vfx, but, if a vfx is very complex and requires local simulation, you can create your own vfx dictionary and your own implementation.
When creating your own scripts, it is important to follow the same principle: Compartmentalization over optimization.
| DO | DON'T |
|---|---|
| Make a grid script that will contain and manage grid positions of gameobjects inside the grid. | Make a grid script that is also a grid spawner, and also responsible for setting the positions of gameobjects. |
| Make a GridNode script to control the position of the attached gameobject according to the grid manager. | Make a GridNode script that is also an AI controller, that is also an Enemy controller. |
Rule of thumb: When a component starts to serve more than one purpose, ask yourself if you can separate it into its own other component.
Purpose: This ensures reusability, and ease of debugging and ease of refactoring.
The most preferred communication between two components is using private serialized field UnityEvents (example below).
| Purpose: you can set these functions in the inspector, and it makes sure that one script does not depend on unrelated things. |
|---|
However, when a component DOES depend on another component (i.e., a physics component requires a collider/rigidbody), then define it in the [RequireComponent] attribute for that particular component.
In the case of managers and their children, direct communication is allowable.
Default to private variables ALWAYS. When a variable needs to be accessible outside of the class, use the below architecture:
| [SerializeField] private float _age; public float Age {get => _age; set => _age = value;} |
|---|
| Purpose: Because getters are traceable which means it's easier to debug. |
|---|
Usually, games only need 3 scenes.
- Main Menu Scene
- Main Game Scene
- GameOver Scene (if applicable, can be replaced with UI popup on main scene)
Level loading should always happen within the same scene. It is way more efficient to delete gameobjects and then recreate what you need rather than loading a whole new scene for different levels.
Which brings me to my second point: DontDestroyOnScene[anything] is BAD architecture. Using this function will make your scenes harder to debug because there is scene-bleed (i.e., previous scene assets bleeding into a new scene). The point of having different scenes in the first place is so that you can encapsulate their loading processes within themselves.
When I enter my maingame scene, I don't want it to error because I didn't come from the mainmenu scene and it required a manager there.
Contain scene setup within each scene.
If you need two scenes to communicate with each other, use static variables with a default value. E.g.:
| static private int _GameMode = 0; static public int GameMode => _GameMode; |
|---|
Static variables carry through scenes. The default value ensures that even if you didn't go through the main menu scene, you still have the default configuration so that the main scene is standalone.
As you may know, in Unity projects, everything developer-made goes into the Assets folder. Inside this Assets folder should be the main GabUnity folder, followed by the following folder structure:
- Prefabs
- Resources
- [ScriptableObjects go here (SingletonDictionary, etc.)]
- Audio
- Short
- Long
- Music
- Dialogue (optional)
- UI
- (textures only)
- Scripts
- Settings
- [Most Unity project files go here.]
- Scenes
When importing meshes, import everything related to that mesh into its own folder. For example, a mesh needs a sub-mesh on a separate file. Put both of them into their own folder. Materials and textures? Same folder.
This is such that we encapsulate the assets per object. Because usually, when we're looking to replace or inspect graphics assets, we get lost in the sea of materials if we jam them into one directory. It is better to split them across object folders that actually group these needed assets together.
You can, of course, have multiple prefabs inside one folder. So long as these prefabs are related. For example, the projectiles folder. There is no reason to separate just one projectile prefab from the rest (unless it's very complex and requires 5 more textures or something).
Step 1: Clone repository
Step 2: Create your own branch based on the Main branch.
Step 3: Do your work at your own branch.
Step 4: If done, ask Gab to merge your branch to main.
Step 5: if your branch is outdated, merge from main unless there is a merge conflict.
| Rule of thumb: If there are merge conflicts, Gab will handle it. |
|---|