Repository navigation
GANI Files
GANI is Gondwana's portable definition format for reusable tile animations.
Choose Import definition… in the Definition toolbar and browse to an existing file of the same format. Legacy singleton files import directly. For a multi-definition file, check the members to import; Select all and Select none adjust the selection. Canceling or importing no members leaves the current document unchanged.
Imported members are independent copies appended to the current document. Existing names are retained when available; conflicts receive -2, -3, and subsequent suffixes. The first imported member becomes active and the destination becomes dirty. Save and Save As retain both existing and imported members; the source file is never changed.
Relative filesystem references, including GAF paths, are rebased to keep pointing at their original resources. Archive entry names and logical dependency selectors are retained. Resources are not copied or moved. Import does not rewrite logical references when a root name conflicts, or resolve collisions among inner sprite IDs, resource keys, or scene layer IDs. Review those relationships before runtime materialization. Incomplete authoring drafts can be imported and saved using the existing draft workflow; malformed document envelopes and duplicate source root identities are rejected.
One physical .gani file contains a document object with a Definitions array.
Every member has a unique, non-empty, case-sensitive Key. The document location
(loose path, or GAF path plus typed entry name) is independent of that logical identity.
{
"Definitions": [
{ "Key": "first" },
{ "Key": "second" }
]
}This illustrates the envelope and identities; supply the normal format-specific fields
shown below for complete definitions. The same envelope is stored as the bytes of a
single AssetTypes.AnimationDefinition GAF entry. GAF remains a typed binary container;
loading or saving a document does not create entries for individual members.
var selected = AnimationDefinitionSerializer.Load("content.gani", "second");
var definitions = AnimationDefinitionSerializer.LoadAll("content.gani");
var document = AnimationDefinitionSerializer.LoadDocument("content.gani");
AnimationDefinitionSerializer.SaveDocument("copy.gani", document);
AnimationDefinitionSerializer.SaveAll("copy.gani", definitions);
var packed = AnimationDefinitionSerializer.Load(assetsFile, "content.gani", "second");
var packedMembers = AnimationDefinitionSerializer.LoadAll(assetsFile, "content.gani");LoadDocument, LoadAll, and member-selecting Load also accept readable streams and
leave them open. Anonymous streams do not claim physical-source provenance.
ValidateDocument reports structural and per-member validation errors, qualified by
logical identity. Parsing rejects null member lists, null members, duplicate identities,
and mixed singleton/envelope objects. Authoring drafts may be structurally valid but
incomplete; runtime materialization runs the format validator.
Existing singleton JSON without an envelope continues to load. Load(path) and other
singleton convenience APIs require exactly one member and fail clearly on empty or
multi-member documents. Save(path, definition) writes a canonical one-member envelope.
ToJson(definition) retains the single-definition JSON projection for existing callers;
use ToDocumentJson(document) for canonical document JSON. Singleton JSON examples
below remain supported and can also be placed inside the envelope.
The editor's Definition toolbar selects, adds, duplicates, removes, and renames roots. Saving writes every member, and Save As rebases references for every member without modifying unrelated content. At least one member remains in an editable session.
Use AnimationDefinitionSerializer.LoadAllCycles to materialize every member.
All cycles are created before next-cycle links are wired, including mutual references.
Load-all operations roll back newly created runtime content if a member fails. Physical/member separation supports future packaging work; content relocation is not implemented here.
Each frame may specify DurationSeconds, a finite positive display duration.
Omitting it (or using null) retains the cycle's ThrottleTime. Timing belongs
to a sequence occurrence, so the same image can appear twice with different
durations. Runtime playback and Studio preview use the currently displayed
frame's duration, including the return leg of ping-pong playback. Select a frame
in Studio to edit its duration in the property grid; clear it to use the default.
For cycles with explicit frame durations, TotalCycleTime sums display durations
(counting interior ping-pong frames twice). Simple cycles include the final
frame's display duration. Cycles without overrides retain their historical total
time calculation, including Simple's transition-time convention. A zero legacy
ThrottleTime still stops playback safely; explicit overrides must be positive.
A .gani file describes one registered animation cycle without serializing the runtime Cycle, FrameSequence, Frame, or Tilesheet object graphs.
The format lives under:
Gondwana.Drawing.Animation.GANI
and follows the same definition-file approach used by GTS and GSCN.
- What GANI represents
- Relationship to GTS
- Relationship to GSCN
- Definition model
- Example
- Loading and saving
- Runtime materialization
- Next-cycle transitions
- Validation
- Packed GANI definitions
- Provenance
- EngineState integration
- Tooling model
- Where to read next
At runtime Gondwana animation is built from:
Tilesheet
|
v
Frame
|
v
FrameSequence
|
v
Cycle
|
v
Animator
GANI represents the reusable definition portion of that pipeline:
.gts / registered tilesheet frames
|
v
AnimationDefinition
|
v
Cycle
|
v
Animator
A GANI file does not store per-object playback state such as:
- which tile or sprite is currently using the animation
- the current frame index of an animator
- whether one specific animator is paused
- animator events
- the owning
Tile
Those remain runtime concerns.
A GANI definition corresponds to one reusable registered Cycle.
GANI depends on tilesheet frames.
It does not embed a Tilesheet or copy GTS image/region metadata into the animation file. Each animation frame stores a lightweight runtime reference:
Tilesheet logical name
Region name
X tile
Y tile
For example:
{
"Tilesheet": "world",
"RegionName": "water",
"XTile": 1,
"YTile": 0
}The corresponding tilesheet must be registered before the GANI definition is materialized into a runtime Cycle.
For authoring tools, GANI can also record where that logical tilesheet definition came from through TilesheetSources. A loose source can point to a relative .gts path, while the model also has a packed form for a GTS entry inside a Gondwana assets file:
"TilesheetSources": [
{
"Tilesheet": "world",
"Kind": "LooseDefinitionFile",
"GtsPath": "../tiles/world.gts"
}
]These source entries are authoring/dependency metadata. They help editors reopen the correct GTS definitions, but runtime materialization does not load those files. AnimationDefinitionSerializer.ToCycle(...) still resolves frame references through TilesheetRegistry.
Relative source paths are interpreted from the containing GANI file.
That gives Gondwana a clear dependency direction:
.gts
|
v
.gani
Changing the source GTS can therefore update the frames available to animations without duplicating tilesheet definitions inside every animation file.
GSCN scene tiles can assign a reusable GANI animation by logical key:
"AnimationKey": "actor.walk",
"StartAnimation": trueThe scene file stores only the key and whether the assignment should begin playing
when the scene is materialized. It does not copy the GANI frames or serialize the
runtime Cycle/Animator graph.
The complete definition dependency direction is therefore:
.gts
|
v
.gani
|
v
.gscn
When materializing a GSCN that contains an AnimationKey, the corresponding GANI
cycle must already be registered. EngineState satisfies this when both
EngineStateParts.Cycles and EngineStateParts.Scenes are selected because
cycles are restored before scenes.
The root type is:
AnimationDefinitionIts persistent fields are:
| Property | Meaning |
|---|---|
Key |
Global animation/cycle key |
ThrottleTime |
Seconds between frame transitions |
CycleType |
Simple, Repeating, or PingPong
|
HideTileOnCycleEnd |
Runtime cycle behavior flag |
NextCycleKey |
Optional key for the follow-on cycle |
TilesheetSources |
Optional authoring-time locations for logical GTS dependencies |
Frames |
Ordered lightweight GTS frame references |
Source |
Definition provenance |
Each AnimationTilesheetSourceDefinition contains:
| Property | Meaning |
|---|---|
Tilesheet |
Logical tilesheet name matched by animation frames |
Kind |
LooseDefinitionFile or PackedDefinitionFile
|
GtsPath |
Loose GTS path, preferably relative to the GANI file |
AssetsFilePath |
Assets-file path for a packed GTS source |
AssetEntryName |
Packed GTS entry name |
Each AnimationFrameDefinition contains:
| Property | Meaning |
|---|---|
Tilesheet |
Registered tilesheet name |
RegionName |
Region within that tilesheet |
XTile |
Zero-based frame column |
YTile |
Zero-based frame row |
The definition deliberately does not persist FrameSequence's current playback index or internal direction state.
A repeating water animation could be written as:
{
"Key": "world.water",
"ThrottleTime": 0.18,
"CycleType": "Repeating",
"HideTileOnCycleEnd": false,
"NextCycleKey": "world.water",
"TilesheetSources": [
{
"Tilesheet": "world",
"Kind": "LooseDefinitionFile",
"GtsPath": "../tiles/world.gts"
}
],
"Frames": [
{
"Tilesheet": "world",
"RegionName": "water",
"XTile": 0,
"YTile": 0
},
{
"Tilesheet": "world",
"RegionName": "water",
"XTile": 1,
"YTile": 0
},
{
"Tilesheet": "world",
"RegionName": "water",
"XTile": 2,
"YTile": 0
}
]
}The file contains no $id, $ref, embedded Tilesheet, or runtime Animator object graph.
Load a definition without creating a runtime cycle:
using Gondwana.Drawing.Animation.GANI;
AnimationDefinition definition =
AnimationDefinitionSerializer.Load("animations/water.gani");Save a definition:
AnimationDefinitionSerializer.Save(
"animations/water.gani",
definition);A runtime Cycle can also be converted directly:
AnimationDefinition definition =
AnimationDefinitionSerializer.FromCycle(cycle);
AnimationDefinitionSerializer.Save(
"animations/water.gani",
cycle);For in-memory workflows:
string json =
AnimationDefinitionSerializer.ToJson(definition);
AnimationDefinition restored =
AnimationDefinitionSerializer.FromJson(json);These methods use the clean GANI serializer settings rather than EngineState.JsonSerializerSettings.
Referenced tilesheets must already be registered.
Then:
Cycle cycle =
AnimationDefinitionSerializer.LoadCycle(
"animations/water.gani");or:
Cycle cycle =
AnimationDefinitionSerializer.ToCycle(definition);Materialization:
- validates the definition
- resolves each logical frame reference through
TilesheetRegistry - builds a runtime
FrameSequence - creates/registers the
Cycle - resolves its follow-on cycle
A missing tilesheet, missing region, or out-of-range frame coordinate fails with an InvalidDataException rather than silently substituting another frame.
TilesheetSources is not consulted by runtime materialization. A stale or unavailable authoring path does not replace the normal registry-based runtime dependency model.
Cycle.NextCycle is represented by logical identity:
"NextCycleKey": "actor.idle"rather than by serializing another Cycle object.
That allows definitions such as:
actor.attack.gani
|
+---- NextCycleKey: actor.idle
|
v
actor.idle.gani
For standalone ToCycle / LoadCycle calls, a different NextCycleKey must already be registered.
EngineState handles this more broadly: it first materializes all selected animation definitions and then resolves next-cycle links in a second pass. That permits circular definition relationships such as:
idle -> blink -> idle
If NextCycleKey is omitted or empty, GANI uses the normal runtime default and points the cycle back to itself.
Definitions can be checked without creating runtime objects:
IReadOnlyList<string> errors =
AnimationDefinitionValidator.Validate(definition);Structural validation includes:
- non-empty animation key
- finite, non-negative throttle time
- valid
CycleType - at least one frame
- non-empty tilesheet and region names
- non-negative frame coordinates
- null frame entries
- duplicate or malformed tilesheet-source entries
Tilesheet existence and actual region bounds are checked during materialization, because those checks require the referenced runtime tilesheets.
A GANI document can also be stored in a Gondwana assets file using:
AssetTypes.AnimationDefinitionLoad a packed definition with:
AnimationDefinition definition =
AnimationDefinitionSerializer.Load(
assetsFile,
"animations/world.water.gani");Or materialize it directly:
Cycle cycle =
AnimationDefinitionSerializer.LoadCycle(
assetsFile,
"animations/world.water.gani");Referenced tilesheets still need to be registered before runtime materialization.
AnimationDefinition.Source records where the definition came from.
AnimationDefinitionSourceKind supports:
| Kind | Meaning |
|---|---|
None |
No source is known |
LooseDefinitionFile |
Loaded from a loose .gani file |
PackedDefinitionFile |
Loaded from a GANI entry in an AssetsFile
|
Generated |
Generated from a runtime cycle or tooling |
Provenance describes the definition's origin. The animation's runtime identity is still AnimationDefinition.Key.
EngineStateParts.Cycles is persisted through GANI.
Inline definitions are the default:
Engine.Instance.State.SaveToFile(
"session.json",
separateGaniFiles: false);To externalize animation definitions:
Engine.Instance.State.SaveToFile(
"session.json",
separateGaniFiles: true);For session.json, Gondwana writes:
session.json
session.animations/
actor.walk.gani
world.water.gani
...
The main state file stores relative GaniPath references when possible.
GANI is independent of the GTS and GSCN storage choices, so each definition family can be embedded or externalized separately.
Because animation definitions depend on tilesheet frames, applying EngineStateParts.Cycles also selects the tilesheet dependency chain. When creating a partial state file intended to restore independently, save its required Tilesheets (and any required AssetsFiles) along with its cycles.
GANI is designed to be an authoring format, not merely an EngineState implementation detail.
A future animation editor can work entirely against AnimationDefinition:
Animation
├── Key / timing / cycle type
├── Frames
│ ├── GTS frame reference
│ ├── GTS frame reference
│ └── ...
├── Next cycle
└── Preview
The editor can validate and save the definition without needing to serialize a live Animator.
The WinForms GANI editor persists loose GTS dependencies in TilesheetSources and reloads them automatically when a GANI document is reopened. For older GANI files that do not yet contain source metadata, the editor performs a deliberately narrow migration check: it looks only beside the GANI file for a GTS definition whose logical Name matches the frame's Tilesheet. If exactly one match exists, it is loaded and the document is marked dirty so the recovered dependency is written on the next save. The editor does not recursively search the filesystem.
Saving or Save As rebases loose dependency paths relative to the new GANI location when both files are on the same filesystem root.
That same separation makes GANI suitable for both a standalone WinForms animation tool and, later, an embedded Gondwana Studio editor.
-
Tile Animation — runtime
FrameSequence,Cycle, andAnimatorbehavior - .gts Files — tilesheet/frame definitions referenced by GANI
- Tilesheets — runtime tilesheet model
- Game State Files — saving animation definitions with EngineState
- Serialization and EngineState — inline/external definition handling
EngineStateSaveOptions supports DefinitionPersistence.PreserveSource for this
format. A definition loaded from a named GAF can retain AssetsFilePath and
AssetEntryName; a loose definition can retain its path. EngineState compares current
serializable state with its loaded baseline and falls back when it changed. Packed
loads use AssetsFile.SourcePath, including an explicitly supplied stream origin;
anonymous archives cannot produce path-based provenance. See
Definition persistence options
for collection ownership, generated identities, relative paths, and safe fallback.
- Home
- Make Your First Game in 30 Minutes
- Engine Architecture Overview
- Game Hosts and Application Startup
- Gondwana Engine Lifecycle
- Gondwana CLI Cheatsheet
- Using Gondwana with ChatGPT and Codex
Core Concepts
Systems
Rendering
Widgets
-
Controls
-
Dialogs
- AboutBox
- ConversationBox
- DialogBox — base class for custom dialogs
-
Hud
-
Layout
-
Menus
-
Overlays