Appearance
Appearance
Orca demonstrates how to build a live-service game using the Metaplay SDK. This document explains what the game is, how it's architected, how the data-driven configuration works, and how it integrates with Metaplay's systems.
Orca is a merge-2 game where players merge items on themed island boards to progress through item chains, manage heroes (Ranger, Fisher, Inventor, Barista) with unique abilities and tasks, explore multiple islands with different themes and unlock requirements, and complete objectives driven by a trigger system that guides progression. Players can also participate in LiveOps events with premium passes, seasonal content, and competitions.
The game demonstrates how to build a data-driven, live-service mobile game using Metaplay's architecture. You can explore the Google Sheets spreadsheet used as the source for game configuration.
Orca is built around a data-first architecture: almost all gameplay, progression and UI flows are driven by game configuration that can be updated over-the-air.
Two complementary systems form the foundation of this approach:
Triggers express the game's "if this happens, do that" rules in spreadsheets instead of in code. When events occur (item discoveries, level-ups, resource thresholds, island unlocks, or hero task completions) the game finds the matching TriggerInfo
, which the TriggerModel
then applies to: update player state, queue UI events, or invoke listeners that animate and sync the client. Because triggers are authored in the game config, designers can iterate on progression, tutorials, and dialogs without changing code or releasing a new client build.
Triggers power a lot of Orca's features. Tutorials (hints, dialogues, UI highlights), content unlocks (islands, heroes, features), discoveries and rewards (first-time finds, premium reveals, milestones), and hero progression flows are all implemented using triggers.
The tutorial system uses triggers to drive conversations. Triggers reference dialogues from the Dialogues sheet, which defines conversation text, speakers, and commands. When a trigger fires with a dialogue reference, the TriggerModel
invokes ClientListener.OnDialogueStarted()
, enqueuing a DialogueTriggerAction
into the TriggerQueue
that displays the dialogue popup.
Designers write dialogue scripts in the Dialogues sheet, connect them to triggers, and chain triggers to game events (item discoveries, task completions, island unlocks). The entire tutorial is authored this way without code changes to modify dialogue, steps, or progression gates.
The live demo runs entirely through this trigger and dialogue system, enabling over-the-air updates to tutorial pacing, dialogue content, and hint timing without client releases.
The project stores the canonical game configuration in spreadsheet-backed game configs that compile into a shared, type-safe SharedGameConfig
available to client and server code. The four primary sheets that drive core gameplay are:
DiscoveredTriggers
and CollectedTriggers
. Each row becomes a ChainInfo
entry used by ItemModel
/MergeBoardModel
at runtime.TriggerInfo
entry. Triggers are data records that map a named trigger id to a set of actions (dialog, UI highlights, unlocks, resource checks, feature flags) which the TriggerModel
executes when fired.DialogueInfo
entry with conversation text, speaker information, and special commands that control the dialogue flow.This setup allows designers to modify progression, item stats, economy tuning and tutorial flows directly in spreadsheets, enabling over-the-air updates without a client release. The compiled config types (the MetaSerializable
-annotated objects and IGameConfigData
interfaces) keep the data consistent and type-safe between the client and the server, which helps preserve determinism and reduces bugs. Systems consume configuration instead of hard-coding rules, separating behavior validation from content data, which speeds up iteration and reduces coupling.
Setting Up Game Configs is a good intro on how to start using game configs.
Build and publish:
Both the client's and server's core systems consume the game config to implement deterministic, cheat-resistant gameplay. The MergeBoardModel
uses ChainInfo
entries to manage merge rules and item progression. The TriggerModel
executes TriggerInfo
reactions to drive tutorials and unlocks. The PlayerWalletModel
references global parameters for economy tuning.
The Unity client uses Zenject dependency injection to manage services for game state tracking and event communication, separating game logic from presentation code.
The PlayerModel
runs at 10 ticks per second and contains all game state: islands, merge boards, wallet, inventory, heroes, and triggers. The model is described in Deep Dive: Game Logic Execution Model and executes identically on both client and server using Deterministic Synchronization. State is serialized with [MetaMember]
attributes, and checksum verification catches any divergence. The model accesses the Game Configs through GameConfig
and communicates with external code via ServerListener
and ClientListener
.
When a player merges two items, collects resources from a building, or purchases something from the market, that interaction flows through actions. They are described in the Creating and Using Actions page. The client optimistically executes the action immediately for responsiveness while simultaneously sending it to the server for validation.
Each action runs through a two-phase execution model:
commit=false
): Checks if the action is allowed (does the player have enough currency? Are the items valid for merging? Is the building ready?). This phase runs without changing state.commit=true
): If validation passes, the action applies its changes to the PlayerModel
, e.g. spending currency, creating merged items, claiming rewards.Both client and server run these same two phases. If the server's result differs from the client (perhaps the player tried to cheat, or there was a race condition with another action), the server's authoritative state wins and sends a correction to synchronize the client. The client ends up in a desynchronized state, the game is reloaded and a new session is prompted.
For this pattern to work, deterministic execution is critical: actions must produce identical results on client and server. The game uses Fixed-Point Math (MetaDuration
, F64
), deterministic collections (MetaDictionary
), and seeded RNG (RandomPCG
) to eliminate floating-point inconsistencies and platform differences. When both client and server execute the same action with the same player state, they arrive at exactly the same outcome.
Actions modify the player model, but the UI needs to know when to update. The Client and Server Listeners pattern enables game logic to notify the UI code when changes occur.
During an action's commit phase, the game logic invokes callbacks on IPlayerModelClientListener
. When an action adds an item to inventory, the action calls player.ClientListener.OnItemAdded(item)
. The client-side implementation receives this callback and responds with UI updates, animations, or visual effects.
This is event-driven rather than poll-based, which means the UI reacts to changes instead of checking model state every frame. Listeners pass contextual data (which item was added, which trigger fired) for efficient, precise updates.
Listeners are particularly important for Orca's Trigger System. When a trigger fires from the data-driven TriggerInfo
configuration, the TriggerModel
invokes listener callbacks that are then synthesized sequences into UI events: dialogues, highlights, navigation, and unlocks. This creates the data-to-visuals pipeline: spreadsheet-authored triggers → deterministic model execution → listener callbacks → visual presentation.
Beyond triggers, listeners handle UI reactions throughout the game. When an item is discovered on the merge board, a listener fires and the client shows discovery feedback and animations; when currency changes, listeners update counters and play reward animations; when a hero completes a task, listeners trigger notifications and refresh hero state. This bridges deterministic model changes to immediate, contextual visuals and feedback.
While the client executes actions optimistically for responsive gameplay, the server acts as the authoritative source of truth. The server is built on Akka.NET actors, independent entities that manage their own state and communicate via message passing. Game Server Architecture covers a high level overview of the game server architecture.
The PlayerActor
runs the same PlayerModel
code as the client, executing actions sent from clients through the same two-phase validation and commit process. When an action arrives, the server validates it against its authoritative state and executes it deterministically.
Beyond player entities, the actor system allows other services to run as independent entities with their own lifecycle. Actors communicate via message passing without tight coupling. Introduction to Entities and Actors explains more about how entities work. The server also handles Game Configs distribution, serving compiled configurations from Google Sheets to clients for over-the-air updates. The system scales horizontally across server nodes, maintaining the deterministic shared-code model for cheat-resistant gameplay at any scale.