10public static class SpriteDefinitionSerializer
12 private static readonly JsonSerializerSettings Settings =
new()
14 Formatting = Formatting.Indented,
15 NullValueHandling = NullValueHandling.Ignore,
16 MissingMemberHandling = MissingMemberHandling.Ignore
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));
40 ArgumentNullException.ThrowIfNull(stream);
41 using var reader =
new StreamReader(stream, leaveOpen:
true);
42 return FromJson(reader.ReadToEnd());
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);
71 ArgumentNullException.ThrowIfNull(definition);
72 return JsonConvert.SerializeObject(definition, Settings);
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.");
95 catch (JsonException ex) {
throw new InvalidDataException(
"Failed to deserialize GSPR JSON.", ex); }
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));
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)
127 source.GtsPath = RebasePath(source.GtsPath);
128 source.AssetsFilePath = RebasePath(source.AssetsFilePath);
130 foreach (var source
in clone.SceneSources)
132 source.GscnPath = RebasePath(source.GscnPath);
133 source.AssetsFilePath = RebasePath(source.AssetsFilePath);
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]));
158 ArgumentNullException.ThrowIfNull(sprites);
159 return new() {
Sprites = sprites.Select(FromSprite).ToList(), Source = SpriteDefinitionSource.Generated() };
167 public static SpriteInstanceDefinition FromSprite(Sprite sprite)
169 ArgumentNullException.ThrowIfNull(sprite);
170 var frame = sprite.CurrentFrame;
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
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];
221 => ToSprites(definition, allowDuplicateNicknames:
false);
224 internal static List<Sprite> ToSprites(
SpriteDefinition definition,
bool allowDuplicateNicknames)
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>();
231 foreach (var entry
in definition.Sprites)
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);
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;
263 SpriteManager.Instance.DefinitionProvenance = definition.Sprites.All(sprite => sprite.Id != Guid.Empty &&
264 !
string.IsNullOrWhiteSpace(sprite.Nickname))
265 ? definition.LoadStamp?.Materialized(definition, () => FromSprites(created))
271 foreach (var sprite
in created)
273 SpriteManager.Instance._spriteList.Remove(sprite);
274 sprite.DisposeImmediate();
280 private static Frame ResolveFrame(SpriteFrameDefinition definition)
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);
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.
@ SpriteDefinition
Gondwana sprite collection definition (.gspr).
@ Sprites
Represents the sprites state, including sprite instances, positions, properties, and sprite-related g...