Introduction to Asynchronous Programming in Unity
- Getting Started
- Setting Up the Starter Project
- Advantages of Asynchronous Programming
- Writing Basic Asynchronous Code
- Defining the async Keyword
- Building Structures With Task
- Placing Temporary Construction Tiles
- Adding await to Start Running Asynchronous Code
- Further Asynchronous Programming Concepts
- Returning Values From Tasks
- Awaiting Multiple Tasks
- Building a House by Parts
- Cleaning up Construction
- Improving Your Asynchronous Code
- Cancelling Tasks
- Cancelling a Task Efficiently
- Catching Exceptions
- Choosing Asynchronous Programming vs. Coroutines
- Advantages of async Over Coroutines
- Deciding When to Use Coroutines
- Where to Go From Here?
Video games are loop-driven applications, meaning that some code executes before a frame draws to the screen. As a result, games often have many things to process in only a fraction of a second. If anything takes longer than a few milliseconds to complete, the frame rate drops and creates a poor player experience. No one likes choppy gameplay!
But sometimes, it’s unavoidable for tasks to take longer than a few milliseconds to complete. This is where asynchronous programming can really come in handy. You can start processes in parallel, allowing the user to continue with their game.
In this tutorial, you’ll create Wenderlich-Topia, a town-building game that allows users to construct roads and houses asynchronously.
In the process of building the game, you’ll learn how to:
- Build roads with basic asynchronous methods.
- Trigger actions once the build process is complete.
- Build houses with multiple asynchronous methods, returning the construction costs once builds complete.
- Cancel asynchronous build methods.
- Handle errors and catch exceptions.
You’ll need Unity 2020.3 or later to build the game.
Download both the starter and final projects by clicking the Download Materials button at the top or bottom of the tutorial.
Explore both projects and familiarize yourself with the setup. In the true spirit of asynchronous programming, you could also explore the projects in parallel with working on the tutorial. :]
Setting Up the Starter Project
Open the starter project in Unity and navigate to the Assets/RW folder. Here are the subfolders you see and what they contain:
- Audio: Background music and the cash register sound effect.
- Prefabs: Complete and partial structures that get built in the project.
- Scenes: Wenderlich-Topia demo scene.
- ScriptableObjects: Data for the structures that you’ll build.
- Scripts: Scripts for all game logic.
- Sprites: Sprites for all isometric art assets.
Open the demo scene located in the Assets/RW/Scenes/Wenderlich-Topia project folder. Set the aspect ratio of the Game View to 16:9 Aspect so the UI displays correctly.
Click Play to start the game. You’ll see the Welcome UI. Click the “X” icon to close the welcome message and observe the starting scene.
Advantages of Asynchronous Programming
The main advantage of asynchronous programming is that you can run multiple tasks simultaneously without blocking the overall execution of the current thread. In Unity, unless you’re getting into the Job System, most gameplay code is synchronous. That is, the code executes sequentially — one line, one instruction at a time. Once a task is finished, another one begins. Generally, this works fine but not always.
For example, in the sample project, a road can take a few seconds to build. If the code were purely synchronous, the player would have to wait for the road to finish building before doing anything else. Building a city would take a lot of time!
This is where asynchronous programming can help.
Writing Basic Asynchronous Code
Using asynchronous code, you’ll allow players to build multiple houses and roads in Wenderlich-Topia asynchronously.
First, you’ll define an entry point to serve as a bridge between synchronous and asynchronous code. Then, you’ll define specific tasks that run asynchronously. And finally, you’ll write sections of code that will run once your tasks are complete.
To summarize the game’s construction loop:
- Player selects a type of structure from the menu.
- Player clicks on the map where they want to place it.
- A temporary construction tile spawns where the structure will go.
- The program takes some time to build the structure.
- Once time is up, the program replaces the construction tile with the finalized road or house tile.
- The program plays a UI effect to display the cost of construction and adds it to the total city cost.
Defining the async Keyword
async, as you might have guessed, defines a method that can run asynchronously.
async goes between the access modifier (public, private, etc.) and the return type.
async, open RW/Scripts/Managers/ConstructionManager.cs in your editor of choice. Add
BuildStructure on line 48. Your
BuildStructure method should now look like this:
public async void BuildStructure(GameObject placementStructure, Vector3 buildPosition)
// Method code goes here
This method serves as the entry point into the remaining asynchronous code you’ll implement in this project. This entry point can await the completion of other asynchronous methods before proceeding.
Take note of the async method’s return type. (In this method’s case it is
void). Methods tagged with
async can have one of three return types:
Task<TResult>. All three return types will be covered in this tutorial.
void as a return type for
async except for event handlers or as an entry point into asynchronous code. This is because, while
async void can wait for asynchronous methods to complete, other methods can’t wait for
async void to complete, and will continue executing, leading to unexpected and out-of-order code execution.
async void can’t catch exceptions in the same way
async Task can. You’ll learn to catch exceptions later in this tutorial. For now, start building some structures in Wenderlich-Topia.
Building Structures With Task
By now you’ve seen the term
Task a few times. But what exactly does it mean? In asynchronous programming,
Task is simply a class that represents a single operation that can run asynchronously.
async method that returns
Task cannot return any values. However, unlike
Task does allow you to check an operation’s completion status. You can execute code after the
Task method is finished.
So, to allow your
BuildStructure method to know when a road is finished building, you’ll need a method that returns a
Task. Add a new method called
BuildRoadAsync to ConstructionManager.cs, after the existing
private async Task BuildRoadAsync(RoadBuildProperties roadProperties, Vector3 buildPosition)
RoadBuildProperties argument is a pre-declared ScriptableObject that contains information pertinent to the construction of the road and
buildPosition is the position that the road will go on the map.