GS2-Grade
キャラクターや装備の育成には短期成長目標としてのレベルアップとは別にグレードアップを用意することが一般的な仕様です。 グレードアップすることによって、レベルキャップを引き上げることができ、キャラクターや装備をより強力に育成することが可能となります。
グレードアップの方法はいくつかの方法が考えられ、育成素材を消費することでグレードアップできるものや、同種のキャラクターや装備を合成することでグレードアップできるものもあります。 GS2-Grade は方法については問わず、グレードごとのレベルキャップを設定することで、GS2-Experience のレベルキャップの運用をより簡易に実装できるようにすることを目的としています。
graph LR Player["プレイヤーの所持物"] -- グレードアップ --> Grade["GS2-Grade<br/>(グレード値が上昇)"] Grade -- ApplyRankCap --> Experience["GS2-Experience<br/>(ランクキャップが反映)"] Experience -- 経験値の獲得 --> LevelUp["レベルアップ"]
グレード
グレードごとに GS2-Experience のランクキャップに設定する値を定義できます。
グレードはキャラクターや装備のような対象を一意に指す propertyId ごとに管理され、Status として個別に保持されます。
| フィールド | 説明 |
|---|---|
gradeName |
グレードモデル名 |
propertyId |
グレードを適用するリソースを示すGRN(例: GS2-Inventory の ItemSet) |
gradeValue |
現在のグレード値(0始まりの整数) |
グレードの初期値
プロパティIDが正規表現にマッチするかによって初期グレードの値を設定できます。 例えば、GS2-Inventory の ItemModel の名前が SSR から始まる場合はグレード3、SR から始まる場合はグレード2で開始するようにすることで ItemModel の種類によって初期のランクキャップを変えることが可能です。
マスターデータの defaultGrades に複数の propertyIdRegex と defaultGradeValue の組を登録することで初期グレードを宣言します。
グレード引き上げ素材判定
グレードアップの方法として「同種のキャラクターや装備を合成する」ケースで、素材として使用しようとしているリソースが「同種のキャラクターや装備」であるかを判定する処理をサポートする機能が用意されています。 グレードのプロパティIDから正規表現によって、パラメーターを取り出して正規表現を構築し、素材として使用しようとしているリソースのプロパティIDがマッチするかを判定することで実現します。
例えば、GS2-Inventory の ItemSet が同種の ItemModel によるものかを判定する場合は以下のように正規表現を設定します。
propertyIdRegex: grn:gs2:{region}:{ownerId}:inventory:namespace-0001:user:(.*):inventory:character:item:(.*):.*
gradeUpPropertyIdRegex: grn:gs2:{region}:{ownerId}:inventory:namespace-0001:user:$1:inventory:character:item:$2:.*これで、propertyIdRegex を使用して「ユーザーID」と「ItemModel の名前」を取り出します。 ユーザーIDが「user-0001」、ItemModel の名前が「item-0001」、ItemSet の名前が「item-set-0001」だとすれば、ItemSetのGRN(プロパティID)は
grn:gs2:{region}:{ownerId}:inventory:namespace-0001:user:user-0001:inventory:character:item:item-0001:item-set-0001となり、gradeUpPropertyIdRegex の置換後の値が
gradeUpPropertyIdRegex: grn:gs2:{region}:{ownerId}:inventory:namespace-0001:user:user-0001:inventory:character:item:item-0001:.*となります。 これによって、同種のアイテムを素材に指定することができる ような表現が可能となります。
報酬加算テーブル
グレードモデルには acquireActionRates を設定でき、グレードに応じて入手アクションの報酬量を調整できます。
これを利用することで、「高グレードのキャラクターはクエスト報酬の獲得量が増える」「グレードごとに経験値倍率が変化する」といった成長要素を組み込めます。
mode |
説明 |
|---|---|
double |
rates に設定した倍率を浮動小数で乗算します。一般的なアイテム獲得量の補正に利用します。 |
big |
bigRates に設定した倍率を文字列形式で扱い、int64 を超える数値を扱える GS2-Inventory の BigItem など大量報酬を取り扱うケースに対応します。 |
トランザクションの入手アクションに Gs2Grade:MultiplyAcquireActionsByUserId を組み込むことで、指定したグレードの倍率を適用した入手アクションのリストへ変換できます。
スクリプトトリガー
ネームスペースに changeGradeScript を設定すると、グレード変化の前後でカスタムスクリプトを実行できます。
トリガーは同期・非同期の実行方式を選択でき、非同期処理では GS2-Script や Amazon EventBridge を利用した外部連携も可能です。
設定できる主なイベントトリガーとスクリプト設定名は以下の通りです。
changeGradeScript(完了通知:changeGradeDone): グレード変化の前後
トランザクションアクション
GS2-Grade では以下のトランザクションアクションを提供しています。
| 種別 | アクション | 説明 |
|---|---|---|
| 検証 | Gs2Grade:VerifyGradeByUserId |
指定したグレード値であることを検証 |
| 検証 | Gs2Grade:VerifyGradeUpMaterialByUserId |
グレードアップ素材として有効か(gradeUpPropertyIdRegex にマッチするか)を検証 |
| 消費 | Gs2Grade:SubGradeByUserId |
グレードの減算 |
| 入手 | Gs2Grade:AddGradeByUserId |
グレードの加算 |
| 入手 | Gs2Grade:AddRankCapByUserId |
グレードに対応するランクキャップを GS2-Experience に反映 |
| 入手 | Gs2Grade:MultiplyAcquireActionsByUserId |
グレードに応じた acquireActionRates の倍率を適用した入手アクションへ変換 |
「グレードアップ素材の検証」を検証アクションとして利用することで、合成に使用しようとしている素材(他のアイテムやキャラクター)を本当に所持しているか、といった検証をトランザクション内で行えます。これにより、不正な素材指定によるグレードアップを防ぐことが可能です。
マスターデータ運用
マスターデータを登録することでマイクロサービスで利用可能なデータや振る舞いを設定できます。
マスターデータの種類には以下があります。
GradeModel: グレードごとのランクキャップや報酬量
以下はマスターデータの JSON 例です。
{
"version": "2022-06-01",
"gradeModels": [
{
"name": "grade-0001",
"metadata": "Character Grade",
"experienceModelId": "grn:gs2:{region}:{ownerId}:experience:namespace-0001:model:experienceModel-0001",
"defaultGrades": [
{
"propertyIdRegex": ".*:item:SSR.*",
"defaultGradeValue": 3
},
{
"propertyIdRegex": ".*:item:SR.*",
"defaultGradeValue": 2
}
],
"gradeEntries": [
{ "metadata": "Grade 0", "rankCapValue": 30 },
{ "metadata": "Grade 1", "rankCapValue": 40 },
{ "metadata": "Grade 2", "rankCapValue": 50 },
{ "metadata": "Grade 3", "rankCapValue": 60 }
],
"acquireActionRates": [
{
"name": "experience",
"mode": "double",
"rates": [1.0, 1.2, 1.5, 2.0]
}
]
}
]
}マスターデータの登録はマネージメントコンソールから登録する他、GitHubからデータを反映したり、GS2-Deployを使ってCIから登録するようなワークフローを組むことが可能です。
実装例
グレードの加算
グレードの加算はゲームエンジン用の SDK では処理できません。
GS2-Exchange で強化素材の消費や、GS2-Enhance の強化報酬としてトランザクションアクション Gs2Grade:AddGradeByUserId を組み込み、グレードを加算するようにしてください。
これにより、不正なクライアント操作によるグレードアップを防ぎつつ、サーバー側で整合性を保ったまま成長要素を実装できます。
グレードの一覧を取得
var items = await gs2.Grade.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).StatusesAsync(
gradeName: "grade-0001"
).ToListAsync(); const auto It = Gs2->Grade->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
)->Statuses(
"grade-0001" // gradeName
);
TArray<Gs2::UE5::Grade::Model::FEzStatusPtr> Result;
for (auto Item : *It)
{
if (Item.IsError())
{
return false;
}
Result.Add(Item.Current());
}グレードの取得
var item = await gs2.Grade.Namespace(
namespaceName: "namespace-0001"
).GradeModel(
gradeName: "grade-0001"
).ModelAsync(); const auto Future = Gs2->Grade->Namespace(
"namespace-0001" // namespaceName
)->GradeModel(
"grade-0001" // gradeName
)->Model();
Future->StartSynchronousTask();
if (Future->GetTask().IsError())
{
return false;
}個別グレード(Status)の取得
特定の propertyId(キャラクター・装備など)に対する現在のグレード値を取得します。
var item = await gs2.Grade.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).Status(
gradeName: "grade-0001",
propertyId: "property-0001"
).ModelAsync(); const auto Future = Gs2->Grade->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
)->Status(
"grade-0001", // gradeName
"property-0001" // propertyId
)->Model();
Future->StartSynchronousTask();
if (Future->GetTask().IsError())
{
return false;
}GS2-Experience のランクキャップを同期
グレードが変化したら、GS2-Experience にランクキャップへの反映は自動的に行われます。 ただし、グレードモデルを更新して特定グレードにおけるランクキャップの値を更新した際には以下の同期APIを明示的に呼び出すことで、更新後のランクキャップを適用することができます。
var result = await gs2.Grade.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).Status(
gradeName: "grade-0001",
propertyId: "property-0001"
).ApplyRankCapAsync(
);
var item = await result.ModelAsync(); const auto Future = Gs2->Grade->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
)->Status(
"grade-0001", // gradeName
"property-0001" // propertyId
)->ApplyRankCap(
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError())
{
return false;
}
// obtain changed values / result values
const auto Future2 = Future->GetTask().Result()->Model();
Future2->StartSynchronousTask();
if (Future2->GetTask().IsError())
{
return Future2->GetTask().Error();
}
const auto Result = Future2->GetTask().Result();