Gondwana Game Engine Development (master) - 2.6.1
Gondwana is a cross-platform 2.5D game and rendering engine written in C#/.NET 8. It provides fine-grained control over rendering, timing, and scene composition, with built-in support for parallax, z-ordering, pixel overhang, collision detection, and particle effects. Gondwana targets desktop and web platforms using SkiaSharp for graphics and pluggable audio backends for desktop and browser targets.
Loading...
Searching...
No Matches
SpriteDefinitionSerializer.cs
1using System.Numerics;
5using Newtonsoft.Json;
6
8
10public static class SpriteDefinitionSerializer
11{
12 private static readonly JsonSerializerSettings Settings = new()
13 {
14 Formatting = Formatting.Indented,
15 NullValueHandling = NullValueHandling.Ignore,
16 MissingMemberHandling = MissingMemberHandling.Ignore
17 };
18
24 public static SpriteDefinition Load(string filePath)
25 {
26 var path = Path.GetFullPath(filePath);
27 var definition = FromJson(File.ReadAllText(path));
28 definition.Source = SpriteDefinitionSource.LooseDefinitionFile(path);
29 definition.LoadStamp = new(definition, filePath: Path.GetFullPath(path));
30 return definition;
31 }
32
38 public static SpriteDefinition Load(Stream stream)
39 {
40 ArgumentNullException.ThrowIfNull(stream);
41 using var reader = new StreamReader(stream, leaveOpen: true);
42 return FromJson(reader.ReadToEnd());
43 }
44
51 public static SpriteDefinition Load(AssetsFile assetsFile, string entryName)
52 {
53 ArgumentNullException.ThrowIfNull(assetsFile);
54 using var stream = assetsFile.Get(AssetTypes.SpriteDefinition, entryName)
55 ?? throw new FileNotFoundException($"GSPR asset entry not found: {entryName}");
56 var definition = Load(stream);
57 definition.Source = string.IsNullOrWhiteSpace(assetsFile.SourcePath)
58 ? SpriteDefinitionSource.None()
59 : SpriteDefinitionSource.PackedDefinitionFile(assetsFile.SourcePath, entryName);
60 definition.LoadStamp = new(definition, assetsFilePath: assetsFile.SourcePath, entryName: entryName);
61 return definition;
62 }
63
69 public static string ToJson(SpriteDefinition definition)
70 {
71 ArgumentNullException.ThrowIfNull(definition);
72 return JsonConvert.SerializeObject(definition, Settings);
73 }
74
80 public static SpriteDefinition FromJson(string json)
81 {
82 try
83 {
84 var definition = JsonConvert.DeserializeObject<SpriteDefinition>(json, Settings)
85 ?? throw new InvalidDataException("GSPR definition is null.");
86 definition.Sprites ??= [];
87 definition.TilesheetSources ??= [];
88 definition.SceneSources ??= [];
89 if (definition.Sprites.Any(sprite => sprite is null) ||
90 definition.TilesheetSources.Any(source => source is null) ||
91 definition.SceneSources.Any(source => source is null))
92 throw new InvalidDataException("GSPR collections cannot contain null entries.");
93 return definition;
94 }
95 catch (JsonException ex) { throw new InvalidDataException("Failed to deserialize GSPR JSON.", ex); }
96 }
97
103 public static void Save(string filePath, SpriteDefinition definition)
104 {
105 ArgumentNullException.ThrowIfNull(definition);
106 var path = Path.GetFullPath(filePath);
107 var directory = Path.GetDirectoryName(path)!;
108 var clone = Rebase(definition, directory);
109 Directory.CreateDirectory(directory);
110 File.WriteAllText(path, ToJson(clone));
111 }
112
117 public static SpriteDefinition Rebase(SpriteDefinition definition, string directory)
118 {
119 var clone = FromJson(ToJson(definition));
120 var origin = definition.Source.GsprFilePath ?? definition.Source.AssetsFilePath;
121 if (string.IsNullOrWhiteSpace(origin)) return clone;
122 var oldDirectory = Path.GetDirectoryName(Path.GetFullPath(origin))!;
123 string? RebasePath(string? path) => string.IsNullOrWhiteSpace(path) ? path :
124 Path.GetRelativePath(Path.GetFullPath(directory), Path.GetFullPath(path, oldDirectory));
125 foreach (var source in clone.TilesheetSources)
126 {
127 source.GtsPath = RebasePath(source.GtsPath);
128 source.AssetsFilePath = RebasePath(source.AssetsFilePath);
129 }
130 foreach (var source in clone.SceneSources)
131 {
132 source.GscnPath = RebasePath(source.GscnPath);
133 source.AssetsFilePath = RebasePath(source.AssetsFilePath);
134 }
135 return clone;
136 }
137
143 public static void Save(string filePath, IEnumerable<Sprite> sprites) => Save(filePath, FromSprites(sprites));
149 public static void Save(string filePath, Sprite sprite) => Save(filePath, FromSprites([sprite]));
150
156 public static SpriteDefinition FromSprites(IEnumerable<Sprite> sprites)
157 {
158 ArgumentNullException.ThrowIfNull(sprites);
159 return new() { Sprites = sprites.Select(FromSprite).ToList(), Source = SpriteDefinitionSource.Generated() };
160 }
161
167 public static SpriteInstanceDefinition FromSprite(Sprite sprite)
168 {
169 ArgumentNullException.ThrowIfNull(sprite);
170 var frame = sprite.CurrentFrame;
171 return new()
172 {
173 Id = sprite.Id,
174 Nickname = sprite.Nickname,
175 SceneId = sprite.SceneLayer.Scene?.ID ?? string.Empty,
176 SceneLayerId = sprite.SceneLayer.ID,
177 Position = sprite.SceneLayerCoordinates,
178 Frame = frame.Tilesheet is null ? null : new() { Tilesheet = frame.Tilesheet.Name, RegionName = frame.RegionName, XTile = frame.XTile, YTile = frame.YTile },
179 Visible = sprite.Visible,
180 ZOrder = sprite.ZOrder,
181 Rotation = sprite.Rotation,
182 HorizAlign = sprite.HorizAlign,
183 VertAlign = sprite.VertAlign,
184 NudgeX = sprite.NudgeX,
185 NudgeY = sprite.NudgeY,
186 RenderSize = sprite.RenderSize,
187 EnableFog = sprite.EnableFog,
188 AdjustCollisionArea = sprite.AdjustCollisionArea,
189 AdjustCollisionAreaByFrame = sprite.AdjustCollisionAreaByFrame,
190 CollisionType = sprite.CollisionType,
191 CollisionTypeByFrame = sprite.CollisionTypeByFrame,
192 CollisionProfileName = sprite.CollisionProfileName,
193 CollisionsEnabled = sprite.CollisionsEnabled
194 };
195 }
196
202 public static List<Sprite> LoadSprites(string filePath) => ToSprites(Load(filePath));
209 public static List<Sprite> LoadSprites(AssetsFile assetsFile, string entryName) => ToSprites(Load(assetsFile, entryName));
215 public static Sprite ToSprite(SpriteInstanceDefinition definition) => ToSprites(new() { Sprites = [definition] })[0];
216
220 public static List<Sprite> ToSprites(SpriteDefinition definition)
221 => ToSprites(definition, allowDuplicateNicknames: false);
222
223 // EngineState retains its historical last-incoming-nickname-wins merge contract.
224 internal static List<Sprite> ToSprites(SpriteDefinition definition, bool allowDuplicateNicknames)
225 {
226 var errors = SpriteDefinitionValidator.Validate(definition, allowDuplicateNicknames);
227 if (errors.Count != 0) throw new InvalidDataException("Invalid GSPR definition:\n" + string.Join("\n", errors));
228 var created = new List<Sprite>();
229 try
230 {
231 foreach (var entry in definition.Sprites)
232 {
233 var scenes = Scene._allScenes.Where(scene => scene.ID == entry.SceneId).Take(2).ToArray();
234 if (scenes.Length != 1)
235 throw new InvalidDataException($"Sprite '{entry.Nickname}': Scene '{entry.SceneId}' is missing or ambiguous.");
236 var scene = scenes[0];
237 var layers = scene.SceneLayers.Where(layer => layer.ID == entry.SceneLayerId).Take(2).ToArray();
238 if (layers.Length != 1)
239 throw new InvalidDataException($"Sprite '{entry.Nickname}': SceneLayer '{entry.SceneLayerId}' is missing or ambiguous.");
240 var layer = layers[0];
241 var frame = entry.Frame is null ? default : ResolveFrame(entry.Frame);
242 if (!string.IsNullOrWhiteSpace(entry.CollisionProfileName) && !scene.CollisionProfiles.GetProfileNames().Contains(entry.CollisionProfileName))
243 throw new InvalidDataException($"Sprite '{entry.Nickname}': collision profile '{entry.CollisionProfileName}' does not exist.");
244 var sprite = SpriteManager.Instance.CreateSprite(layer, frame, entry.Nickname, entry.CollisionProfileName);
245 created.Add(sprite);
246 if (entry.Id != Guid.Empty) sprite.Id = entry.Id;
247 sprite.SetPosition(new Vector2(entry.Position.X, entry.Position.Y));
248 sprite.Visible = entry.Visible;
249 sprite.ZOrder = entry.ZOrder;
250 sprite.Rotation = entry.Rotation;
251 sprite.HorizAlign = entry.HorizAlign;
252 sprite.VertAlign = entry.VertAlign;
253 sprite.NudgeX = entry.NudgeX;
254 sprite.NudgeY = entry.NudgeY;
255 if (!entry.RenderSize.IsEmpty) sprite.RenderSize = entry.RenderSize;
256 sprite.EnableFog = entry.EnableFog;
257 sprite.AdjustCollisionArea = entry.AdjustCollisionArea;
258 sprite.CollisionType = entry.CollisionType;
259 sprite.AdjustCollisionAreaByFrame = entry.AdjustCollisionAreaByFrame;
260 sprite.CollisionTypeByFrame = entry.CollisionTypeByFrame;
261 sprite.CollisionsEnabled = entry.CollisionsEnabled;
262 }
263 SpriteManager.Instance.DefinitionProvenance = definition.Sprites.All(sprite => sprite.Id != Guid.Empty &&
264 !string.IsNullOrWhiteSpace(sprite.Nickname))
265 ? definition.LoadStamp?.Materialized(definition, () => FromSprites(created))
266 : null;
267 return created;
268 }
269 catch
270 {
271 foreach (var sprite in created)
272 {
273 SpriteManager.Instance._spriteList.Remove(sprite);
274 sprite.DisposeImmediate();
275 }
276 throw;
277 }
278 }
279
280 private static Frame ResolveFrame(SpriteFrameDefinition definition)
281 {
282 var sheet = TilesheetRegistry.Instance.GetOrNull(definition.Tilesheet)
283 ?? throw new InvalidDataException($"GSPR tilesheet '{definition.Tilesheet}' is not registered.");
284 var region = sheet.GetRegion(definition.RegionName)
285 ?? throw new InvalidDataException($"GSPR region '{definition.RegionName}' is missing on '{definition.Tilesheet}'.");
286 if (definition.XTile >= region.Columns || definition.YTile >= region.Rows)
287 throw new InvalidDataException($"GSPR frame ({definition.XTile}, {definition.YTile}) is outside region '{definition.RegionName}'.");
288 return sheet.GetFrame(definition.RegionName, definition.XTile, definition.YTile);
289 }
290}
string? SourcePath
Gets the archive's reconstructible origin, including an optional stream origin.
Stream? Get(AssetTypes type, string name)
Retrieves a stream for the specified asset type and name.
AssetTypes
Defines the types of assets that can be stored and managed by the engine.
Definition AssetTypes.cs:8
@ SpriteDefinition
Gondwana sprite collection definition (.gspr).
Definition AssetTypes.cs:65
@ Scene
Displays scene layer, grid-cell, and active tile-animation counts.
@ Sprites
Represents the sprites state, including sprite instances, positions, properties, and sprite-related g...