A set of utility classes and components useful to any Unity project, 2D or 3D.
- Make sure you have both Git and Git LFS installed before adding this package to your Unity project.
- Add the UnityNuGet scoped registry so that you can install NuGet packages through the Unity Package Manager.
- Install dependencies in your Unity project. This is an opinionated list of 3rd party assets/packages that UnityUtil leverages for certain features.
Unfortunately, some of these assets cost money. In the future, UnityUtil's features will be broken up into separate packages,
so that users can ignore specific packages and not spend money on their Asset Store dependencies.
- Odin Inspector (v3.0.12 or above). We strongly recommend not installing Odin as an embedded UPM package, as it just makes later updates to the asset more difficult.
- In the Unity Editor, open the Package Manager window, click the
+
button in the upper-left and chooseAdd package from git URL...
. - Paste one of the following URLs:
https://github.com/DerploidEntertainment/UnityUtil.git?path=/UnityUtil/Assets/UnityUtil#main
for the latest stable versionhttps://github.com/DerploidEntertainment/UnityUtil.git?path=/UnityUtil/Assets/UnityUtil#<branch>
for experimental features on<branch>
You can update this package from Unity's Package Manager window, even when it is imported as a git repo. Doing so will update the commit from which changes are imported. As the API stabilizes, I will move this package to OpenUPM and add a changelog to make its versioning more clear.
Work in progress!
This package has been under open-source development since ~2017, but only since late 2022 has it been seriously considered for "usability" by 3rd parties, so documentation content/organization are still in development.
Sometimes, you need to preserve code elements from managed code stripping during builds.
For example, your app may produce runtime code that doesn't exist when Unity performs the static analysis, e.g. through reflection and/or dependency injection.
You can use Unity's PreserveAttribute
mechansim to preserve these elements in your own code;
however, UnityUtil intentionally does not annotate any code with [Preserve]
so that you have total control over the size of your builds.
Therefore, if you need to preserve UnityUtil code elements (types, methods, etc.),
then you must use the link.xml
approach described in the Unity Manual.
Docs coming soon!
Order of service registration does not matter.
Multiple scenes may have scene-specific composition roots, even multiple such roots;
their registered services will all be combined.
You should also define an OnDestroy
method in these components, so that services can be unregistered when the scene unloads.
This allows a game to dynamically register and unregister a scene's services at runtime.
We recommend setting composition roots components to run as early as possible in the Unity Script Execution Order,
so that their services are registered before all other components' Awake
methods run.
Note, however, that an error will result if multiple composition roots
try to register a service with the same parameters. In this case, it may be better to create a 'base' scene
with all common services, so that they are each registered once, or register the services with different tags.
Docs coming soon!
Docs coming soon!
Docs coming soon!
Docs coming soon!
All UnityUtil namespaces use the Microsoft.Extensions.Logging.Abstractions
types for logging.
These types are designed by Microsoft to abstract any actual logging framework used by apps/games, such as Serilog, NLog, Log4Net, etc.
See Logging in .NET for more info (especially if you're new to log levels, scopes, message templates, etc.).
The MS Extension libraries are published as NuGet packages, as are most .NET logging frameworks.
By adding the UnityNuGet UPM scoped registry (described in Installing), you can effectively use any logging framework with UnityUtil in a ".NET native" way.
This is useful, as most .NET log frameworks are much more powerful/extensible than Unity's built-in logger.
For example, if you wanted to use Serilog in your game code, you would add the following UPM (NuGet) packages to your Unity project
(via the Package Manager window or by manually editing Packages/manifest.json
).
// Packages/manifest.json
{
"dependencies": {
"org.nuget.serilog": <version>",
"org.nuget.serilog.extensions.logging": "<version>",
// ...
},
// ...
}
The org.nuget.serilog
package adds Serilog itself, while org.nuget.serilog.extensions.logging
adds a "glue" library to make Serilog usable with Microsoft.Extensions.Logging.Abstractions
.
With this setup, you can register an ILoggerFactory
for dependency injection.
Client types can then inject this service and use it as follows:
class MyClass {
private ILogger<MyClass>? _logger;
// ...
public void Inject(ILoggerFactory loggerFactory) {
_logger = loggerFactory.CreateLogger<MyClass>();
}
// ...
public void DoStuff() {
_logger!.LogInformation(new EventId(100, "DoingStuff"), "Currently doing stuff in {frame}...", Time.frameCount);
}
}
The MS ILogger
extension methods expect every log message to have an EventId
,
which encapsulates an integer ID and a string name.
This presents a challenge, as every UnityUtil library, along with your app, are now sharing the same "space" of EventId
s.
To prevent ID "collisions", you can define a custom logger class for your app that exposes a unique public method for every log message.
Following the example above, you could define the following custom logger:
public void MyAppLogger<T> {
private readonly ILogger<T> _logger;
// ...
public void MyAppLogger(ILoggerFactory loggerFactory) {
_logger = loggerFactory.CreateLogger<T>();
}
// ...
public void DoStuff(int frame) {
_logger.LogInformation(new EventId(100, nameof(DoStuff)), "Currently doing stuff in {{{nameof(frame)}}}...", frame);
}
}
and use it in MyClass
as follows:
class MyClass {
private MyAppLogger<MyClass>? _logger;
// ...
public void Inject (ILoggerFactory loggerFactory) {
_logger = new(loggerFactory);
}
// ...
public void DoStuff() {
_logger!.DoStuff(Time.frameCount);
}
}
While this "custom logger pattern" is more verbose, it provides a number of benefits (all of which are visible in the above code):
- All
EventId
IDs are now visible in a single file (MyAppLogger.cs
), making it simpler to find and prevent ID collisions. - All
EventId
names can use thenameof
operator on the name of the method, making it easier to keep your log event names unique and free of typos. - All log event messages are now visible in a single file, making it easier to keep wording and log property names consistent.
- The log methods can accept and work with parameters, keeping your calling code clean and free of log-specific logic. For example, your log method might accept a single parameter, but access multiple members of that parameter in the encapsulated log message.
- Your log methods can also use the
nameof
operator in the underlying log message, to keep log property names free of typos.
However, you still have to ensure that all log events have a unique ID.
UnityUtil provides a BaseUnityUtilLogger
class from which you can derive to simplify the custom logger pattern.
Using it, the MyAppLogger
example above would become:
public void MyAppLogger<T> : BaseUnityUtilLogger<T> {
public void MyAppLogger(ILoggerFactory loggerFactory, T context)
: base(loggerFactory, context, eventIdOffset: 1_000_000) { }
// ...
public void DoStuff(int frame) {
LogInformation(id: 0, nameof(DoStuff), "Currently doing stuff in {{{nameof(frame)}}}...", frame);
}
}
First, notice that BaseUnityUtilLogger
's constructor requires a context
parameter.
This is useful in Unity code: if your logging object derives from MonoBeheviour
,
then it is saved to your log message's scope,
which specific logging frameworks can then extract and use as the context
for underlying Unity Debug.Log
calls.
In short, when you then click on the log event message in the Unity Console, the Editor highlights the logging object in the Hierarchy Window.
This can be very useful for understanding which components on which GameObjects are generating which logs during play testing.
Next, notice that BaseUnityUtilLogger
's constructor requires an eventIdOffset
parameter.
This offset is added to the ID passed in calls to its protected Log
method.
In other words, you can use simple increasing IDs (0, 1, 2, etc.) in your custom logger class,
and BaseUnityUtilLogger.Log
converts those IDs into "disambiguated" IDs that are unique across all UnityUtil libraries and your code!
The rule of thumb here is that every assembly (UnityUtil library or your app) gets a "subspace" of messages per LogLevel
(Information
, Warning,
Error, etc.). Every ID's first digit matches its
LogLevelenum value. For example, all
Warningmessages (enum value
3), start with a
3`.
Each UnityUtil library gets 1000 messages per LogLevel
by default;
your app can use as many messages as it wants, as long as they use a base eventIdOffset
of 1,000,000 or more.
See BaseUnityUtilLogger
's API documentation for a deeper explanation of how it "partitions" the EventId
ID space.
The "registered" eventIdOffset
s for the UnityUtil namespaces are shown below.
If you are developing a library to work with UnityUtil, please avoid using these offsets, and/or submit a PR adding your library to this list
so that other authors know not to collide with your library's IDs!
- 0:
UnityUtil
- 1000: Reserved
- 2000:
UnityUtil.DependencyInjection
- 3000:
UnityUtil.Inputs
- 4000:
UnityUtil.Interaction
- 5000:
UnityUtil.Inventories
- 6000:
UnityUtil.Legal
- 7000:
UnityUtil.Logging
- 8000:
UnityUtil.Math
- 9000:
UnityUtil.Movement
- 10,000:
UnityUtil.Physics
- 11,000:
UnityUtil.Physics2D
- 12,000:
UnityUtil.Storage
- 13,000:
UnityUtil.Triggers
- 14,000:
UnityUtil.UI
- 15,000:
UnityUtil.Updating
- 16,000:
UnityUtil.Editor
Docs coming soon!
Docs coming soon!
Docs coming soon!
Docs coming soon!
Docs coming soon!
Docs coming soon!
Docs coming soon!
For bug reports and feature requests, please search through the existing Issues first, then create a new one if necessary.
Make sure you have Git LFS installed before cloning this repo.
To build/test changes to this package locally, you can:
- Open the test Unity project under the
UnityUtil/
subfolder. There you can run play/edit mode tests from the Test Runner window. - Open the Visual Studio solution under the
src/
subfolder. Building that solution will automatically re-export DLLs/PDBs to the above Unity project. - Import the package locally in a test project. Simply create a new test project (or open an existing one), then import this package from the local folder where you cloned it.
See the Contributing docs for more info.