6

Interactions and Mechanics

Now that we have a character with basic locomotion and an environment to work with, let’s take a look at how this character should interact with this environment. Unity allows us to use C# to build logic around GameObjects that the player can interact with. This is the basis of game design and helps tell the story or experience through actual interaction.

You’ll learn more about the specific interactions and mechanics that can be implemented with Unity in this chapter. We will cover:

  • Game loops
  • Mechanics toolbox
  • Interactions within our project
  • Stairs
  • Rings puzzles
  • Tight spaces
  • Interactive volumes
  • Design and implementation

Game loops

Video games have a unique concept called a game loop. As you might be able to guess, it’s a loop of mechanics that are performed throughout the experience. The game loop itself could be very short, such as Call of Duty’s multiplayer team deathmatch. The loop looks something like this, where the goal is to kill more enemies than the number of times you die:

  1. Kill enemies
  2. Die and respawn

There’s more to it than that, and if you are a professional Call of Duty player, you may think this is an over-generalization of the gameplay. Ultimately, however, it really is the case 90% of the time. Now let’s look at Minecraft’s game loop:

  1. Gather resources in the day
  2. Build in the day
  3. Survive at night

We are going to simplify this: there are specific circumstances that fall outside this loop, such as creepers in the day and rainfall, which reduces light levels such that it essentially becomes night. Let’s assume those two factors aren’t part of this study. This is interesting, as this loop is particularly complex. By this, I mean that surviving doesn’t always happen in this loop. The majority of the game is 1, then 2. Only at night does 3, Survive at night, become a large portion of the gameplay, visually represented in Figure 6.1. The core game loop needs to be as concise as it can possibly be.

Figure 6.1: Minecraft game loop

Take a look at your favorite games and break down their main game loops. You may find that there are layers of game loops. Sometimes this is called meta-progression. In the game Hades, the game loops are as follows:

  1. (Optional) Talk to NPCs
  2. (In lobby) Choose skills to upgrade
  3. (In lobby) Choose weapon for the next run
  4. (In game) Fight
  5. (In game) Earn currency for in-lobby upgrades
  6. (In game) Upgrades to strengthen this playthrough
  7. Die and respawn in lobby

The meta-progression takes place at step 2. Base health and damage upgrades make it slightly easier to progress further. This is a common factor in rogue-like genres where the game’s experiences are focused on skill mastery and game progression through death.

You’ll notice that in the Call of Duty loop we didn’t mention meta-progression, even though there is heavy meta-progression in that game. This is because meta-progression is essentially cosmetic. You do not have to change anything between matches in Call of Duty. Any equipment that is gained in Call of Duty will be the same as another player’s equipment with the same mods. If you put a player that has played 1000 hours against a player with the same exact loadout, it would only come down to skill. In Hades, however, you have to spend points on upgrades to actually complete the game.

These loops are interesting, but we should take some time to go deeper into the interactions that make up these loops. In the next section, we will cover a broad set of game mechanics individually.

Mechanics toolbox

An interaction is an action taken using a mechanic. For example, the mechanic of Use Item could be used to pull a lever, push a button, or use a phone. Those three examples are interactions; the mechanic allows the player to interact with the items through a button press. If we only had the ability to interact with something in this way, we would have very few genres to play with. Luckily for us, there is a broad world of mechanics available for us to use to make interactions. Using interactions, we can design amazing experiences!

To begin this chapter, we thought it would be a good idea to provide a list of mechanics and some interactions that come from those mechanics. We will not be able to go through every mechanic, but we will be going over some primary concepts to get a good feel for the mechanics landscape.

What we will be providing here is an understanding of mechanics and how they can be viewed. If this interests you, take some time to read through several different authors on the subject, as their views on the mechanics may differ from our explanations. Our way of seeing mechanics is that they are layers of experiential movement. There are core concepts that can be layered on top of each other to form an interaction. These include:

  • Resource management
  • Risk versus reward
  • Spatial awareness
  • Collection
  • Research
  • Limitations

Read on to get an understanding of these modular core concepts of game design.

Resource management

You may know of this as a primary mechanic of real-time strategy games, or RTSs. Starcraft, Age of Empires, and Total Annihilation are examples of popular resource management-focused games. The concept here is that there are finite resources that you need to gather and spend on something that can help you win. This could be soldiers for an army or experiments to make your soldiers stronger. A non-combat-related scenario is a city builder. You need to monitor the people of your city and build things to make them happy, and you manage the money coming in from them.

Risk versus reward

This mechanic is used in many combat-oriented games. It’s usually given in the form of cooldowns. Do you want to use your ultimate right now, to take an example from the popular title League of Legends? It could take out an enemy and give you a major advantage. However, it could also potentially put you at a disadvantage if you miss, because the enemy will know you have one less power to use. This is the risk vs. reward concept. The simplest form of this is in Super Mario Bros. Should you try for those coins that are hard to reach? You want those points for an extra life, but at the same time, there is a pit that you might fall into if you don’t jump just right.

Spatial awareness

It is common to find this in first-person shooters. Call of Duty and Overwatch utilize this in several ways. First, you have spatial awareness of the enemy on the screen. You need to be able to place your cursor where they are on the screen to shoot at them. Secondly, there is spatial awareness of the entire map. If you are not spatially aware of the map, you can easily get caught unawares. This is the core of platforming games as well. Understanding your position in space in 2D and being able to maneuver deftly is the name of the game in any action platformer. Celeste takes full advantage of this by giving the player tight control schemes that move as you expect every time. The movement is so well put together that when you make a mistake, you feel as though it’s your fault.

This is interesting: if you have loose controls in a game that needs tight controls, the player can feel cheated by the game and may stop playing. This is undesirable!

Collection

Any CCG gamers out there? It’s in the name! Collectable Card Game. Magic: The Gathering, Hearthstone, Yu-Gi-Oh!, and Pokémon are just a few examples. Although this mechanic is not flashy, the concept of collection is used in all sorts of games.

Skills can be collected, as well as weapons, codex entries, armor, and the list can go on forever. Humans enjoy collecting stuff. It could be that you want every card in the season to unlock an achievement. This is double collecting, as you want the cards, but you also want to collect those achievements. It could be that you want to collect all the codices in a game, such as in Mass Effect, where the lore of the game comes from interacting with as many unique things as possible and your journal updates a codex, which holds information on unique items, characters, races, history, etc.

Research

Research is the ability to engage in investigation to establish facts and rules to your surroundings. We can use the concept of research in a few unique ways. One thought is that the player should be the one doing the research, rather than the character. Meaning the player sees the environment for the character. Because of this, the player can learn about subjects and things that may be outside of the character’s knowledge. We as designers can utilize this knowledge and relay information easier to the player by outlining interactable objects or by making climbable ledges a specific color.

On the other hand, the concept of research could refer to the character themselves. The character in-game researches and concludes something new to them in their world and becomes stronger while expanding their awareness both physically and mentally. This may seem similar to collection and resource management; however, if it involves knowledge transfer from the character to the player or things being inherently learned from play, it should be considered research.

Limitations

Pressure makes diamonds. Rather than being a mechanic in and of itself, limitations can be seen as modifiers of other/all mechanics, but we like to break it out as its own mechanic, as not every interaction requires there to be heavy limitations. There can be overarching limitations that affect the overall gameplay. For example, adding a timer to the game as a whole is a limitation. Another is giving the player only 3 lives before ending the play session. In a CCG, you may see there is a hard cap to the decks. This limits the number of turns that can be taken.

When you take the time to figure out how these mechanics fit together to build interactions and make and experience, you’ve designed a complete mechanic. How all these pieces fit together is the crux of mechanics and interaction design. Let’s take some time to go over some of the nuances involved.

Design and implementation

In good design fashion, we need to break down the reasons for the mechanics and interactions that we are going to use. In general, you want to minimize the number of mechanics in a game while spreading their use to many unique interactions. Mega Man is a great example of minimal mechanics with elegant use for slight variations. Locomotion, jumping, and shooting are the only things you have to worry about. After defeating the enemies, you gain different shooting abilities or skills, but you will still use the same button to engage the shooting mechanic. This mechanic is kept to a single button press all the way up until Mega Man 4; when the character is able to charge up his weapon and the button designation changes to adapt to the skill change.

This is an interesting thought: the gameplay involves a very limited number of changes to the mechanics, instead just changing graphics and narrative. When you begin designing this portion of your game, think about the smallest action your player should take to progress and break it down to its smallest components.

If you’re thinking about designing a game that is heavily combat-driven, you need to ask yourself some questions:

  • What type of combat style is it?
  • Does the combat style match the historical theme of the surrounding environment?
  • Does the combat style align with the character or contrast their morals?

How do the answers to all of the above questions align with the emotional experience you are asking the player to feel? These questions aren’t exhaustive by any means. Each of the questions should lead to a further clarification of the game mechanics and shape the experience you want for your players.

It is all too easy to look at the game you are making and fall into the trap of comfort, just falling into line with other games from the genre that you’re developing for. If you find yourself designing something and thinking to yourself, “This is how it’s always been done,” you need to evaluate that interaction. First-person shooters (FPSs) are a great example of this because of the restrictions of the first-person viewpoint.

There is one major unique outlier that did very well in the FPS space: Half Life. Valve created an FPS with physics-based puzzle mechanics and a heavy emphasis on narrative. This was highly unique compared to the run-and-gun hyper-destruction that was the focus of previous FPS games.

Since we are talking about interactions and mechanics through the design lens, we need to talk about the game Undertale. Undertale is a game that begins as a low-graphical-fidelity role-playing game. The gameplay narrative feels normal at first; then combat happens! You quickly learn the combative mechanics you need to win and combat gameplay feels great. However, hurting things isn’t always what you want to focus on. There is a subversion of the players’ expectations, where the game sees you asking the character to hurt people that the character may have an emotional tie to in the game. That emotional difference is being brought into view to show the use of what is standard in a way that turns the players’ expectations on their head. This is only available if you, the designer, study and know game design intimately.

This entire chapter could easily be spent talking about the design of other games’ mechanics and interactions. Instead of breaking down loads of games, let’s work through our own project and investigate some simple examples of mechanics and interactions to use within them.

Throughout the sections, we will also be looking at the implementation of various game designs. Metaphorically speaking, our hope here is that we will break down the puzzle pieces from top to bottom in interaction, to show you that developing a game is all about breaking down each piece while keeping the whole picture in mind.

One piece of advice while reading through these sections is that the implementations of these game interactions are not set in stone, nor are they the only way to use these interactions. They are just examples of our approach. Try to imagine how you might change the design of each piece.

Our project

We are building a 3D puzzle adventure game. Our initial mechanics will be using research as the primary component. Later in the book, in Chapter 7, Rigid Bodies and Physics Interaction, regarding our design of telekinesis, we will layer spatial awareness on top of this. With this understanding, we will build out our game loop. To make an experience worth playing, we can define the game loop as follows:

  1. Search environment for clues
  2. Solve puzzles from clues

With the game loop defined and the understanding that we are focusing on research as a primary mechanic, we now need to build interactions to make an experience.

To get started on our interactions, we are going to work through a couple of simple non-physics-focused actions. The character, Myvari, needs to be able to interact with the environment to complete puzzles and enter areas to get to her destination. Thematically the environments are ruins from her race’s past. Through our demonstrative vertical slice, Myvari will encounter multiple environmental puzzles where she will need to take in her surroundings and overcome obstacles. The player of this game, leading Myvari with their controller, is to pay attention to the detail of the environment and learn how to work through the environmental puzzles. The first interaction the character will face is the stairs. Let’s dive into the design of that to get a real sense of what the rest of the interactions will need in terms of definition.

The stairs

In this demonstrative level, there exists a set of stairs woven within the environment for the character to traverse. Understanding what these stairs convey to the player helps to establish a foundational sense of early interaction affordance, meaning that your environment will be the primary guiding factor for players navigating the level. Let’s work through designing this initial interaction, as it is the first experience the player truly gets to be part of.

Design

When the player enters the game, Myvari will enter from the woods into a cave that seems normal. Moving into the cave brings you into a small hallway that leads to an opening with a steep slope that’s too steep to walk up. There are two pools, one on each side of this slope. There are levers on each side. All that needs to happen is that each level needs to be interacted with. Figure 6.2 shows the blockout of how this will flow.

Figure 6.2: Overview of initial interaction, the stairs

In the first open space encounter, you can get a sense of wonderment. Here is a cave with manmade features. In the distance, there is a semblance of a door and a path leading to it. Making your way toward the door, you will notice the path ahead becoming very steep. Walking around, you research the area and find levers. These levers bring stairs to the path so you can climb to the door surrounded by what looks to be ruins.

Simple environment design needs to be in place here with “light pooling.” This is when you add lighting to an area to draw the attention of the player. We tend to go to areas with more light. Therefore we need the players to interact with particular models within the scene. To make it noticable to the player, you add player affordances to these designated models. For example when you get close enough to the levers, they will become highlighted slightly. Suddenly, a tooltip will be displayed to show you which button to press to interact with it.

Interacting with both levers creates a clicking noise. Moving away from your current camera, a small cinematic will play showing the stairs rising and moving into place. From this point, you may move up the stairs onto the Rings area. The Rings will be the initial puzzle with environmental research at play. We have a sense of how this should be played out, but implementation always throws us a few kinks. We need to put things into practice to see if the design works. Let’s get into Unity and see how it feels!

Implementation

First things first, we need something that we can interact with. Let’s break this down just a little bit. There are 3 points to this implementation. We need an interaction block, a stair blocker, and a manager to work with these two elements.

Interaction block

We know we have two interaction points that both need to be interacted with to satisfy the success of the stairs. This means we should build a trigger that we can use more than once. This cube needs to have a box collider on it, as we will be using collision to help with setting the states.

We’re now going to look at some code. As with Chapter 4, Characters, we won’t be going over each and every line of code – we’ll only look closer at code if we haven’t gone over it before or if, for some reason, we make a change to previously explained code. We are going over InteractionTrigger.cs, which can be found in the Assets/Scripts folder of the project’s GitHub. If you haven’t set up GitHub yet, please refer to the start of this book for instructions on how to do so. When implementing something for the first time, there may be some key areas that you couldn’t design for, so it’s a good idea to work through some visual debugging code to make it easier for you. We want to implement a simple cube that, when you enter it, you can interact with. unclear wording and noticing when we interact with it. The way we do this is by using some colors.

public Color idleColor = new Color(1f, 0f, 0f, 0.5f);
public Color occupiedColor = new Color(1f, 1f, 0f, 0.5f);
public Color interactColor = new Color(0f, 1f, 0f, 0.5f);

We define these in the beginning so we can reference them when we define the states a bit later. We are using the input action interact from the input system we defined in Chapter 4, Characters. In this case, we need to pay attention to that input, so we put it on Update.

void Update()
    {
        interactPressed = interactInput.action.ReadValue<float>() > 0f;
    }

The input here is either 0 or 1, but we want to use it as a bool. This allows us to make simple if checks when we want to change the state. To do this, we ask if the value is above 0. If the assigned interaction button is being pressed, the value is 1, which sets interactPressed to true; otherwise, it’s set to false.

In the next sections, we are going to use some MonoBehaviour methods that we haven’t gone over yet. These are OnTriggerEnter, OnTriggerStay, and OnTriggerLeave. As the names suggest, these methods are useful for dealing with collision states for when something enters, stays, or leaves a collision box.

We will start with OnTriggerEnter. We are only using this to set the color of the box so that we can see that we have entered it. This isn’t mechanically useful, but it is visually helpful. Maybe later on during the polishing stage, we may want to spawn some particles or change some lights to show the player that they are in an area that can be interacted with. For now, let’s just change the color of the cube’s material for the visual debugging.

void OnTriggerEnter(Collider other)
{
   MyvariThirdPersonMovement player = other.GetComponent<MyvariThirdPersonMovement>();
   if (player != null)
   {
      mat.SetColor("_BaseColor", occupiedColor);
   }
}

What’s happening here is that when the player collides with the box’s collision box, we are looking to see if the other component that made the collision has MyvariThirdPersonMovement script. Since no other item that can collide should have that component, this is a good check. We assign that to the player variable and then do a small check asking if the player value isn’t null, then change the color to the occupied color. Now we need to work through OnTriggerStay, which will be where we allow the player to interact with this previously collided-with object.

void OnTriggerStay(Collider other)
    {
        MyvariThirdPersonMovement player = other.
GetComponent<MyvariThirdPersonMovement>();
        if (player != null)
        {
            if (interactPressed)
            {
                mat.SetColor("_BaseColor", interactColor);
                OnInteract?.Invoke();
                Debug.Log($"Interacted with {gameObject.name}");
                if (disableOnInteract)
                {
                    this.enabled = false;
                    this.GetComponent<BoxCollider>().enabled = false;
                }
            }
        }
    }

This should all look similar to the enter trigger until we get to the if block that is waiting for the interact button to be pressed. When the interact button is pressed, we do a few things:

  1. Set the color to an interact color
  2. Invoke an action
  3. Log it out for another debug check
  4. Disable it so we can’t interact with it again

We’ve already set a color previously, so this should seem familiar. We are using the interact color instead here, which also makes sense!

The next portion is invoking an action. This has a bit of a two-part explanation. Our manager will be listening for the action to be invoked. When we get to our manager, we will go over how this works fully. For now, understand that another item will be waiting for a signal to enact the action fully.

We set debugging to the console so we can see what is happening in the logic. When we remove the debugging color, the console debug will be our guide if there is a bug in the future.

The last part is to disable it so we can’t interact with it again. We need to disable both the object and the collider. We’re doing this as this interaction only needs to be pressed once per side.

That’s it! We now need to go over the stair blocker before we get into the manager.

Stair blocker

We know that there will be an effect to blocking the stairs beyond, but we have finished up the look of this blocking mechanism. For now, it is a debugging red block with a collider. This isn’t a problem, as we know how we want the experience to play out, so we need to make a blocker that just doesn’t allow the player through to the stairs yet. We will add the visual portion of that later in Chapter 12, Final Touches. This could be in the form of the stairs being flat so that the player can’t walk up to them, and we make it look slippery, or maybe there can be a rock obstructing the stairs that disappears after the right interaction.

There isn’t any scripting to be done here. We will be offloading any logic for this onto the manager. One of the reasons why we need to have this manager is that we cannot have a script on the stairs themselves to turn themselves on or off. If you have a GameObject that is disabled, the scripts won’t be able to be activated without some outside object with its reference to the disabled GameObject enabling it. So we need to do this with the manager. Let’s do that now.

Interaction manager

Stitching together scripts is much easier if you have a parent object for interactive items in a manager of sorts. In the editor, this is often done by creating a prefab that the parent prefab houses a script to hold the state of the interaction. What we are doing here is making sure that the stairs cannot be turned on without both buttons being pressed. It would be difficult for this to be done without a GameObject knowing the state of each item. Getting into the code, we define our public variables and class variables to set up as we normally do, and then we get into the second part of the events we talked about in the Interaction block section. In the Awake and OnDestroy sections, we need to handle the events listening.

void Awake()
{
   leftTrigger.OnInteract.AddListener(OnLeftTriggerInteract);
   rightTrigger.OnInteract.AddListener(OnRightTriggerInteract);
}
void OnDestroy()
{
   leftTrigger.OnInteract.RemoveListener(OnLeftTriggerInteract);
   rightTrigger.OnInteract.RemoveListener(OnRightTriggerInteract);
}

We defined each of our triggers publically and they both have their own events. On Awake, we listen to the OnInteract event, and if it’s invoked, we will then run the function that is the argument of the listener. In this case, it’s OnLeftTriggerInteract for the left side. The one for the right side is similarly named. We’re only going to look in detail at the left side as the right side is very similar.

void OnLeftTriggerInteract()
{
   leftTriggerFired = true;
   if (rightTriggerFired)
   {
      stairsRaised = true;
      OnStairsRaised?.Invoke();
      stairsBlocker.SetActive(false);
      Debug.Log("RAISE STAIRS HERE");
   }
}

If the left side gets triggered, we immediately set leftTriggerFired to true. That checks if the right side has been triggered already. If it hasn’t been, then nothing happens. If it has, then we will set stairsRaised to true, invoke another action, set the stair blocker GameObject to not active, and log out a string to help with later debugging.

The OnStairsRaised UnityAction will fire, but there isn’t anything attached to this yet. After we finish up this area and finalize what exactly we need, we will add more to this action.

Interestingly, this setup allows the player to start from the left or the right without problems. It also sets us up for future development. We don’t need to have everything laid out, but we do need to have an understanding of what the general idea is so we can write the architecture accordingly.

This completes the current implementation of the stairs puzzle. Now that Myvari is up the stairs, we need to work through our first main puzzle, the rings.

The rings

Passing through the stairs, we’re now faced with a door and rings.

The door signifies the first narrative-driven answer to the puzzle versus lighting drawing them to an area. The puzzle will only be able to be figured out if you pay attention to the image on the door and correlate it to the puzzle rings. Let’s break down the design of the Rings puzzle.

Design

The first puzzle the player will need to work through is the rings. When you get onto the platform of the puzzle, your necklace will animate in front of you and both the necklace and the middle pillar will glow a soft blue before fading. On the door, there will be a time-worn inscription of what the pillars should look like if manipulated properly.

What the player needs to do is push the pillars in the rings to match an image of the celestial bodies found on the door. This allows for multiple levels of research and interaction within a small scene. The player already knows that there is interaction within the environment from the hints given previously, and a small outline will indicate the button to press to interact with the pillars. The new information gathering would be the shape of the imagery in the door to fit the shape of the ground. Figure 6.3 shows a concept of the area. The big blank area at the back is the door. The pillars are within the circles outside the middle pillar. There are three rings in all.

Figure 6.3: The Rings and environmental research puzzle

Working through this puzzle will open the door, but time has not been kind to this door or the area surrounding the ruins in general. When it tries to open, there is debris that collapses within the hallways leading further into the cave. We will take this opportunity to work through another simple interaction, which is navigating tight spaces.

Implementation

We thought a lot about how we would put this one together. Two pillars on either side of each ring. Three rings in total. We needed a certain configuration to end with that would fit our design ideas of a constellation as well. Another problem with this is how to deal with Myvari moving these things. At first, we thought of pushing and pulling, but to make things simpler, we went with pushing only. This allowed us to only worry about rotating in one direction as well as cutting out an animation. Myvari isn’t a large character and pulling might not make much sense. We need to come up with two scripts. The first script will look similar to the one for visual volumes we worked with previously. We will use this to tell which side of a pillar Myvari is on. This tells us which way to rotate it. After we have it rotated to the correct position, we need to have a puzzle manager to know where to initially place the pillars, what the victory rotation value looks like, and how to deal with the ending of the puzzle. Let’s run through the easy one first and look at the puzzle trigger volumes.

Puzzle triggers

This item is simple. We need a box that we will change colors for debugging, just as previously had for the stairs, and then we need to have a few choices that are properties in the inspector that we get to choose before the game starts. These choices (outer, middle, and inner) will be which ring they are located on and which direction they should move. The direction is counterclockwise or clockwise.

Even though we’ve seen it before, in this implementation of color changing, we did something a little bit different that involves the puzzle accessing a method of this class.

public void SetColor(Color color)
{
    meshRenderer.material.color = color;
}

Something to notice here is the public access modifier. It takes in a color. Remember this when we go over the manager of the puzzle script right after this. Next, there are two defined enums. We will put them both below: FirstPuzzleTriggerType and FirstPuzzleTriggerDirection.

public enum FirstPuzzleTriggerType
{
    Outer = 0,
    Middle,
    Inner
}
public enum FirstPuzzleTriggerDirection
{
    Clockwise = 0,
    CounterClockwise
}

We’ve made public enums in the top portion of this class and here we are defining them. These definitions will allow us to choose the ring and direction for each trigger. Look below at Figure 6.4 to see an example of what the enum looks like in the inspector.

A screenshot of a computer  Description automatically generated with medium confidence

Figure 6.4: Display of a public enum in the inspector

If you were to select either of these, they would display the options that are seen in the code above. One more small detail in the code is the first value in the enum, which we are assigning as 0. This will happen by default; however, making it explicit may be a good habit to get into. When someone looks at this code, they know for sure that the enum value will start at 0.

The Puzzle pieces

Open the FirstPuzzle.cs file that is in the scripts folder and attached to the FirstPuzzle GameObject in the hierarchy. We start as we always do by defining the variables we want to use. For this puzzle manager, it needs to have a reference to each transform of the pillar sections, the center pillar, which takes care of finalizing the puzzle, and the properties of the puzzle’s timing. Directly after the public variables that we will assign in the inspector, we have quite a few variables that are not public but are assigned and used within the class’ logic. Take a few moments to read over the comments on them. We will be referencing those class variables throughout the rest of this section.

Though we’ve seen this a few times, this is a larger piece of the definition than we’ve seen previously. We will pull in the whole initialization and go over each piece.

void Start()
    {
        // Cache references to the trigger volumes and the player
        triggers = GetComponentsInChildren<FirstPuzzleTrigger>();
        playerController = FindObjectOfType<CharacterController>();
        // Random starting positions
        outerPillars.eulerAngles = new Vector3(0f, Random.Range(-180f, 180f), 0f);
        middlePillars.eulerAngles = new Vector3(0f, Random.Range(-180f, 180f), 0f);
        innerPillars.eulerAngles = new Vector3(0f, Random.Range(-180f, 180f), 0f);
        // Starting center spire position
        centerSpire.position = new Vector3(centerSpire.position.x, centerSpireStartHeight, centerSpire.position.z);
    }

Regarding the MonoBehaviour class we’re inheriting from, we are using the Start method to initialize the cache references and starting positions of the pillars when the game starts. First off, we need to cache the references to each trigger volume. We are using a UnityEngine.Component method that we have available due to us having a using UnityEngine; directive at the top of this file. This is GetComponentsInChildren<FirstPuzzleTrigger>();. The type that is used in this is called a generic type. You could place any type in the place of FirstPuzzleTrigger, in the code above. This could be Image or Transform. In this case, we only want to grab each trigger. We will clarify why we need them in this manner shortly. Just know that they are all in a bucket waiting to be called.

Next, we need to use FindObjectOfType, which is another UnityEngine method, but it’s on the Object class. It’s part of the UnityEngine library and we’re already requesting access to its methods. It’s going to find the character controller and return it to the playerController variable.

The next three lines are for setting the rotation of the rings. We wanted them to be unique, so if someone played the game more than once, it would be a little bit different each time.

Finally, we have the position of the puzzle being set. We are using this line to set the height of the center spire. After the puzzle is completed, the center spire will raise up to be interacted with. This will move you on to the next section. We wanted this to be animated as you completed the puzzle to reveal the way forward.

We’re going to now move on to the Update method. This is again from MonoBehaviour. The interesting thing about this puzzle is that there are a lot of points where we don’t do much. We mostly just need to wait until the character moves the pillars into the right positions. The way that we are running the update section is like a lock system in a waterway. You must finish the first step to get to the next step. We have a very simplified logic flow for this system. You can see it in Figure 6.5.

Diagram  Description automatically generated

Figure 6.5: Basic lock flow for the puzzle manager

Let’s keep Figure 6.5 in mind while we finish up the puzzle manager. The first step here is to check for victory. Let’s dig into that process block. Victory is dependent on all three pillars being closely enough aligned to the desired rotation values.

outerAligned = CheckAlignment(outerPillars, correctRotationOuter);

We’re checking every frame for the right alignment. Because we are checking the alignment for three separate items, we shouldn’t write the code on all three rings. Let’s write it once and then ask it to refer to a method for each pillar instead. This is called refactoring. Digging in even further, we should break down how it checks for alignment.

bool CheckAlignment(Transform pillarGroup, float correctRotation)
{
    return Mathf.Abs(pillarGroup.eulerAngles.y - correctRotation) < correctThreshold;
}

First, we need it to return a bool. This is very helpful when you want to run a conditional against the response. We’re asking for the current pillar and the correct rotation value. We look at the absolute value of the current rotation in the y value minus the correct rotation. We take that value and check if it’s less than the threshold we’re allowing for “closeness.” If it is, then outerAligned will return true. If all three pillars are returning true, then CheckForVictory will return true, which allows us to move on to the next block in our lock.

The next block is displaying victory. This seems like an innocent block that is just a display for debug; however, there is one small bit of logic in here that helps us with the end block.

        victoryStartTime = Time.time;
        outerStartVictory = outerPillars.eulerAngles;
        middleStartVictory = middlePillars.eulerAngles;
        innerStartVictory = innerPillars.eulerAngles;

These four values being set are important. We need to have set the values before we move on to the next block. We could potentially have this done in the final block; however, sometimes it’s a good idea to set up each bit of logic in a lock so that you can debug easily and know exactly where you are and what the data is that you need at that exact point in the logic. To finish the last block, we know that we need to record what the pillars’ current information is. We have tolerance as to where we need to place the pillars, so this doesn’t mean the puzzle’s solution is always the same. Now that we’ve stored values of the pillars and displayed our victory in the console for debug, we can move on to the final block.

The final block is in the PerformVictoryLerp method. Take your time looking over the whole method. We will break down a single Lerp below. Interestingly, this method is mostly just animating some environmental items for a final piece and allowing us to finish the block, so we don’t check for rotations in this puzzle anymore.

outerPillars.eulerAngles = Vector3.Lerp(outerStartVictory, outerEndVictory, lerpVal);

We’ve seen something similar in the character with using the Slerp method. That one was better for spherical needs. The Lerp is a linear interpolation. You’re transforming a value to another value of the same type over a period. In this case, it’s the rotational values of the pillars to the predetermined end victory rotational values we need to get the pillars to, because there is a small amount of leeway we give to each section. It may feel daunting to work through this method. If you are feeling overwhelmed, just look at a single line and slowly work through it. Each line has a task and it’s providing context for the Lerp time, or it’s lerping itself to another value over that time.

We also have at the end a PrintDebug method outside of our lock system. This allows us to check for what’s going on in the puzzle at any given moment. Take some time to go over this method and surmise what it might show you, and then run the game to see if your assumptions are correct. Did something print in the console you didn’t expect? See if you can find it in the code by following the game’s logic and identifying when you saw the console message arrive.

The next questions that might come to mind are, “That’s a great way to do it, but how do we actually control it? Also, why didn’t we cover the RotatePillar method?” These are great questions! Let’s explore them in the next section.

Puzzle control

Should we have put the control on the puzzle trigger volume? Our thinking is that all control mechanisms should be on the object that’s containing the control. We made another script called FirstPuzzleControl.cs in the scripts folder, which will be attached to the Character GameObjects. This script is responsible for setting up the color of the trigger volume as well as calling the rotate from the FirstPuzzle class. We wrote it in this manner as we wanted to ensure the manager of the puzzle would oversee the rotation of each ring. Even if the character is the object initiating the RotatePillar method with input, the puzzle manager needs to rotate whichever pillar section the player is interacting with, as it owns those GameObjects. Thinking about it this way is a little unique. Try to imagine a manager owning the GameObjects and telling them what to do. We have them already being referenced in this script; we should keep them in this script. The other option would be to also reference them in the control script that is on the character, and then you have multiple references, and could potentially cause a bug that you may not see. Attempt to centralize your GameObjects in a single script that manages them as much as possible.

The RotatePillar method is slightly tricky. In this method, we need to not only rotate the rings, but also need to push the character along with it as well. How are we doing this? Let’s look into it.

public void RotatePillar(FirstPuzzleTrigger trigger)
{
    // Rotate pillars
    float rot = (trigger.triggerDirection == FirstPuzzleTriggerDirection.Clockwise ? pushSpeed : -pushSpeed) * Time.deltaTime;
    trigger.transform.parent.parent.Rotate(Vector3.up, rot);
    // Keep player locked in trigger volume, facing the pillar. We need to disable the CharacterController here
    // when setting a new position, otherwise it will overwrite the new 
position with the player's current position
    playerController.enabled = false;
    float origY = playerController.transform.position.y;
    playerController.transform.position = new Vector3(trigger.transform.position.x, origY, trigger.transform.position.z);
    playerController.transform.forward = trigger.transform.forward;
    playerController.enabled = true;
}

We first need to know how far we’re going to rotate the pillar GameObjects. We’re going to assign the rotation angle to a variable in the method’s scope. We will use ternary to tell if it’s clockwise or counterclockwise and multiply by deltatime to deal with framerate changes. Then we will rotate the item using parent.parent.Rotate along its up vector. The rotation angle and direction are decided on the line above, defined as rot.

One problem is that the character needs to move along with the pillar they are interacting with. The second problem is that the pillar rotates, so we need to face the character toward the pillar she’s pushing. To do this, we will turn off the player’s ability to move and then directly manipulate the character to the trigger’s location and then keep her there while the interact button is held down. We will also point the character toward the pillar using the trigger volume’s forward vector. In the end, we will transfer control back to the player. This makes it so that the character doesn’t get stuck pushing the pillar forever.

That is it! We just made our first puzzle. What happens next after solving this puzzle is that the door tries to open, but it breaks a bit and only has a small space to move through. Let’s take some time to break down why this might be useful.

Tight spaces

There are times when your game needs to load the next scene, but you may not want the loading screen to pull your player out of the immersion, or you may just want to add some environmental tension. Possibly you want to achieve both! Tight spaces is a common tool to use to achieve either of these situations. Let’s go through how we’re using tight spaces in our work.

Design

The concept of tight spaces is an interesting design use. We are using it in two ways. The first way is to add some tension to the exploration and movement. You have a character who must get through a space that is very narrow that she just watched collapse into place.

The second point is that this is a common design used for transitioning. Since we are only using this design concept on a small vertical of the game and we do not need to load multiple parts of the map, it isn’t needed for that reason, but it is a good teaching point to you, the aspiring designer.

This helps with setting expectations for the player, letting them know that there will be slow-moving sections with tight animation and the camera close to the player to push up the intensity. While this longer animation and movement is happening, it will give time for the system to load into memory the next area without a loading screen. This trick is great as it doesn’t hurt the immersion while allowing for detail to be retained. Nothing breaks the suspension of disbelief more than watching objects load into existence in front of you. After you finish working your way through the closed-off space, the rocks will fall naturally and close off the hallway. Not only does this stop any backward movement, but this also gives a feeling of necessity to move forward.

Implementation

The initial implementation of this is simple. We will make a Cinemachine camera move through the spaces to get the sense of timing needed for the cinematic while not allowing the player any inputs. We do this starting implementation through code as follows:

Void SetPlayerEnabled(bool enabled)
{
var cams = playerRoot.GetComponentsInChildren<CinemachineVirtualCamera>(true);
  foreach(var cam in cams)
  {
     cam.gameObject.SetActive(enable);
   }
   playerRoot.GetComponentInChildren<MyvariThirdPersonMovement>().enabled = enable;
}

We need to find the virtual cameras in the children and enable them while also disabling the player character. In Chapter 12, Final Touches, we will call this code when we trigger the cinematic. But instead of the virtual cameras that are in the children, we will call the camera made for the cinematic.

This implementation works very well for setting up the logic, without worrying about the nuance of each cinematic, which is very time- and animation-intensive.

Interactive volumes

This is the Swiss Army knife of mechanics. There are so many uses of interactive volumes that we can’t cover them all in this book. This would also not make much sense as defining this takes away from the creativity that could be designed. This is not a tool that should be detailed in high granularity. Instead, let’s go over how we will use it as well as some thoughts on it in general.

Design

As this is an adventure puzzle game, there will be points at which we need volumes where, when the character enters them, something happens. This definition is purposely broad. We are also using Cinemachine for our character’s main camera. This allows us to link up virtual cameras in certain places when you trigger your volumes. Here are some examples of what we can perform with interactive volumes:

  • Moving the camera over a cliff to give a sense of heightened anxiety
  • Triggering rocks to fall
  • Changing the walking animation to be slower when you walk through water
  • Changing the lighting in an environment
  • Spawning GameObjects

This list is not exhaustive by any means, as volumes are a creative tool to allow for interaction. We’re using them in only a few ways, but the possibilities are endless. Let your imagination run wild while designing with interactive volumes.

This is powerful for many games to have, especially for us, with our environment-driven, research-focused mechanics. Our interactions require the environment to explain to the player what is going on and how to move forward. In the scripting section below, we will go through each interactive volume for this chapter. You can expect to see more volumes being used in future chapters, especially in the polishing phase as we can enhance the experience with many small interactions. This will help to make the environment and gameplay immersive.

Implementation

Luckily for us, you’ve seen two versions of interactive volumes. Look back up in the implementations of what we looked at earlier and pay close attention to the volumes. They are unique in their uses and can teach you some valuable lessons about developing in an environment without all the art assets being completed.

It may be a good idea to also think about some other ways we could’ve used interactive volumes so far. Were there any volumes you may have wanted to add?

Why don’t we go over a bit of a summary of where we’re using them in our game?

  • We’re using interactive volumes in any place where we can use an input action. An example of this might be the stairs buttons for accessing the stairs.
  • We’ve added a volume to know the player is in the zone of the first puzzle. This allows the camera to move to a more advantageous position to understand the puzzle visually.
  • Triggers on the puzzle pieces let you know you are close enough to interact with them.
  • There is a volume for you to be able to tell you’ve entered the trigger point for entry into the tight spaces.
  • Crossing the bridge, there is a small volume to change camera angles for a more cinematic shot.
  • At the end of the bridge, there is a volume to trigger another tight space cinematic.
  • While on a ledge, there is a trigger that unleashes a boulder to fall on you, whereupon you lift your arm up in defense. This will trigger you to discover a new power, which is a new mechanic.
  • More triggers are used to open another door.
  • There are triggers on items that you can interact with using your telekinetic powers.
  • There are triggers on the final puzzle pieces.

This is a summary of all the triggers that are part of our core gameplay. There are a few others that deal with ambient flora and fauna, but they are simple collision triggers responsible for small changes or the simple movement of birds or deer. They are in random locations for cosmetic purposes.

Summary

In this chapter, we went over the design and implementation of interactions and mechanics. Even though the player’s experiences and interactions seemed quite simple, the depth of design of player afforadances allowed the player to know their limits and navigate the gameplay. We spent a good amount of time talking about interactions and mechanics. We defined the game loops and broke down parts of the mechanics toolbox. This was a very quick and short lesson into various game experiences. Finally, we broke down a bit of our game.

We broke down the stairs interaction and how that would be managed. We also went over why the stairs problem exists and where the solutions need to happen. Then, after that was completed, we went over the design of the first puzzle. After that was fully explained, we broke down our version of the implementation. Once this puzzle is completed, it’s followed by a tight space segment, which could be used for loading the rest of the level, if we were on a larger scale project. Finally, there was a small section on how to use interactive volumes. As we used two different types of interactive volumes in our previous implementation, we went over them as well.

Overall, this chapter was very dense in information. Take some time to pause here and digest what you have just learned. Even if you feel like you could move on, let’s just relax and let your brain process it all. In the next chapter, we will go over physics mechanics and interactions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset