GS2-LoginReward
ゲームに毎日ログインしたプレイヤーへ報酬を配布する仕組みです。 日替わりのアイテム配布や、7日/30日サイクルでループするログインボーナス、見逃した日の報酬を後から補償する機能など、運営現場でよく使われるパターンを汎用的に提供します。
モード
ログインボーナスの提供方法は《スケジュールモード》と《ストリーミングモード》2種類の方式があります。
graph LR
Mode{ボーナスモード} --> Schedule["スケジュールモード<br/>(mode: schedule)"]
Mode --> Streaming["ストリーミングモード<br/>(mode: streaming)"]
Schedule -- 日付に固定 --> SchEx["1日目=金貨、2日目=銀貨…<br/>取り逃しはスキップ"]
Streaming -- 順番に消化 --> StrEx["1個目=金貨、2個目=銀貨…<br/>取り逃しても次回に持ち越し"]
Streaming --> Repeat{"repeat 設定"}
Repeat -- enabled --> Loop["終端到達後に先頭から再開"]
Repeat -- disabled --> Stop["終端で停止"]スケジュールモード
GS2-Schedule のイベントと関連付けて利用します。 報酬として配布するトランザクションアクションを各日程分定義します。 イベントの開始日時から24時間ごとに報酬が変化し、各報酬を1回受け取ることができます。 途中で取り逃がした報酬があった場合は、スキップされます。
ストリーミングモード
ストリーミングモードはストリームに設定された報酬として配布するトランザクションアクションを先頭から順番に配布します。 受け取らなかった日があったとしても、スキップはされずストリームの次の報酬が手に入ります。
ストリーミングモードのログインボーナスに GS2-Schedule のイベントを関連づけると、イベントの開始日時から24時間ごとにストリームの次の報酬を受け取ることができます。 設定しない場合は、報酬の種類が変化する時間をUTCタイムゾーンの24時間単位で指定して利用します。
繰り返し
ストリーミングモードのログインボーナスには繰り返し設定ができます。
繰り返し設定(repeat: enabled)を有効にすると、ストリームの終端に到達した場合 翌日はストリームの先頭から報酬の受け取りを再開できます。
この機能を利用することで、常設のログインボーナスを7日ごとや30日ごとにループさせることができます。
repeat: disabled を選択すると、ストリームの最後まで配布した後はそのボーナスからの報酬入手は停止します。期間限定キャンペーンなどに利用できます。
見逃し補償
スケジュールモードで取り逃がした場合や、ストリーミングモードでもイベント開催期間中ではストリームのすべてのアイテムを入手できないような状態に陥った時に使える機能が見逃し補償機能です。 設定されたコストを支払うことで、取り逃がしたアイテムを入手することができます。
見逃し補償が利用できるのは GS2-Schedule のイベントと関連づけられたログインボーナスのみで、イベントの開始日からの経過日数までの報酬のみが受け取れます。 つまり、未来のログインボーナスはコストを支払っても受け取ることはできません。
見逃し補償のコストとして、missedReceiveReliefConsumeActions に GS2-Money2 などの消費アクションを設定でき、ジェムを消費して見逃した日を取り戻すといった運用が可能です。
受け取りの前提条件として missedReceiveReliefVerifyActions に検証アクションを指定することもできます。
バフによる補正
GS2-Buff と連携するとボーナスモデルの acquireActions や missedReceiveReliefConsumeActions にバフを適用し、報酬内容や見逃し補償のコストをイベントに応じて動的に調整できます。
例えば「キャンペーン期間中はログインボーナスを2倍にする」「VIPプレイヤーは見逃し補償コストを半額にする」といった運用が可能です。
スクリプトトリガー
ネームスペースに receiveScript を設定するとログインボーナス受取処理の前後で receive/receiveDone のスクリプトフックを呼び出せます。スクリプトは同期・非同期の実行方式を選択でき、非同期では GS2-Script や Amazon EventBridge を利用した外部連携にも対応します。
設定できる主なイベントトリガーとスクリプト設定名は以下の通りです。
receiveScript(完了通知:receiveDone): ログインボーナス受取の前後
マスターデータ運用
マスターデータを登録することでマイクロサービスで利用可能なデータや振る舞いを設定できます。
マスターデータの種類には以下があります。
BonusModel: 日次やストリーミング方式の報酬定義
| マスター項目 | 説明 |
|---|---|
name | ボーナスモデル名 |
mode | schedule(スケジュール)または streaming(ストリーミング) |
periodEventId | 関連付ける GS2-Schedule のイベントID(オプション) |
resetHour | ストリーミングモードでイベント未関連時に報酬切り替えを行う UTC 時刻 |
repeat | ストリーミング時のループ動作(enabled / disabled) |
rewards | 各ステップで配布する acquireActions の一覧 |
missedReceiveRelief | 見逃し補償の有効化(enabled / disabled) |
missedReceiveReliefVerifyActions | 見逃し補償実行前の検証アクション |
missedReceiveReliefConsumeActions | 見逃し補償実行時の消費アクション |
以下はマスターデータ JSON の例です。
{
"version": "2020-10-19",
"bonusModels": [
{
"name": "bonus-0001",
"metadata": "7days",
"mode": "streaming",
"resetHour": 15,
"repeat": "enabled",
"rewards": [
{ "acquireActions": [ { "action": "Gs2Inventory:AcquireItemSetByUserId", "request": "..." } ] },
{ "acquireActions": [ { "action": "Gs2Inventory:AcquireItemSetByUserId", "request": "..." } ] }
],
"missedReceiveRelief": "disabled"
}
]
}マスターデータの登録はマネージメントコンソールから登録する他、GitHubからデータを反映したり、GS2-Deployを使ってCIから登録するようなワークフローを組むことが可能です。
トランザクションアクション
GS2-LoginReward では以下のトランザクションアクションを提供しています。
消費アクション
| アクション | 用途 |
|---|---|
Gs2LoginReward:MarkReceivedByUserId | 指定したステップを受け取り済みとしてマークします。 |
入手アクション
| アクション | 用途 |
|---|---|
Gs2LoginReward:DeleteReceiveStatusByUserId | 受け取り状態をリセットします。最初からログインボーナスを受け取り直させたい場合に利用します。 |
Gs2LoginReward:UnmarkReceivedByUserId | 指定したステップを未受け取り状態に戻します。 |
「受け取り状態のリセット」を入手アクションとして利用することで、特定のアイテムを入手した際や、イベントの節目などに、ログインボーナスの受け取り状況を最初からやり直させるといった処理が可能になります。これにより、定期的なキャンペーンのリセットや、特別な条件達成によるボーナス再獲得の機会をプレイヤーに提供できます。
実装例
ログインボーナスの受け取り
ReceiveAsync は次に受け取り可能なボーナスを自動的に受け取ります。配布物は EzTransactionDomain として返されるため、戻り値に対して WaitAsync などを呼び出して結果を反映します。
var transaction = await gs2.LoginReward.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).Bonus(
).ReceiveAsync(
bonusModelName: "bonus-0001",
config: null
);
await transaction.WaitAsync(); const auto Future = Gs2->LoginReward->Namespace(
"namespace-0001" // namespaceName
)->Me(
AccessToken
)->Bonus(
)->Receive(
"bonus-0001", // bonusModelName
nullptr // config
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError()) return false;
const auto Transaction = Future->GetTask().Result();
const auto Future2 = Transaction->Wait();
Future2->StartSynchronousTask();
if (Future2->GetTask().IsError()) return false;見逃した日の報酬の受け取り(見逃し補償)
MissedReceiveAsync を呼び出すことで、missedReceiveReliefConsumeActions に定義されたコストを支払い、過去に取り逃がした特定のステップを受け取れます。
var transaction = await gs2.LoginReward.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).Bonus(
).MissedReceiveAsync(
bonusModelName: "bonus-0001",
stepNumber: 2,
config: null
);
await transaction.WaitAsync(); const auto Future = Gs2->LoginReward->Namespace(
"namespace-0001" // namespaceName
)->Me(
AccessToken
)->Bonus(
)->MissedReceive(
"bonus-0001", // bonusModelName
2, // stepNumber
nullptr // config
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError()) return false;ログインボーナスの受け取り状態を取得
ReceiveStatus の ReceivedSteps には、各ステップの受け取り済み有無が真偽値の配列として格納されます。
UI 側で「○日目を受け取り済み」とカレンダー表示する際などに利用できます。
var item = await gs2.LoginReward.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).ReceiveStatus(
bonusModelName: "bonus-0001"
).ModelAsync();
for (var i = 0; i < item.ReceivedSteps.Count; i++) {
Debug.Log($"step {i + 1}: {(item.ReceivedSteps[i] ? "received" : "not yet")}");
} const auto Domain = Gs2->LoginReward->Namespace(
"namespace-0001" // namespaceName
)->Me(
AccessToken
)->ReceiveStatus(
"bonus-0001" // bonusModelName
);
const auto Future = Domain->Model();
Future->StartSynchronousTask();
if (Future->GetTask().IsError()) return false;
const auto Item = Future->GetTask().Result();ログインボーナスの内容を確認
UI に「明日もらえる報酬」「最終日にもらえる報酬」をプレビュー表示する際は、BonusModel の Rewards から各ステップの AcquireActions を取得できます。
var item = await gs2.LoginReward.Namespace(
namespaceName: "namespace-0001"
).BonusModel(
bonusModelName: "bonus-0001"
).ModelAsync();
foreach (var reward in item.Rewards) {
foreach (var action in reward.AcquireActions) {
Debug.Log($"{action.Action}: {action.Request}");
}
} const auto Domain = Gs2->LoginReward->Namespace(
"namespace-0001" // namespaceName
)->BonusModel(
"bonus-0001" // bonusModelName
);
const auto Future = Domain->Model();
Future->StartSynchronousTask();
if (Future->GetTask().IsError()) return false;
const auto Item = Future->GetTask().Result();