Skip to Content
SDK Integration
UnityExamplesCustom GameState

Overriding Default GameState

The game_state function is a crucial component that provides AI agents with essential information about the current game environment. This section explains how to customize the GameState to expose additional context to your AI agents.

Understanding the Default GameState

By default, the SDK provides a minimal GameState implementation with the following fields:

[Serializable] private class DummyGameState { [SerializeField] private bool _can_interact = true; [SerializeField] private string _hint_key = SceneManager.GetActiveScene().name; [SerializeField] private string current_scene = SceneManager.GetActiveScene().name; }
  • _can_interact: A boolean value that indicates whether the agent can interact with the game. If false, the agent will poll until the environment is ready for interaction again.
  • _hint_key: A string value used to expose hints to the agent. By default, it is set to the current scene name.
  • current_scene: A string value that stores the current scene name, that the agent can actually see.

Fields prefixed with an underscore (e.g., _can_interact) are private and used internally by the framework. They are NOT exposed directly to the agent.

The default implementation is registered as a volatile function, allowing for easy override:

[FlayerFunction("game_state", "returns minimal game state needed", volatileFunction: true)] [Preserve] public static string HandleGameState(PacketLogger logger) { var gameState = new DummyGameState(); return JsonUtility.ToJson(gameState); }

Creating a Custom GameState

To provide richer context to your AI agent, you can override the default implementation with a custom one that includes game-specific information.

Here’s a comprehensive example of a custom GameState implementation:

#if USE_NUNU_SDK using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Nunu.Flayer; using Nunu.Network.Logging; using UnityEngine; using UnityEngine.SceneManagement; namespace NunuFunctions { [FlayerFunction("game_state", "returns the actual game state")] [Preserve] public static string HandleGameState(PacketLogger packetLogger) { // create a new empty json object var gameState = new JObject(); // set can interact & hint key gameState["can_interact"] = GameManager.Instance != null; gameState["hint_key"] = $"{SceneManager.GetActiveScene().name}-{GameManager.Instance.GetLevelName()}-{MenuManager.Instance.GetMenuName() ?? "NO_MENU_OPEN"}"; // add custom fields that are exposed to the agent gameState["player_data"] = GetPlayerData(); gameState["inventory"] = GetInventory(); return JsonConvert.SerializeObject(gameState); } } #endif

Hint Key

The hint key is used to expose hints to the agent. By default, it is set to the current scene name. You can override this value to expose different hints to the agent.

Usually it makes sense to concat different state values to create a unique hint key. Some useful values to include are:

  • The current scene name.
  • Current level name
  • In Combat/Out of Combat
  • Menu Open/Close
  • Dead or Alive
  • Role/Class/Character Name

this way you can define hints that only are shown when for example the player is in combat or in a specific level.

For Instance MainHub_NON_COMBAT_INVENTORY_OPEN_WIZARD let’s you write a hint that only applies to the main hub, when the player is not in combat, has the inventory open and is playing as a wizard.

Because we are using regex matching for hint keys, you can create flexible patterns:

  • .*INVENTORY_OPEN_WIZARD.* to match all wizard inventories regardless of location or combat state
  • MainHub_.*(WIZARD|WARRIOR) to define hints for wizards and warriors, but only in the main hub
  • .*COMBAT_.* to create hints that apply in any combat situation across all scenes

Additional Fields

Additional fields can be added to reduce repetitive lookups in the agent. For example, you can expose the player’s health, inventory, or other game-specific data to the agent.

They can also be used to expose parameters that are needed to execute some of the action flayer functions, like:

  • If you have a navigate_to(x, y, z) function, expose coordinates of important locations in the game state ("quest_marker": {"x": 120.5, "y": 10.0, "z": 45.8})
  • For an equip_item(item_id) function, include a list of available items with their IDs ("equipment_options": [{"id": "sword_01", "name": "Iron Sword"}, ...])
  • If you have a cast_spell(spell_id, target_id) function, provide both spell IDs and potential target IDs

By including this contextual information, you help the agent make more informed decisions.

Best Practices

To verify your GameState override is working correctly:

  • Use the Flayer Debugger to inspect the returned JSON
  • Check that all expected fields are present and correctly formatted
  • Verify that the hint key changes appropriately as game conditions change

Example

#if USE_NUNU_SDK using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Nunu.Flayer; using Nunu.Network.Logging; using UnityEngine; using UnityEngine.SceneManagement; namespace NunuFunctions { public static class CustomGameState { [FlayerFunction("game_state", "Returns detailed game state with player and environment information")] [Preserve] public static string HandleGameState(PacketLogger logger) { // Create a JSON object to hold our game state var gameState = new JObject(); // Required framework fields gameState["_can_interact"] = GameManager.Instance != null && !Player.Instance.IsInCutscene; // Build a comprehensive hint key string currentMenu = UIManager.Instance.CurrentMenuName ?? "NO_MENU"; string combatState = CombatSystem.Instance.InCombat ? "IN_COMBAT" : "PEACEFUL"; string playerState = Player.Instance.IsDead ? "DEAD" : "ALIVE"; gameState["_hint_key"] = $"{SceneManager.GetActiveScene().name}_{currentMenu}_{combatState}_{playerState}"; // Visible fields for the agent gameState["current_scene"] = SceneManager.GetActiveScene().name; gameState["player_health"] = Player.Instance.CurrentHealth; gameState["player_max_health"] = Player.Instance.MaxHealth; gameState["player_level"] = Player.Instance.Level; gameState["current_objective"] = QuestManager.Instance.CurrentObjectiveDescription; // Add nearby interactive objects gameState["nearby_interactables"] = GetNearbyInteractables(); // Add inventory information gameState["inventory"] = GetInventoryData(); // Log what we're returning logger.Log("Providing updated game state to agent", sendToAI: true); return JsonConvert.SerializeObject(gameState); } private static JArray GetNearbyInteractables() { var interactables = new JArray(); // Find all interactable objects within range foreach (var interactable in InteractionSystem.GetNearbyInteractables(5.0f)) { interactables.Add(new JObject { ["id"] = interactable.Id, ["type"] = interactable.Type, ["name"] = interactable.DisplayName, ["distance"] = Vector3.Distance(Player.Instance.transform.position, interactable.transform.position) }); } return interactables; } private static JObject GetInventoryData() { var inventory = new JObject(); // Add equipped items var equipped = new JObject(); foreach (var slot in InventorySystem.Instance.EquippedSlots) { if (slot.Value?.Item != null) { equipped[slot.Key] = slot.Value.Item.ItemId; } else { equipped[slot.Key] = null; } } inventory["equipped"] = equipped; // Add key inventory items var items = new JArray(); foreach (var item in InventorySystem.Instance.GetImportantItems()) { items.Add(new JObject { ["id"] = item.ItemId, ["name"] = item.DisplayName, ["count"] = item.Count }); } inventory["key_items"] = items; return inventory; } } } #endif
Last updated on