Detecting The Player’s Controller Type With the Unity Input System

Hello all, this is a walkthrough of my solution for detecting the device that the player is using in a single player “gamepad” based game. In my specific case, I’m using this information to have my UI constantly represent the correct “controls” UI for the type of device my player is using. At the time of writing this, my system is specifically built to sort between PlayStation & Xbox controllers, with Xbox being the default. With that said, this system is easily extendable, and I hope that will be clear through my code & explanations. This guide is meant to help those who have routed all of their input beneath the “game pad” umbrella in the Unity Input System, but still want the specific type of controller used by the player to be accessible.

This post is using the “New” Unity Input System, NOT the Unity Legacy Input

I ran into a lot of very interesting road blocks in my quest to create an abstracted UI Image system which would live react to the type of controller being used by the player…. It sounds funny, because you would think it would be simple to detect the type of controller being used by the player, but the way that the Unity Input System deals with game pad style controllers seems to do a pretty good job of burying this data.

There were 2 ways which I thought I could approach this. The first would be to go into my actual Input Map and specify control schemes by controller type, and then have a manager which detects input from any/all control schemes. and switches state based upon the most recent input. I chose not to do this because, in my opinion, one of the biggest appeals of the Unity Input System is that it supports the most common controller types all beneath the gamepad umbrella.

It’s important to note that in my project, I have my PlayerInput Behavior set to Invoke C# Events.

My first step in this system was listening in to the InputSystem.onDeviceChange, which must be assigned to a function which takes an InputDevice and InputDeviceChange. This event will fire off every time a change to device is detected. These include a controller being: added, disconnected, removed, & reconnected. InputDeviceChange represents which one of these events was detected. Here’s what those look like:

Note: _currentController is just a basic enum meant to internally store the controller state

    private void GameManagerRegisterInput()
    {
        //Binds onDeviceChange event to InputDeviceChanged
        InputSystem.onDeviceChange += InputDeviceChanged;

    }

    //Method called  when a device change event is fired
    private void InputDeviceChanged(InputDevice device, InputDeviceChange change)
    {
        switch (change)
        {
            //New device added
            case InputDeviceChange.Added:
                Debug.Log("New device added");
                

                break;
               
            //Device disconnected
            case InputDeviceChange.Disconnected:
                controllerDisconnected.Invoke();
                Debug.Log("Device disconnected");
                break;
            
            //Familiar device connected
            case InputDeviceChange.Reconnected:
                controllerReconnected.Invoke();
                Debug.Log("Device reconnected");
                

                break;
                
            //Else
            default:
                break;
        }
    }//Method called  when a device change event is fired
    public void InputDeviceChanged(InputDevice device, InputDeviceChange change)
    {
        switch (change)
        {
            //New device added
            case InputDeviceChange.Added:
                Debug.Log("New device added");
                
                //Checks if is Playstation Controller
                if (device.description.manufacturer == "Sony Interactive Entertainment" && _currentController != CurrentControllerType.PlayStation)
                {
                    //Sets UI scheme
                    Debug.Log("Playstation Controller Detected");
                    currentImageScheme.SetImagesToPlaystation();
                    _currentController = CurrentControllerType.PlayStation;
                    controllerTypeChange.Invoke();
                }
                //Else, assumes Xbox controller
                //device.description.manufacturer for Xbox returns empty string
                else if(device.description.manufacturer != "Sony Interactive Entertainment" && _currentController != CurrentControllerType.Xbox)
                {
                    Debug.Log("Xbox Controller Detected");
                    currentImageScheme.SetImagesToXbox();
                    _currentController = CurrentControllerType.Xbox;
                    controllerTypeChange.Invoke();
                }
                break;
               
            //Device disconnected
            case InputDeviceChange.Disconnected:
                controllerDisconnected.Invoke();
                _currentController = CurrentControllerType.Other;
                Debug.Log("Device disconnected");
                break;
            
            //Familiar device connected
            case InputDeviceChange.Reconnected:
                controllerReconnected.Invoke();
                Debug.Log("Device reconnected");
                
                //Checks if is Playstation Controller
                if (device.description.manufacturer == "Sony Interactive Entertainment" && _currentController != CurrentControllerType.PlayStation)
                {
                    //Sets UI scheme
                    Debug.Log("Playstation Controller Detected");
                    currentImageScheme.SetImagesToPlaystation();
                    _currentController = CurrentControllerType.PlayStation;
                    controllerTypeChange.Invoke();
                }
                //Else, assumes Xbox controller
                //device.description.manufacturer for Xbox returns empty string
                else if(device.description.manufacturer != "Sony Interactive Entertainment" && _currentController != CurrentControllerType.Xbox)
                {
                    Debug.Log("Xbox Controller Detected");
                    currentImageScheme.SetImagesToXbox();
                    _currentController = CurrentControllerType.Xbox;
                    controllerTypeChange.Invoke();
                }
                break;
                
            //Else
            default:
                break;
        }
    }    private void GameManagerRegisterInput()
    {
        //Binds onDeviceChange event to InputDeviceChanged
        InputSystem.onDeviceChange += InputDeviceChanged;

    }

    //Method called  when a device change event is fired
    private void InputDeviceChanged(InputDevice device, InputDeviceChange change)
    {
        switch (change)
        {
            //New device added
            case InputDeviceChange.Added:
                Debug.Log("New device added");
                

                break;
               
            //Device disconnected
            case InputDeviceChange.Disconnected:
                controllerDisconnected.Invoke();
                Debug.Log("Device disconnected");
                break;
            
            //Familiar device connected
            case InputDeviceChange.Reconnected:
                controllerReconnected.Invoke();
                Debug.Log("Device reconnected");
                

                break;
                
            //Else
            default:
                break;
        }
    }

While this was the first step I took, I was working backwards a bit. With this event being listened to, we can react to new controllers being connected, to controllers losing connection, and a bunch of other events. These events usually represent a reason to re-evaluate the current images being displayed for controls.

I wrote a fairly simple Scriptable Object which holds all the textures we will be using in our “controls” UI. So this scriptable objects holds all the images used to represent xbox and playstation controls. Additionally, it has a set of private textures which hold the “current” image for that button. Through this abstraction, I can have the Scriptable Object flip it’s set of “current” images, and have all UI read from this ScriptableObject for the image it needs to display.

Now that I had the foundation, and a way to store/represent the “controller type” state, now I just needed to determine what type of controller was being connected/added, and pass that state on to my Scriptable Object. Using the API for the type Device, which is passed on this event as a direct reference to the Device that instigated the event, I was able to determine that the field description has sub fields which are used to define the specific type of controller, the manufacturer, and more. The field product is a string which would come out something like “Dual Shock 4” or “Xbox One Elite”. While this is certainly useful, I wanted things to remain as generic as they could be. The manufacturer proved to be the most abstracted I could get, while still distinguishing between the specific types of gamepads. However, herein lies the first issue I encountered. While the device.description.manufacturer field on a Playstation controller returns a nice & neat “Sony Interactive Entertainment”, that same field for the Xbox controller is entirely empty. Since I only have to support the 2 types, I wrote these conditions into an if/else, and called it a day. But as I extend the system to include more controller types, I would make use of the device.description.product and distinguish between the remaining types of controllers. Here’s what that same chunk from above looks like with these cases written in.

A note: currentImageScheme is my Scriptable Object described above. This scriptable object provides the texture to raw image prefabs in my UI. controllerTypeChange is an event which is listened to by the UI. The UI will react to this event by setting their texture to the “current” texture stored in the ScriptableObject

//Method called  when a device change event is fired
    public void InputDeviceChanged(InputDevice device, InputDeviceChange change)
    {
        switch (change)
        {
            //New device added
            case InputDeviceChange.Added:
                Debug.Log("New device added");
                
                //Checks if is Playstation Controller
                if (device.description.manufacturer == "Sony Interactive Entertainment")
                {
                    //Sets UI scheme
                    Debug.Log("Playstation Controller Detected");
                    currentImageScheme.SetImagesToPlaystation();
                    controllerTypeChange.Invoke();
                }
                //Else, assumes Xbox controller
                //device.description.manufacturer for Xbox returns empty string
                else
                {
                    Debug.Log("Xbox Controller Detected");
                    currentImageScheme.SetImagesToXbox();
                    controllerTypeChange.Invoke();
                }
                break;
               
            //Device disconnected
            case InputDeviceChange.Disconnected:
                controllerDisconnected.Invoke();
                Debug.Log("Device disconnected");
                break;
            
            //Familiar device connected
            case InputDeviceChange.Reconnected:
                controllerReconnected.Invoke();
                Debug.Log("Device reconnected");
                
                //Checks if is Playstation Controller
                if (device.description.manufacturer == "Sony Interactive Entertainment")
                {
                    //Sets UI scheme
                    Debug.Log("Playstation Controller Detected");
                    currentImageScheme.SetImagesToPlaystation();
                    controllerTypeChange.Invoke();
                }
                //Else, assumes Xbox controller
                //device.description.manufacturer for Xbox returns empty string
                else
                {
                    Debug.Log("Xbox Controller Detected");
                    currentImageScheme.SetImagesToXbox();
                    controllerTypeChange.Invoke();
                }
                break;
                
            //Else
            default:
                break;
        }
    }

So remember when I said I was working backwards? At this point, my system detects and reacts to big controller events, but at the start of the program has no idea what controller is being used. This is where I encountered the majority of my hardship, but I’m very proud of the end result!

Something I learned quickly is that the Unity Input System stores all known input devices, regardless of them being currently connected or not. So if my player is playing on their xbox controller, it’s batteries die, and they switch to their PS controller, InputSystem.devices still stores both devices. Additionally, while the type Device has a property enabled, this returns true for all devices registered to the Unity Input System. So all of this results in perhaps the most notable roadblock I encountered with this System: I could not find a way in which the Unity Input System distinguishes between the currently connected/in use controllers and those which are simply known by the Unity Input System, ie not in use at all. Devices can easily be individually assigned, and switched between, and detected, but there is no property of the Type Device (that I could find) which represents whether an individual device is actively connected. I pray I’m wrong about that, but the closest I could get was InputDevice.lastUpdateTime, which InputSystem inherits from. Even that field returns extremely unreliable values, as on PS it counts gyroscopic data as input, every frame, and in my tests, was returning the same value for all my devices, connected or not.

TlDr; I could not find a way to distinguish between connected and non connected input devices.

My response to this, however, is where I’m most proud of this code! I found that if I remove all the stored devices on game start, the controller being used by the player instantly re-constructs itself, and is quickly the only device stored in InputSystem.devices[]. From here, it’s easy to just check InputSystem.devices[0].manufacturer, and we have the same check on game start as we do when a controller event happens. Here’s that code!

Once again, currentImageScheme is my SO

//Called in GameManagerGameStart() to set the UI initially
    private void UIImageSchemeInitialSet()
    {
        //Disables all devices currently read by InputSystem
        for (int rep = 0; rep < InputSystem.devices.Count - 1; rep++)
        {
            InputSystem.RemoveDevice(InputSystem.devices[rep]);
        }

        if (InputSystem.devices[0] == null) return;
        
        //Checks the first slot of the InputSystem devices list for controller type
        if (InputSystem.devices[0].description.manufacturer == "Sony Interactive Entertainment")
        {
            //Sets UI scheme to PS
            Debug.Log("Playstation Controller Detected");
            currentImageScheme.SetImagesToPlaystation();
            _currentController = CurrentControllerType.PlayStation;
            controllerTypeChange.Invoke();
        }
        else
        {
            //Sets UI scheme to XB
            Debug.Log("Xbox Controller Detected");
            currentImageScheme.SetImagesToXbox();
            _currentController = CurrentControllerType.Xbox;
            controllerTypeChange.Invoke();
        }
    }

From there, to complete my system, all I had to write was a MonoBehaviour which stores a reference to my SO, and listens to my controllerTypeChange event, reacting by just setting its image to the corresponding image stored in the Scriptable Object. Here’s that code, for those who are curious!

public class ControllerImageScript : MonoBehaviour
{
    public UIImageSchemeSO uiImageScheme;
    
    public enum buttonImage
    {
        southButton,
        eastButton,
        northButton,
        westButton,
        rightTrigger,
        leftTrigger
    }

    public buttonImage myButtonImage;

    private RawImage myImage;
    
    // Start is called before the first frame update
    void Start()
    {
        myImage = gameObject.GetComponent<RawImage>();
        SetButtonImage();
    }

    public void SetButtonImage()
    {
        if (myButtonImage == buttonImage.southButton)
        {
            myImage.texture = uiImageScheme.GetSouthButton();
        }
        else if (myButtonImage == buttonImage.eastButton)
        {
            myImage.texture = uiImageScheme.GetEastButton();
        }
        else if (myButtonImage == buttonImage.northButton)
        {
            myImage.texture = uiImageScheme.GetNorthButton();
        }
        else if (myButtonImage == buttonImage.westButton)
        {
            myImage.texture = uiImageScheme.GetWestButton();
        }
        else if (myButtonImage == buttonImage.leftTrigger)
        {
            myImage.texture = uiImageScheme.GetLeftTrigger();
        }
        else if (myButtonImage == buttonImage.rightTrigger)
        {
            myImage.texture = uiImageScheme.GetRightTrigger();
        }
    }
}

I spent all day on this code, and I found it a surprisingly underdocumented topic. I hope that this proves helpful to those who are in my situation, just as all the wonderful discussions/threads online helped me!

The Wicker Design Breakdown and Update

Hello all! Happy winter! I wanted to write a post today to document and detail what I have been working on over my winter break from school! As I had briefly mentioned in my October Update, I am currently working on a Vertical Slice of a horror game, which is now confidently titled The Wicker. I have been working on this side project with 1 other developer since October, and we are approaching the final sprint of our development. I have worked as the only Engineer and have worked together with my partner (Alan Karbachinsky) on the design of this game. Additionally, we have been working with a composer, Zoe Morfas, to provide music and SFX for our highly sound centric design.

The Wicker is played on the Mouse and Keyboard, and experiments with using abstract environmental feedback as a means of communicating game state to the player. The experience goal of this game is to make the player feel as though they are attempting to solve a puzzle with a blindfold. A dark and constantly unsettled abstract arena is accompanied by another entity’s looming presence, causing discomfort and fear. Additionally, we hope to instill uneasiness in players through interpretation & understanding of environmental factors. Below, I would like to breakdown some of the nuances and highlights (in my personal opinion) of the design of my WIP game, The Wicker.

Overview

Players are spawned into a 3 dimensional environment, surrounded by suspended and spinning cubes (shown above). Players can walk through these suspended cubes, which disappear upon contact, but the player is slown down by this. The environment is dark, and players hear a constant ambiance “drone” (which is being played through a low pass). Players use general WASD movement and First Person mouse input to move and turn. Holding shift allows players to sprint, endlessly, but they are slowed to the same speed upon walking into a cube. Using the mechanics here and below, players are tasked with finding the seer eye, and delivering it to the socket. Both of these objectives are hidden in the game arena, which takes a sprinting player about 45 seconds to traverse along the x axis, and 25 along the y axis.

The Game Loop:

The “30 Second Test” Loop:

Mechanics

Create Tunnel – Left Click: Casts forth a conjured wall from the player, in the facing direction. This wall removes all suspended cubes it comes into majority contact with. This is used by players to create pathways down which they can move, unhindered by the slowing effect of the cubes. This wall moves much quicker than the player. This ability is on a 5 second cooldown.

Cast Light – Right Click: Casts forth a light orb from the player, in the facing direction. This light orb provides some light as it moves through the air, but upon contact with a cube it will turn that cube into a light cube, greatly illuminating the surrounding area. This light orb moves only slightly quicker than the player’s sprint speed, and will expire after 7 seconds uncontacted. This ability is on a 7 second cooldown.

Connect with Objective – Mouse Input: To understand where the current objective is located, players must use their mouse to look in a full circle, and proceed in the direction indicated by both an audio stimulus (low pass filter removed from the ambiance drone) and a UI indicator (a music note in the bottom right hand corner). This mechanic is a simple collider on the objective and raycast from the player forward, but I have attempted to utilize the affordances of this schematic to our advantage. The Objective (which has the collider on it) is programmed to manipulate the size of the collider, as well as just outright disable it for brief moments. This dynamicity adds a nice touch of uneasiness in one of our core mechanics. An example of this in action would be if a player is pursuing the direction in which they receive the auditory & visual cures, this is because their raycast is hitting the objective collider. but as they pursue that direction, their raycast may lose contact with the collider, causing their cues to cut out, and forcing them to realign to the movement of the objective collider. This mechanic is something I worked a lot on in the beginning of production, and I feel has turned out very fun. I was BEYOND excited to see an extremely similar (pretty much identical(and much more polished)) mechanic present in Spider Man: Miles Morales, released a few weeks ago (The Sound Sample minigame located throughout the map).

The Spirit

Players are being pursued. The spirit AI is trying to stop the player from bringing the eye to the socket, and although the AI itself is a simple navigation mesh agent, I have tried to create complexity through the affordances of such a style of pursuit. For those unfamiliar, nav-mesh agents are given a destination, and will pursue at a set speed, along specified meshes. In The Wicker, this destination is set every frame to be the player’s current location. However, since the player spends a majority of their time moving, this often leads to the AI approaching the player from behind. This was something we didn’t want, as we thought the player should always be able to witness the spirit…. atleast a little. Instead, I began to view the nav-mesh agent component of the enemy as more of a dynamic timer: in a constant pursuit of the player, at a manipulated speed, but with the mesh renderer disabled entirely, and just counting down until it will trigger an actual enemy encounter. This count down is dynamic because it is affected by the player’s movement. In more plain terms: The nav-mesh agent has no visual representation while it is in pursuit of the player. The enemy is simply an invisible collider in pursuit of the player… however, once this AI reaches the player, this begins what we have dubbed the encounter sequence, which is where the player will actually run the risk of being dragged to their death by the spirit.

Encounter Sequences

Once the invisible nav mesh agent has reached the player’s position, 1 of 2 things can happen.

The first thing: The AI is teleported to 1 of 5 positions in front of the player, and an encounter begins. The encounter is indicated by an audio cue (screams), an environmental cue (change of color of light cubes and directional light), a “juice” cue (camera shake), and then of course the appearance of the enemy. The enemy’s mesh renderer becomes enabled during an encounter sequence, and the enemy is now pursuing the player head on. The teleport locations are relative to the player’s position & rotation, and are at such a distance from the player that the enemy spawning is un-rendered by the player camera. Additionally, there is a slight (purposeful) delay between the enemy appearing and the cues listed above^. This is done to help time-up the player & AI’s encounter with the mood shift of the level, while still indicating to the player that they are in immediate danger. This style of encounter ends when either the player creates enough distance between themselves and the AI, or the AI catches the player. This sequence can also be escaped by placing the eye in the socket, and thus winning the game.

The second thing that can happen is the AI “fakes out” the player. In an effort to create the mood of uneasiness, I added this style of encounter which has the same probability as all other encounters (specifically the AI is either teleported to 1 of the 5 positions above, or sent into this “fake out” sequence). In the fake out sequence, the enemy is first made visible far out in front of of the player. This is where the “encounter” cues are enabled, all the same as the above encounter type, but with the use of a specific audio. The AI’s position is then lerped from there to directly in front of the player. Right before it runs through the player, the enemy vanishes, a low pass filter is applied to the specified audio, and the bot is teleported to it’s spawn location. Then the encounter sequence ends (the AI becomes invisible and all encounter indicators are disabled).

Here I’d like to share the progression of the “fake out” in a few gifs.. Please ignore T-pose, still in development! Top: Experimenting with working a “fake” encounter into my existing system. Middle: Added position lerp and environmental reaction. Bottom: Added a new shader meant to conceal the unrealistic movement of a direct position lerp.

Movement

To avoid the simplicity of the generic nav mesh agent pursuit, I created a unique movement scheme meant to give the agent a more frightful approach (in one of the normal encounters). So, just to clarify, while the enemy AI is invisible (and essentially a dynamic timer), this movement scheme is NOT being used. This movement solution is only for when an actual encounter (non fake out) begins. The first thing I did was give the AI 6 children locations (which move relative to the AI’s position) that it could choose to move to. All are in front of the AI (aka the direction it is heading), with 3 to the AI’s left and then 3 mirrored on it’s right. Then, I placed the AI’s movement in a thread where, upon reaching a set time interval, it will teleport itself to one of the nearby child locations, randomly. This worked well, and was very promising with how unsettling the movement felt, but it was still rusty and missing something.

To try and mask the obvious teleporting of position, created a second thread within the initial one mentioned above. This thread will be called once per movement interval, and essentially applies 1 of the 3 shaders we have for our enemy, randomly. Our enemy shader is brilliant, and the only thing in this game we have not produced ourselves. All credit to Inan Evin on the Unity Asset Store. This “glitch” shader proved to be very customizable, and we were easily able to transform them into proper “horror” material. Adding these shaders into the movement quickly made the movement more fear inciting, as well as masked up the “transitions” in the movement scheme I had built. Here’s a gif of it all put together!

Abstract Environmental Feedback in The Wicker

One of the core features of The Wicker that I have not yet touched on is the significance of the spinning cubes. This is one of the many things in our environment which we are trying to use to communicate the game state to the player. With The Wicker, we set out to toe the line of how little information we would directly be giving to the player for them to understand our complex game. This has proven an immense task in design, but one of the ways we chose to address this was using the environment to communicate with the player. The ways in which we have chosen to do this are all in pursuit of an visual aesthetic goal of minimalism, and other worldly-ness, and “play feel” aesthetic of curiosity & fulfillment, relying heavily on pattern recognition, and all with a tone of horror, of course!

The spinning cubes, and more specifically the speed at which the cubes rotate represents how close the enemy AI is to the player IN ALL STATES. So even while the AI is invisible, and a dynamic timer, the cubes are still spinning relative to how close the AI is to the player’s position (with SODA I’m sure you see how this is so easy). Managing literally thousands of cubes is no small task, and I opted out of using ECS. So all of the managing of my cubes comes down to rigorous abstraction from cube, to individual “storm unit” (around 90 cubes), and then to a storm manager. The storm manager controls which cubes are rendered (once again relative to players location(SODA, DUDE!)). The storm manager also dishes out the tick to each individual storm unit, which then rotates each cube accordingly. The spinning of the cubes set out to do 2 things: Tell the player about their position relative to AI and incite an uneasiness/faster pace while the AI is near.

The lights in The Wicker are another facet through which we try to communicate the game state to the player. As can be seen in many of the gifs above, the default lighting color of all lights in the game is either a bright purple(player spawn light), or a purplish-pink(lights cast by player). However, upon an enemy encounter, all lights in the scene are changed to a deep red for the duration of the encounter. This, in turn, not only signals to the player the beginning of an encounter, but the end to one as well. This feature of the lights is what adds the most value to the “Cast Light” mechanic. Ideally, players are using their lights throughout the duration of the game, and by placing them throughout the arena they are able to better understand the state of their encounter. In addition to turning the light cubes color, other environmental lights are also signaled on the event of an encounter, and switch to the same deep red. This includes the spawn light and the directional light.

The ambiance is another extremely important indicator to the player. While the player is faced away from the current objective, the ambiance audio (a mild drone and occasional chirps) is played under a low pass audio filter. However, when the player is facing the current objective, the frequency cutoff of the low pass is disabled, creating a noticeable jump in audio, which is also synced up with a UI indication on the player’s screen. In our playtesting, we found that allowing players both the visual and audio cue made it easier to understand that there may be an interest in continuing that direction. Our use of the ambiance in this way has become one of the integral features to one of our core mechanics, “connect with objective”.

Tutorialization of Our Unconventional Game

Left Image: the Hub a few months ago.
Right Image: the Hub a few minutes ago.
Note: time is relative.

Up to this point, all I have discussed is what me and my peer consider to be the actual game, however it is not the only part of our experience. In fact, it’s not the front facing part of the experience at all. Players spawn into The Wicker in the “Hub”. This hub is where we have placed all the tutorialization of our game. The Wicker has not been an easy game to teach people… as I’m sure you understand at this point in the post! There is a lot of intended “learn on your own” moments. With that said, we have worked hard to tutorialize in a way that would remain true to our aesthetic goals, and came onto 5 main solutions. Within the hub, players are not pursued, and they interact with the environment to become familiar with their surroundings, before being led to a teleport into the “actual” game.

Interactable environmental “signs” in the hub, which host pop-up text, are an extremely effective way of communicating a LOT of information while remaining within an extremely realistic scope. Through a simple event based prefab, multiple of these were placed around the hub. To stay to the intended aesthetic, the text is carefully written, and my teammate has modeled an awesome “obelisk” with etchings on the side, which are tied to the interactable text pop-up. These give our hub some visual depth, as well as a platform from which we can communicate necessary tutorial information to the player’s who seek to complete the experience.

Scrolling mechanic explanation HUDs at the bottom of the screen, which are only present while in the hub, have become a useful tool for us. We use this “scrolling text” (4 tips which alternate in 5 second intervals of being displayed at the bottom of the screen) as an opportunity to explain the core mechanics of our game, as well as delineate the Hub area from the main game. When players enter the “actual” game, this UI disappears completely, but reappears as soon as the player is back in the hub. This is meant to create a more “tutorial” representation of the hub in the player’s mind, and separate it from the actual experience.

The eye and socket serves 2 great purposes. Firstly, it allows players to interact with the eye & socket mechanism, which helps create familiarity when they encounter it in the “actual” game. Secondly, once the player places the eye in the hub socket, a light appears and leads them towards a bright light in the distance. Upon approaching that light, players hear a “drone” noise getting louder and louder. As the players walk into the blindingly bright light, the noise now begins to gain heavy bass (as a low pass filter is lifted). Just as the noise reaches max volume and weight, it cuts completely silent. The screen remains blinded by light, but the player has actually been teleported out of the hub, and into the main game arena for the beginning of the game.

A splash screen was good enough for Start Wars, so it’s good enough for us. By splash screen I mean at the beginning of the game, when the player enters from the menu, a chunk of text pops up on their screen. The text is meant to heavily adhere to our aesthetic, but beneath the cryptic language it depicts the general goal of the game. Bring the seer eye to the socket. Additionally, having this text upon entry into the game helps to set the narrative tone of the game.

I told you it’s cryptic!

A comprehensive narrative direction is foundational to all the other solutions listed above, as it helps us adhere completely to our aesthetic goals, and create an equally unsettling and intriguing experience for the player. Additionally, writing some background on the world in which The Wicker takes place has helped give us a narrative voice through which we can use to communicate with the player, while staying true to our aesthetic goals.

Final Thoughts

The Wicker has been a real amazing game to work on. I have enjoyed both the engineering and design side of the project, and am excited to see it finally taking a form which is more true to our expectations for the project. As I mentioned above, we are entering the final sprint in this coming week, and plan to have a “release candidate” build probably sooner than that 2 week sprint ends. I will absolutely be posting the itch link here, and it will of course be free. I’d like to leave you, for now, with a gameplay video! This will hopefully be good reference for understanding all the audio mentioned above. Thanks for reading, and have a great day!

Splat-mapping Abstraction in Unity

Hello all! I’m posting today with a brief follow up on my post about terrain texture detection in Unity. If you haven’t checked that out, it will definitely be informing this post! I have been working further on getting this system to work with my current PlayerMovement system, and have come to a neat solution of abstraction which can be branched to communicate anywhere in your game what the current terrain texture is beneath the player.

Here’s what this solution solves:

Our player’s movement is effected constantly by what type of ground the player is on. So for example, braking power & amount of friction (2 separate variables in the PlayerMovement). Since we need these values to be dynamic, a layer of abstraction is helpful in allowing these values to be continuously modified by factors such as the terrain.

Our designers have not finalized the variables effecting movement at large, this solution allows for testing of multiple combinations of these movement variables as extreme ease to the design team. This will be used for AB testing in our playtests down the road.

The solution:

It’s very simple, but has helped a lot with working this data into my PlayerMovement in a clean and clutter free way. All I have done is created a new Scriptable Object type, MovementVariables, and moved all of the “designer variables” into this scriptable object. Additionally, I have created a simple class, TerrainType, which stores all of the variables that are dynamic and dependent upon the terrain the player is on. I’ve made this class serializable, and within my MovementVariables I have a public array of TerrainType that allows the designers to set each terrain type uniquely for each variant of the MovementVariable type.

MovementVariables has a public function, SetToTerrain(), that takes in an int representing the terrain texture the player is currently on (remember this is stored in an int by the Alpha map). Upon taking that int, MovementVariables will set the necessary variables to match those of the corresponding TerrainType in the local array. So, for example, terrainTypes[0] is created by the designer to have float frictionLevel = 1, and float brakingPower = 3. Once SetToTerrain() takes in an int 0, then MovementVariables will set the frictionLevel and brakingPower according to whatever is in terrainTypes[0].

From here, all that is necessary for setup (besides setting the designer variables) is to create a reference to a MovementVariables in both the PlayerMovement and the TerrainTextureGetter. The former will simply read the scripted values from this Scriptable Object, and the latter will pass the int representing the texture into SetToTerrain().

… and that’s it! It’s a super simple solution but really has helped me in passing these terrain settings into my movement, in making my PlayerMovement less beefy, and in aiding the design team with finalizing their movement variables… Here’s some code

[System.Serializable]
public class TerrainType
{
   public string terrainTypeName;
   [Range(0f, 100f)]public float stridePushPower;
   [Range(0f, 15f)] public float brakingPower;
   [Range(0f, 5f)] public float slidingFrictionPower;
   [Range(0f, 10f)] public float turningSpeed;
}

This is the TerrainType class which is key in allowing this abstraction to work. These are the variables in PlayerMovement which we want to be altered by the terrain type beneath the player.

//Variables set by terrain
[ShowOnly]public float turningSpeedMod;
[ShowOnly]public float slidingFrictionPower;
[ShowOnly]public float brakingPower;
[ShowOnly]public float stridePushPower;

public void SetToTerrain(int terrainNumber)
{
    if (terrainNumber == 0 && terrainVariableSets[0] != null)
    {
        turningSpeedMod = terrainVariableSets[0].turningSpeed;
        slidingFrictionPower = terrainVariableSets[0].slidingFrictionPower;
        brakingPower = terrainVariableSets[0].brakingPower;
        stridePushPower = terrainVariableSets[0].stridePushPower;
    }
    else if (terrainNumber == 1 && terrainVariableSets[1] != null)
    {
        turningSpeedMod = terrainVariableSets[1].turningSpeed;
        slidingFrictionPower = terrainVariableSets[1].slidingFrictionPower;
        brakingPower = terrainVariableSets[1].brakingPower;
        stridePushPower = terrainVariableSets[1].stridePushPower;
    } 
    else if (terrainNumber == 2 && terrainVariableSets[2] != null)
    {
        turningSpeedMod = terrainVariableSets[2].turningSpeed;
        slidingFrictionPower = terrainVariableSets[2].slidingFrictionPower;
        brakingPower = terrainVariableSets[2].brakingPower;
        stridePushPower = terrainVariableSets[2].stridePushPower;
    } 
    else if (terrainNumber == 3 && terrainVariableSets[3] != null)
    {
        turningSpeedMod = terrainVariableSets[3].turningSpeed;
        slidingFrictionPower = terrainVariableSets[3].slidingFrictionPower;
        brakingPower = terrainVariableSets[3].brakingPower;
        stridePushPower = terrainVariableSets[3].stridePushPower;
    }
    else if (terrainNumber > terrainVariableSets.Length - 1)Debug.LogError("Unsupported Terrain Type. Add to MovementVariablePackage");
}

This chunk of code above is located within my MovementVariables Scriptable Object. The top variables are what are being referenced within PlayerMovement, but they are being set below. SetToTerrain() is called every time a terrain texture change is detected. An important note is the [ShowOnly] editor attribute was written and made public by Stack Overflow user Lev-Lukomskyi. Huge thanks for that, as it keeps the designers from touching things they shouldnt ;). I’m just kidding… I hope that this post was helpful for anyone who needed a follow up from my last post about terrain texture detection! Until next time!

Detecting Textures Across Multiple Terrains In Unity

Hello all, I hope that as you read this, all is well in your world. The last week has been a truly fitting capstone on the crazy year that has been 2020, especially for U.S. citizens. I want to take your mind off the madness for a moment and talk about something I worked on over the last few days.

As you can read about in my October Update, I’ve been making a skiing game! It’s been awesome so far, and really enjoyable. Something that is crucial to the design of our game is different textures on the terrain beneath the player having differing effects on gameplay. For example, we have a texture that represents “heavy snow”, which will bring down the speed at which the player treks through the snow, as well as an “ice” texture, which would instead speed the player up at a much higher rate, and slow down at a much slower rate. The process itself is not complicated, it just involves getting the terrainData of terrain below the player, and using the player’s relative position to the terrain to calculate what the texture is that is present at that point on of the terrain, and in what strength. While these calculations are somewhat daunting (involving the 3D float array used to represent an alpha map), they’re actually not too complex when broken down.

Where my specific need for this process differs from a lot of what I’ve seen online is that our Unity scene involves upwards of 20 separate terrain objects (and therefore over 20 individual TerrainData data type). The solution here for me was to setup a function to work in tandem with the terrain splat mapping calculations above. This function takes the player’s position, and compares it to the center point of the terrains stored in the array Terrain.activeTerrains. The terrain returned is then interpolated on to determine what textures is beneath the player. Here’s the code return the closest terrain to the player! Just remember I use Scriptable Objects to store live values (such as player posisiton), hence the need for me to specify “.value” to get the Vector3.

Terrain GetCurrentTerrain()
{
    //Array of all terrains
    Terrain[] totalTerrains = Terrain.activeTerrains;

    //Checks on length
    if (totalTerrains.Length == 0) return null;
    else if (totalTerrains.Length == 1) return totalTerrains[0];
    
    //closest terrain, Initialized with totalTerrains[0]
    Terrain closestTerrain = totalTerrains[0];
    
    //Center of terrain at totalTerrains[0]
    Vector3 terrainCenter = new Vector3(closestTerrain.transform.position.x + closestTerrain.terrainData.size.x / 2, playerPos.value.y, closestTerrain.transform.position.z + closestTerrain.terrainData.size.z / 2);
   
    //will be closest distance between player a terrain. Initialized with totalTerrains[0]
    float closestDistance = Vector3.Distance(terrainCenter, playerPos.value);
    

    //Iterate through list of all terrains
    for (int rep = 1; rep < totalTerrains.Length; rep++)
    {
        //currently selected terrain
        Terrain terrain = totalTerrains[rep];
        terrainCenter = new Vector3(terrain.transform.position.x + terrain.terrainData.size.x / 2, playerPos.value.y, terrain.transform.position.z + terrain.terrainData.size.z / 2);

        //Check on distance compared to closest terrain
        float d = Vector3.Distance(terrainCenter, playerPos.value);
        if (d < closestDistance)
        {
            closestDistance = d;
            closestTerrain = totalTerrains[rep];
        }
    }
    //Returns the closest terrain
    return closestTerrain;
}

So now that we have the closest terrain to our player, we need to convert the player’s position in the game to their position on the specific alpha map. This process looks something like this, noting that currentTerrain has just been set to whatever is returned by GetCurrentTerrain():

void GetPlayerTerrainPosition()
{
//Player position relative to terrain
Vector3 playerTerrainPosition = playerPos.value - currentTerrain.transform.position;

//Player position on alphamap of terrain using offset
Vector3 alphamapPosition = new Vector3 (playerTerrainPosition.x / currentTerrain.terrainData.size.x, 0, playerTerrainPosition.z / currentTerrain.terrainData.size.z);

//Properly scales players x and z
float xCoord = alphamapPosition.x * currentTerrain.terrainData.alphamapWidth;
float zCoord = alphamapPosition.z * currentTerrain.terrainData.alphamapHeight;

//Casts as int and sets
xPos = (int)xCoord;
zPos = (int)zCoord;
}

We get out of this call with now our 2 xPos and zPos fields set to the player’s coordinates on the terrain. All that’s left is to take these coordinates and get the alpha map at the player’s position, and determine which terrain texture is applied at that location. One important note is how alpha maps store references to textures. the alpha map is a 3d array where the third value refers to the texture being checked for. For example alphaMap[0,0,0] will return the strength of the texture in slot 0 of the terrain texture layer. alphaMap[0,0,1] will return the strength of texture in slot 1. Hence, splatmapping! We are able to interpolate on various combinations of strength of textures, not just simply player is or isnt on ice. Instead, we can say 30% ice, 70% regular snow, and have our movement variables adjust to that specific combination…. I’m getting off track, but just know this:

textureValues[] is an array of floats representing the strength of each texture at the specified x & z pos. The length of this array is simply set to the number of textures in our terrain layers.

here, rep is used to tie the corresponding spot in textureValues to the value of the texture in that slot of the 3d array

SetPlayerMovementVariables() is currently where we are interpolating on the data gathered here, but essentially the value is clamped from 0 to 1, representing how much of the splatmap at that point is of the texture in the corresponding spot in aMap, and from there we are setting values in our PlayerMovement script. Take a look!

//gets the float (clamped between 0 and 1) of how much of each texture is present at the x & z coord
void CheckTextureBelowPlayer()
{
//Will store the alpha map of the current terrain
float[,,] aMap;

//Uses x position and z position to set aMap to correct alpha map
aMap = currentTerrain.terrainData.GetAlphamaps(xPos, zPos, 1, 1);

//textureValues stores the current stength of the texture stored in the corresponding slot in the alpha Map
for (int rep = 0; rep < textureValues.Length; rep++ )
{
//stores stength of values at that point
textureValues[rep] = aMap[0, 0, rep];
}


//Iterates through to check if any values are greater than 0
for(int rep = 0; rep < textureValues.Length; rep++ )
{
//If terrain is present, sets player movement values
if(textureValues[rep] > 0)
{
SetPlayerMovementVariables(rep, textureValues[rep]);
}

}
}

This flow of operations effectively allows my team’s PlayerMovement script to iterate as usual, but be fed different live values decided by the terrain type. So far this works really well for me, but if I find more to change and tweak, I absolutely will update it here! I hope this helps anyone who is setting out to do this themselves, much like I was a few days ago! Hope you enjoy the code!

October 2020 Update

Hello All! I’m deep in the semester currently (my second to last), but wanted to pop in here to try and share what I have been working on game wise! Firstly, if you haven’t checked out Liquidators yet, please please do! The game can be found on Steam and Itch.io, and you can read a little bit about the mission here. I worked as the Lead Producer and Designer of this project over the last year, and am SUPER proud of it. This has been taking up a fair amount of my time lately, as I have learned more about the “post-production” process. It has been amazing experience seeing our game played by so many online, and being received so well in reviews. We just passed 14,000 units today, and taking part in this awesome team has been one of the most fullfilling experiences in my life. Here is a video of Tomato Gaming playing Liquidators in a livestream… One of my favorite playthroughs so far!


Aside from post-production work on Liquidators, I have also been working on a new full year project. I have been working as the Lead Engineer on The Trials of Snowshoe Thompson. The game emphasizes a unique movement mechanic, and realistic skiing physics, to take players through a narrative experience based around the famed mail delivery worker, John “Snowshoe” Thompson. This is by far the largest project I have ever taken part of, with over 20 team members, and we hope to be finished with Alpha sometime in January. The project has truly tested me as an Engineer, and I have loved every second of it. I lead a team of 5 other Engineers, and so far the most rewarding part of the game is working with such hard working individuals. Communication is something I think is most valuable in a game development environment, and being entirely remote has presented me with some unique challenges, but having a hard working and dedicated team has really made these issues non-existent.

So far, our Engineering team has conquered a lot of ground… I’m not sure how much I should really be showing of this, but ANYTHING for my website… (and I’ll keep it pretty concise for now). We have almost completed the movement system which is meant to simulate a realistic skiing experience. Furthermore, we have implemented a unique style of map meant to entirely replicate how a traditional map would be used to triangulate one’s position. The mechanic utilizes a compass, and a functionality which aligns the Map’s north with the World’s north. Obviously there is a lot more at work, but these are the most unique systems that we have (and the easiest to show off here). If I had to highlight any specific code chunk as my favorite from the project so far, I would have to say it is how we are handling the Player movement state. The Player is currently under the control of a finite state machine, which has 3 states: Moving, Not moving, and Map. But the movement state contains a sub-state enum which allows us to pretty seamlessly (and realistically) play with the player’s ability to move in certain scenarios. For example, a substate in the Movement State is “Right Stride Window”, which allows us to track (from within our Movement State tick) when the window for another stride opens up, and then expect/adjust behavior accordingly. And then based on this behavior, the Player Script (not to be confused with the state tick) will operate and adjust the active substate, which is then fed back into the Player Movement State Tick… Here’s a clip of some of our skiing movement!

And then a clip of the Map in action!

I’d like to emphasize that this has been a team effort for all of what we have accomplished, and I don’t in any way mean to take credit for this. These have been a product of the hard work of my team, and I’m just lucky to have people who care enough about a good product.

The final project that I’m currently working on is for a class, and the production takes place over about 6 weeks, but we plan to take this to a full vertical slice, by working through the month of December. So far, we are about 4 weeks in. The game currently has a working title of Wicker. The basis: You are being pursued. You must run through a storm of cubes to escape. The closer the pursuer gets to you, the faster the storm moves. Running through the storm slows you down. Left clicking clears a tunnel infront of you, right clicking places a light for you to see…. I’m not sure if that makes sense, but hopefully images and clips will help you understand!

I’m working on this project with the same team as made Boomer & Zoomer!
I have been writing all of the code, and have really enjoyed my first venture into a “pursuer” style horror game. While we are still working on a lot of the horror aspects, we have finished a fairly comprehensive sound system which plays a pivotal role in one of our core mechanics. Essentially, players are following their ears, trying to reach an objective, while evading an ever pursuing AI. Here’s a clip of some gameplay we have:

Thanks for checking in guys! I really appreciate the continued support on my site! I’ll hopefully have something playable for you in the next post from both Snowshoe and Wicker.

Avoidance AI Behavior

Hello all, I hope everyone has had a nice holiday season, and beginning to their year! I’ve been pretty busy trying to keep up with my game projects just as much as trying to keep up with my friends who’ve also been on break! But amongst all this I’ve actually been working on a ton of code for “Project RAT”, as my friends and I have been calling it. And specifically, over the last 2 days I’ve been working on a behavior for one of our ranged enemy types, who is meant to avoid the player, and avoid collisions with walls and pitfalls.

How I did this wasn’t terribly difficult, but it definitely proved very fun to write as it did take a fair amount of effort! It operates on a List which stores all Walls and Players OnTriggerEnter() and removes them OnTriggerExit(). From there I’m just taking the Vector of the object in my list, normalizing it, and multiplying it by the reverse distance from the transform of the enemy. Oh! and then we made that shit negative. Easy enough! We go through all elements in the List with that same operation, adding the resulting Vector2 to a field which we then use Vector2.MoveTo()to send our enemy in that direction.

This Behavior is complete, but still requires some polishing! I would like to make the movement of the AI smoother and less exact, while also creating a better catch for when the AI is backed into a corner (as of right now it just stops moving). The Bot has 2 states. The idle state is obviously just a place holder which has the bot pacing back and forth. The Avoid behavior is the real highlight here! But without further ado, here’s some code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//The abstract keyword enables you to create classes and class members that are incomplete and must be implemented in a derived class.
public class Avoidance: MonoBehaviour
{
    //Variables for the walk speed, and the duration of the pace back and forth
    public FloatVariable walkSpeed;
    public FloatVariable paceDuration;
    public FloatVariable visionDistance;
    public StringVariable wallsTag;
    public SphereCollider aiVision;


    //Arr to store all obstacles, Vector for avoid, bool for is avoiding, and then moveRight for idle
    public List<GameObject> Radius = new List<GameObject>();
    public Vector2 avoidVector = new Vector2();
    public bool isAvoiding;
    public bool moveRight;
    private bool playerIn;

    //Timer used for pacing back and forth
    private float timer = 0.0f;


    private void Start()
    {
        aiVision.radius = visionDistance;
    }

    // Update is called once per frame
    void Update()
    {
        //As long as not avoiding
        if (!isAvoiding)
        {
            //Will proceed with idle
            idleMovements();
        }
        else
        {
            AvoidMovement();
        }

    }

    //The default for the enemy with no walls or players around
    private void idleMovements()
    {

        //Will AI move to the right
        if (moveRight)
        {
            //moves AI right
            transform.Translate(2 * Time.deltaTime * walkSpeed, 0, 0);

            //flips AI
            transform.localScale = new Vector2(transform.localScale.x, transform.localScale.y);
        }
        else
        {
            //moves AI left
            transform.Translate(-2 * Time.deltaTime * walkSpeed, 0, 0);

            //overrides the central motherboard of the player, installing 15 terabytes of My Little Pony episodes
            transform.localScale = new Vector2(-transform.localScale.x, transform.localScale.y);
        }

        //adds to timer
        timer += Time.deltaTime;

        //once timer is over the duration
        if(timer > paceDuration)
        {

            //move left
            if (moveRight) moveRight = false;
            else moveRight = true;

            //reset
            timer = timer - paceDuration;
        }       
    }


    //Controls avoidance behaviors
    private void AvoidMovement()
    {

        transform.Translate(Vector2.MoveTowards(transform.position, avoidVector, 1) * Time.deltaTime);
        
    }


    //Recalculates the avoid vecotr for avoidance behavior
    private void RecalculateAvoid()
    {
        //clears vector2 and instantiates a temp for reverse distance
        float tempDistance = 0f;
        Vector2 tempVec;

        //If the player isnt present or if the player is present and there is only 1 wall
        if (!playerIn || playerIn && Radius.Count < 3) { 

            //Iterates through array of Positions
            for (int rep = 0; rep < Radius.Count; rep++)
            {
                tempVec = new Vector2(Radius[rep].transform.position.x, Radius[rep].transform.position.y);
                ClearAvoid();


                //Gets the reverse distance from the AI transform.
                tempDistance = (visionDistance - (Vector2.Distance(tempVec, transform.position)));

                //Normalized and multiplies by reverse distance
                tempVec.Normalize();
                tempVec *= tempDistance;

                //Adds to the "avoid" vector
                avoidVector += tempVec;
            }

            //Flips the sign of the AvoidVector
            avoidVector = avoidVector * -1;
        }
        
        else if (playerIn)
        {
            ClearAvoid();
        }

    }

    //Resets vector field
    private void ClearAvoid()
    {
        avoidVector = Vector2.zero;
    }

    //On player or wall enter into collider
    private void OnTriggerEnter(Collider other)
    {

        Debug.Log("Triggered");
    

        //If Game Object is wall
        if(other.gameObject.tag == wallsTag)
        {
            //Set temp
            GameObject temp = other.gameObject;

            //add and recalculate
            Radius.Add(temp);
            RecalculateAvoid();
            isAvoiding = true;
        }

        //If Game Object is player
        else if(other.gameObject.tag == "Player")
        {
            //Set temp
            GameObject temp = other.gameObject;

            //Add and recalculate
            Radius.Add(temp);
            RecalculateAvoid();
            isAvoiding = true;
            playerIn = true;

            Debug.Log("Gonna recalculate!");
        }
    }

    //When a tracked object leaves the zone
    private void OnTriggerExit(Collider other)
    {

        //If player exits
        if (other.gameObject.tag == "Player")
        {

            //Iterate through List
            for(int rep = 0; rep < Radius.Count; rep++)
            {

                //If the selected tag is player
                if(Radius[rep].gameObject.tag == "Player")
                {

                    //Remove player, recalculates avoid vector, and break
                    Radius.RemoveAt(rep);
                    RecalculateAvoid();
                    playerIn = false;
                    break;
                }
            }
        }

        //If it's a wall
        else if(other.gameObject.tag == wallsTag)
        {

            //Iterate through list
            for(int rep = 0; rep < Radius.Count; rep++)
            {
                //If the position of the selected matches that which exited
                if(Radius[rep].gameObject.transform.position == other.gameObject.transform.position)
                {

                    //Remove, recalculate avoid vector, and break
                    Radius.RemoveAt(rep);
                    RecalculateAvoid();
                    break;
                }
            }
        }

        //If the List is empty
        if (Radius.Count == 0)
        {
            //Reset all
            isAvoiding = false;
            ClearAvoid();
        }
    }
}
pics make the site look cleaner 😉

Scriptable Object Events in Unity and My Cutscene Manager

Hello all. Finals season is over, and I have the most persistent cold I’ve ever experienced… which means I have a lot of time. Luckily, I’ve been working on this project with my friends and had the opportunity to write some really cool code that I’d like to share here. The other engineer on the project, and myself have been trying to build the architecture of this game utilizing a very powerful tool: Scriptable Objects. If you don’t know what these are, I highly recommend watching this talk by Ryan Hipple. TL;DR You can utilize Scriptable Objects to store data, as opposed to individual method calls and dependencies within code, to keep the architecture modular, easily debuggable, and less prone to collapse on you. A Scriptable Object will store a variable and allow any function to access it, or modify it, so that all methods can just respond to this one value. Think “global variable” that exists in the editor.

Another very cool principle in the talk by Ryan is using Scriptable Object Game Events (which take the same above principal, but utilize UnityEngine.Events;) to interface between everything in the scene that would otherwise be dependent through code. So here’s what I wrote: A cutscene manager for my game using Unity Timeline and Scriptable Object Game Events.

How it works: I have a UnityEvent StartScene that is Invoked OnTriggerEnter().

public class StartCutscene : MonoBehaviour
{

    //Event to start scene
    public  UnityEvent startScene;


    //on trigger enter
    private void OnTriggerEnter(Collider other)
    {
        //if player
        if(other.tag == "Player")
        {
            //raise
            startScene.Invoke();
            Debug.Log("Invoked");

        }
    }

}

Then this is what it looks like in editor. As a matter of fact, take my whole “CutsceneTrigger” prefab while you’re at it:

Note the UnityEvent is calling on the Game Event Scriptable Object “PlayCutscene” and Raising it. This signals to any Game Event Listeners (Once again, a Scriptable Object implementing the functionality of a Unity Game Event Listener) elsewhere in the scene that were set to listen to our Game Event Scriptable Object. In this case, I have 2 prefabs listening in on “PlayCutscene”. The first one is my CutsceneManager.

So take a nice long look at that beauty, and I think you might fully see what’s going on here. First off, I have the Game Event Listener which is making a call within my CutsceneManager Script to startNextCutscene(). startNextCutscene utilizes a Dictionary, which I have serialized at the top of the prefab. This dictionary takes the string name and the associated PlayableDirector, which is what controls Unity Timeline Playables. If you’re familiar with Dictionaries then you know they’re not serializable in Editor, but we can fix that with a nifty little work around in code.

//Serialized Dictionary Class
[System.Serializable]
public class Cutscenes
{
    public string cutsceneName;
    public PlayableDirector cutscene;

}


//Cutscene Manager will play the next applicable cutscene, storing all in a dictionary
public class CutsceneManager : MonoBehaviour
{

    //Note to design
    [TextArea]
    public string Notes = "Names should have no spaces or numbers and triggers should be place in order of encounter";

    //Array of Cutscenes, which contain our Dictionary compontents
    public Cutscenes[] toDictionary;

    //Dictionary that takes string name and PlayableDirector
    public Dictionary<string, PlayableDirector> listOfScenes;

    //Int to monitor which have played
    private int selection;

    //Unity Event to on trigger end of scene
    public UnityEvent endCutsceneEvent;


    //establishes Dictionary from serialized "Cutscenes"
    public void Awake()
    {

        //Instantiates Dictionary
        listOfScenes = new Dictionary<string, PlayableDirector>();

        //Fills that shit up
        for(int rep = 0; rep < (toDictionary.Length); rep++)
        {
            listOfScenes.Add(toDictionary[rep].cutsceneName, toDictionary[rep].cutscene);
        }


    }



    //Starts next cutscene
    public void startNextCutscene()
    {

        //Sets temp Playable Director
        Debug.Log("Signal Recieved");
        PlayableDirector temp = listOfScenes[toDictionary[selection].cutsceneName];


        //Starts cutscene
        Debug.Log("Starting...");
        temp.Play();

        //Event "stopped" is assigned to endCutscene, will call this function on raise
        temp.stopped += endCutscene; 
 
        //Increments cutscenes
        selection++;



    }

    //Invokes UnityEvent to tell rest of scene
    private void endCutscene(PlayableDirector aDirector)
    {
        //Ends the cutscene
        endCutsceneEvent.Invoke();

        Debug.Log("Cutscene Ended");
    }

}

So I’ll try to break this down for those who can’t understand my comments, but basically at the very top we have the serialized class Cutscenes, which I’m taking all the components necesary for my dictionary. Then storing them in an array toDictionary. Then, on Awake() I’m taking those individual values from the array and storing them together in my Dictionary ListOfScenes. Also note that I have a UnityEvent here at the top.

So this is where startNextCutscene() comes into play. I take the next PlayableDirector in order and store it in my temp, but note how in the search through my Dictionary ListOfScenes I’m using the corresponding string stored in the Array toDictionary to index the needed PlayableDirector. Just thought that was sick. A little wonky… but cool.

Then temp is played, which cues the animation sequence of the PlayableDirector, otherwise known as the Playable. The PlayableDirector then sets off an event upon finishing which I’m listening to and invoking my own UnityEvent endCutsceneEvent.

This is where my CameraManager comes in. Note that in the above picture of my CutsceneManager Prefab, my Unity Event endCutsceneEvent is attached to another Scriptable Object Game Event “EndCutscene”

From the bottom up, the CameraManager is listening to 2 events, EndCutscene and PlayCutscene. Remember, these are our scriptable events, any listener in the scene has access to them! So these are both invoking responses within my CameraManager script. At the top you can see the manager takes 2 Cameras, as this script is responsible for switching between the cutscene camera, used in our PlayableDirector Playables, and the main camera, used for gameplay. Don’t worry, this is not the only script working on the camera right now, this is just helping us manage between the 2 specifically with Playables. Here’s that code:

[System.Serializable]

public class CameraManager : MonoBehaviour
{

    //Declares the cameras needed
    public Camera playerCam;
    public Camera cutsceneCam;

    public void Awake()
    {
        switchAfterCutscene();
    }


    //Starts the cutscene, swapping cameras
    public void cutsceneStart()
    {

        //Cameras swapped
        switchBeforeCutscene();
        
        //Debug
        Debug.Log("play cutscene");


    }

    //Ends the cutscene, swapping cameras
    public void cutsceneEnd()
    {
        //Cameras swapped back
        switchAfterCutscene();

        Debug.Log("Cutscene end");
    }


    //Swaps active cameras
    private void switchBeforeCutscene()
    {
        playerCam.enabled = false;
        cutsceneCam.enabled = true;

    }

    //Swaps active cameras
    private void switchAfterCutscene()
    {
        cutsceneCam.enabled = false;
        playerCam.enabled = true;
    }
}

Pretty self explanatory, but just note that the methods switchBeforeCutscene() and switchAfterCutscene() are both triggered by the corresponding Game Events in scene, in addition to the startNextCutscene() in the CutsceneManager. I wanted to show you to give an example of how using GameEvent Scriptable Objects allows multiple scripts to interface with one another without ever knowing the other exists!

You have to see how this is such a god send for devs who want to keep dependencies low and the engine easy to use for designers. Here, a designer who has been briefed can easily establish a whole system of cutscenes in their level without ever having to see a single class.

Happy holidays to everyone! I’ll be back soon with more.

Object Interaction

Hello all! Wow… what a few years can do, huh? I have a habit of posting on my birthday, because that’s on of the few days a year you get to do exactly what you want, and writing code is what I want to do! And sharing that code is always fun too! So I thought that in honor of this yearly tradition, we would visit some old code! February of 2018 saw me write some of my first scripts in Unity. I was already pretty well versed in writing code, but Unity was new to me! I ended up writing a few scripts, but one of the scripts I was most proud of was my Object Interaction script. Simple, yeah I know, but I was very proud of this!

Well, here we are now as I turn 23, and am much more confident in my programming, my Unity ability, design ability, and as a human being in general! So why not create an object interaction interface that more properly represents me as I am now? Lets start with just that!

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface IInteractable
{
    string IPrompt();

    void IAction();

    bool IsInteractable();
}

So lets say that IPrompt is the string that pops up on your screen when you’re within range of interacting with the game object. IAction is what the object does upon interacting. IIsInteractable will help us turn off items after one use or once they are not to be used any more!

Easy Enough! Bye!

Just kidding…….. So from here we have to build ourselves an Interacter which we will attach to the player, that will allow the player to interact with items that inherit from IInteractable! So here I’ll sort of get our of detail and loosely tell you what’s going on here! I wanted to make the interactions like they are in Dark Souls, where you can cycle through multiple intractable objects if they are present, and choose which one you would like to interact with! So I build this interacter around an ArrayList, which stores the currently “selected” element in an int. From here, I just have an OnTriggerEnter and Exit that will add the IInteractables to the ArrayList, and allow the player to sort through their IPrompts, and interact with their different IActions!

That really is it for this one! Lots of private methods to just manage the backend of scrolling and interacting, but I’ll include it all here… Please excuse my debug notes! This was also built as a part of the Project Rat I mentioned in my previous post.. I hope you guys like this code and can see the difference in my skill from 2 years ago to now…. Well I should hope you see the difference…. Here’s some code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Interacter : MonoBehaviour
{

    //LIFO will keep 
    // private Stack<IInteractable> LIFO = new Stack<IInteractable>();
   
    //AL Instantiation    
    ArrayList InteractionAL = new ArrayList();

    public int selection = 1;



    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.E))
        {
            doSomething();
        }
        else if (Input.GetKeyDown(KeyCode.R))
        {
            scrollSelection();
        }
    }

    //Adds Object to the ArrayList if is interactible
    private void OnTriggerEnter(Collider other)
    {

        Debug.Log("Something entered my zone!");

        //Gets the game object from the collider entered
        //HERE
        IInteractable newInteractable = other.GetComponent<IInteractable>();

        //If is interatable, add
        if (newInteractable.IsInteractable())
        {
            InteractionAL.Add(newInteractable);

            //Debug Log
            Debug.Log("Added");

        }

    }

    //Removes most recent from the ArrayList on exit
    private void OnTriggerExit(Collider other)
    {
        //Debug Log
        Debug.Log("-1 in array");

        //Finds the collider exited, and removes from the AL
        InteractionAL.Remove(other.GetComponent<IInteractable>());

        
    }

    //Returns the prompt of the IInteractible 
    public string getPrompt(IInteractable temp)
    {

        //Debug Log
        Debug.Log(temp.IPrompt());

        //Returns prompt
        return temp.IPrompt();
    }
    

    //Calls IAction
    //Possibly remove if one time use only
    private void doSomething()
    {

        //Temp IInteractabale
        IInteractable temp = getSelected();

        //If not null
        if(temp != null)
        {

            //Do Something
            temp.IAction();
           
            //Debug
            getPrompt(getSelected());

        }

  
    }

    //Retrieves the IInteractable which is currently to be displayed
    //Incremented by scrollSelecion()
    private IInteractable getSelected()
    {
        //If not empty
        if(InteractionAL.Count != 0)
        {
            //castes the IInteractable as an IInteractable
            return (IInteractable)InteractionAL[(selection - 1)];
        }

        //else
        else
        {
            return null;
        }
      
    }

    //Increments the int which getSelected returns
    private void scrollSelection()
    {
        //Takes size of ArrayList starting at 1
        int tempSize = InteractionAL.Count;

        //Increments selection as long as it's not at the end
        if (selection < tempSize)
        {
            selection++;
        }

        //Resets the scroll selection to the front. 
        //I hate hardcoding this, but this wont change as long as we use .Count
        else if(selection == tempSize) 
        {
            selection = 1;
        }

    }
}

Object Pooling

Hello all! I know I know, more than 1 post in the span of a month? How lucky are you! Well the answer is you’re not, because my last post was a month and a week ago now… so calm down, you. But I do bring some cool stuff to share! Ever heard of Object Pooling in Unity? In brief: If you have a GameObject which you’re constantly creating new copies of and then destroying right after, such as a projectile or an effect, it can be really unnecessarily taxing on your CPU. So an Object Pool just instantiates all necessary instances of the GameObject at the start of the game, and just recycles them, setting them active and inactive when needed, and expanding if necessary, much like an ArrayList, to accommodate however many instances of the object your game needs.

I’m just beginning work as an engineer on a game with some of my friends at school. For now we have dubbed the game “Project Rat”, and this game is why I’ve built this Object Pooler! It’s a really easy object to build but I’ll talk a little about it. I’d like to also start by saying that I read and learned about Object Pooling from Mark Placzek. To start, we define an ObjectPoolItem Class which contains an Int that defines the initial size, a GameObject which will hold the prefab that is to be pooled, and then a bool to represent if we want this size to be dyamic, or a fixed value.

From here we define the ObjectPooler Class, which is a monoBehaviour. Here we declare the List that will be our pool, as well as the List that will contain all of the pools, for easy access by the GameManager. The pool list is then instantiated in Start(), where we then have a loop instantiating as many copies of the GameObject as defined in our ObjectPoolItem (both the GameObject and the size are defined there). In this loop the newly instantiated GameObject is set inactive then added to our List.

And… That’s really it! Then just create a function that takes a string of the tag of item being sought, and returns the first instance of an item with that tag which isn’t already .activeInHeirarchy(). It’s here that we can also create a check on the size of the List and compare it to how far we have to go into our List to get a new GameObject, and decide if we want the List to expand or not, using the bool we defined in the ObjectPoolItem, and just instantiating a new GameObject as we did in Start().

Sweet! Now you just need to keep in mind that the Object Pooler is storing inactive GameObjects, so however you call them you must remember to set them active! And then set inactive once they are no longer in use so that they may be recycled by our object pool! Here’s some code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


//Declares necessary values
[System.Serializable]
public class ObjectPoolItem
{
    public int amountToPool;
    public GameObject objectToPool;
    public bool expand = true;

}

public class ObjectPooler : MonoBehaviour
{

    public static ObjectPooler SharedInstance;

    //Declares List to hold pools
    public List<ObjectPoolItem> listOfPools;

    //Declares List to hold GameObjects 
    private List<GameObject> objectPool;


    void Awake()
    {
        SharedInstance = this;
    }

    void Start()
    {
        //Instantiates List
        objectPool = new List<GameObject>();

        foreach (ObjectPoolItem item in listOfPools)
        {
            // Instantiates the "Object to pool". sets inactive, and stores in List for number in amountToPool
            for (int rep = 0; rep < item.amountToPool; rep++)
            {
                GameObject obj = (GameObject)Instantiate(item.objectToPool);
                obj.SetActive(false);
                objectPool.Add(obj);
            }
        }
    }

    //Returns an INACTIVE instance of the pooled object, need to match tag
    //Can also specify when to expand and when not to.
    public GameObject GetPooledObject(string tag)
    {
        //Cycles through active and inactive elements in List
        for (int rep = 0; rep < objectPool.Count; rep++)
        {
            //If Inactive
            if (!objectPool[rep].activeInHierarchy && objectPool[rep].tag == tag)
            {
                return objectPool[rep];
            }

        }

        //expands necesary lists all at once
        foreach (ObjectPoolItem item in listOfPools)
        {
            if (item.objectToPool.tag == tag)
            {
                if (item.expand)
                {
                    GameObject obj = (GameObject)Instantiate(item.objectToPool);
                    obj.SetActive(false);
                    objectPool.Add(obj);
                    return obj;
                }
            }
        }

        return null;
    }
}

Boundless

I have been really really bad about maintaining this site for the last few months… I’ve been super busy with school… and for that I am sorry… but I have something to offer to win your forgiveness… A game! This last weekend I participated in a Game Jam at USC run by MEGA. My team was made up of Sheehan Ahmed and Myself as engineers, Taygh Atwal as level designer & engineer, and Alan Karbachinsky, Steven Atha, Jack Bailey, and Alex Tomkow as designers and artists! with MUSIC and SOUND BY – Zong Chiang.

We made the game in 48 hours and it was truly a great experience. We all worked hard and pushed for a product that we are proud of and won best overall! So I’ll attach a link on the bottom of the page where it will be downloadable to all of you!

NOTES: This game is NOT finished. It plays through, but there are bugs.

The game was made from the theme provided by MEGA: “Boundless”… so we took the theme and really ran with it.

This game is still a work in progress as of this version! But please do enjoy it!

Please ignore poor quality of images provided! I couldn’t get a good capture! I promise it looks MUCH better! Please click them for (somewhat) better quality!

Cheers!!

https://alextomkow.itch.io/boundless