D20 RPG – Skill Check

In this lesson we will be putting our adventurer’s skills to the test.

Overview

Having a hero with skill “values” gets us a long way to our goals. In this game, we don’t simply ask if your skill is a certain level, but add some randomness. Just like in real life, you may be a fast runner but don’t always win the race – you might trip, or feel tired for some reason. That element of randomness is the focus of what we will add in this lesson. We will make the final system that puts a hero’s skills to the test to see if they can really do what they set out to accomplish.

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.

Check System

The game mechanics we will be implementing in this system come from the Pathfinder general rules around “Checks” – read more about them here.

In my own words, this is the system that handles the randomness involved in the completion of an action (attacking an enemy or climbing a wall, etc.). It determines whether the action was a success or failure, and also the “degree” to which you succeeded or failed.

Create a new folder at Assets -> Scripts named Actions. Next create a new C# script in that folder named CheckSystem and add the following:

public enum Check
{
    CriticalFailure,
    Failure,
    Success,
    CriticalSuccess
}

The Check enum represents the four possible outcomes from an action. Basically, you can either succeed or fail, and either side has a “critical” variation. As an example of how this applies in the game, if you get a “Success” in an attack against an enemy, then you can deal damage to it. However, if you get a “Critical Success” then the damage you deal might be doubled.

Add the following:

public interface ICheckSystem : IDependency<ICheckSystem>
{
    Check GetResult(int modifier, int difficultyCheck);
}

This is the interface for our system. Our interface only defines a single method called “GetResult” which is the method we will call to handle all the rules around rolling a dice, adding modifiers, and making the final check.

The method accepts as a parameter a “modifier” – which actually represents the sum of all modifiers (bonuses and penalties, etc) that could be applied to an action. As an example, when checking an ability to jump over a hole, it may include the “Athletics” skill value as a modifier, but may also have added a penalty for heavy armor depending on your strength. All of those rules should be determined per action. The sum of those values is simply passed to this system.

The method also accepts as a parameter the “difficultyCheck” (usually seen as “DC”) which represents how hard it should be to complete the action itself.

Finally, the method returns an ActionResult that reveals the result of our attempt on the action.

Add the following:

public class CheckSystem : ICheckSystem
{
    public Check GetResult(int modifier, int difficultyCheck)
    {
        var roll = DiceRoll.D20.Roll();
        var attempt = roll + modifier;

        Check result;
        if (attempt >= difficultyCheck + 10)
            result = Check.CriticalSuccess;
        else if (attempt >= difficultyCheck)
            result = Check.Success;
        else if (attempt <= difficultyCheck - 10)
            result = Check.CriticalFailure;
        else
            result = Check.Failure;

        if (roll == 20 && result < Check.CriticalSuccess)
            result += 1;
        else if (roll == 1 && result > Check.CriticalFailure)
            result -= 1;

        return result;
    }
}

Here we have defined the actual system that conforms to the interface. The GetResult method begins by rolling a D20 dice roll. The roll is then added to our “modifier” to see the overall value of the attempt to complete an action. The output of the method will be a Check and there are a few things that determine the result:

  1. If the value of our attempt is greater than or equal to the difficulty check plus 10, then we have critically succeeded.
  2. If the value of our attempt is greater than or equal to the difficulty check, then we have succeeded.
  3. If the value of our attempt is less than or equal to the difficulty check minus 10, then we have critically failed.
  4. If the value of our attempt is less than the difficulty check then we have failed.

In addition to the above, there are special rules around the dice roll itself:

  1. If the dice roll was a ’20’ you can increase the rank of the check by 1 (not exceeding critical success).
  2. If the dice roll was a ‘1’ then you decrease the rank of the check by 1 (not falling below critical failure).

Injection

Create a new C# Script in the same folder named ActionInjector and add the following:

public static class ActionInjector
{
    public static void Inject()
    {
        ICheckSystem.Register(new CheckSystem());
    }
}

Next, open the main Injector script and add the following statement to its Inject method:

ActionInjector.Inject();

Skill Explore Entry Option

Now that we have a hero complete with skills, and a system that can run checks based on those skills, we have everything necessary to implement some exploration options that are dependent on a skill check.

Create a new C# script at Assets -> Scripts -> SoloAdventure -> Explore -> EntryOptions named SkillExploreEntryOption and add the following:

using UnityEngine;

public class SkillExploreEntryOption : MonoBehaviour, IEntryOption
{
    [SerializeField] string text;

    public string Text
    {
        get { return text; }
    }

    public void Select()
    {

    }
}

To begin, we have created a new subclass of MonoBehaviour that we can use as another type of IEntryOption. Like the other options, this is a component that can be attached to an Entry asset. To implement the interface, we need to provide the Text that appears on the option button, and need a method that handles the action to apply when choosing the option.

Add the following fields to the class:

[SerializeField] Skill skill;
[SerializeField] int difficultyCheck;
[SerializeField] string criticalFailureEntry;
[SerializeField] string failureEntry;
[SerializeField] string successEntry;
[SerializeField] string criticalSuccessEntry;

Now we start making this option special. We define what kind of “skill” this option is dependent upon. We also specify the difficultyCheck for the relevant task defined in the entry. Then we specify a different entry asset for each possible outcome of our skill check.

Add the following method:

string GetEntry(Check result)
{
    switch (result)
    {
        case Check.CriticalFailure:
            return criticalFailureEntry;
        case Check.Failure:
            return failureEntry;
        case Check.Success:
            return successEntry;
        default: // case Check.CriticalSuccess:
            return criticalSuccessEntry;
    }
}

We use the GetEntry method to map from the Check result enum to one of the four Entry fields we added to this asset.

Add the following to the Select method:

var hero = ISoloHeroSystem.Resolve().Hero;
var modifier = hero[skill];
var result = ICheckSystem.Resolve().GetResult(modifier, difficultyCheck);
var entry = GetEntry(result);
IEntrySystem.Resolve().SetName(entry);

First we grab a reference to the solo adventure’s hero entity. From the entity we can grab the hero’s relevant skill modifier to use as the modifier for our check. We pass it to the CheckSystem along with the DC to get our check result. We then pick the entry according to the check result and apply it as the next entry for our adventure.

Entry Assets

Let’s create assets for each possible outcome of the attempt to jump the hole. Don’t forget that you will need to make the new assets addressable. We will also need to update the Entry where the skill check occurs so that we can use our new skill based exploration component.

Critical Failure Entry

Create a new Entry prefab asset named Entry_08 for the critical failure entry. For the Entry Text I have added:

If someone were recording this you’d probably win America’s Funniest Home Video! You fail to jump the gap in a glorious fashion. Hopefully another adventurer will come along to save you… eventually.

Add an ExploreEntryOption with the text: “Game Over”.

Failure Entry

Create a new Entry prefab asset named Entry_09 for the failure entry. For the Entry Text I have added:

You made a valiant attempt, but sadly did not make it to the other side. If only you were a chicken.

Add an ExploreEntryOption with the text: “Game Over”.

Success Entry

Create a new Entry prefab asset named Entry_10 for the success entry. For the Entry Text I have added:

Hey you actually made it. I wasn’t expecting that… now if only there was something interesting over here for you to do. Maybe you can make your own adventure!

Add an ExploreEntryOption with the text: “You Win!”.

Critical Success Entry

Create a new Entry prefab asset named Entry_11 for the success entry. For the Entry Text I have added:

What a show-off. Not only did you make it across the hole but you looked like a superhero while doing it. Someone as great as you can probably continue, but you’ll need to implement the rest of this adventure yourself. Good luck!

Add an ExploreEntryOption with the text: “You Win!”.

The Jump Entry

Next, select the Entry_06 asset. Change the ExploreEntryOption so that the “Text” now says “Turn back” and the “Entry Name” says “Entry_03”.

Then add a new SkillExploreEntryOption with the following:

  • Text: Jump
  • Skill: Athletics
  • Difficulty Check: 15
  • Critical Failure Entry: Entry_08
  • Failure Entry: Entry_09
  • Success Entry: Entry_10
  • Critical Success Entry: Entry_11

Try It Out

It is a great time to test out what we have accomplished so far. Go ahead and play the game – navigate through our adventure until you reach the skill check. How does it go? Do you make it across? Try a few times and see if get a different result.

Summary

In this lesson we created a system that handles the logic around rolling a check for an action. It is a generic and reusable system that takes a modifier and difficulty check as parameters and uses them with its own dice roll to determine success or failure. It handles special rules around total attempt values that meet or miss by 10 or more, as well as conditions where the dice roll itself is very lucky or unlucky and how that can change the overall result.

After creating the check system, we created a new type of explore entry option that can navigate based on a skill check. We modified the story entry where the hero attempts to jump over a hole so that it uses the new component, and has a different message based on the check result.

At this point I will consider the exploration part of the game’s mechanics to be complete. While there is always more we could do, I hope that I have set up enough of a foundation that you can continue to extend it on your own. Starting from the next post we will begin implementing the encounter part of the game – that means its battle time!

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!

Leave a Reply

Your email address will not be published. Required fields are marked *