It’s time to go exploring!
Overview
The initial demo project I am working toward making is based on the Solo Adventure at the start of the Pathfinder Beginner Box Hero’s Handbook. There are two phases of gameplay in this initial demo: exploration and encounter. During the exploration phase, an “Entry” is presented to the player. You can think of an “Entry” as something like a journal entry. It is a blob of story from which the user can make choices about how to continue the adventure. In this lesson we will take the first steps of building the exploration phase.
Getting Started
Feel free to continue from where we left off, or download and use this project here. It has everything we did in the previous lesson ready to go.
Next, to help provide context about what we are working towards, import this package .
The package includes a new scene named “Explore” which holds some already configured User Interface elements including a panel where we will display an “Entry” as well as a panel to handle showing an alert. There are scripts to accompany both panels, each with some Fields assigned to objects within the hierarchy. The package also includes an asset that applies a “style sheet” to the main text area of our entry panel.
Immediately after importing the package, Unity will complain at you with the following error:
Assets/Scripts/SoloAdventure/Explore/EntryPanel.cs(3,7): error CS0246: The type or namespace name ‘TMPro’ could not be found (are you missing a using directive or an assembly reference?)
You may remember from before that the use of assembly definitions in the project requires us to take extra steps – that is the case here. In the Assets -> Scripts folder, select the Scripts.asmdef asset. In the Inspector, Click the ‘+’ button to add a new entry below “Assembly Definition References”. Add “Unity.TextMeshPro” then hit Apply at the bottom.
The errors should all now be resolved.
Explore Scene
From the file menu, choose File -> Build Settings…. Drag the “Explore” scene into the “Scenes In Build” section of the dialog. This is necessary for us to load the scene dynamically as part of the game flow. If you forget this step, you will see an error like this:
Scene ‘Explore’ couldn’t be loaded because it has not been added to the build settings or the AssetBundle has not been loaded.
Open the “Explore” scene and take a look at what all is there and how it is built.
We won’t implement the “Alert” as part of this lesson, so for now, select it from the Hierarchy and disable its game object. Now all you should see is the panel that displays an Entry.
The top half of the Entry panel holds a scrollable text area. This is where a user will read about what is happening. The blue and red text appear thanks to the “EntryStyleSheet” that has been applied to the Text component. In a future lesson, we will be able to interact with those elements by clicking on them.
The bottom half of the Entry panel holds what will be a dynamic list of buttons that represent branching paths for the explorer. I decided that the game would not allow more than four options, partly because that was enough to represent the solo adventure, and partly because too many buttons would take up too much of the screen and make it harder to read the story. If on your own project you decide you need more than four buttons, you could always consider putting them in a scrollable section of their own.
Entry System
We will use a new system to handle which “Entry” the user is currently located at. Add a new C# script at Assets -> Scripts -> SoloAdventure -> Explore named EntrySystem and add the following:
public partial class Data { public string entryName; }
We start with a partial definition of the Data class. This time the data isn’t associated with any particular Entity – you can just think of it as a bookmark representing which entry to show the user. It is just a “name” for an Entry. We will use the name to load an asset via Addressables and will then configure our UI based on that loaded asset.
Add the following:
public interface IEntrySystem : IDependency<IEntrySystem> { void SetName(string name); string GetName(); }
Next we define an interface for our system. It is very basic, and simply has the ability to “set” or “get” the entry name from the game Data.
Add the following:
public class EntrySystem : IEntrySystem { public void SetName(string name) { IDataSystem.Resolve().Data.entryName = name; } public string GetName() { return IDataSystem.Resolve().Data.entryName; } }
Here I have provided a class that implements the interface. As I said before, we implement the “set” and “get” methods by assigning to or reading from the game Data.
Game System
Now that we have the concept of an “Entry” for our game, we should update the game system so that it assigns the initial “Entry” for when you begin a New Game. Open the GameSystem and add this line to the NewGame method, just before we await the completed task.
IEntrySystem.Resolve().SetName("Entry_01");
Entry Flow
Create a new C# script at Assets -> Scripts -> Flow named EntryFlow and add the following:
using Cysharp.Threading.Tasks; using UnityEngine.SceneManagement; using System.Threading; public interface IEntryFlow : IDependency<IEntryFlow> { UniTask Play(); } public class EntryFlow : IEntryFlow { public async UniTask Play() { // MARK: - Enter await SceneManager.LoadSceneAsync("Explore"); // TODO: Load an Entry asset by name // TODO: Resolve the Entry Panel // TODO: Configure the panel with the asset // TODO: Enter transition for the panel // MARK: - Loop while (true) { // TODO: Interact with the panel await UniTask.NextFrame(); } // MARK: - Exit // TODO: Exit transition for the panel } }
We start out with a new injectable interface for the flow, and added the definition of a class that implements the method. As is the case for our flows before, we only need a single method to “Play” the flow.
For the most part, the flow has been left un-implemented. It does manage to load our new scene, but otherwise everything is still a “TODO” comment. Once the scene loads, at least for now, you will be stuck at that scene.
Game Flow
Open the GameFlow script. Now we can begin to flesh out the “Game Loop” where we alternate phases between exploration and encounters. Replace the current version of the Loop method with the following:
async UniTask Loop() { while (true) { var entryName = IEntrySystem.Resolve().GetName(); if (!string.IsNullOrEmpty(entryName)) await IEntryFlow.Resolve().Play(); else break; await UniTask.NextFrame(); } }
The game loop will check to see if our EntrySystem has the name of an Entry asset to display. If it does have a name, then it will branch through the entry flow portion of our code. Later we will have another branch to handle encounter flows.
Interface Injection
Open the FlowInjector script and add the following line to the Inject method:
IEntryFlow.Register(new EntryFlow());
Create a new C# script at Assets -> Scripts -> SoloAdventure named SoloAdventureInjector and add the following:
public static class SoloAdventureInjector { public static void Inject() { IEntrySystem.Register(new EntrySystem()); } }
Finally, open the main Injector script and add the following line to the Inject method:
SoloAdventureInjector.Inject();
Try It Out
Starting from the LoadingScreen scene, go ahead and Play our game so far. After clicking “New Game” at the Main Menu, you should now see the Expore scene appear! You can scroll the main text and click the buttons, though at the moment nothing will happen.
Summary
In this lesson we got our first look at the scene where a player does the exploration part of the solo adventure game. We updated and added new flows to make sure that the game would load the new scene at the appropriate time. We also added a new system that controls what entry will be the current entry.
If you got stuck along the way, feel free to download the finished project for this lesson here.
If you find value in my blog, you can support its continued development by becoming my patron. Visit my Patreon page here. Thanks!