Improving Accessibility in Unity Games – Part 2
In Part 2 of Improving Accessibility in Unity games, you’ll add support for motor and cognitive disabilities and add some options to help guide players. By Mark Placzek.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Improving Accessibility in Unity Games – Part 2
50 mins
- Addressing Motor Disability in Unity Games
- Being Sensitive to Sensitivity
- Enabling the Sensitivity Adjustments
- Adding Rebindable Keys
- Setting Up Your UI Controls
- Testing Your New Settings Window
- Rebinding Your Inventory Controls
- Adding the Rebind Logic
- Finishing Up
- Applying the Rebinding
- Keyboard Navigation
- Fixing Navigation in Other Panels
- Coding the Selection for Each Menu
- Beating Button Mashing
- Preparing CrosshairRay for a Mouse Hold
- Addressing Cognitive Disabilities
- Adding Clues
- Setting Up Your Trigger
- More Fun With Subtitles
- Adding Colors to the Subtitles
- Highlighting Interactables
- Animating Your Interactive Hints
- Switching Between the Regular and Animated Cursor
- Adjusting Your Subtitle Duration
- Making a Slider to Adjust the Subtitle Duration
- Saving the Settings
- Creating a Save Method
- Saving Your Player Settings
- Where to Go From Here?
Welcome back! In part one of this tutorial, you worked on improvements to Puzzley Dungeon to make it easier to play for people with visual or hearing impairments.
In the final part of this tutorial, you’ll learn about even more improvements to help people with motor and cognitive disabilities. You’ll tweak control methods, add a clue system and add quality-of-life changes that all your players will appreciate!
To get started, open your completed project from Part 1 or use the Download Materials link at the top or the bottom of this tutorial to download the Improving Accessibility in Unity Games Part 2 Starter project. Open the starter project.
Addressing Motor Disability in Unity Games
Nerve, muscle and joint conditions have challenging consequences for motor control. Some make fine motor control very challenging, others can limit a person’s range of movement. This can cause problems for gamers.
You can help by building options into your Unity games that give people with motor disabilities more flexibility. For example, every player should be able to adjust the mouse’s sensitivity to suit their needs. And adding remappable keys can help people with limited mobility or those who can only use one hand.
Throughout this tutorial, you’ll see how to use Unity’s built-in tools to make your game as flexible as possible.
Being Sensitive to Sensitivity
If you have problems with fine motor control, being able to change your mouse sensitivity to suit your needs can make a game much more playable. Adding that capability will be your first step to making Puzzley Dungeon more accessible for people with motor disabilities.
To do it, you’ll use Unity’s character controller to expose the multiplier for movement and rotation. Start by selecting the RigidBodyFPSController in the Hierarchy and scrolling down to the Rigidbody First Person Controller component in the Inspector. You’ll see the following public values under Mouse Look:
- X Sensitivity: Controls the mouse rotation from side to side.
- Y Sensitivity: Handles the up and down rotation of the mouse.
Next, you’ll enable some new controls in the Settings panel.
In the Hierarchy, navigate to Canvas ▸ SettingsMenu ▸ SettingsPanel. Select and enable both MouseXSettingComponent and MouseYSettingComponent in the Inspector.
This is starting to look like a real settings menu for a real game! However, those controls don’t work yet.
Enabling the Sensitivity Adjustments
To access Unity’s Character controller, you need to add a namespace to the top of the script. Open the SettingsManager.cs script and add the following using
statement at the top:
using UnityStandardAssets.Characters.FirstPerson;
Next, add the following variable at the top of the class:
public RigidbodyFirstPersonController rigidbodyFirstPersonController;
This variable holds a reference to the Character controller. Now, you have just two more methods to complete to enable customized mouse sensitivity.
Add the following to the empty SetMouseSensitivityX
method:
rigidbodyFirstPersonController.mouseLook.XSensitivity = sliderValue;
SaveSettings("MouseX", sliderValue);
Additionally, add the following to SetMouseSensitivityY
method:
rigidbodyFirstPersonController.mouseLook.YSensitivity = sliderValue;
SaveSettings("MouseY", sliderValue);
These methods take the values of the sliders and sets the sensitivity values exposed in the Character controller for each axis (X and Y). The SaveSettings
method simply saves the values to the built-in Unity PlayerPrefs store, to persist these settings between game sessions.
There’s one final thing to do before you test your work. Select the SettingsManger in the Hierarchy and drag the RigidBodyFPSController from the Hierarchy to fill the reference on the newly exposed editor field: Rigidbody First Person Controller.
Ok, click Play, head into the Settings Menu and play with the mouse sensitivity. Test it out and see how it affects gameplay. Just try not to make yourself motion sick! :]
Adding Rebindable Keys
Sometimes, default keymappings won’t work for someone with a motor disability. Maybe they can’t stretch far enough to reach a certain key, or they need to be able to reach all the relevant keys with one hand. Allowing players to rebind their keys gives them the flexibility to play their games in a way that works well for them.
Luckily, Unity makes this easy to do because it has a built-in method for rebinding keys when your app is running.
Start by opening the Project Settings menu by selecting Edit ► Project Settings in the toolbar. From this window, select Input from the list on the left. Here, you can assign key bindings and see the ones that Unity has set up out of the box.
By default, the character controller expects the player to use a mouse to look around or to rotate their character. However, for those that can’t use a mouse or who can use only one hand, being able to play your game with the keyboard alone makes your game more accessible.
You’ll need to do a little work on the UI controls to navigate and operate them with the keyboard only.
Setting Up Your UI Controls
Right-click on Axes/MouseX and select Duplicate Array Element. Do the same for Axes/MouseY. You now have two sets of each axis for the mouse movement. You’ll keep one set as is and assign a new control method for the other.
Use the drop-down arrow to expand the new Mouse X and adjust the following:
- Negative button: q
- Positive button: e
- Gravity: 3
- Sensitivity: 3
- Snap: Checked
- Type: Key or Mouse Button
Now, expand the new Mouse Y and adjust the following:
- Negative button: f
- Positive button: r
- Gravity: 3
- Sensitivity: 3
- Snap: Checked
- Type: Key or Mouse Button
These values assign all the movement and character rotation controls to keys that are within easy reach of the ‘WASD’ movement keys.
Testing Your New Settings Window
Build and run your game by selecting File ▸ Build And Run from the Toolbar and you’ll see a lovely settings window for your game.
There are two tabs to adjust settings, one for Graphics and one for Input. This mirrors the settings from the Editor Input menu. Scroll down to see your new key bindings.
This menu allows you to adjust key bindings that will persist between launches. However, right now it only exists between launches of your game and only if the developer enables the menu. You can do better!
Once again… back to the Settings Panel.
Navigate to Canvas ▸ SettingsMenu ▸ SettingsPanel in the Hierarchy and select RebindKeyComponent. Then enable it in the Inspector.
Rebinding Your Inventory Controls
Your next step is to create a rebind control for opening the inventory. This will be a button that displays the key to open the inventory. When the user presses it, they’ll be able to assign a new key to this task, which will then display on the button.
Expand the RebindKeyComponent in the Hierarchy and select Inventory. Inventory is an unusual name for a button, but the button’s name will be sent to the Settings Manager so it knows which control to rebind.
This makes it easy to duplicate the control for other keys you’d like to rebind without needing duplicate methods for each key. Now, in the Inspector, find and open the RebindHelper component (double-click it) to load the script in your IDE.
Add the following using
statement to the top of RebindHelper:
using UnityEngine.UI;
This lets you use Unity’s UI namespace.
Next, add the following variables to the top of the class and Start
to populate them (you can replace the existing Start
method with the below code too):
//1
private string controlToRebindName;
//2
private Text buttonInstanceText;
//3
private SettingsManager settingsManager;
private void Start()
{
controlToRebindName = gameObject.name;
buttonInstanceText = gameObject.GetComponentInChildren<Text>();
settingsManager = GameObject.FindGameObjectWithTag("SettingsManager").GetComponent<SettingsManager>();
}
- This variable holds the name of the control to rebind, which it extracts from the name of the button GameObject. This means that if you duplicate this control for another binding, you only have to change the GameObject’s name. The Settings Manager receives the result so it knows which binding to change.
- This
Text
variable is the text in the button. You need to access this so that you can change it to display the newly-assigned keybinding. - This variable holds a reference to the Settings Manager.
Finally, add this last method:
public void RebindPressed()
{
settingsManager.HandleRebindOfControl(controlToRebindName, buttonInstanceText);
}
The Rebind Helper sits between the rebind button and the Settings Manager to make sure the Settings Manager knows which control it needs to rebind. Head into the SettingsManager.cs script to add the rebind logic.
Adding the Rebind Logic
First, add these variables at the top of the class:
private Text controlToRebindText;
private string controlToRebind;
private bool isRebinding = false;
public Dictionary<string, KeyCode> buttonkeys = new Dictionary<string, KeyCode>();
The first two variables hold the information passed from the RebindHelper.
The SettingsManager uses Update
to wait for a key press from the player. The isRebinding
Boolean will make sure the code in Update
only runs when the player tries to rebind a key.
Finally, the buttonkeys
dictionary holds all your game’s custom keybindings. Right now, that’s only one button, but you could want to add more.
Finishing Up
You’ll want to track the isRebinding
Boolean flag in Game Manager too. Open GameManager.cs and add the this variable to the top of the GameManager
class:
public bool isRebinding = false;
Next, open SettingsManager.cs and complete the HandleRebindOfControl
method that RebindHelper will call:
public void HandleRebindOfControl(string controlPressed, Text buttonInstanceText)
{
controlToRebind = controlPressed;
controlToRebindText = buttonInstanceText;
isRebinding = true;
gameManager.isRebinding = true;
}
Here, you set the variables so other methods can access them. You enable the code in the Update
method (coming up) by setting isRebinding
to true
.
Add this code to the Update
method:
if (isRebinding)
{
//1
if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.Space))
{
return;
}
//2
if (Input.anyKeyDown)
{
foreach (KeyCode keyCode in Enum.GetValues(typeof(KeyCode)))
{
if (Input.GetKeyDown(keyCode))
{
//3
controlToRebindText.text = keyCode.ToString();
HandleKeyBindChangefor(controlToRebind, keyCode);
isRebinding = false;
gameManager.isRebinding = false;
break;
}
}
}
}
Here’s what’s happening in this code:
- You don’t want to map the Space bar or Return key to another control since you’ll need them later, so you ignore these key presses.
- Sadly, there’s no “listen for the next key press and tell me what it is” function, so you need to iterate through all the keycodes in a
foreach
loop. IfGetKeyDown
matches any of these keys, you know that was the one that the player pressed. - When you get a new keycode, a few things happen:
- The keycode is converted to a string and applied to the text field of the rebind button.
- You send the keycode to a new method, which you will create in a second, to actually apply the rebind.
- You stop looking for key presses in
Update
by settingisRebinding
tofalse
.
Applying the Rebinding
To actually apply the rebinding, you need to complete HandleKeyBindChangefor
method by adding the following code:
buttonkeys[controlToRebind] = keyCode;
PlayerPrefs.SetString(controlToRebind, keyCode.ToString());
if (controlToRebind == "Inventory")
{
gameManager.inventoryKeyCode = keyCode;
}
Here, the new keybind is saved in both the buttonkeys
dictionary and PlayerPrefs
.
The GameManager is responsible for watching for the player to open the inventory. So you need to tell the GameManager what the new key is.
When you enable rebinding, you don’t want the game to respond to button presses when it shouldn’t. This is the reason for adding the boolean isRebinding
flag earlier.
In the Update
method of the GameManager script, change this first line of code:
if (gameStarted && !isSettingsMenuOpen && Input.GetKeyDown(inventoryKeyCode))
To this:
if (gameStarted && !isRebinding && !isSettingsMenuOpen && Input.GetKeyDown(inventoryKeyCode))
The last thing to do is to enable the RebindPressed()
method to be called when the Inventory Button is clicked in the UI. For this you’ll wire up the On Click event to the RebindPressed()
method using the Unity editor.
In the Hierarchy, select the Inventory Button again. Using the Inspector, drag and drop the Button component onto the On Click () Runtime Only event field. Then, select the RebindHelper.RebindPressed item from the drop-down list.
Great, now you can take it for a test drive. Click Play and see if this works!
Go to the Settings Menu and tap the Rebind button. Nice! That works to rebind the the key now :]
Start the game, then tap “M.” Notice that “M” and not “I” now opens the Inventory. Additionally, tap the keyhole in the first dungeon door. The subtitle now also indicates the new keybinding.
Keyboard Navigation
Unity’s new UI controls have some great built-in features, including the ability to navigate UI controls with the keyboard. However, the default navigation may not produce the behavior you want.
Switch to the Scene view in Unity and ensure you have selected the 2D view mode. In the Hierarchy, enable the SettingsMenu GameObject.
Click on any UI element (such as a Slider) and ensure you’ve selected Visualize in the Navigation section of the Inspector.
When using visualization mode in the Scene view, arrows appear to indicate how the change of focus is set up for the collection of UI controls as a group. This allows you to see which UI control will get focus next from any one other UI control.

Yay, Spaghetti!
As you see, the arrows dip in and out of different panels that are on- and off-screen. You need to ensure that navigation cycles intuitively, and only between elements that are currently on-screen.
Start with the StartMenu located in the Canvas in the Hierarchy. Automatic Navigation is dipping into the Inventory, which you don’t want.
Use the drop-down to expand the StartMenu in the Hierarchy to reveal the two buttons: StartButton and SettingsButton. You want to toggle between the two when you tap up or down on the keyboard.
Select StartButton so you can see how the navigation works. In the Inspector, look at the Button component and find Navigation. Use the drop-down to select Explicit only. You may need to deselect the others.
Now, four input boxes will appear. They allow you to drop other UI controls from the Hierarchy into each direction you can select.
- In the StartButton navigation, drag the SettingsButton from the Hierarchy over Select On Up and Select on Down.
- For SettingsButton, set the Navigation drop-down to Explicit and drag the StartButton over Select On Up and Select on Down.
The Visualize Navigation will update accordingly.
Fixing Navigation in Other Panels
Next, you need to adjust the navigation in three other panels:
- The EndMenu
- The Inventory and InventoryMenuSettingsButton
- The SettingsMenu.
The single-buttoned EndMenu is straightforward. Navigate to Canvas ▸ EndMenu ▸ RestartButton in the Hierarchy. In the Inspector, set the EndMenu button’s Navigation to None. Super easy!
The inventory is up next. You want to be able to scroll up and down through the inventory slots. Left and right taps should move you between the Settings button and the Inventory slots.
To implement this, go to the Hierarchy and expand Canvas ▸ Inventory to reveal the ItemSlots. Additionally, enable the InventoryMenuSettingsButton in Canvas. To make things a bit clearer to see in the Scene view, you may wish to also temporarily disable the StartMenu GameObject.
Now, Control-click on each of the ItemSlots and the InventoryMenuSettingsButton to select them all. Set their Navigation to Explicit only.
Then drag ItemSlot0 from the Hierarchy over Select On Right on the InventoryMenuSettingsButton.
Finally, for each ItemSlot, drag the ItemSlot above it to the Select On Up and the ItemSlot below it to Select On Down.
Your new Navigation arrows should look like this:
Finally, you’ll handle the SettingsMenu. Re-enable it to view the panel in the Scene view.
As with the ItemSlots, you only need to navigate up and down to the next and previous controls. You just need to find them all in the Hierarchy!
In the Hierarchy under SettingsMenu ▸ SettingsPanel enable the ButtonHoldToggleComponent and SubtitleDurationComponent GameObjects. You’ll work with these a little later.
In the Hierarchy, expand each SettingComponent in the SettingsPanel using their drop-down arrows. The actual UI controls you’ll need to connect are children of these SettingComponents.
You can see all these controls selected in the image above.
For each control, find the Navigation component in the Inspector and set it to Explicit. Drag the control above it from the Hierarchy into the Select On Up slot. Drag the control below it from the Hierarchy into the Select On Down slot.
If you’ve wired everything up correctly, the navigation should look like this:
Fantastic! You’re all done, right?!
Well, try it out, and no cheating… don’t touch that mouse! Click Play and try to navigate the menu and select one of the menu options… Hmm.
There’s one tiny issue… it doesn’t work
Unless one of the buttons is selected at the start, you can’t move around your options.
Here’s a walk-through of what you need to do to fix this:
- The Start button needs to be selected when the game starts.
- When you enter the inventory, the first slot needs to be selected.
- After the player presses the Settings button from either the Start Menu or the inventory screen, you need to select the UI Element at the top of the Settings menu.
- The Start Menu’s Settings button needs to be reselected when exiting the Settings Menu, if the player hasn’t started the game.
- Finally, the Restart button needs to be selected when the game ends.
The easiest way to accomplish this is to code the selection when the player activates each menu.
Coding the Selection for Each Menu
The GameManager can handle the initial selection of the Start Game button, and it also contains the logic for toggling the inventory. Add three public variables to the top of the class to hold the following controls:
public Button startGameButton;
public Button restartGameButton;
public Button firstInventorySlot;
Add the following to Start
to select the start button when the game launches.
startGameButton.Select();
Now, move down to the ToggleInventoryScreen
method. This is divided into an if
and else
statement, depending on whether the inventory screen is open or not.
The else
statement handles opening the inventory. Add the following code to the top of the else
statement:
firstInventorySlot.Select();
This ensures that the first slot is selected when the player opens the inventory.
Finally, move further down to the CompleteLevel
method and add the following code.
restartGameButton.Select();
Now, you’ve made sure that the Restart Game button will be selected when the game ends.
Nice! Now, return to Unity and populate the three public variables you created. Select the GameManager in the Hierarchy to expose the variables in the Inspector. From the Hierarchy:
- Drag Canvas ▸ StartMenu ▸ StartButton over startGameButton.
- Next, drag Canvas ▸ EndMenu ▸ RetartButton over restartGameButton.
- Finally, drag Canvas ▸ Inventory ▸ ItemSlot0 over the firstInventorySlot.
Your Game Manager component should now look like this:
Next up, the Settings Menu. The player uses one of two buttons to open this menu.
Open the SettingsManager.cs script and add the following new method:
public void SelectFirstUIElement()
{
volumeSlider.Select();
}
You can add this method to the list of methods that execute when the player presses either of the Settings buttons.
In the Hierarchy, find and select the SettingsButton in Canvas ▸ StartMenu. In the Inspector, click the + button at the bottom of OnClick() to add a new method for the button to call.
Drag the SettingsManager from the Hierarchy over the input box and use the drop-down to select SettingsManager ► SelectFirstUIElement
Repeat these steps with the other Settings button in the inventory screen: Canvas ▸ InventoryMenuSettingsButton.
Nice! You can now navigate the settings menu using only the keyboard when you enter it.
When you exit the settings, you’ll find yourself back at the Start Menu if the game has not started. You’ll need to reselect the Settings button on that screen to keep keyboard navigation possible.
Open the SettingsManager.cs script and add the following public variable at the top of the class:
public Button startMenuSettingsButton;
The Exit Menu button already calls a method in the SettingsManager called SettingsDidExitOrLoad
.
Under this section of code:
if (gameManager.gameStarted)
{
gameManager.ToggleInventoryScreen()
}
Add the following:
else
{
startMenuSettingsButton.Select();
}
This is really simple. If the game has started, toggle the Inventory screen. Otherwise (if in the menu), the Start Menu Settings button is selected.
Back in Unity, select the SettingsManager in the Hierarchy. Wire up the startMenuSettingsButton by dragging Canvas ▸ StartMenu ▸ SettingsButton from the Hierarchy over the Start Menu Settings Button field in the Inspector.
Ok, head back into Unity and click Play. Now, see if you can complete the game with only the keyboard, and try adjusting some settings as well.
Wow, good work. You can now play the entire game using only one hand!
But… how about that lever? Repetitive strain injury setting in? That brings you to your next topic!
Beating Button Mashing
Oftentimes, games require that their players mash buttons repetitively to achieve a goal or to overpower an enemy. However, many find this tiresome or difficult to maintain, and some people with motor difficulties find it impossible.
Puzzley Dungeon uses this mechanic for its rusted gate lever. To make it more accessible, you’re going to build an alternative: a sustained hold of the button.
In the previous section, you enabled ButtonHoldToggleComponent in the SettingsPanel. Now you’ll use this as a toggle to enable the alternative setting.
Open the SettingsManager.cs script and complete the ToggleHoldButton
method, which connects to the toggle.
crosshairRay.isHoldButtonEnabled = isHoldEnabled;
SaveSettings("HoldEnabled", isHoldEnabled.ToString());
What’s crosshairRay
?
Imagine that there’s an invisible line from your player into the 3D world in front of it, called a Raycast. This lets you determine if you are facing something and whether you are close enough to interact with it.
If you are, you can execute appropriate methods to do stuff with the object. The crosshair on the screen is a visual representation of where this ray points.
Preparing CrosshairRay for a Mouse Hold
The crosshairRay
sits between a mouse click and a possible interaction. Unity treats a mouse click and a hold differently, so crosshairRay
needs to know to look out for a hold, if that’s the method the player will use.
Add the following variable to the top of SettingsManager.cs.
public CrosshairRay crosshairRay;
Now, head back into Unity. In the Hierarchy, find and click on the MainCamera child in RigidBodyFPSController.
In the Inspector, find the Crosshair Ray component and open it for editing in your IDE. Then add the following variable to the top of the class:
public bool isHoldButtonEnabled;
Update
is highly commented, have a read through to better understand the flow of logic, but the gist of it is that it is used to determine what kind of interactable objects are being clicked on in-game, if any. If an interactable object is clicked on, then the interaction is executed in code.
With the hold, you have a new scenario to work into the method. Under the comment labeled 2
, change the following line from this:
else if (Input.GetButtonDown("Fire1") && isInteractableHit)
To this:
else if (!isHoldButtonEnabled && Input.GetButtonDown("Fire1") && isInteractableHit)
This ensures that if the player hasn’t enabled the button hold, the game works as it did before.
Under this else if
statement, add the following new else if
statements to add a new, third scenario:
// 3 If the player has enabled the button hold and the character is in range of a "strength" interactable,
// rather than use GetButtonDown, which is only true for the first frame,
// you use GetButton, which will remain true for the entire time the player holds the button.
else if (isHoldButtonEnabled
&& isInteractableHit
&& currentInteractable.interactableType
== InteractableScript.InteractableType.strengthInteractable
&& Input.GetButton("Fire1"))
{
currentInteractable.Interaction();
}
else if (isHoldButtonEnabled
&& isInteractableHit
&& currentInteractable.interactableType
!= InteractableScript.InteractableType.strengthInteractable
&& Input.GetButtonDown("Fire1"))
{
currentInteractable.Interaction();
}
There are a few different types of interactable in the game. strengthInteractable
is the one you need for sustained button presses or a sustained hold.
The first else if
statement ensures a strengthInteractable
keeps receiving a message for the duration of a button hold by using GetButton
as opposed to GetButtonDown
. The second else if
ensures other interactables still only receive a single message.
For the final step, return to Unity and wire everything up.
Select SettingsManager in the Hierarchy. Drag the MainCamera from the Hierarchy under RigidBodyFPSController to the Crosshair Ray field in the Inspector.
OK, click Play, toggle ‘Enable button hold’ in the Settings Menu and go to that rusted door lever. Ha! No amount of rust can slow you down anymore!
Addressing Cognitive Disabilities
Your audience is broad, and no matter how you’ve targeted your game, people with a diverse range of ages and abilities will play it. Adjustable difficulty and balancing can help people young and old and of all levels of ability to enjoy your game.
It may be difficult to balance the desire to challenge your players with the need to let more people play. But there are many ways to incorporate adjustments that consider everyone’s needs. For example:
- You keep the main story more accessible but add side quests for players that like an extra challenge.
- Offer an optional hint system. The Room series by Fireproof Games is a good example.
- Use Unity Analytics and funnels to identify sticking points and adjust the level design to ensure an enjoyable, challenging but not impossible playthrough. For more detail about Unity Analytics, have a look here.
For now, you’ll make a few adjustments to your simple game. You’ll add a clue system that can offer hints when you enter a new room and you’ll make a few quality-of-life changes.
Adding Clues
You already created a system to provide information to the player via subtitles. In this section, you’ll use that system to add hints.
In the Hierarchy, use the Create drop-down at the top to select Create Empty and add a new, empty GameObject to the Scene. Drag it into SceneObjects to keep your scene organized and select it so you can make some adjustments in the Inspector.
Adjust the objects Transform/Position to (X:40.5, Y:2, Z:-34) and rename it HintTriggerZone. Next, you’ll add a CapsuleCollider with the Add Component button. Adjust the Radius to 2 and, if you don’t want the player walking straight into an invisible wall, tick the Is Trigger property.
This should place a trigger zone just on the inside of the gate to the room with the plinths.
Here is how it should look like from above (using Wireframe view in the Scene window).
This is how it looks from a different perspective in the Scene view.
Next, you’ll add some logic to your trigger.
Setting Up Your Trigger
Click Add Component a second time and type SubtitleTriggerScript. Press Enter followed by Create and Add to create a new script.
Move the script from the root of your Project Assets folder into Assets ► RW ► Scripts.
Now, open it and add the following variables to the top of the class:
private GameManager gameManager;
private bool clueWasTriggered = false;
public string clueText;
The first variable grants access to the GameManager so that you can post a subtitle. The clueWasTriggered
Boolean ensures the subtitle isn’t continuously triggered. The third variable, clueText
, enables you to add an appropriate hint from the Inspector.
Now, add the following code to Start
:
gameManager = GameObject.FindGameObjectWithTag("GameController").GetComponent<GameManager>();
This stores a reference to the GameManager.
Finally, add this OnTriggerEnter
method:
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player" && !clueWasTriggered)
{
gameManager.PublishSubtitle(clueText);
clueWasTriggered = true;
}
}
This method will send the clue subtitle when the player enters the trigger area. You then set clueWasTriggered
to true
to ensure you do not send multiple clues each time the player wanders through the trigger zone.
Head back into Unity and add an appropriate subtitle to the public clueText
variable in the Inspector:
“A gate with no lock? Perhaps closer inspection of the plinths will yield a clue.”
Click Play and try it out. Perhaps now, you’ll finally conquer this fiendish puzzle!
More Fun With Subtitles
Dialog between multiple characters can be confusing if there’s no indication of who’s speaking. There are several ways to make the speaker clear, as you might have noticed if you’ve played any games yourself. For example, you could put the name of the speaker, or a thumbnail of the character, at the start of each subtitle.
You’ve already used a method that takes a string to instantiate a subtitle; all you need to do is add additional variables to further customize the generated subtitles.
Puzzley Dungeon doesn’t have multiple characters, but how about adding a little color to the subtitles to distinguish between information, closed captions and clues?
Adding Colors to the Subtitles
Open the GameManager.cs Script and find the PublishSubtitle
method. Copy the entire method and paste it below where it currently is to duplicate it. In the duplicate, add a new argument to the method so that the method signature now looks like this:
public void PublishSubtitle(string textToPublish, Color subtitleTint)
At the bottom of the else
statement, add the following line:
currentText.color = subtitleTint;
This lets you apply a new color to the text
Now, you’ll send a color from each method that asks for a subtitle.
In the HandleAudioDescription
method in SettingsManager.cs, add a yellow color to the audio description by changing this line at the end:
gameManager.PublishSubtitle(subtitle);
To this:
gameManager.PublishSubtitle(subtitle, Color.yellow);
Similarly, find OnTriggerEnter
in SubtitleTriggerScript and change:
gameManager.PublishSubtitle(clueText);
To the following:
gameManager.PublishSubtitle(clueText, Color.cyan);
Many parts of the project send information via subtitles. You’ll turn PublishSubtitle
into an overloaded method by creating another method with the same signature as before. Then, all the other calls will still work without modification and can make the text white by default.
If you’re interested in method overloading in C#, and how it works, you can read more about it over here.
Add another PublishSubtitle
method in GameManager.cs, above the original:
public void PublishSubtitle(string textToPublish)
{
PublishSubtitle(textToPublish, Color.white);
}
Back in Unity, click Play, enable the audio description toggle and play through to see your changes.
Highlighting Interactables
Another subtle but worthwhile quality-of-life change is to let the user know when they hover the cursor over an object they can interact with. This is a simple change that can remove sticking points without dumbing down the gameplay.
There are many ways to accomplish this sort of effect. Changing the material to highlight the object would be one way, but you’ve already dabbled with adjusting the shader material of the gems, so you’re going to choose a different method.
Select the Canvas in the Hierarchy. At the top of the window, select Create ► UI ► Image to create a brand-new white square in the middle of your game UI.
Using the Inspector, ensure that the Rect Transform component has it’s default zeroed out settings by right-clicking the component name in the Inspector and choosing Reset. Re-name the new object InteractionCursorImage.
As much as you love white squares, you don’t want to stop there. Add a Source Image to the Image component in the Inspector. In the Project view, navigate to the six cursor sprites in Assets/RW/Textures. Drag cursor1 over the Source Image slot.
Here is how the component in the Inspector, and the Game UI window should now look like:
OK, it’s animation time!
Animating Your Interactive Hints
In Unity, open the Animation window by selecting Window ► Animation ► Animation from the toolbar. This will open a new window with a timeline area on the right.
With InteractionCursorImage still selected in the Hierarchy, click the Create button in the Animation window’s timeline area. You’ll see a prompt to save a new animation.
Call the file InteractionCursorAnimation and navigate to Assets/RW/Animations to keep everything nice and neat. Click Save. This will do two things: It will create an animator for your InteractionCursor and create an animation clip.
In the Project view, Navigate to the six cursor sprites in Assets/RW/Textures again. Drag all six of them over the timeline in the Animator window.
This automatically creates a short animation with the selected frames. In the panel at the left of the Animation Window, adjust the Samples to 10 to slow down the animation. If you don’t see the Samples setting, you might need to enable it. Use the settings menu at the top right corner of the Animation window to Show Sample Rate in this case.
To preview the animation, click the Play button at the top of the Animation window. You’ll see the animated cursor in the Game view.
Once you’ve finished watching the animation, select the InteractionCursorImage in the Hierarchy. Then, in the Inspector, click the checkbox at the top to disable the GameObject, and therefore the animation so it’s not visible by default.
You already have a method that detects interactive objects in the Crosshair Ray component in RigidBodyFPSController/MainCamera. You’ll need to add some code to Update
to swap out the cursor sprite with the animated one.
Switching Between the Regular and Animated Cursor
Open the CrosshairRay.cs script and add the following two public variables at the top of the class.
public GameObject cursorGameObject;
public GameObject interactionCursorGameObject;
Scroll down to the Update
method. The first conditional in this method checks to see if the inventory screen is open. The second conditional uses a Raycast to detect an interactable. The result of this sets isInteractableHit
to either true
or false
. Here is where you’ll change the cursor.
Under the line:
isInteractableHit = true;
Add the following:
cursorGameObject.SetActive(false);
interactionCursorGameObject.SetActive(true);
Then, under the line:
isInteractableHit = false;
Add the following:
cursorGameObject.SetActive(true);
interactionCursorGameObject.SetActive(false);
Back in Unity, wire up these public variables. Expose them in the Inspector by selecting RigidBodyFPSController/MainCamera in the Hierarchy. Drag CursorImage over Cursor Game Object and drag InterationCursorImage over Interaction Cursor Game Object.
And you’re done! Click Play and see your cursor update as you explore the dungeon.
Adjusting Your Subtitle Duration
Congratulations! You’ve reached the setup for the final setting control.
Whether you are a younger player, have a visual impairment or just need more time to read the subtitles, being able to adjust the subtitle duration is helpful for everyone.
If you haven’t enabled the SubtitleDurationSettingComponent when you set up keyboard navigation, find and select it in the Hierarchy under Canvas ▸ SettingsMenu ▸ SettingsPanel and enable it in the Inspector.
Open the SettingsManager.cs script and add the following two public variables at the top of the class:
public float subtitleDurationSelected;
public TextMeshProUGUI subtitleDurationLabel;
The first variable holds the value selected by the player and the second updates the ‘duration in seconds’ label to display the player’s selection.
Complete the SetSubtitleDuration
method in the SettingsManager.cs script to set these values from the UI Slider when player’s interact with it in-game by adding the following code to the empty method:
subtitleDurationSelected = sliderValue;
subtitleDurationLabel.text = sliderValue.ToString() + " secs";
SaveSettings("SubtitleDuration", sliderValue);
This should be familiar by now. You save the slider value so other classes can access it, then convert the slider value to a string so you can apply the value back to the controller for the player to read.
Making a Slider to Adjust the Subtitle Duration
The SubtitlePrefab prefab has a simple component attached to it called DestroyTimer, which does exactly what it says. Currently, it’s hard-coded to remove the subtitle after three seconds, so you’ll need to change that to make it customizable.
Find and open DestroyTimer.cs script in the Project view of Assets/RW/Scripts.
DestroyTimer needs to ask the SettingsManager for the subtitle duration, so add this private variable to the top of the class:
private SettingsManager settingsManager;
Then add the following line of code at the beginning of Start
:
settingsManager = GameObject.FindGameObjectWithTag("SettingsManager").GetComponent<SettingsManager>();
Here, you find and assign the SettingsManager instance to the variable you created previously when the game starts.
Following that, still in Start
, you need to adjust the hard-coded 3.0f
(3 seconds) in GameObject.Destroy
with the value selected by the player in SettingsManager. The line of code should now read like this:
GameObject.Destroy(gameObject, settingsManager.subtitleDurationSelected);
All that’s left is to connect the slider’s label with the SettingsManager‘s subtitleDurationLabel variable.
Back in Unity, select SettingsManager in the Hierarchy. Use the drop-down arrows in the Hierarchy to drill down to the SubtitleDurationLabel in Canvas ▸ SettingsMenu ▸ SettingsPanel ▸ SubtitleDurationSettingComponent ▸ SubtitleDurationLabel.
Drag it over to the Subtitle Duration Label field slot in the Inspector and you’re done!
Click Play and adjust the subtitle duration in the Settings Menu to six seconds. Now you can marvel at your subtitles’ new enhanced longevity!
Saving the Settings
You’re almost done, but you have a few loose ends to tie up.
Another small but important adjustment you should make is saving and loading all the setting changes the player makes. No one wants to make the same setting changes every time they launch their game, but this is a particularly important consideration for those that need to adjust settings for accessibility.
This last part of the tutorial will walk you through setting up a save and load for one of your controls. The process will be essentially identical for all the controls, so you can finish the rest yourself.
Open up SettingsManager and navigate to SetFontType
, the first method you created in this tutorial.
At the very end of this method, just before the final closing bracket, add this line of code:
SaveSettings("FontType", dropdownValue);
When the player adjusts the font, you want to save the value. To do it, you’ll need some code that will send the name of the setting and the customized value to a save method. So you’ll create that now.
Creating a Save Method
Ahh, but you already have a SaveSettings
method. Two, in fact!
This is called method overloading, it’s identical to the duplicated PublishSubtitle
method you created with the added Color
argument.
In SettingsManager.cs, Add a new overload method to take an Int
as an argument:
private void SaveSettings(string keyString, int intToSave)
{
PlayerPrefs.SetInt(keyString, intToSave);
}
Player Preferences is a simple way to persist a data value using a string key so that you can retrieve it later.
You save the font the player selected using the key FontType
. You’ll use that key to retrieve the saved setting when the game launches.
Head to LoadSettings
and add the following under the existing code:
if (PlayerPrefs.HasKey("FontType"))
{
int fontTypeInt = PlayerPrefs.GetInt("FontType");
SetFontType(fontTypeInt);
fontDropdown.value = fontTypeInt;
}
else
{
SetFontType(0);
fontDropdown.value = 0;
}
While you’re editing LoadSettings
, also uncomment the block of code there. This will enable loading for all the other settings in the menu.
fontDropdown
variable not yet existing. Don’t worry though, you’ll add that next :]
Saving Your Player Settings
The SettingsManager‘s Start
calls LoadSettings
, which executes when the player launches the game.
This code checks if the player preferences has a key FontType
. If it does, that indicates that there is previously-saved data to reference. So you get the value from the player preferences and use the same method that the actual drop-down calls to set the value for your game.
Finally, you need to reflect the loaded data in the drop-down component in the Settings Menu. You’ll create that public variable in a moment.
If there’s no FontType
in the player preferences, set the default value for the game and the Settings Menu drop-down component.
At the top of the SettingsManager class, add the public variable.
public Dropdown fontDropdown;
Return to Unity to wire up this variable. Click on the Settings Manager in the Hierarchy. Drag the Dropdown GameObject from Canvas ▸ SettingsMenu ▸ SettingsPanel ▸ FontStyleSettingComponent onto the Font Dropdown field slot in the Inspector.
That’s it! Click Play and, in the Settings Menu, select the Simple font from the drop-down. Now, restart the game. That setting should persist, even when testing it in the editor.
Where to Go From Here?
Well done! this was a big tutorial to work through. Accessibility is not a small topic but hopefully, you saw that adding these features, even for a fully-developed game, is both possible and worthwhile.
You can download the final project using the Download Materials button at the top or bottom of this tutorial.
During your time here, you worked through improvements for people with sight, hearing, motor and cognitive disability.
Some of your accomplishments include:
- Making huge improvements to the game’s subtitles with adjustable sizes, fonts, added color, closed captions and even adjustable speed.
- Ensuring that players can control the game with just a keyboard.
- Addressing customizable keybindings, giving the player control over the sensitivity of controls and banishing button-mashing mechanics.
- Addressing complications with color and sound puzzles and other considerations regarding levels of cognitive skill.
Remember the Game Accessibility Guidelines Website too. There is a lot of very useful information to help you in your quest to improve accessibility in your games over there, so check out the full list page here.
Game accessibility starts with all these things, as they address a broad subset of disability. However, you can go farther! Are there any other accessibility features you’d like to learn about? Talk about it in the comments below.
All videos. All books.
One low price.
A Kodeco subscription is the best way to learn and master mobile development — plans start at just $19.99/month! Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.
Learn more