Skip to content

A set of utility classes and components useful to any Unity project, 2D or 3D.

License

Notifications You must be signed in to change notification settings

DerploidEntertainment/UnityUtil

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

UnityUtil

A set of utility classes and components useful to any Unity project, 2D or 3D.

Contents

Installing

  1. Make sure you have both Git and Git LFS installed before adding this package to your Unity project.
  2. Add the UnityNuGet scoped registry so that you can install NuGet packages through the Unity Package Manager.
  3. 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.
  4. In the Unity Editor, open the Package Manager window, click the + button in the upper-left and choose Add package from git URL....
  5. Paste one of the following URLs:
    • https://github.com/DerploidEntertainment/UnityUtil.git?path=/UnityUtil/Assets/UnityUtil#main for the latest stable version
    • https://github.com/DerploidEntertainment/UnityUtil.git?path=/UnityUtil/Assets/UnityUtil#<branch> for experimental features on <branch>

Updating

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.

Features

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.

General notes

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.

Configuration

Docs coming soon!

Dependency Injection

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.

Inputs

Docs coming soon!

Interaction

Docs coming soon!

Inventories

Docs coming soon!

Legal

Docs coming soon!

Logging

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 EventIds. 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):

  1. All EventId IDs are now visible in a single file (MyAppLogger.cs), making it simpler to find and prevent ID collisions.
  2. All EventId names can use the nameof operator on the name of the method, making it easier to keep your log event names unique and free of typos.
  3. All log event messages are now visible in a single file, making it easier to keep wording and log property names consistent.
  4. 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.
  5. 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 itsLogLevelenum value. For example, allWarningmessages (enum value3), start with a3`.

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" eventIdOffsets 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

Math

Docs coming soon!

Movement

Docs coming soon!

Physics

Docs coming soon!

Physics2D

Docs coming soon!

Storage

Docs coming soon!

Triggers

Docs coming soon!

UI

Docs coming soon!

Support

For bug reports and feature requests, please search through the existing Issues first, then create a new one if necessary.

Contributing

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.

License

MIT

About

A set of utility classes and components useful to any Unity project, 2D or 3D.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages