ドキュメント

サポート

Cloud Code

ユースケースサンプル: 個々のプレイヤーに合わせてゲームの難易度を調整する

Tune game difficulty level for individual players based on their progress and skill level.
読み終わるまでの所要時間 15 分最終更新 23日前

このサンプルは、Triggers を使用して、Cloud Save のプレイヤーデータと Leaderboards のスコアに基づいて難易度を調整する方法を示します。 サンプルでは、Cloud Save サービスによって発行された
key-saved
イベント、Leaderboards サービスによって発行された
score-submitted
イベント、Authentication サービスによって発行された
signed-up
イベントを使用します。
サンプルでは、Cloud Save のプレイヤーデータを使用してプレイヤーの進行状況を追跡し、Leaderboards のスコアを使用してプレイヤーのスキルレベルを追跡します。サンプルでは、プレイヤーの進行状況とスキルレベルを使用して、プレイヤーの個々のゲーム難易度を調整します。 このサンプルには、さまざまな方法でゲーム難易度に影響する複数のトリガーが設定されています。
  • プレイヤーがハイスコアを送信したときに、ゲーム難易度を上げます。
  • プレイヤーが特定のセッション数をプレイしたときに、ゲーム難易度を上げます。
  • プレイヤーが特定の数の実績のロックを解除したときに、ゲーム難易度を上げます。
  • プレイヤーが特定の数の遭遇戦をプレイしたときに、ゲーム難易度を上げます。
  • プレイヤーが特定の数の課題を正常に完了したときに、ゲーム難易度を上げます。
  • プレイヤーが特定の数の経験値を獲得したときに、ゲーム難易度を上げます。
  • プレイヤーが特定の数の課題に失敗したときに、ゲーム難易度を下げます。
トリガーは、フィルターを使用してイベントペイロードを評価し、特定の Cloud Save キーが更新されたときにのみ Cloud Code ロジックをトリガーします。

前提条件

まず、必要なアクセスロールを持つサービスアカウントを作成する必要があります。

サービスアカウントを使用した認証

Triggers サービスを呼び出す前に、サービスアカウントを使用して認証する必要があります。
  1. Unity Dashboard に移動します。
  2. Administration (管理) > Service Accounts (サービスアカウント) を選択します。
  3. New (新規) ボタンを選択し、サービスアカウントの名前と説明を入力します。
  4. Create (作成) を選択します。
製品ロールを追加し、キーを作成します。
  1. Manage product roles (製品ロールの管理) を選択します。
  2. 以下のロールをサービスアカウントに追加します。
    • LiveOps ドロップダウンから、Triggers Configuration Editor (Triggers 設定編集者) および Triggers Configuration Viewer (Triggers 設定閲覧者) を選択します。
    • Admin (管理者) ドロップダウンから Unity Environments Viewer (Unity 環境閲覧者) を選択します。
  3. Save (保存) を選択します。
  4. Add Key (キーの追加) を選択します。
  5. base64 エンコードを使用して Key ID (キー ID) と Secret key (秘密鍵) をエンコードします。形式は "key_id:secret_key" です。この値をメモしておきます。
詳細については、Authentication を参照してください。

Leaderboards の設定

スコアを送信するには、リーダーボードを設定する必要があります。Leaderboards をまだ使用したことがない場合は、Leaderboards の使用の準備 を参照してください。 Unity Dashboard を使用してリーダーボードを設定できます。
  1. Unity Dashboard に移動します。
  2. Leaderboards を選択します。
  3. Add Leaderboard (リーダーボードの追加) を選択します。
  4. リーダーボードの名前を入力します。
  5. リーダーボード設定を設定します。
  6. Finish (終了) を選択します。
リーダーボード ID を書き留めます。

イベントを調べる

ユースケースでは、以下のイベントを使用します。 イベントは、イベントペイロードをパラメーターとして Cloud Code に渡します。

Cloud Save: キー保存

Cloud Save サービスは、Cloud Save でキーが更新されたときに
key-saved
イベントを発行します。
イベントペイロードには以下の情報があります。
{ "id": "7LpyhpsIvEczGkDI1r8J6gHhHezL", "idType": "player", "key": "LEVEL", "value": 1, "valueIncluded": true, "writeLock": "7b8920a57912509f6b5cbb183eb7fcb0", "accessClass": "default", "modifiedDate": "2021-03-04T09:00:00Z"}
詳細については、Cloud Save: キー保存 を参照してください。

Leaderboards: スコア送信

Leaderboards は、スコアがリーダーボードに送信されたときに
score-submitted
イベントを発行します。
イベントペイロードには以下の情報があります。
{ "leaderboardId": "leaderboard", "updatedTime": "2019-08-24T14:15:22Z", "playerId": "5drhidte8XgD4658j2eHtSljIAzd", "playerName": "Jane Doe", "rank": 42, "score": 120.3, "tier": "gold"}
詳細については、Leaderboards: スコア送信 を参照してください。

Authentication: サインアップ

Authentication サービスは、プレイヤーがサインアップしたときに
signed-up
イベントを発行します。
イベントペイロードには以下の情報があります。
{ "playerId": "string", "providerId": "string", "createdAt": "string"}
詳細については、Authentication: サインアップ を参照してください。

新規プレイヤーのサインアップ時に Cloud Save データを初期化する

プレイヤーの進行状況を追跡するには、新しいプレイヤーの Cloud Save データを初期化する必要があります。データを初期化するには、Authentication: サインアップ イベントを使用して、Cloud Code モジュールエンドポイントをトリガーします。 新しいプレイヤーの Cloud Save データを初期化する関数が含まれる Cloud Code モジュールを作成します。

プレイヤーデータを保存するクラスの定義

プレイヤーのデータを Cloud Save に保存する
Config
クラスを作成します。
using Newtonsoft.Json;namespace TuneGameDifficulty;/** * Config class is used to define the data structure of the player's data in Cloud Save. * The PlayerConfig class contains all the player's data. Any new player data should be added to this class. * The DifficultySettings class defines predefined difficulty levels and their corresponding settings for global game difficulty. * * The player is initialized with player data and default difficulty settings on Authentication. * However, the player can manually change the difficulty level at any time, and the settings will be updated accordingly, with the player's data reset. * * When the player's difficulty settings are modified by the use of Triggers, the values will not be accurate to the base settings defined in this class. * The default values for each difficulty levels are used as a base to calculate the new values. */public abstract class Config{ // The DifficultySettings class defines the settings for each difficulty level. // You can use this class to define your own difficulty levels and their corresponding settings. public class DifficultySettings { [JsonProperty("EnemySpawnRate")] public double? EnemySpawnRate { get; set; } [JsonProperty("EnemyHealthMultiplier")] public double? EnemyHealthMultiplier { get; set; } [JsonProperty("ChallengeRewardsMultiplier")] public double? ChallengeRewardsMultiplier { get; set; } [JsonProperty("CurrencyRewardsMultiplier")] public double? CurrencyRewardsMultiplier { get; set; } [JsonProperty("TimeLimitMultiplier")] public double? TimeLimitMultiplier { get; set; } } // The DifficultyLevel enum defines the available difficulty levels for the game. public enum DifficultyLevel { VeryEasy, Easy, Medium, Hard, VeryHard, Custom } // The GlobalSettings dictionary defines the default settings for each difficulty level in the game. // The modifiers allow to increase or decrease the difficulty of the game. private static readonly Dictionary<DifficultyLevel, DifficultySettings> GlobalSettings = new() { { DifficultyLevel.VeryEasy, new DifficultySettings { EnemySpawnRate = 0.3, EnemyHealthMultiplier = 0.6, ChallengeRewardsMultiplier = 0.6, CurrencyRewardsMultiplier = 1.5, TimeLimitMultiplier = 2 } }, { DifficultyLevel.Easy, new DifficultySettings { EnemySpawnRate = 0.5, EnemyHealthMultiplier = 0.8, ChallengeRewardsMultiplier = 0.8, CurrencyRewardsMultiplier = 1.2, TimeLimitMultiplier = 1.5 } }, { DifficultyLevel.Medium, new DifficultySettings { EnemySpawnRate = 1, EnemyHealthMultiplier = 1, ChallengeRewardsMultiplier = 1, CurrencyRewardsMultiplier = 1, TimeLimitMultiplier = 1 } }, { DifficultyLevel.Hard, new DifficultySettings { EnemySpawnRate = 1.5, EnemyHealthMultiplier = 1.2, ChallengeRewardsMultiplier = 1.2, CurrencyRewardsMultiplier = 0.8, TimeLimitMultiplier = 0.7 } }, { DifficultyLevel.VeryHard, new DifficultySettings() { EnemySpawnRate = 2, EnemyHealthMultiplier = 1.5, ChallengeRewardsMultiplier = 1.5, CurrencyRewardsMultiplier = 0.5, TimeLimitMultiplier = 0.5 } } }; private const DifficultyLevel DefaultDifficulty = DifficultyLevel.VeryEasy; private const int DefaultValue = 0; // The PlayerConfig class defines the player's data structure in Cloud Save. // Any new player data should be added to this class. public class PlayerConfig { public long SessionsPlayed { get; set; } = DefaultValue; public long UnlockedAchievements { get; set; } = DefaultValue; public long EncounterFrequency { get; set; } = DefaultValue; public long CompletedChallenges { get; set; } = DefaultValue; public long FailedChallenges { get; set; } = DefaultValue; public long ExperiencePoints { get; set; } = DefaultValue; [JsonProperty("GameDifficulty")] public DifficultySettings GameDifficulty { get; set; } // Default constructor with optional parameter public PlayerConfig(DifficultyLevel difficulty = DefaultDifficulty) { GameDifficulty = GlobalSettings[difficulty]; } public PlayerConfig(DifficultySettings difficultySettings) { GameDifficulty = difficultySettings; } }}
クラスには、Cloud Save でのプレイヤーのデータ構造を定義する
PlayerConfig
クラスが含まれます。

Cloud Save データを初期化するモジュールエンドポイントの定義

新しいプレイヤーの Cloud Save データを初期化する
TuneGameDifficulty
クラスを作成します。クラスには、プレイヤーがサインアップしたときにトリガーされる関数
InitializeNewPlayer
が含まれます。クラスは、Cloud Save でプレイヤーのデータを初期化するために
Config
クラスに依存します。
using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Logging;using Unity.Services.CloudCode.Core;using Unity.Services.CloudCode.Apis;using Unity.Services.CloudCode.Shared;using Unity.Services.CloudSave.Model;namespace TuneGameDifficulty{ public class TuneGameDifficulty { private readonly ILogger<TuneGameDifficulty> _logger; public TuneGameDifficulty(ILogger<TuneGameDifficulty> logger) { _logger = logger; } /** * InitializeNewPlayer is called when a new player is created. * It initializes the player's data in Cloud Save with default values and is called on Authentication sign-up event. */ [CloudCodeFunction("InitializeNewPlayer")] public async Task InitializeNewPlayer(IExecutionContext ctx, IGameApiClient gameApiClient, string playerId) { try { var response = await gameApiClient.CloudSaveData.GetItemsAsync(ctx, ctx.ServiceToken, ctx.ProjectId, playerId); if (response.Data.Results.Count == 0) { var dataToSave = new Config.PlayerConfig(); var items = dataToSave.GetType().GetProperties().Select(property => new SetItemBody(property.Name, property.GetValue(dataToSave)!)).ToList(); await gameApiClient.CloudSaveData.SetItemBatchAsync(ctx, ctx.ServiceToken, ctx.ProjectId, playerId, new SetItemBatchBody(items)); _logger.LogInformation("The player {id} has been initialized with default values.", playerId); } } catch (ApiException e) { _logger.LogError("Failed to initialize player data for player {playerId} in Cloud Save. Error: {Error}", playerId, e); throw new Exception($"Failed to initialize {playerId} data in Cloud Save. Error: {e.Message}"); } } public class ModuleConfig : ICloudCodeSetup { public void Setup(ICloudCodeConfig config) { config.Dependencies.AddSingleton(GameApiClient.Create()); } } }}
これで、プレイヤーがサインアップしたときにトリガーできるモジュールが完成しました。 Now you have a module that can trigger when a player signs up.

トリガーの設定

これで、新しいプレイヤーの Cloud Save データを初期化するモジュールが完成したので、トリガーを設定できます。 デプロイ用のトリガー設定を保存する
trigger_configurations.json
ファイルを作成します。

新規プレイヤーのサインアップ時に Cloud Save データを初期化する

プレイヤーがサインアップしたときに
InitializeNewPlayer
関数をトリガーするために、トリガー設定を
trigger_configurations.json
ファイルに追加します。
[ { "name": "initialize-player-data-on-signup", "eventType": "com.unity.services.player-auth.signed-up.v1", "actionType": "cloud-code", "actionUrn": "urn:ugs:cloud-code:TuneGameDifficulty/InitializeNewPlayer" }]
この設定は、Authentication サービスによって発行された Authentication: サインアップ イベントを使用します。 ユースケースサンプル: 新規プレイヤーのサインアップ時に Cloud Save データを初期化する で詳細を参照してください。

ゲームの難易度の調整

ゲームの難易度を調整するには、さまざまなイベントによってトリガーされる複数のトリガーを作成する必要があります。トリガーは、フィルターを使用してイベントペイロードを評価し、定義されたフィルター条件が満たされた場合にのみ Cloud Code ロジックをトリガーします。 ゲームデザインに基づいてフィルター条件をカスタマイズできます。例えば、特定の値範囲でのみ Cloud Save イベントをトリガーできます。 以下のトリガー設定を
trigger_congigurations.json
ファイルに追加します。
[ { "name": "increase-difficulty-on-high-skill", "eventType": "com.unity.services.leaderboards.score-submitted.v1", "actionType": "cloud-code", "actionUrn": "urn:ugs:cloud-code:TuneGameDifficulty/TriggerOnHighSkill", "filter": "data['score'] > 1000 && data['score'] < 5000" }, { "name": "increase-difficulty-on-sessions-played", "eventType": "com.unity.services.cloud-save.key-saved.v1", "actionType": "cloud-code", "actionUrn": "urn:ugs:cloud-code:TuneGameDifficulty/TriggerOnSessionsPlayed", "filter": "data['key']=='SessionsPlayed' && data['value'] > 0" }, { "name": "increase-difficulty-on-achievement-unlocks", "eventType": "com.unity.services.cloud-save.key-saved.v1", "actionType": "cloud-code", "actionUrn": "urn:ugs:cloud-code:TuneGameDifficulty/TriggerOnAchievements", "filter": "data['key']=='UnlockedAchievements' && data['value'] > 0" }, { "name": "increase-difficulty-on-encounter-frequency", "eventType": "com.unity.services.cloud-save.key-saved.v1", "actionType": "cloud-code", "actionUrn": "urn:ugs:cloud-code:TuneGameDifficulty/TriggerOnEncounterFrequency", "filter": "data['key']=='EncounterFrequency' && data['value'] > 0" }, { "name": "increase-difficulty-on-winning-challenges", "eventType": "com.unity.services.cloud-save.key-saved.v1", "actionType": "cloud-code", "actionUrn": "urn:ugs:cloud-code:TuneGameDifficulty/TriggerOnChallengeCompletion", "filter": "data['key'] == 'CompletedChallenges' && data['value'] > 0" }, { "name": "increase-difficulty-on-experience-points", "eventType": "com.unity.services.cloud-save.key-saved.v1", "actionType": "cloud-code", "actionUrn": "urn:ugs:cloud-code:TuneGameDifficulty/TriggerOnExperiencePoints", "filter": "data['key'] == 'ExperiencePoints' && data['value'] > 0" }, { "name": "decrease-difficulty-on-failed-challenges", "eventType": "com.unity.services.cloud-save.key-saved.v1", "actionType": "cloud-code", "actionUrn": "urn:ugs:cloud-code:TuneGameDifficulty/TriggerOnChallengeFailure", "filter": "data['key'] == 'FailedChallenges' && data['value'] > 0" }]

トリガーのデプロイ

トリガーを作成するには、以下の
bash
スクリプトを実行します。
trigger_configurations.json
ファイルがスクリプトと同じディレクトリにあることを確認してください。
キー ID と秘密鍵の base64 エンコードされた値を
Authorization
ヘッダーの値として使用します。
#!/bin/bashAPI_URL='https://services.api.unity.com/triggers/v1/projects/<PROJECT_ID>/environments/<ENVIRONMENT_ID>/configs'HEADERS=(-H "Content-Type: application/json" -H "Authorization: Basic YOUR_KEY_ID:YOUR_SECRET_KEY")jq -c '.[]' < trigger_configurations.json | while IFS= read -r line; do curl -X POST "$API_URL" "${HEADERS[@]}" --data-raw "$line"done

ゲームの難易度を調整するロジックの定義

トリガーごとにゲーム難易度を上げるすべての関数を含む Cloud Code モジュールを作成します。 モジュールに
MultplierConfig
クラスを追加します。クラスは、難易度モディファイアの初期値を定義します。サンプルでは、​​乗数を使用して、トリガーの各難易度の新しい値を計算します。このクラスを使用して、ゲームデザインに基づいて難易度モディファイアをカスタマイズできます。

C

namespace TuneGameDifficulty;/** * MultiplierConfig class is used to store the default values for the difficulty modifiers. * The multipliers are used to calculate the new values for each difficulty level on triggers. */public static class MultiplierConfig{ public enum ConfigurationSetting { SessionsPlayed, UnlockedAchievements, EncounterFrequency, CompletedChallenges, FailedChallenges, ExperiencePoints, } public static readonly Dictionary<ConfigurationSetting, double> DefaultModifiers = new() { { ConfigurationSetting.SessionsPlayed, 0.1 }, { ConfigurationSetting.UnlockedAchievements, 0.2 }, { ConfigurationSetting.EncounterFrequency, 0.2 }, { ConfigurationSetting.CompletedChallenges, 0.4 }, { ConfigurationSetting.FailedChallenges, 0.5 }, { ConfigurationSetting.ExperiencePoints, 0.6 } };}
モジュールにクラス
DifficultyTriggerHandler
を追加して、各トリガーのロジックを定義します。クラスには、各トリガーの関数が含まれています。
  • 関数は、その関数をトリガーするイベントに関連付けられます。例えば、
    TriggerOnHighSkill
    関数は
    score-submitted
    イベントに関連付けられます。
  • 関数は、イベントペイロードをパラメーターとして受け取ります。例えば、
    TriggerOnHighSkill
    関数は、
    score-submitted
    イベントペイロードから
    score
    playerId
    をパラメーターとして受け取ります。
  • 関数は、
    MultiplierConfig
    クラスを使用して難易度設定のデフォルトモディファイアを計算します。例えば、
    TriggerOnHighSkill
    関数は、
    SessionsPlayed
    キーのデフォルトモディファイアを使用して、
    EnemySpawnRate
    難易度設定の新しい値を計算します。
  • 関数は、
    GenerateNewValue
    ヘルパーメソッドを使用して難易度設定の新しい値を計算します。これは、ゲームデザインに基づいてカスタマイズできます。
using Microsoft.Extensions.Logging;using Newtonsoft.Json;using Unity.Services.CloudCode.Apis;using Unity.Services.CloudCode.Core;using Unity.Services.CloudCode.Shared;using Unity.Services.CloudSave.Model;namespace TuneGameDifficulty;/** * This class contains the Cloud Code functions that are tied to Trigger events. * The functions are associated with the following events: * - Leaderboards Score Submitted * - Cloud Save Key Saved * * The functions are associated with the following triggers: * - TriggerOnHighSkill is associated with the Leaderboards Score Submitted event. * - TriggerOnSessionsPlayed is associated with the Cloud Save Key Saved event. * - TriggerOnAchievements is associated with the Cloud Save Key Saved event. * - TriggerOnEncounterFrequency is associated with the Cloud Save Key Saved event. * - TriggerOnChallengeCompletion is associated with the Cloud Save Key Saved event. * - TriggerOnChallengeFailure is associated with the Cloud Save Key Saved event. * - TriggerOnExperiencePoints is associated with the Cloud Save Key Saved event. */public class DifficultyTriggerHandler{ private const string GameDifficultyKey = "GameDifficulty"; private readonly ILogger<DifficultyTriggerHandler> _logger; public DifficultyTriggerHandler(ILogger<DifficultyTriggerHandler> logger) { _logger = logger; } /** * Generates a new value for particular difficulty setting based on the default modifier and the new value of the affecting key. * - newAffecting value is the new value of the affecting key, for example the new value of SessionsPlayed key when the player plays a new session. * - defaultModifierForAffectingValue is the default modifier for the affecting value, for example the default modifier for SessionsPlayed key found in MultiplierConfig. * - lastDifficultyValue is the last value of the difficulty setting, for example the last value of EnemySpawnRate. * - increaseValue is a boolean that indicates whether the new value should be added or subtracted from the last value. * * The new value is calculated as follows: * - if newAffectingValue for the SessionsPlayed key is 2, the new value for EnemySpawnRate is calculated by adding 2 * 0.1 to the last value of EnemySpawnRate. */ private static Task<double> GenerateNewValue(double newAffectingValue, double defaultModifierForAffectingValue, double lastDifficultyValue, bool increaseValue = true) { // Ensure the new value is not less than 0.1 const double minValue = 0.1; double scalingFactor = 0.1; // Adjust this value to control the rate of reduction double newValue = increaseValue ? lastDifficultyValue + newAffectingValue * defaultModifierForAffectingValue : lastDifficultyValue - scalingFactor * newAffectingValue * Math.Abs(defaultModifierForAffectingValue); return Task.FromResult(Math.Max(minValue, newValue)); } /** * TriggerOnHighSkill function is associated with the Leaderboards Score Submitted event. * The default filter for the trigger is set to trigger the function when the player's score 1000 > x > 5000. */ [CloudCodeFunction("TriggerOnHighSkill")] public async Task TriggerOnHighSkill(IExecutionContext ctx, IGameApiClient gameApiClient, double score, string playerId) { var scoreDifficultyMapping = new Dictionary<double, int>() { {1000, 1}, {2000, 2}, {3000, 3}, {4000, 4}, {5000, 5} }; var scoreLevel = scoreDifficultyMapping.First(x => x.Key > score).Value; var currentDifficultyForPlayer = await ReadCurrentPlayerData(ctx, gameApiClient, playerId); var newEnemySpawnRate = await GenerateNewValue(scoreLevel, 0.3, (double)currentDifficultyForPlayer.EnemySpawnRate!); var newEnemyHealthMultiplier = await GenerateNewValue(scoreLevel, 0.2, (double)currentDifficultyForPlayer.EnemyHealthMultiplier!); await UpdatePlayerGameConfiguration(ctx, gameApiClient, currentDifficultyForPlayer, new Config.DifficultySettings { EnemySpawnRate = newEnemySpawnRate, EnemyHealthMultiplier = newEnemyHealthMultiplier }, playerId); } /** * TriggerOnSessionsPlayed function is associated with Cloud Save Key Saved event. * The default filter for the trigger is set to trigger the function when the player's SessionsPlayed key is updated. */ [CloudCodeFunction("TriggerOnSessionsPlayed")] public async Task TriggerOnSessionsPlayed(IExecutionContext ctx, IGameApiClient gameApiClient, double value, string id) { await TriggerOnConfiguration(ctx, gameApiClient, value, id, new List<(MultiplierConfig.ConfigurationSetting, Func<Config.DifficultySettings, double?>, Action<Config.DifficultySettings, double>, bool)> { (MultiplierConfig.ConfigurationSetting.SessionsPlayed, ds => ds.EnemySpawnRate, (ds, newMultiplier) => ds.EnemySpawnRate = newMultiplier, true) }); } /** * TriggerOnAchievements function is associated with the Cloud Save Key Saved event. * The default filter for the trigger is set to trigger the function when the player's UnlockedAchievements key is updated. */ [CloudCodeFunction("TriggerOnAchievements")] public async Task TriggerOnAchievements(IExecutionContext ctx, IGameApiClient gameApiClient, double value, string id) { await TriggerOnConfiguration(ctx, gameApiClient, value, id, new List<(MultiplierConfig.ConfigurationSetting, Func<Config.DifficultySettings, double?>, Action<Config.DifficultySettings, double>, bool)> { (MultiplierConfig.ConfigurationSetting.UnlockedAchievements, ds => ds.ChallengeRewardsMultiplier, (ds, newMultiplier) => ds.ChallengeRewardsMultiplier = newMultiplier, true) }); } /** * TriggerOnEncounterFrequency function is associated with the Cloud Save Key Saved event. * The default filter for the trigger is set to trigger the function when the player's EncounterFrequency key is updated. */ [CloudCodeFunction("TriggerOnEncounterFrequency")] public async Task TriggerOnEncounterFrequency(IExecutionContext ctx, IGameApiClient gameApiClient, double value, string id) { await TriggerOnConfiguration(ctx, gameApiClient, value, id, new List<(MultiplierConfig.ConfigurationSetting, Func<Config.DifficultySettings, double?>, Action<Config.DifficultySettings, double>, bool)> { (MultiplierConfig.ConfigurationSetting.EncounterFrequency, ds => ds.EnemySpawnRate, (ds, newMultiplier) => ds.EnemySpawnRate = newMultiplier, true), (MultiplierConfig.ConfigurationSetting.EncounterFrequency, ds => ds.TimeLimitMultiplier, (ds, newMultiplier) => ds.TimeLimitMultiplier = newMultiplier, false) }); } /** * TriggerOnChallengeCompletion function is associated with the Cloud Save Key Saved event. * The default filter for the trigger is set to trigger the function when the player's CompletedChallenges key is updated. */ [CloudCodeFunction("TriggerOnChallengeCompletion")] public async Task TriggerOnChallengeCompletion(IExecutionContext ctx, IGameApiClient gameApiClient, double value, string id) { await TriggerOnConfiguration(ctx, gameApiClient, value, id, new List<(MultiplierConfig.ConfigurationSetting, Func<Config.DifficultySettings, double?>, Action<Config.DifficultySettings, double>, bool)> { (MultiplierConfig.ConfigurationSetting.CompletedChallenges, ds => ds.CurrencyRewardsMultiplier, (ds, newMultiplier) => ds.CurrencyRewardsMultiplier = newMultiplier, true), (MultiplierConfig.ConfigurationSetting.CompletedChallenges, ds => ds.ChallengeRewardsMultiplier, (ds, newMultiplier) => ds.ChallengeRewardsMultiplier = newMultiplier, true) }); } /** * TriggerOnChallengeFailure function is associated with the Cloud Save Key Saved event. * The default filter for the trigger is set to trigger the function when the player's FailedChallenges key is updated. */ [CloudCodeFunction("TriggerOnChallengeFailure")] public async Task TriggerOnChallengeFailure(IExecutionContext ctx, IGameApiClient gameApiClient, double value, string id) { await TriggerOnConfiguration(ctx, gameApiClient, value, id, new List<(MultiplierConfig.ConfigurationSetting, Func<Config.DifficultySettings, double?>, Action<Config.DifficultySettings, double>, bool)> { (MultiplierConfig.ConfigurationSetting.FailedChallenges, ds => ds.EnemyHealthMultiplier, (ds, newMultiplier) => ds.EnemyHealthMultiplier = newMultiplier, false), (MultiplierConfig.ConfigurationSetting.FailedChallenges, ds => ds.EnemySpawnRate, (ds, newMultiplier) => ds.EnemySpawnRate = newMultiplier, false), (MultiplierConfig.ConfigurationSetting.FailedChallenges, ds => ds.TimeLimitMultiplier, (ds, newMultiplier) => ds.TimeLimitMultiplier = newMultiplier, true), }); } /** * TriggerOnExperiencePoints function is associated with the Cloud Save Key Saved event. * The default filter for the trigger is set to trigger the function when the player's ExperiencePoints key is updated. */ [CloudCodeFunction("TriggerOnExperiencePoints")] public async Task TriggerOnExperiencePoints(IExecutionContext ctx, IGameApiClient gameApiClient, double value, string id) { await TriggerOnConfiguration(ctx, gameApiClient, value, id, new List<(MultiplierConfig.ConfigurationSetting, Func<Config.DifficultySettings, double?>, Action<Config.DifficultySettings, double>, bool)> { (MultiplierConfig.ConfigurationSetting.ExperiencePoints, ds => ds.TimeLimitMultiplier, (ds, newMultiplier) => ds.TimeLimitMultiplier = newMultiplier, false), (MultiplierConfig.ConfigurationSetting.ExperiencePoints, ds => ds.EnemyHealthMultiplier, (ds, newMultiplier) => ds.EnemyHealthMultiplier = newMultiplier, true), }); } /** * TriggerOnConfiguration is a helper method that updates the player's game difficulty settings in Cloud Save. * The method takes a list of tuples that contain the following: * - The configuration setting that is being updated * - A function that returns the last value of the configuration setting * - An action that sets the new value of the configuration setting * - A boolean that indicates whether the new value should be added or subtracted from the last value * The method loops through the list and updates the configuration settings accordingly. */ private async Task TriggerOnConfiguration(IExecutionContext ctx, IGameApiClient gameApiClient, double value, string id, List<(MultiplierConfig.ConfigurationSetting, Func<Config.DifficultySettings, double?>, Action<Config.DifficultySettings, double>, bool)> settings) { var currentDifficultyForPlayer = await ReadCurrentPlayerData(ctx, gameApiClient, id); var newConfigSettings = new Config.DifficultySettings(); // for each, append the new value to the new config settings object foreach (var (configurationSetting, getLastValue, setNewValue, increaseValue) in settings) { var multiplier = MultiplierConfig.DefaultModifiers[configurationSetting]; var lastValue = getLastValue(currentDifficultyForPlayer); var newValue = await GenerateNewValue(value, multiplier, (double)lastValue!, increaseValue); setNewValue(newConfigSettings, newValue); } await UpdatePlayerGameConfiguration(ctx, gameApiClient, currentDifficultyForPlayer, newConfigSettings, id); } // ReadCurrentPlayerData is a method that reads the player's configuration settings from Cloud Save, including the game difficulty settings as a PlayerConfig object. private async Task<Config.DifficultySettings> ReadCurrentPlayerData(IExecutionContext ctx, IGameApiClient gameApiClient, string playerId) { try { var response = await gameApiClient.CloudSaveData.GetItemsAsync(ctx, ctx.ServiceToken, ctx.ProjectId, playerId); var gameDifficulty = JsonConvert.DeserializeObject<Config.DifficultySettings>(response.Data.Results.First(x => x.Key == GameDifficultyKey).Value.ToString()); return gameDifficulty!; } catch (ApiException e) { _logger.LogError("Failed to read player {playerId} game difficulty settings in Cloud Save. Error: {Error}", playerId, e.Message); throw new Exception($"Failed to initialize {playerId} data in Cloud Save. Error: {e.Message}"); } } // UpdatePlayerGameConfiguration is a method that updates the player's game difficulty settings in Cloud Save. private async Task UpdatePlayerGameConfiguration(IExecutionContext ctx, IGameApiClient gameApiClient, Config.DifficultySettings currentSettings, Config.DifficultySettings newSettings, string playerId) { try { var mergedSettings = MergeSettings(currentSettings, newSettings); await gameApiClient.CloudSaveData.SetItemAsync(ctx, ctx.ServiceToken, ctx.ProjectId, playerId, new SetItemBody(GameDifficultyKey, mergedSettings)); } catch (ApiException e) { _logger.LogError("Failed to update player {playerId} game difficulty settings in Cloud Save. Error: {Error}", playerId, e.Message); throw new Exception($"Failed to initialize {playerId} data in Cloud Save. Error: {e.Message}"); } } // MergeSettings is a helper method that merges the current settings with the new settings. Only the new settings that are not null are updated. private static Config.DifficultySettings MergeSettings(Config.DifficultySettings currentSettings, Config.DifficultySettings newSettings) { var mergedSettings = new Config.DifficultySettings(); var properties = typeof(Config.DifficultySettings).GetProperties(); foreach (var property in properties) { var currentValue = (double?)property.GetValue(currentSettings); var newValue = (double?)property.GetValue(newSettings); // Update only if the new value is not null property.SetValue(mergedSettings, newValue ?? // If new value is null, retain the current value currentValue); } return mergedSettings; }}
これで、モジュールにトリガーごとにゲーム難易度を上げるすべての関数が追加されました。

プレイヤー設定管理の設定

ヘルパーメソッドを追加するクラス
PlayerConfigManagement
を定義します。
ヘルパーメソッドを使用して、Cloud Save 内のプレイヤーのデータを操作し、イベントを起動できます。ヘルパーメソッドを使用して、スコアをリーダーボードに送信することもできます。 プレイヤーが手動の難易度変更をリクエストしたときに、
ChangeDifficulty
メソッドを使用して、Cloud Save 内のプレイヤーのデータを初期値にリセットできます。このメソッドはトリガーに関連付けられていないことに注意してください。このメソッドはゲームクライアントから呼び出すことができます。
using Microsoft.Extensions.Logging;using Unity.Services.CloudCode.Apis;using Unity.Services.CloudCode.Core;using Unity.Services.CloudCode.Shared;using Unity.Services.CloudSave.Model;using Unity.Services.Leaderboards.Model;namespace TuneGameDifficulty;/** * PlayerConfigManagement class is used to manipulate the player's data in Cloud Save and fire Triggers. * It contains helper methods to update the player's data and submit scores to leaderboards to activate Triggers. */public class PlayerConfigManagement{ private readonly ILogger<PlayerConfigManagement> _logger; public PlayerConfigManagement(ILogger<PlayerConfigManagement> logger) { _logger = logger; } // This method is called when the player requests a manual difficulty change. // All the current configuration is to default values alongside the player data and the new difficulty is applied. [CloudCodeFunction("ChangeDifficulty")] public async Task ChangeDifficulty(IExecutionContext ctx, IGameApiClient gameApiClient, Config.DifficultyLevel newDifficulty) { try { var defaultPlayerConfig = new Config.PlayerConfig(newDifficulty); var items = defaultPlayerConfig.GetType().GetProperties().Select(property => new SetItemBody(property.Name, property.GetValue(defaultPlayerConfig)!)).ToList(); await gameApiClient.CloudSaveData.SetItemBatchAsync(ctx, ctx.ServiceToken, ctx.ProjectId, ctx.PlayerId, new SetItemBatchBody(items)); _logger.LogInformation("The player has reset their difficulty to {newDifficulty}. They progress has been reset.", newDifficulty); } catch (ApiException e) { _logger.LogError("Failed to reset player data in Cloud Save. Error: {Error}", e.Message); throw new Exception($"Failed to reset player data in Cloud Save. Error: {e.Message}"); } } // Use this helper method to manipulate the player's data in Cloud Save and fire Triggers. [CloudCodeFunction("UpdatePlayerStats")] public async Task<string> UpdatePlayerStats(IExecutionContext ctx, IGameApiClient gameApiClient, string playerId, string key, double newValue) { try { await gameApiClient.CloudSaveData.SetItemAsync(ctx, ctx.ServiceToken, ctx.ProjectId, playerId, new SetItemBody(key, newValue)); _logger.LogInformation("The player's {key} has been updated to {newValue}", key, newValue); return $"The player's {playerId} {key} has been updated to {newValue}"; } catch (ApiException e) { _logger.LogError("Failed to update player data for player {playerId} in Cloud Save. Error: {Error}", playerId, e.Message); throw new Exception($"Failed to update player data for player {playerId} in Cloud Save. Error: {e.Message}"); } } // Use this helper method to submit a score to the leaderboard. // Note: requires a leaderboard to be created first. [CloudCodeFunction("SubmitScore")] public async Task<string> SubmitScore(IExecutionContext ctx, IGameApiClient gameApiClient, string playerId, string leaderboardId, double score) { try { await gameApiClient.Leaderboards.AddLeaderboardPlayerScoreAsync(ctx, ctx.ServiceToken, new Guid(ctx.ProjectId), leaderboardId, playerId, new LeaderboardScore(score)); _logger.LogInformation("The player's {playerId} score of {score} has been submitted to {leaderboardId}", playerId, score, leaderboardId); return $"The player's {playerId} score of {score} has been submitted to {leaderboardId}"; } catch (ApiException e) { _logger.LogError("Failed to submit score to leaderboard {leaderboardId}. Error: {Error}", e.Message, leaderboardId); throw new Exception($"Failed to submit score to leaderboard {leaderboardId}. Error: {e.Message}"); } }}

Cloud Code モジュールをデプロイする

トリガーごとにゲーム難易度を上げる関数を含む Cloud Code モジュールをデプロイします。モジュールのデプロイ方法を学習するには、Hello World のデプロイ を参照してください。

ユースケースのテスト

ユースケースをテストするには、トリガーに関連付けられているイベントをトリガーします。 API リクエストの場合、
Authorization
ヘッダーの値として
Bearer
トークンを使用する必要があります。サービスアカウントまたはステートレストークンを使用してプレイヤーまたは信頼されているクライアントとして認証するには、認証 を参照してください。受信したトークンを Bearer トークンとしてリクエストヘッダーで HTTP 認証に使用します。
例えば、以下の cURL コマンドを使用してスコアをリーダーボードに送信して、
TriggerOnHighSkill
関数をトリガーできます。
curl -XPOST -H 'Authorization: Bearer <BEARER_TOKEN>' -H "Content-type: application/json" --data-raw '{ "params": { "playerId": "<PLAYER-ID>", "leaderboardId": "<LEADERBOARD_ID>", "score": 2000 }}' 'https://cloud-code.services.api.unity.com/v1/projects/<PROJECT_ID>/modules/TuneGameDifficulty/SubmitScore'
また、Cloud Save でプレイヤーのデータを更新して他の関数をトリガーすることもできます。例えば、
TriggerOnSessionsPlayed
関数をトリガーするには、
SessionsPlayed
キーを使用して
UpdatePlayerStats
関数を呼び出します。
curl -XPOST -H 'Authorization: Bearer <BEARER_TOKEN>' -H "Content-type: application/json" --data-raw '{ "params": { "key": "SessionsPlayed", "newValue": 10 }}' 'https://cloud-code.services.api.unity.com/v1/projects/<PROJECT_ID>/modules/TuneGameDifficulty/UpdatePlayerStats'
Cloud Save データへの変更を検証するには、Unity Cloud Dashboard でプレイヤーの Cloud Save データを調べます。
  1. Unity Dashboard に移動します。
  2. Player Management (プレイヤー管理) を選択します。
  3. 検索フィールドにプレイヤー ID を入力し、Find Player (プレイヤーを検索) を選択します。
  4. Cloud Save > Data (データ) セクションに移動します。
  5. イベントをトリガーしながら、データ変更の進行状況を追跡します。