GS2-SkillTree

Skill tree feature

This microservice is designed to realize the skill tree functionality commonly used as a growth factor for characters and other objects. A skill tree has a tree structure like the one shown below, and the release of nodes improves the parameters of a character. Releasing a node requires a cost, and GS2-SkillTree allows you to set the “Consume Action” provided by each microservice.

flowchart TD
  Base --> Node1[STR+5]
  Node1 --> Node2[DEF+5]
  Node2 --> Node3[SPD+5]

  Node3 --> Node4[STR+5]
  Node4 --> Node5[DEF+5]
  Node5 --> Node6[STR+5]
  Node6 --> Node7[DEF+5]

  Node3 --> Node10[SPD+5]
  Node10 --> Node11[DEF+5]
  Node11 --> Node12[SPD+5]
  Node12 --> Node13[DEF+5]

  Node11 --> Node30[SPD+5]
  Node30 --> Node31[STR+5]
  Node31 --> Node32[SPD+5]

  Node7 --> Node20[STR+5]
  Node13 --> Node20
  Node20 --> Node21[STR+5]
  Node21 --> Node22[STR+5]

Prerequisite node

Each node can have a “prerequisite node” to create a tree structure. Up to 10 nodes can be set as a prerequisite node, and a node cannot be released unless the node set as a prerequisite node is in the released state.

Note that it is not necessary to set all the nodes leading up to the node as a prerequisite node, but only the one immediately before it to create the tree structure.

Restore a released node to un-released

Each individual node or all nodes can be returned to the unreleased state.

Cost reimbursement

In doing so, the cost consumed to release the node can be returned based on a specified percentage.

It is important to note that there are two types of transaction consume actions: reversible and non-reversible. A “reversible” consume action is returned, while a “non-reversible” consume action is not.

Please check each microservice reference to see if a consume action is “reversible”.

Manipulating nodes in the middle of the tree

If a node that depends on this node has already been released, this node cannot be returned to the unreleased state.

Batch release of nodes

Multiple nodes can be released at once. You do not need to specify nodes in dependency order; GS2-SkillTree handles the release processing by considering the dependency order and determining whether each release is possible.

Script Triggers

Setting releaseScript and restrainScript in the namespace allows you to call custom scripts before and after node release or re-restraint operations. Configuring doneTriggerTargetType also enables asynchronous execution via Amazon EventBridge or GS2-Script.

The main configurable event triggers and script names are as follows:

  • releaseScript (Completion notification: releaseDone): Before and after node release
  • restrainScript (Completion notification: restrainDone): Before and after reverting a node to unreleased state

Master Data Operations

Registering master data allows you to configure data and behaviors available to microservices.

Master data types include:

  • NodeModel: Skill tree node definitions

Master data can be registered via the Management Console, reflected from GitHub, or integrated into workflows using GS2-Deploy for CI registration.

Buff Adjustments

Using GS2-Buff, you can adjust the releaseVerifyActions, releaseConsumeActions, and restrainReturnRate of a node model via buffs. This allows you to modify release conditions, costs, and return rates dynamically based on events.

Transaction Actions

GS2-SkillTree provides the following transaction actions:

  • Consume Action: Mark node as unreleased
  • Acquire Action: Mark node as released

By using “Mark node as released” as an acquire action, it is possible to safely perform processes within a transaction to directly mark specific nodes in the skill tree as released as a reward for purchasing a product at a shop or completing a quest. This enables flexible character growth experiences, such as immediately granting special skills to players who achieve specific conditions without them needing to spend additional costs.

Implementation Example

Obtains the release status of a node

    var domain = gs2.SkillTree.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
    );
    var item = await domain.ModelAsync();
    const auto Domain = Gs2->SkillTree->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Status(
    );
    const auto Future = Domain.Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

Node release

    var domain = gs2.SkillTree.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
    );
    var result = await domain.ReleaseAsync(
        nodeModelNames: new string[] {
            "node-0001",
        }
    );
    const auto Domain = Gs2->SkillTree->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Status(
    );
    const auto Future = Domain->Release(
        []
        {
            const auto v = MakeShared<TArray<FString>>();
            v->Add("node-0001");
            return v;
        }()
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

Restore the released state of a node

    var domain = gs2.SkillTree.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
    );
    var result = await domain.RestrainAsync(
        nodeModelNames: new string[] {
            "node-0001",
        }
    );
    const auto Domain = Gs2->SkillTree->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Status(
    );
    const auto Future = Domain->Restrain(
        []
        {
            const auto v = MakeShared<TArray<FString>>();
            v->Add("node-0001");
            return v;
        }()
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

Reset the release status of a node

    var domain = gs2.SkillTree.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
    );
    var result = await domain.ResetAsync(
    );
    const auto Domain = Gs2->SkillTree->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Status(
    );
    const auto Future = Domain->Reset(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

Detailed Reference