Trending
Opinion: How will Project 2025 impact game developers?
The Heritage Foundation's manifesto for the possible next administration could do great harm to many, including large portions of the game development community.
Featured Blog | This community-written post highlights the best of what the game industry has to offer. Read more like it on the Game Developer Blogs or learn how to Submit Your Own Blog Post
In a game like Starr Mazer, with a huge amount of content, it's important you make tools for your team to work uninhibited. This event system for Unity allows devs without scripting experience to implement content without it going through a programmer.
After finishing Sportsball, a published console game where I was responsible for the design, art, and programming, I was able to take a step back and look at how I spent my time. Every project has its unique challenges, but it seems the majority of time in game development is spent on programming. It has been the case with my previous projects, indicated by the size of the coding team compared to other departments. And it was true in Sportsball with somewhere around 60% of all development time spent on coding. An ideal project would spend an equal amount of time on each discipline. I started that way and ended up programming for most of my day and fitting other tasks where I could.
With this in mind, as I approached Starr Mazer, a Point-and-Click Adventure SHMUP, I realized that the major problem to solve here would be the content pipeline. The reason being is a core feature of the game is the Open Middled Gameplay (OMG). This system allows for players to encounter story scenarios from different entry points and end up in different exit points through their actions, which are accounted for from all previous encounters. And these story segments are (semi) randomly selected. Although there are some interesting challenges with the OMG narrative and dynamic SHMUP missions (articles which may be written in the future) those are problems that only need to be solved once; and our current solution with those both require lots of content. If I'm implementing every piece of content as the primary programmer on the team, I will soon become the bottleneck of the whole project. The artist and writer on the team pump out content way faster than I could possibly keep up, so I needed to create a toolset which would allow them to implement content directly into the game without code getting in the way.
Our solution is a very simple component based Event System for Unity 3D. This system may sound familiar. It's similar to tools like Play Maker, Game Maker, Construct, or my personal inspiration, the Starcraft Map Editor. I can leverage Unity’s UI to create an interface to game mechanics without our writer or artist having to learn a scripting language. The premise is that every event in the game is a sequence of small events triggered by certain conditions. For example if Brick (our protagonist) walks up to the Starr Wolf (his wicked sick ride), we need:
a condition of the player approaching an object (collision trigger)
the event of an animation of Brick stepping into the Starr Wolf (animation event)
the control scheme changing to control the ship (game mode event)
and the UI changing to show the SHMUP view (UI mode event)
By breaking the game down into simple event sequences that each do individual tasks, we're able to build a robust event system that allows anyone on the team to use a drag and drop UI to implement a vast array of content instead of requesting coding time. When new functionality is requested to make content, I can break features down into triggers and events that will allow devs access to the new feature and expand the possibilities of the event system at the same time. When I remove myself from the process, creative developers are able to iterate scenarios much faster.
To see this system in action, you can watch me build a new boss in the Unity editor using event components and not code:
https://www.youtube.com/watch?v=YCpKIvc4_c0
You can check out this open source event system on GitHub. This includes the core system which handles sequencing events, a global messaging system for generic events, a method to manage branching event sequences, and sample triggers and events. The code is documented with a sample project to show off all of the features of the system.
The rest of this article will focus on showing how event sequences are composed in the editor and how to write new triggers and events. Other features are discussed in the GitHub repository with example scenes.
Every event sequence is represented as a Game Object. Here we have a game object selected in the hierarchy called Event Look At Table. This event will trigger when the player selects Look and clicks the table which will display text about the table. This has a four event components attached to it:
Event Sequence
This is the first component in every event object. This manages triggers and events. You can set event sequence properties here, such as if this sequence should be repeatable, or if it should automatically play when active trigger conditions are met.
Event Trigger Click
This is a subclass of the EventTrigger class which must have its conditions met before the event sequence will play. In this case the player must click on the Table Click Collider. This also requires we provide it a ClickOn script, which handles all our click events in the game. Because this is responding to a click event, this is an ‘active’ trigger and will request the EventSequence to check trigger conditions when the click event is true.
Event Trigger Mode
This trigger (also a subclass of EventTrigger) is a ‘passive’ trigger, which means its condition must be met to start the sequence, but does not initiate the event sequence on its own. We’ve set this one to pass when the click mode is set to LOOK.
Event Display Look At Text
This is the event that is called when the above conditions are met. This exposes a number of variables that will allow us to display a message on screen. Some of these are references to game objects in the scene which help perform the event (such as the UI game objects) while the important data is the string field which will display when the player clicks the table.
This event results in the following player action, where clicking on the table presents the text indicated in the event component.
The event system handles loading up the attached components and going through them in sequence. It will be the subclasses that you write that perform actions within your game. Here’s the code for the subclasses that are used in this sequence:
public class EventTriggerClick : EventTrigger {
[SerializeField]
ClickOn clickOn;
[SerializeField]
GameObject[] objectsToClick;
void Start(){
clickOn.Clicked += HandleClicked;
}
void OnDestroy(){
clickOn.Clicked -= HandleClicked;
}
void HandleClicked (object sender, GlobalEventHandlers.ClickArgs e)
{
for(int i = 0; i < objectsToClick.Length; i ++){
if(objectsToClick[i] == e.ClickedObject){
OnTrigger();
}
}
}
}
EventTriggerClick.cs extends off of the EventTrigger base class and listens to a delegate event provided by the ClickOn script, which handles sending a raycast and returns what we’ve clicked on. This trigger just has to compare the clicked object with any of the objects in the objectsToClick array in order to actively call the OnTrigger() function which lets the event sequence know this trigger has passed. As stated before, this trigger is ‘active’ because when it’s true, it uses OnTrigger() to tell the EventSequence to check if it can start performing events.
public class EventTriggerMode : EventTrigger {
[SerializeField]
ModeSelect modeSelect;
[SerializeField][BitMask(typeof(ModeSelect.ModeType))]
ModeSelect.ModeType requiredMode;
public override bool ConditionsMet()
{
if(requiredMode == ModeSelect.ModeType.NONE ||
requiredMode == modeSelect.CurrentMode){
return true;
}
return false;
}
}
EventTriggerMode.cs does not call OnTrigger() actively. Instead, it has an override from the EventTrigger base class called ConditionsMet(). In here we perform the logic for testing against the current game mode given by the ModeSelect class which handles the game state. The EventSequence class will call ConditionsMet() for all trigger components attached to the game object whenever an active trigger (like the click one above) is called. This is useful because we can have rare events (like clicking) drive the action to check if an event is ready, as opposed to checking the state of all triggers each frame.
public class EventDisplayLookAtText : EventBase {
[SerializeField]
tk2dUIItem closeLookAt;
[SerializeField]
tk2dTextMesh lookAtTextMesh;
[SerializeField]
ModeUIView modeUIView;
[SerializeField]
string lookAtText;
// Use this for initialization
void Start () {
closeLookAt.OnClick += HandleOnClick;
}
void OnDestroy(){
closeLookAt.OnClick -= HandleOnClick;
}
void HandleOnClick ()
{
modeUIView.SetActionPanel(true);
modeUIView.SetLookAtPanel(false);
if(waitForEventComplete){
OnEventComplete();
}
}
public override IEnumerator PerformEvent ()
{
modeUIView.SetActionPanel(false);
lookAtTextMesh.text = lookAtText;
modeUIView.SetLookAtPanel(true);
if(!waitForEventComplete){
OnEventComplete();
}
yield return 0;
}
}
EventDisplayLookAtText.cs is the event that is performed after triggers are returned true. The main function here is the PerformEvent() coroutine. This performs all the actions of this event, which is to set the main UI to false, set the text of the text mesh, and to turn that text mesh on. Next, provided the developer wants to wait for this event to complete before going on to the next one, we wait for a click because this particular event isn’t considered complete until the player has clicked (indicating that he or she has read the text and is ready to continue).
These three example classes are the types of classes you’ll write to implement the event system into your game. (If you’re interested in the other classes that manage the system, please check out the GitHub repository). As long as you can break down a potential sequence in your game into an order of events, you can control it with the event system, and thus, give the power to have that content made by non programmers. This works particularly well with Point-and-Click adventures, and fairly well with SHMUPS, so it’s suited for Starr Mazer. But, I can imagine this working even in a fighting game, where each fighter’s move is an event sequence triggered by button inputs (with a way to cancel out of event sequences added in). With each trigger or event being specifically designed to handle one task you’ll enable and encourage your team to request features to be added to give them power as creators, as opposed to feature requests to implement a specific design.
What’s interesting about this particular event system is how easy it was to integrate into the game at an early stage. It was great when discovering that the only code I had to write to implement a new enemy weapon type was the class to fire the weapon. The animation, timing, and state machine to determine when to fire is all controlled through the event system. Since I had already written events for animation, changing enemy states, and time delays, I had all the tools I needed to create a new attack pattern.
As I continue to use this system I also find that I’m encouraged to write more modular code. I always make an effort to keep my code separated, but this system forces me to go the extra step and figure out how to break any feature request down to its simplest parts.
As devs use this system, I’ll want to spend time iterating the UI for it. Although I’m able to leverage Unity to present a default UI, some aspects are not handled gracefully. For example, instead of writing the name of an animation file to play, it would be much better to select an animation from a dropdown box with a preview. Features like that can be developed as needed, in order to get your team producing content faster. When working on your system, make sure to spend time watching how others use your tools and work on fixing the worst bottlenecks. Then you can get back to programing the game, and your team can make more content then you would ever be able to implement on your own.
We already have a few examples of what we’ve done with this event system on our Starr Mazer dev blog. If you like what you see, support our Kickstarter which launches on January 22nd.
Read more about:
Featured BlogsYou May Also Like