About designing a flexible system of character abilities in games

    The character’s ability system is perhaps the most demanding of flexibility in the game. It is impossible to predict at the design stage which spells will appear in the final version or in subsequent updates. This post will be about how I abstracted the process of performing abilities.

    Ability itself is nothing more than a set of actions. The minimum interface of the ability consists of one method: “apply”, but not so simple about the difficulties under the cut.

    image

    Every ability begins with a series of checks to see if it can be applied. Among them are ordinary ones, such as checking recharge, mana availability, checking distance and others. It is already evident here that not all checks are needed by all abilities. For example, there are abilities applied at any distance. That is, different abilities require different sets of checks before execution. However, it is clear that many of the checks will be repeated, and often many abilities require the same set of checks.

    In total, part of the checks will be logically repeated, which means they must be changed as agreed, that is, immediately in all places. Moreover, the sets of parts of the checks in the general case will be different.

    If parts of checks are selected into separate objects that implement one interface and built into a singly linked list, we get a template chain of responsibilities.

    image

    In the case of a successful check in the link, the check in the next link will start, if there is no next link, then the entire check can be considered successful. In addition to the check itself, the link may also contain an error handler. For example, if when checking for mana, it turned out that it is not enough, then the link can notify the player about this.

    Using the chain of duties, for the ability [Powerful shot], we can easily insert an additional link, checking whether the character is wearing a bow or link, verifying that the character has a health level below 30%, for the ability [Second Wind].

    Let's roll back and remember that there are chains of checks that are the same for many abilities. Let's highlight the essence of the ability fulfillment request and describe each of the types of test chains with its class.

    The request is only required to draw up a chain of duties, launch it and cancel it when the player gives the appropriate command.

    image

    We will make chains in query implementations already.

    At the moment, we have already learned how to make flexible tests of the ability to perform abilities. Now, in case of a successful check, it is still necessary to fulfill the ability.

    I preferred to do this without changing the interfaces, adding the last always successful link that the ability performs as a side effect. Here is an example implementation of it:

    public class TerminalChecker: ICastChecker {
        CastChecker next { get; set; } 
        ISkill skill;
        public TerminalChecker(ISkill skill) {
            this.skill = skill;
        }
        public bool check() { 
            skill.cast();
            return true; 
        }
    }
    

    This implementation allows us to make requests asynchronous. This is useful when we need additional information from the user. For example, an ability must be applied to some area that the player selects with the mouse. Of course, you cannot stop the game at this time.

    Now we need to match requests with abilities. We will do this, of course, using polymorphism by adding a property to the ability interface. At this stage, we expanded the ability to this interface:

    image

    After all the work done, let's think about what an ability is. In the current implementation, this is a set of actions that is preceded by a series of checks. Note that at a high level, we are in no way dependent on specific game logic. With the initial idea to describe the system of abilities as applied to spells, we got a system that, according to certain rules, gives or does not allow us to perform arbitrary actions.

    Due to this property, this system can describe any modification of the game world. For example, a sales transaction or a building construction team.

    Let's take a look at everything in general again

    image

    In this example, a Sprint ability is a normal ability without a target; the class that implements the request for such capabilities is NontargetCastRequest, which in turn forms a chain of checks from ManaChecker, CooldownChecker, and TerminalChecker.

    The calling code does not depend on the details of the implementation of this system, that is, we will not break the game logic by adding or changing the ability.

    This is the system of character abilities in a minimal form. This model lacks the means of alerting the calling code, transferring capabilities to the user interface, and other details of life. You can think of them yourself.

    Also popular now: