Click here to Skip to main content
15,901,284 members
Articles / Programming Languages / C# 7.1

Learning concepts from Video Games. Rule Systems in C#

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
7 May 2024CPOL5 min read 1.5K   1   2
Rule Systems and How they work with Games.
Rule Systems with Game Development.

Introduction

Let's say you are making a game that allows for combo moves to be activated by the player, this approach allows for you to do this in a simple manner.

Background

One of the video games I played a lot as a kid and purchased the remake of as soon as it was available for preorder was Saga Frontier.

https://store.steampowered.com/app/1295270/SaGa_Frontier_Remastered/

The combat system was unlike any other game I had played at the time. Your team could execute combo attacks, but only if their attacks align perfectly in the correct order of execution. Each character learns new abilities based on how many times they execute an ability, which isn’t described to you at all, it is something you need to figure out. The last thing that happens in this game is that certain equipment allows for that character to gain a new ability.

This led me down the path of wondering, “How could I recreate this?”

As it turns out, it isn’t all that complicated, I mean, it is but the theory behind it is pretty simple once you understand the concepts. All of this stems from what is called a Rule Based System.

Rule Based Systems are great for not only Software Development, but also game development and Web Development. A Rule Based System is simply a system where rules are defined, and the software will execute tasks based on those rules.

In Web Development, this can easily be seen in CRMs such as Salesforce. In Salesforce, you have Workflow Rules, which automate an organization’s standard process. In other words, you can have the database update records based on specific criteria being reached.

In Software Development, this can be done in many different contexts. Let’s go with a simplistic one, a controller for a stop light. Based on certain rules or conditions, it will determine if the light should be red or green. This will control how long the light should be red or green and if the rules don’t apply to either, then the light should be yellow.

In Game Development, there are two main contexts that we can see being used. For Game AI Systems and for Player controlled entities. For Game AI Systems, it would be used in context such as the AI should path along a specific path provided that the player or some other force is not interacting with it, if it is interacting with something, then it should change to a new state or move along a new path.
With the context of Player controlled entities, it could be used in the context of skills learned by the entity based on conditions being met.

This is cool and all, but we know why you are reading this post, you wanna know how to implement it. I got you covered. We will be doing a basic implementation of this in C#.

Using the code

We are going to have a grand total of 7 classes. Character Role, Move, Character, Combo Attack, Party, RuleEngine and Program.

The first thing we are going to do is define an enum called CharacterClass. Nothing too fancy with it in the slightest. Why an enum? Simply, in an RPG context, you would have a character class role associated with the character and so I went with a combination of Warcraft and Final Fantasy naming conventions for that.

C#
public enum CharacterClass
{
    Warrior,
    Mage,
    Rogue,
    Healer,
    Tank
}

Next, we need to define our Move class. We want to make sure to indicate if the move can be learned and the chance for the move to be learned.

C#
public class Move
{
    public string Name { get; set; }
    public bool IsLearnable { get; set; } // Indicates if the move can be learned
    public double LearningChance { get; set; } // Chance to learn this move

    public Move(string name, bool isLearnable = false, double learningChance = 0)
    {
        Name = name;
        IsLearnable = isLearnable;
        LearningChance = learningChance;
    }
}

Next up, we are going to create our ComboAttack class. This will allow for us to specify which moves are required to execute the combo and check if a combo has been made.

C#
public class ComboAttack
{
    public string Name { get; set; }
    public List<string> RequiredMoves { get; set; }

    public ComboAttack(string name, List<string> requiredMoves)
    {
        Name = name;
        RequiredMoves = requiredMoves;
    }
}

The Character class. Since this is a console application, we want to simulate move selection and using a move. We also want to incorporate some randomness in order to simulate how it would work in an actual game.

C#
public class Character
{
    public string Name { get; set; }
    public CharacterClass Class { get; set; }
    public List<Move> Moves { get; set; } = new List<Move>();
    private Random rng = new Random();

    public void LearnMove(Move move)
    {
        if (move.IsLearnable && rng.NextDouble() < move.LearningChance)
        {
            Moves.Add(move);
            Console.WriteLine($"{Name} learned a new move: {move.Name}");
        }
    }
}

Since Saga Frontier had a party based structure, we should probably include that as well, so we will create a list of characters and randomize the order by which they can attack. In a real game, this would obviously be covered by their stats.

C#
public class Party
{
    public List<Character> Members { get; set; } = new List<Character>();
    private List<string> moveHistory = new List<string>();
    private Random rng = new Random();

    public Party(IEnumerable<Character> members)
    {
        Members.AddRange(members);
    }

    public void RandomizeAndUseMoves(RuleEngine ruleEngine, List<Move> learnableMoves)
    {
        var randomizedMembers = Members.OrderBy(x => rng.Next()).ToList();

        foreach (var character in randomizedMembers)
        {
            if (character.Moves.Any())
            {
                var moveIndex = rng.Next(character.Moves.Count);
                var selectedMove = character.Moves[moveIndex];
                Console.WriteLine($"{character.Name} uses {selectedMove.Name}.");
                
                moveHistory.Add(selectedMove.Name);

                // Check for learnable moves
                character.LearnMove(selectedMove);

                // Check for combo execution
                ruleEngine.CheckForCombo(moveHistory.Select(name => new Move(name)).ToList());
            }
        }
    }
}

Now for the RuleEngine. This defines what a combo move is and how to add a combo move.

C#
public class RuleEngine
{
    public List<ComboAttack> Combos { get; set; } = new List<ComboAttack>();

    public void AddCombo(ComboAttack combo)
    {
        Combos.Add(combo);
    }

    public void CheckForCombo(List<Move> performedMoves)
    {
        foreach (var combo in Combos)
        {
            var recentMoves = performedMoves.Select(m => m.Name).Reverse().Take(combo.RequiredMoves.Count);
            if (recentMoves.SequenceEqual(combo.RequiredMoves))
            {
                Console.WriteLine($"Combo executed: {combo.Name}");
                break;
            }
        }
    }
}

And finally, our Program.cs file. We will be able to define combo attacks here as well as the Rules for that combo attack being executed. We also define the players, their roles and the moves they have available to them.

C#
public class Program
{
    public static void Main()
    {
        var warrior = new Character { Name = "Chuck Norris", Class = CharacterClass.Warrior };
        warrior.Moves.Add(new Move("Punch"));
        warrior.Moves.Add(new Move("Kick"));

        var mage = new Character { Name = "Lucy Liu", Class = CharacterClass.Mage };
        mage.Moves.Add(new Move("Fireball"));

        var rogue = new Character { Name = "Jet Li", Class = CharacterClass.Rogue };
        rogue.Moves.Add(new Move("Stab"));
        var healer = new Character { Name = "Misty", Class = CharacterClass.Healer };
        healer.Moves.Add(new Move("Lesser Heal"));
        healer.Moves.Add(new Move("Defend"));
        var tank = new Character { Name = "Mike Tyson", Class = CharacterClass.Tank };
        tank.Moves.Add(new Move("Provoke"));
        tank.Moves.Add(new Move("Shield Bash"));

        var party = new Party(new List<Character> { warrior, mage, rogue, healer, tank });

        var ruleEngine = new RuleEngine();

        ruleEngine.AddCombo(new ComboAttack("Flaming Punch", new List<string> { "Fireball", "Punch" }));
        ruleEngine.AddCombo(new ComboAttack("Radiant Eclipse", new List<string> {"Punch", "Lesser Heal", "Shield Bash", "Stab", "Fireball"}));
        ruleEngine.AddCombo(new ComboAttack("Mystic Flurry", new List<string> { "Stab", "Kick", "Fireball" }));
        //... Add other combos

        party.RandomizeAndUseMoves(ruleEngine, warrior.Moves);
    }
}

I want to note that we could add a move and specify if it is learnable in this code as well.

C#
warrior.Moves.Add(new Move("Shield Slam", true, 99));

The boolean represent if the move is learnable and the value is a double for the chance to learn the move. In this case, it has a 1% chance to not learn the move. Now, we should probably run the program and see what we get.

Image 1

Running the application in Jetbrains Rider

This is a very simplistic implementation of a Rule Based System and is not to be taken as a full implementation. While it isn’t a full implementation, I hope this serves as a great primer to this concept for you.

Points of Interest

This was an excellent way for me to learn about new systems by dissecting mechanics in games I love. This introduced me to Rule Systems and how they can be applied to video games.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Seasoned Software Consultant & Senior Salesforce Developer | Expert in Salesforce Development | Proficient in Full-Stack Solutions with .NET Framework | Agile Methodology Enthusiast | Salesforce Certified Administrator

Comments and Discussions

 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA9-May-24 19:13
professionalȘtefan-Mihai MOGA9-May-24 19:13 
GeneralRe: My vote of 5 Pin
GameDevMadeEasy10-May-24 3:10
GameDevMadeEasy10-May-24 3:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.