使用示例:为各个玩家调整游戏难度级别
Tune game difficulty level for individual players based on their progress and skill level.
阅读时间19 分钟最后更新于 3 天前
此示例展示了如何使用触发器根据 Cloud Save 中的玩家数据和 Leaderboards 中的分数来调整游戏难度级别。
此示例使用了 Cloud Save 服务发出的
key-savedscore-submittedsigned-up- 当玩家提交高分时,增加游戏难度级别。
- 当玩家完成一定数量的游戏会话时,增加游戏难度级别。
- 当玩家解锁一定数量的成就时,增加游戏难度级别。
- 当玩家完成一定数量的遭遇战时,增加游戏难度级别。
- 当玩家成功完成一定次数的挑战时,增加游戏难度级别。
- 当玩家获得一定数量的经验值时,增加游戏难度级别。
- 当玩家挑战失败达到一定次数时,降低游戏难度级别。
先决条件
首先需要创建具有所需访问角色的服务帐户。使用服务帐户进行身份验证
在调用 Triggers 服务之前,必须使用服务帐户进行身份验证。- 导航到 Unity Dashboard。
- 选择 Administration(管理)> Service Accounts(服务帐户)。
- 选择 New(新建) 按钮并输入服务帐户的名称和描述。
- 选择 Create(创建)。
- 选择 Manage product roles(管理产品角色)。
- 将以下角色添加到服务帐户:
- 从 LiveOps 下拉选单中,选择 Triggers Configuration Editor(Triggers 配置编辑者) 和 Triggers Configuration Viewer(Triggers 配置查看者)。
- 从 Admin(管理)下拉选单中,选择 Unity Environments Viewer(Unity 环境查看者)。
- 从 LiveOps 下拉选单中,选择 Triggers Configuration Editor(Triggers 配置编辑者) 和 Triggers Configuration Viewer(Triggers 配置查看者)。
- 选择 Save(保存)。
- 选择 Add Key(添加密钥)。
- 使用 base64 编码方式对 Key ID(密钥 ID) 和 Secret key(密钥) 进行编码。格式为“key_id:secret_key”。请记下此值。
设置 Leaderboards
要提交分数,您需要设置 Leaderboards。如果您以前没有使用过 Leaderboards,请参阅开始使用 Leaderboards。 您可以使用 Unity Dashboard 设置 Leaderboards。- 导航到 Unity Dashboard。
- 选择 Leaderboards。
- 选择 Add Leaderboard(添加排行榜)。
- 输入排行榜的名称。
- 配置排行榜设置。
- 选择 Finish(完成)。
检查事件
此用例使用以下事件: 这些事件会将事件有效负载作为参数传递给 Cloud Code。Cloud Save:保存键
在 Cloud Save 中更新键时,Cloud Save 服务会发出key-saved如需了解更多信息,请参阅 Cloud Save:保存键。{ "id": "7LpyhpsIvEczGkDI1r8J6gHhHezL", "idType": "player", "key": "LEVEL", "value": 1, "valueIncluded": true, "writeLock": "7b8920a57912509f6b5cbb183eb7fcb0", "accessClass": "default", "modifiedDate": "2021-03-04T09:00:00Z"}
Leaderboards:提交分数
在将分数提交到排行榜时,Leaderboards 服务会发出score-submitted如需了解更多信息,请参阅 Leaderboards:提交分数。{ "leaderboardId": "leaderboard", "updatedTime": "2019-08-24T14:15:22Z", "playerId": "5drhidte8XgD4658j2eHtSljIAzd", "playerName": "Jane Doe", "rank": 42, "score": 120.3, "tier": "gold"}
Authentication:注册
在玩家注册时,Authentication 服务会发出signed-up如需了解更多信息,请参阅 Authentication:注册。{ "playerId": "string", "providerId": "string", "createdAt": "string"}
在新玩家注册时初始化 Cloud Save 数据
为了追踪玩家进度,您需要为新玩家初始化 Cloud Save 数据。要初始化数据,您可以使用 Authentication:注册事件并触发 Cloud Code 模块终端。 创建一个 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; } }}
PlayerConfig定义模块终端来初始化 Cloud Save 数据
创建一个TuneGameDifficultyInitializeNewPlayerConfig现在您有了一个可以在玩家注册时触发的模块。 Now you have a module that can trigger when a player signs up.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()); } } }}
配置触发器
现在有了为新玩家初始化 Cloud Save 数据的模块后,可以配置触发器。 创建一个trigger_configurations.json在新玩家注册时初始化 Cloud Save 数据
在trigger_configurations.jsonInitializeNewPlayer此配置使用 Authentication 服务发出的 Authentication:注册事件。 请参阅使用示例:在新玩家注册时初始化 Cloud Save 数据以了解更多信息。[ { "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" }]
调整游戏难度级别
为了调整游戏难度级别,您需要创建由不同事件触发的多个触发器。这些触发器使用过滤器来评估事件有效负载,以便仅在定义的过滤条件满足时才触发 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" }]
部署触发器
运行以下bashtrigger_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 模块,其中包含增加每个触发器的游戏难度级别的所有函数。 向该模块添加一个MultplierConfigC
向该模块添加一个类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; }}
配置玩家配置管理
定义一个类PlayerConfigManagementChangeDifficultyusing 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 请求,您需要使用BearerAuthorizationTriggerOnHighSkill还可以更新 Cloud Save 中的玩家数据以触发其他函数。例如,要触发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'
TriggerOnSessionsPlayedSessionsPlayedUpdatePlayerStatscurl -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 数据。
- 导航到 Unity Cloud Dashboard。
- 选择 Player Management(玩家管理)。
- 在搜索字段中,输入玩家 ID,然后选择 Find Player(查找玩家)。
- 导航到 Cloud Save > Data(数据) 部分。
- 在触发事件时追踪数据变化进度。