r/gamedev•
TASagent

Open-source Unity Tools and Demo Project (MIT-License)

BGC_Tools and CleanBGCProject

I recently posted to share news about BGC_Tools, our internal utility repository for Unity projects, going public. However, that post was perhaps a little premature and a little brief. In order to facilitate use I knew we really needed to accompany it with a demo project showing some of the features in in use. This post is to introduce CleanBGCProject, a small unity project showing a number of the utilities in use.

But allow me to back up...

Who the fuck do we think we are?

The UC Riverside Brain Game Center (or BGC for short) is a psychology and neuroscience research lab at University of California, Riverside. The BGC's Director is Dr. Aaron Seitz, and we have several post-docs and graduate students. We have a team of 3 full-time programmers (myself and two others), and a few undergrad volunteers, working on support and development for about 8-10 projects. The BGC projects, both current and past, include but are not limited to:

  • Vision and Hearing Rehabilitation
  • Neuroplasticity and the improvement (or "training") of working memory
  • The Design, Development, and Validation of automated cognitive assessments (tests) of memory, inhibitory control, and fluid intelligence
  • Electrophysiology studies (EEG and ERP) of the neurocorrelates of some of our training tasks and assessment
  • The creation of a psychoacoustics toolbox (P.A.R.T.) for developing and deploying completely shareable and reproducible hearing tests

Development for research is different from working in the games industry in some ways that I find interesting. A given project is almost never "complete". You're always either implementing new features, making highly lateral or incompatible changes, significantly scaling up how many subjects your project can support concurrently, or it's retired because you've created something brand new that does what it did, but better.

Why should you care?

This shit's got the MIT License - who doesn't like free stuff?

Less facetiously, our Unity projects undergo a lot of constant evolution, and we have a number of them, so we've needed to maintain a flexible, shared set of generalized tools. BGC_Tools has been built to work well when added to a Unity project as a submodule in order to make it easy maintain for all the projects it's being used in, and make sure some of the effort we put in for one project can transfer to others. See the "WTF is a Git Submodule?" footnote at the bottom if you use git but don't know what a submodule is.

We've had the opportunity to work with some really clever people, and I'd be delighted if that cleverness could be useful.

NOTE: Behavior in Editor

It has always been important to us to support rapid prototyping and easy testing. If a developer is working on some internal game scene, we want it to be convenient and easy to just press Run and test it. As such, all the systems that will be mentioned have been designed with either well-behaved default fallback behavior, or trivial initialization that can just be slapped into a scene and forgotten about.

The Good Stuff

Once again, the demo project is CleanBGCProject. The releases are here, as well.

What to look forward to:

Player Profiles (BGC.Users.PlayerData)

It's got a system for easily managing multiple user accounts, complete with serialization to JSON. The default behavior in the editor and on desktop is to store player profile data in the project directory, under ./SaveData/, and in an appropriate alternative location on iOS and Android. The format and location make it really easy to inspect, modify, and share. It's not at all obfuscated because that's not a problem for us, though it could be for you. Cheaters... *SMH*

The SettingsMenu (next) as well as both TestA and TestB in CleanBGCProjects makes use of PlayerData. Take a look at the persistent values accessed and written in /TestA/SettingsDemo.cs and /TestB/TestBPanel.cs, specifically the PlayerData references in the Properties defined towards the top of each of the mentioned classes.

Settings Menu (CleanBGCProject: /Core/Settings/SettingsMenu.cs)

This one doesn't live in BGC_Tools yet largely because a number of projects have made nontrivial, individualized changes to it, and it's so easy to add new settings that it's nearly criminal. Settings can be defined with scopes of either Global or User, default values, etc.

TestA in CleanBGCProjects makes a lot of use of the settings controlled by the settings menu. Take a look /TestA/SettingsDemo.cs, specifically SettingsDemo.FocusAcquired() to see where the settings are applied.

SystemInitializer Idiom (CleanBGCProject: /Core/SystemInitializer.cs)

Just a convenient method of slicing up the linear initialization of system elements and being able to use the same initialization method in either less disruptive coroutine chunks (ie Standard launch through the splash screen), or immediately inplace before anything else wakes up in an internal scene (ie when directly launching a scene for testing in the editor).

The Audio System (BGC.Audio.*)

I've had to design and build a fairly sophisticated AudioStream system to support the real-time mathematical generation of auditory stimuli that we use in several of our projects. Working with Unity's AudioClips and AssetBundles to load audio from file was far too constraining, being limited to the main thread and gobbling up memory as they do. Then gaining access to the underlying sample data required passing in a large buffer, doubling up our memory inconveniences. So I read up on the WAV specs and built BGC.Audio.WaveEncoding to load (and save) audio from file in a background thread and have direct access to the data, handling a number of the most common formats WAV encoding formats. Then I began layering Audio filters and effects...

Then, since I was able to synthesize some simple instruments, I figured I might as well go for it and I implemented a simple MIDI renderer (trust me, this was so much more work than I expected going into it). Check out BGC.Audio.Midi.MIDIEncoding to see the entry point, or MidiEncodingTests.cs to see what it looks like to use. You may need to download some midis to substitute in the source for some tests, since I didn't explicitly have a license to redistribute them.

How much code does it take to render "./Test/toccata1.mid" and save the output to a WAV file "./Test/toccata1.wav"?

MidiEncoding.LoadFile(
    filePath: DataManagement.PathForDataFile("Test", "toccata1.mid"),
    midiFile: out MidiFile midiFile);

WaveEncoding.SaveStream(
    filepath: DataManagement.PathForDataFile("Test", "toccata1.wav"),
    stream: new MidiFileStream(midiFile).SafeCache().SlowRangeFitter()),
    overwrite: true);

I haven't spent much time defining how each instrument should be synthesized, but the tools here are pretty powerful, so BGC.Audio.Midi.Synth.InstrumentLookup should be easy to fill out.

Both TestA and TestB in CleanBGCProject utilize the audio system in fairly obvious ways. Take a look at /TestA/SettingsDemo.cs and /TestB/TestBPanel.cs

Programmatic State Machine (BGC.StateMachine)

We have built a fairly robust and powerful programmatic StateMachine, connecting custom States, Transitions, and TransitionConditions. Some features in one of our projects started off implemented in Unity's animation state machines, but they were too restrictive and slow. A maximum one state transition per frame was causing issues for design.

TestB in CleanBGCProject uses a StateMachine to manage game flow. Check out /TestB/TestBPanel.cs, specifically TestBPanel.ConstructStateMachine(ITestBMessenger messenger), where we simulate constraining access to members through a simple interface.

Action Channels (BGC.MonoUtility.Interaction and BGC.MonoUtility.Interpolation)

What started as an attempt to better manage Coroutines that continuously modify gameobjects (hah!) became BGC.MonoUtility.Interpolation.LerpChannel<T>. Each instance of a LerpChannel is kicked off with a specific duration and can support up to 1, each, of an IContinuousAction<T>, ILerpAction<T>, an Action<T> finishedCallback, and an Action<T> interruptedCallback.

These guys are responsible for the craziness in TestA (SettingsDemo). Take a look at SettingsDemo.KickOffMessAround() to follow the logic. Admittedly, the use case here is simple enough that the instances basically disregard the templated argument (because all the action channels were just attached to SettingsDemo), but I'm confident you'll get the point.

Maths (BGC.Mathematics.*)

Fourier transforms, complex numbers, Bessel functions, prime number factorization, LCM computation. If you need it, here it is. If you don't need it... figure out a way to need it. Math is great. Don't overlook BGC.Audio.Visualization.Spectrogram, which can generate spectrograms, and some other visualizations.

Other Small Utilities

  • BGC.IO.DataManagement has been surprisingly useful for streamlining file access across platforms
  • BGC.Utility.ApplicationVersion is a struct handling sematic versioning logic
  • BGC.Utility.Epoch gives you the Posix time in Milliseconds
  • FileBrowser scene in BGC_Tools lets you navigate the local files and view text files. On mobile this is super convenient. (See CleanBGCProject)

But this is so ugly...?

Be that as it may, what we're showing off here is tech completely decoupled from the presentation.

Style and Unity Idioms

This one is going to be more contentious, but I've developed a number of particular preferences for how to work with Unity in ways that minimize errors and unnecessary work while maximizing clarity. Just as in any other programming context, it's important to understand how your various systems are coupled together. Unfortunately, when complex instructions are implemented in serialized delegates in the Unity inspector (even just simple UI callbacks like UnityEngine.UI.Button.onClick()) information and interactions are obfuscated, refactors are obnoxious at best, and rebuilding them after a scene collision or anomolous scene hiccup can be nightmarish. For this reason, we limit inspector-populated delegates to a very limited set of designer-oriented tools.

To build interaction between scripts on static scene elements (like fixed UI), we generally include private serialized fields to capture all the references we'll need in the script, and assign callbacks in the Awake() method. Like so:

public class SettingsInnerPanel : ModePanel
{
    [Header("Engine References")]
    [SerializeField]
    private ModePanelManager settingsPanelManager = null;
    [SerializeField]
    private SettingsMenu generalSettingsMenu = null;
    [SerializeField]
    private UserIDMenu userIDMenu = null;

    [Header("Settings Menu Tab Buttons")]
    [SerializeField]
    private Button generalSettingsButton = null;
    [SerializeField]
    private Button userSelectButton = null;

    [Header("Settings Menu UI")]
    [SerializeField]
    private Button lockButton = null;

    // ---Snip---

    void Awake()
    {
        generalSettingsButton.onClick.AddListener(() =>
            TabButtonClicked(SettingPanelMode.General));
        userSelectButton.onClick.AddListener(() =>
            TabButtonClicked(SettingPanelMode.UserSelect));
        lockButton.onClick.AddListener(LockPressed);
    }

    // ---Snip---
}

Using this approach, it is generally really obvious what reference belongs in each serialized field in the inspector, it's easy to confirm that you haven't missed anything, and most refactors just work, or are effectively compiler-checked.

Conclusion

I don't know why I included this section. I don't really have a lot else to say... If you have any questions, feel free to drop them here or shoot me an email.

We are open to pull requests that improve functionality and/or clarity.

Thank you for your time, and I hope something here might be useful for some of you.


Footnotes

WTF is a Git Submodule?

I started writing a summary of what git submodules are and how to use them, but it was exploding into a post bigger than the whole of this one. So I made it into a blog post. I'll be pleased if at least someone finds this useful, but at the very least I'll be able to use it as reference materials for trainees.

The abridged version is this: A git submodule is an excellent and flexible way of managing code that is shared by multiple projects. It is a core feature of git.


Edit: tpyos

Posted Apr 20, 2019, 12:57 AM UTC · edited

8913
Skjalg•

Git submodule is really handy. We use that ourselves in our own internal toolkit. I am in the process of moving it over to using the package manager though, that way its even easier to set up and use for the team. Have you had the chance to look at that?

5
TASagentOP•

I haven't really looked into using Unity's Package Manager recently. Last I did, admittedly a while ago, it seems the overhead in the form of the extra effort to deploy and maintain them was more than made sense for my team, just 3 developers who all use the git CLI to do everything. It's impossible to overstate how little work is involved in sharing changes to a submodule.

What advantages of the Package Manager motivated you guys to make the switch? There are clear advantages externally to having a UnityStore package to distribute the features, but what of the effects internally?

1
Skjalg•

The deploy and maintainence is really similar. to your current approach. You just link in a package with the git url in the package manifest and it’ll automatically download the git repo and add it as a folder under packages (instead of adding a folder under assets) it’ll automatically lock the latest commit hash as well for you. What you need in your package git repo is that it is set up correctly with assembly definition files and a package manifest.

So difference is mainly that instead of adding a reference to a git submodule in your git settings you add it to your package manifest. Bonus is that it appears outside your assets folder and is read only so people dont unintentionally tamper with things that might affect other projects.

3
TASagentOP•

Ahh, cool. That does actually sound like it's become much more convenient. But I do look at the fact that the submodule can be edited within the project as a benefit, providing the context of the project while working on changes. Granted, we also do code reviews for our undergrad interns, and my eyes generally look over changes to our tools repo.

When working with the package manager, do you generally just configure a temporarily project where it's imported as a submodule instead when working on more nuanced changes that require some real integration testing?

3
Skjalg•

Yeah I have a test project with all the packages as sub modules hehe :)

2
shadowmint•

Cool stuff.

Just a general comment: There's a tendency for people to recreate the wheel from scratch over and over again for this sort of stuff in unity.

For example, see this other similar project: https://github.com/keijiro/Klak

The problem with monolithic libraries is that even if someone is interested in a part of it (eg. BGC.IO.DataManagement), there's a significant overhead to using that part; and given that there are limited maintainers of the repository, the chances that the specific piece of functionality you're interested in will continue to be actively maintained is limited; and the chance of picking up the maintenance of just that piece is basically non existent, because it has to fit with the 'ecosystem' of the entire package.

You'll see this problem repeated over and over again with unity projects.

Simple assets often 'vendor' a specific dependency for their own little purposes (eg. native camera management), and you end up with 50 ways of doing the same thing.

I recommend you split your package into smaller parts, and pull them in as needed, like a real package manager would.

​

tldr; This kind of 'all-in-one' kitchen-sink type approach is great for game jams and creative coding, where you write lots of little similar applications using entirely the same stack and don't have to maintain them long term, but its bad software engineering practice.

​

Checkout what they're doing with the package manager; that's how this will all work in the future, (along with https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html to improve dependency workflows).

​

...but, there's some really nice stuff in here. What license is the main tools repo? (I see the demo is MIT, but there's no mention on the tools I can see?)

3
TASagentOP•

It's possible to take a smaller, more discrete package-based approach. But in practice that amounts to a lot more overhead in terms of development for comparatively little practical benefit internally. Developing applications for reseach actually shares a number of things in common with code jams.

It's true that it would be easier for third-parties to work with the libraries without modification and any unnecessary overhead if they were split up, but there are several other options that interested parties have if they are concerned about overhead and only want some of the features: fork and cull, or just cherry pick files from the repo directly. The former makes it relatively painless to integrate future changes, the latter might be totally fine if there's just some nugget you want to run with in a totally different direction.

As for the license, as the CleanBGCProject's license file mentions, the BGC_Tools license is in the root of that project. All that we have written for it (almost all of it) is covered by the MIT License, there are a few files covered by other group's MIT licenses, and 2 files covered by the similarly-permissive Microsoft Public License.

1
S2-RT•

Super cool. At work atm So I can’t dive too deep into it right now, but as some one majoring in psychology with a minor in media arts and game development, this sounds super interesting. :)

2
TASagentOP•

Yeah, I'm looking to eventually get a suite of a large number of adaptive algorithms sufficiently generalized, as we've done a lot of thinking about procedurally navigating a multidimensional parameter space in order to probe for thresholds. But that generalizes to game development well in any case where you have parameterized some difficulty factors and you want a system that dynamically adjusts to user performance. Everything is a psychometric function if you look hard enough...

1
poodleface•

As a former research game developer, thanks for taking the time to make an example project with simple scenes to demonstrate functionality. This is where a lot of open source projects with a lot of features far short. I do agree that if adoption is your goal you might look into Package Manager, but I know it is hard enough to take the time to clean up internal code for public release. A lot of tools we built (e.g. logging framework for analyzing player actions within a game experience) never saw wider distribution for that reason.

I co-taught a computer audio course for a number of years.... I’m eager to check out what you’ve done with that.

2
TASagentOP•

Yeah, we have our logging framework and all the code to upload and sync log files with our S3 instance in a different repo because it necessarily delves into our lambdas quite clearly and explicitly. As I'm sure you did, we have put a lot of time into writing logging functionality. The remaining exception logger is using a really old system. Also, all timestamps are Posix time in milliseconds, which makes logs delightfully explicit.

If anyone actually wanted to use the whole package as-is, they would be more than welcome, of course. But I largely expected that the widest form of realistic adoption would be someone seeing a thing or two they liked and snatching it for one of their projects. Or, if they wanted to use a sizeable chunk that wasn't too entangled with other systems, they could fork it and delete the stuff they didn't need.

1
TASagentOP•

Regarding the audio system: it's a fairly standard stream decorator pattern, with a few extra features.

The IBGCStream is a stream of floats that reports how many channels are represented. But there is also an IAnalyticStream which represents a single channel of Complex numbers representing an analytic signal, (or the stream + the Hilbert transform if that's not defined). There are some fun and efficient transforms you can do on a stream if you have it represented as an analytic signal, like applying frequency modulation using the FFT Shift theorem.

1
TotesMessenger•

I'm a bot, bleep, bloop. Someone has linked to this thread from another place on reddit:

 ^(If you follow any of the above links, please respect the rules of reddit and don't vote in the other threads.) ^(Info ^/ ^Contact)

1
[deleted]•

[deleted]

1