Meta

Unity 3D

Working backwards (a little anyway, what developer worth his salt doesn’t wander from task to task in a strange order after all!), and with a view to getting a new FPS project up and running as rubber-stamped in my last post, I decided to head towards something I could achieve. Namely, a tidier NotificationsManager class that didn’t rely on using Unity’s in-built object messaging functionality (and its heavy dose of reflection) and instead picked a different approach. You’ve got to do something when a heavy dose of insomnia kicks in!

Based on my own thoughts and a 2 minute trawl online this is what I’ve come up with. This definitely falls into the realm of prototype but I think something along this line could certainly have legs. So without further ado here’s a run-down of the sample project and code I’ve just cooked up, most likely with a view to improve upon once a project gets off the ground.

The game built involves collecting spheres to gain points; 50 points and you win! AAA right there I think you’ll agree! We pull the following components together to form the basic structure/functionality of this game:

A GameManager Class (the core class handling central game interactions).

A NotificationsManager class (the object responsible for handling events).

A SphereScript Class (simply triggers an event to increment the players score on collision).

The Game World is structured like this:

Game World Configuration.

As with the FPS project I created and reviewed in my previous post, this sample will utilise a core GameManager class. Only one instance of this class will exist per game (I’ve taken steps to ensure this will be a singleton object) and it will ultimately control access to the NotificationsManager directly (for other game objects to call) and handle the game win condition, aka collecting enough spheres. As a cheeky extra, which wouldn’t normally live in this class, a small piece of GUI logic has been added to render the player score in the top left hand corner of the screen.

using UnityEngine;
/// <summary>
/// The core test project GameManager.
/// </summary>
[RequireComponent(typeof(NotificationsManager))] //A GameManager (when added in the Unity Editor) will automatically attach a NotificationsManager script (if one does not exist)
public class GameManager : MonoBehaviour
{
#region Private Data Fields
//A reference to the Player Controller object (on the First Person controller)
private PlayerController playerCont = null;
//A cheeky reference to a rectangle (to draw a score on the screen in the OnGUI method)
private Rect screenRect = new Rect(2.0f, 2.0f, 45.0f, 30.0f);
#endregion Private Data Fields
#region Private Static Data Fields
//Our core, singleton, GameManager instance
private static GameManager gameManagerInstance = null;
//A singleton instance (accessible through this class) of the NotificationsManager
private static NotificationsManager notifications = null;
#endregion Private Static Data Fields
#region Public Static Properties
/// <summary>
/// Global access to a singleton GameManager class (for all game
/// objects to utilise during play).
/// </summary>
public static GameManager GameManagerInstance
{
get
{
//Create a new GameObject and add a GameManager script to it if it doesn't already exist (allocating this to the gameManagerInstance field)
if (gameManagerInstance == null)
{
gameManagerInstance = new GameObject("GameManager").AddComponent<GameManager>();
}
//Return the instance for global use
return gameManagerInstance;
}
}
/// <summary>
/// Global access to a singleton NotificationsManager class (for all game
/// objects to utilise during play).
/// </summary>
public static NotificationsManager Notifications
{
get
{
//Set the private notifications field to reference the NotificationsManager script on the GameManagerInstance.
if (notifications == null)
{
notifications = GameManagerInstance.GetComponent<NotificationsManager>();
}
//Return the instance for global use
return notifications;
}
}
#endregion Public Static Properties
#region Awake/Start
/// <summary>
/// In Unity horrific things happen when you use constructors. To that end,
/// ensure that only one GameManager comes into existence at game start.
/// </summary>
void Awake()
{
if (gameManagerInstance != null && gameManagerInstance.GetInstanceID() != GetInstanceID())
{
DestroyImmediate(gameObject);
}
else
{
gameManagerInstance = this;
DontDestroyOnLoad(gameObject);
}
}
/// <summary>
/// After Awake, when we've establised one instance of this class for global use, ensure
/// that this object has a valid reference to a player object (to read out the score to
/// game screen). Register for the OnAllSpheresCollected event.
/// </summary>
void Start()
{
playerCont = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerController>();
//As we've registered for the OnAllSpheresCollected event we don't need to check the player score
//in the Update method (although we could as we have a PlayerController reference). When a sphere is touched it's
//collider will trigger an event picked up in this class (minimising the amount of processing we have to do each frame)
Notifications.OnAllSpheresCollected += Notifications_OnAllSpheresCollected;
}
#endregion Awake/Start
#region OnGUI
/// <summary>
/// Very rudimentary GUI operation. Write
/// the player score to the screen.
/// </summary>
void OnGUI()
{
if (playerCont != null)
{
GUI.Label(screenRect, new GUIContent(playerCont.Score.ToString()));
}
}
#endregion OnGUI
#region Event Handlers
/// <summary>
/// Pick up on the NotificationsManager triggering the
/// OnAllSpheresCollected event. In this case, this is the
/// win condition for the game (simply close and exit).
/// </summary>
/// <param name="sender">The component that triggered the event (PlayerController).</param>
void Notifications_OnAllSpheresCollected(Component sender)
{
//We could interrogate the state of the Component here if we needed to (aka the player controller).
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
#endregion Event Handlers
}

Beforehand, our NotificationsManager maintained a list of strings/components and used the strings (as defined event names) to call the SendMessage method on applicable objects. The GameManager listed above begins to hint at a slightly different approach in this regard. Notice, within the Start method, how an event is being registered (against the NotificationsManager OnAllSpheresCollected event) instead with a handler being exposed within this class; making this class a listener for this event. The Notifications_OnAllSpheresCollected event, which could easily have been an anonymous method (using a lambda expression) has been mocked up to simply end the game.

A NotificationsManager component, as before, is still marked as required and will be added whenever a GameManager script is added to a GameObject within the Unity Editor. Below is an illustration of how the GameManager object is configured in the Unity Editor; showing the scripts added for reference:

Game Manager Configuration.

Let’s get into the real beef (not that there is much ‘beef’ in this project, it’s more of a low-fat implementation really!) of the changes made to the NotificationsManager. The tweaked mock-up relies on a single delegate outlining a method that has no return type, which I could expand on later down the line if necessary, and that supports a single argument of type ‘Component’ (most likely another script).

Two public events are then configured to allow objects to register as listeners for the ‘sphere collected’ and ‘all spheres collected’ events. In the first iteration listed here I haven’t given consideration yet to how this should ultimately work (i.e. should these be instance or object level based, aka static – Perhaps even considering the whole class declaration), it’s bare bones. As the NotificationsManager exists as a singleton in the previous project (is instance based), and I only want to prove a concept, having these as non-static and accessible at the instance level works ok for now and allows appropriate access through the GameManager instance.

Finally, two public methods allow objects to ‘post’ notifications to any listening objects, provided the event handler is not null.

The NotificationsManager is inherently tied to the GameManager and exists on the same GameObject, in the Unity Editor, as the Game Manager.

We’ve started to see the concept of how an event/delegate system could work in Unity for listening/posting objects. The player and sphere objects and associated scripts should hopefully prove the concept in a very basic sense.

Starting with the PlayerController class; the implementation simply allows for a players score to be recorded and the object to be marked as a listener for any object triggering the OnSphereCollected event. This is taken care of by the logic found with the Start initialisation method and the Notifications_OnSphereCollected event handler. The event handler, triggered by a sphere on collision, will just increment the players score by 10 points. If the players score hit 50 points or above then an AllSpheresCollected event can be triggered for all listening objects.

In addition, notice the little bit of debugging listed in the Notifications_OnSphereCollected event handler. We’ll see this in action a little later on. The Player, First Person Controller, GameObject configuration is as follows:

Player Configuration.

The last component we need is something that will trigger our NotificationsManager.SphereCollected method; in turn triggering the relevant event to move the game forward (and push the player towards the AllSpheresCollected event that will end the game). The following class definition completes the puzzle.

When a sphere collides with another object the objects tag is inspected. If the object turns out to be the player we can post a notification to the public method found on the NotificationsManager class to trigger the appropriate event (sphere collected) on all listening objects. The final configuration of the sphere GameObject looks like this:

Sphere Configuration.

Proof is in the pudding so they say so let’s spin the game up and see if we can:

Physically ‘collect’ sphere objects and see the players score increment by 10 points (proving that the OnSphereCollected event is being handled).

Verify that the game ends when the players score is equal to or greater than 50 points (proving that the OnAllSpheresCollected event is being handled).

Verify we receive appropriate debug information during the game.

So, the game fires up and our sphere collecting quest begins!

We’ve toiled hard and finally triumphed; the first sphere has been discovered! Our score is currently 0:

I call that a successful test of a 10 minute prototype. I’ve got a strong feeling that, under load, this will definitely perform better than the SendMessage approach and this is still very much open to refinement; given a few hours to think about it I’ll probably overhaul everything. It’s possible to bypass the events entirely and just use raw delegates or setup interface based solutions. However, this seems like a nice step up from the implementation I originally had introduced to me during the previous project.

I’ll definitely plod my way over to Blender shortly and get working on content creation but I’m happy that, given a short amount of time, I can come up with modest solutions to improve upon the backbone components of any proposed game engine I plan to write. I wanted to share the most simple implementation I could think of; this is by no means a complex or definitive example, but I hope it proves a point. If anyone reading does have any suggestions or links to ‘you should do it this way, it’s the best’ documentation or resources then I’d be happy to hear about it.

I hope everyone has been having a super, duper New Year so far. In the process of getting back into the swing of things at work and preparing for my impending big day (well, I say impending, June – Nevertheless I’m sure it’ll come around quickly!) I’ve been spending little pockets of time looking at game development. Specifically, rooting around in Unity 3D in the efforts to produce an FPS shooter.

I ended up with a small wedge of cash for Christmas so I did what anyone nowadays does (not save for the wedding, cough cough) and splurged on a few development books. In addition to a book on AI programming and shaders this little number also crossed my path:

The content has been pretty damn good by all accounts and the resources provided have been top notch. The parts that have particularly caught my eye in my short time with this so far have been in handling event notifications between game objects and general asset management (i.e. the import and configuration of meshes created in Blender/Maya). The events manager, as implemented here, is an incredibly simple configuration that consists of the following NotificationsManager class:

using UnityEngine;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// Represents our core Event Handling Class for the
/// CMOD FPS game.
/// </summary>
public class NotificationsManager : MonoBehaviour
{
#region Private Data Fields
//Private dictionary that tracks listeners for given event types
private Dictionary<string, List<Component>> listeners = new Dictionary<string, List<Component>>();
#endregion Private Data Fields
#region Public Methods
/// <summary>
/// Ties the provided listener object (component) to the specified
/// event type. If the event type is not yet being handled then a new dictionary
/// entry is created for the event type (notification name) and a new list of components
/// instantiated ready for additions as required.
/// </summary>
/// <param name="sender">The component to be notified of a given event.</param>
/// <param name="notificationName">The event to tie to the provided listener object.</param>
public void AddListener(Component sender, string notificationName)
{
//Check to see if this notification (event) type is currently stored locally. If not, create a new dictionary entry for it
if (!listeners.ContainsKey(notificationName))
{
listeners.Add(notificationName, new List<Component>());
}
//Tie a listener object to the given notification (event) type
listeners[notificationName].Add(sender);
}
/// <summary>
/// Allow specific listeners to unregistered themselves for a given
/// event/notification type.
/// </summary>
/// <param name="sender">The object that no longer needs to listen for the given event.</param>
/// <param name="notificationName">The event/notification type to be removed from.</param>
public void RemoveListener(Component sender, string notificationName)
{
//Debug.Log("Removing listeners");
//See if the notification type is supported currently. If not, then return
if (!listeners.ContainsKey(notificationName))
{
return;
}
//Remove 'all' references that match (by instance id) for the given notification type
listeners[notificationName].RemoveAll(li => li.GetInstanceID() == sender.GetInstanceID());
}
/// <summary>
/// Allow for an event 'poster' to trigger a named method (based on the notification
/// name) on all listening objects.
/// </summary>
/// <param name="sender">The poster who has latched onto an event in the first instance.</param>
/// <param name="notificationName">The event/notification name (ties to a method name on listening objects).</param>
public void PostNotification(Component sender, string notificationName)
{
//If there are no references based on the notification name then simply return (no work to do)
if (!listeners.ContainsKey(notificationName))
{
return;
}
//Notify each, relevant, object of that a specific event has occurred
listeners[notificationName].ForEach(li =>
{
if (li != null)
{
li.SendMessage(notificationName, sender, SendMessageOptions.DontRequireReceiver);
}
});
}
/// <summary>
/// Removes redundant listeners (to cover scenarios where objects might be removed
/// from the scene without detaching themselves from events).
/// </summary>
public void RemoveRedundancies()
{
//Create a new dictionary (ready for an optimised list of notifications/listeners)
Dictionary<string, List<Component>> tempListeners = new Dictionary<string, List<Component>>();
//Iterate through the notification/listener list and removing null listener objects. only keep a notification/listener dictionary entry if one or more
//listening objects still exist for a given notification
listeners.ToList().ForEach(li =>
{
li.Value.RemoveAll(listObj => listObj == null);
if (li.Value.Count > 0)
{
tempListeners.Add(li.Key, li.Value);
}
});
//Set the listener dictionary based on the optimised/temporary dictionary
listeners = tempListeners;
}
/// <summary>
/// Removes all listener dictionary references.
/// </summary>
public void ClearListeners()
{
listeners.Clear();
}
#endregion Public Methods
}

Using a centralised game manager class (that holds a reference to a single instance of the NotificationsManager class) it’s been surprisingly quick to setup a solid event based system. I want to tweak this implementation however for future projects. As you can see here, this class revolves around a dictionary of type string/Component. This allows for classes registering as ‘listeners’ for a given event to provide a reference to themselves (the component) along with a string representing an event name (which matches a public method implemented on the class registering as a listener). For this to work, posting notifications relies on calling the SendMessage method on listening objects, a process which relies heavily on reflection based on my studies. In a small scale project where limited amounts of messages will be getting passed around this will perform fine (and this has been the case thus far for me). In the long run however (maybe my next project), it seems like a better approach will be to define interfaces and perhaps use a dedicated system structured around delegates. High levels of reflection are not going to cut the mustard when the levels of notifications being passed around start hitting even modest levels.

As far as handling the content pipeline and getting stuck into Lightmapping, Navigation Meshes (for AI Pathfinding) and the construction of Colliders when importing assets the instruction provided has been incredibly easy to follow.

Sprites are provided with the book assets and with a couple of basic scripts it was fairly simple to get a billboard sprite enemies up and running (with flip-book style animations). I’ve worked with 3D models in the past (using the Unity Mecanim system) and have produced rigged and animated models but I’ve really enjoyed utilising some 2D animation techniques (quite a bit of the 2D support now in Unity is quite new so it’s always great to skim over new material like this).

Unity UI.

The enemies themselves use a basic FSM (Finite State Machine) to manage Patrol, Chase and Attack states as shown below. I’ve defined the class as abstract with the coroutines that handle each enemy state made virtual (to allow certain enemies to override the behaviour as necessary). Surfacing variables in the Unity Editor requires them to be public (and they can’t be properties), something that is driving me a little nuts – Treating as a nuance for the time being:

Coroutines (that have a return type of IEnumerator) are a useful feature within the Unity engine that allow you to mimic asynchronous behaviour. When previously using Unity, I would definitely be guilty of cramming all of the logic into the Update method (run every frame) which invariably lead to performance issues. Using an event based system and coroutines it’s been possible (in most cases) to abstract heavy lifting code out of areas where it gets hit every frame and is instead called when required (or as a side-line to the main thread).

This class also showcases the use of a NavMeshAgent. During the patrol state (where the most interesting implementation lies) a location is ‘sampled’ and then the nearest, valid position found on the scene Navigation Mesh is chosen as the enemy destination. When the destination is reached, within a certain tolerance range (time out also provided just in case), the enemy marches on his way to the next generated destination (until the player is found and a different state is initiated or the enemy is killed). This works surprisingly well.

I’ve got a book that talks through more advanced scenarios using Behaviour Trees which I’ll try and implement in a future project.

In all honesty, I think that’s probably where I’m headed. The assets provided in this book allow the rapid creation of a modular level environment and to this end the internet has been scoured for tutorials on creating this in Blender (including environment texturing and UV creation). I’ve picked a direction for this blog at long last, hoorah! A preliminary promise of sorts then…I aim to cover (not all strictly code based but indulge me!):

Stick with the FPS formula and create a new project from scratch.

Undertake some 3D content creation in Blender (modular environments and UV creation). I don’t have a clue what I’m doing here so I expect that to be damn funny to witness!

Keep with the 2D sprite setup for enemies, perhaps making a set of 5 or 6 enemy types and at least 3 weapon types.

Make a handful of 3D game world assets using Blender.

Implement an enhanced NotificationsManager in Unity.

Get all of the content into Unity and make a multi-level FPS shooter.

Investigate the new UI components in Unity (to build a dynamic GUI of some sort).

Produce my own sound content. I’ve tried this already via some online music/sound effect creation tools, the results were not good. If my brother is reading this he’ll know that I’ve never been all that musical and is probably cringing at the thought of anything knocked up by me in this department!

Big promises eh! Let’s see what gets delivered. I’m not expecting an AAA experience to fall out of the dredges of my file system and arrive on the doorstep of steam, this will just be for fun!

I’ll leave you with a few screenshots taken from the (pretty much finished) product. Enjoy!