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
CompositeSprite.cs
1using System.Collections.ObjectModel;
2using System.Drawing;
3using System.Numerics;
4using System.Runtime.Serialization;
7using Newtonsoft.Json;
8
10
16{
17 [JsonProperty]
18 private readonly List<Sprite> _children = new();
19
23 [JsonProperty]
24 public CompositeAnchorMode AnchorMode { get; set; } = CompositeAnchorMode.TopLeft;
25
26 #region ctor
27
31 public CompositeSprite() { }
32
37 public CompositeSprite(List<Sprite> sprites)
38 {
39 foreach (var sprite in sprites)
40 Add(sprite);
41 }
42
47 public CompositeSprite(params Sprite[] sprites) : this(sprites.ToList())
48 {
49 }
50
51 [OnDeserialized]
52 private void OnDeserialized(StreamingContext context)
53 {
54 foreach (var sprite in _children)
55 sprite.Disposing += sprite_Disposing;
56 }
57
58 #endregion ctor
59
60 #region IMovable / IMovableOnSceneLayer
61
65 [JsonIgnore]
67
72 [JsonIgnore]
74 {
75 get
76 {
77 if (_children.Count == 0)
78 throw new InvalidOperationException("CompositeSprite has no children; SceneLayer is undefined.");
79
80 // Enforced by Add(...) invariants.
81 return _children[0].SceneLayer;
82 }
83 }
84
90 public Vector2 GetPosition()
91 {
92 Rectangle r = Range;
93 if (r == Rectangle.Empty)
94 return Vector2.Zero;
95
96 PointF anchorWorldPx = AnchorMode switch
97 {
98 CompositeAnchorMode.TopLeft => new PointF(r.Left, r.Top),
99 CompositeAnchorMode.Center => new PointF(
100 r.Left + (r.Width / 2f),
101 r.Top + (r.Height / 2f)),
102 _ => new PointF(r.Left, r.Top)
103 };
104
105 PointF anchorGrid = SceneLayer.WorldPxToGrid(anchorWorldPx);
106 return new Vector2(anchorGrid.X, anchorGrid.Y);
107 }
108
113 public void SetPosition(Vector2 pos)
114 {
115 Vector2 oldAnchor = GetPosition();
116 Vector2 delta = pos - oldAnchor;
117
118 if (delta == Vector2.Zero || _children.Count == 0)
119 return;
120
121 // Translate all children by delta based on their CURRENT positions.
122 // This preserves independent child movement naturally.
123 foreach (var sprite in _children)
124 {
125 Vector2 childPos = GetSpritePosition(sprite);
126 sprite.SetPosition(childPos + delta);
127 }
128 }
129
134 public void Translate(Vector2 delta)
135 {
136 if (delta == Vector2.Zero)
137 return;
138
139 SetPosition(GetPosition() + delta);
140 }
141
142 #endregion IMovable / IMovableOnSceneLayer
143
144 #region methods
145
152 public void Add(Sprite sprite)
153 {
154 if (sprite is null) throw new ArgumentNullException(nameof(sprite));
155 if (_children.Contains(sprite)) return;
156
157 EnforceSameSceneLayer(sprite);
158
159 sprite.Disposing += sprite_Disposing;
160 _children.Add(sprite);
161 }
162
170 public void AddChildAtPosition(Sprite sprite, Vector2 absolutePosition)
171 {
172 if (sprite is null) throw new ArgumentNullException(nameof(sprite));
173 if (_children.Contains(sprite)) return;
174
175 EnforceSameSceneLayer(sprite);
176
177 sprite.Disposing += sprite_Disposing;
178 _children.Add(sprite);
179
180 sprite.SetPosition(absolutePosition);
181 }
182
191 public void AddChildWithOffset(Sprite sprite, Vector2 offsetFromCompositeAnchor)
192 {
193 if (sprite is null) throw new ArgumentNullException(nameof(sprite));
194 if (_children.Contains(sprite)) return;
195
196 EnforceSameSceneLayer(sprite);
197
198 // If this is the first child:
199 // - Its CURRENT position becomes the anchor.
200 // - Offset is interpreted relative to that, so we only reposition if offset != 0.
201 if (_children.Count == 0)
202 {
203 sprite.Disposing += sprite_Disposing;
204 _children.Add(sprite);
205
206 Vector2 firstPos = GetSpritePosition(sprite);
207 Vector2 target = firstPos + offsetFromCompositeAnchor;
208
209 if (offsetFromCompositeAnchor != Vector2.Zero)
210 sprite.SetPosition(target);
211
212 return;
213 }
214
215 // Otherwise offset is from the current composite anchor (derived from Range).
216 Vector2 anchor = GetPosition();
217 AddChildAtPosition(sprite, anchor + offsetFromCompositeAnchor);
218 }
219
224 public void Remove(Sprite sprite)
225 {
226 if (sprite is null) return;
227 if (!_children.Contains(sprite)) return;
228
229 sprite.Disposing -= sprite_Disposing;
230 _children.Remove(sprite);
231 }
232
233 private void sprite_Disposing(Sprite sprite)
234 {
235 sprite.Disposing -= sprite_Disposing;
236 _children.Remove(sprite);
237 }
238
239 private void EnforceSameSceneLayer(Sprite sprite)
240 {
241 if (_children.Count == 0)
242 return;
243
244 if (!ReferenceEquals(sprite.SceneLayer, _children[0].SceneLayer))
245 throw new InvalidOperationException("All sprites in a CompositeSprite must belong to the same SceneLayer.");
246 }
247
248 private static Vector2 GetSpritePosition(Sprite sprite)
249 {
250 // Adjust this if your Sprite uses a different property name/type.
251 // Assumed: SceneLayerCoordinates is PointF.
252 PointF p = sprite.SceneLayerCoordinates;
253 return new Vector2(p.X, p.Y);
254 }
255
256 #endregion methods
257
258 #region properties
259
263 [JsonIgnore]
264 public ReadOnlyCollection<Sprite> Children => _children.AsReadOnly();
265
270 [JsonIgnore]
271 public Rectangle Range
272 {
273 get
274 {
275 if (_children.Count == 0)
276 return Rectangle.Empty;
277
278 int minX = int.MaxValue;
279 int minY = int.MaxValue;
280 int maxX = int.MinValue;
281 int maxY = int.MinValue;
282
283 foreach (var sprite in _children)
284 {
285 var r = sprite.DrawLocationWorld;
286
287 if (r.Left < minX) minX = r.Left;
288 if (r.Top < minY) minY = r.Top;
289 if (r.Right > maxX) maxX = r.Right;
290 if (r.Bottom > maxY) maxY = r.Bottom;
291 }
292
293 return Rectangle.FromLTRB(minX, minY, maxX, maxY);
294 }
295 }
296
297 #endregion properties
298}
299
304{
309
314}
Vector2 GetPosition()
Gets the current position of the composite sprite in scene-layer grid coordinates,...
void AddChildWithOffset(Sprite sprite, Vector2 offsetFromCompositeAnchor)
Add a sprite and place it by offset from the composite anchor. Offset is interpreted in grid coordina...
SceneLayer SceneLayer
Gets the scene layer that all child sprites belong to.
void Translate(Vector2 delta)
Translates the composite sprite by the specified delta vector.
void SetPosition(Vector2 pos)
Sets the position of the composite sprite by moving all child sprites to maintain their relative posi...
ReadOnlyCollection< Sprite > Children
Gets a read-only collection of all child sprites in this composite.
CompositeSprite(params Sprite[] sprites)
Initializes a new instance of the CompositeSprite class with the specified array of sprites.
CompositeSprite(List< Sprite > sprites)
Initializes a new instance of the CompositeSprite class with the specified list of sprites.
void AddChildAtPosition(Sprite sprite, Vector2 absolutePosition)
Add a sprite and place it at an absolute position (in the composite's PositionSpace).
Rectangle Range
Gets the bounding rectangle that encompasses all child sprites. Returns Rectangle....
void Remove(Sprite sprite)
Removes a sprite from this composite.
CompositeAnchorMode AnchorMode
Gets or sets the anchor mode that determines how the composite's position is calculated relative to i...
void Add(Sprite sprite)
Adds an existing sprite at its current position (no reposition).
MovementSpace PositionSpace
Gets the movement space used for positioning this composite sprite.
CompositeSprite()
Initializes a new instance of the CompositeSprite class.
Represents a movable visual element on a scene layer.
Definition Sprite.cs:20
void SetPosition(Vector2 pos)
Sets the sprite position in scene-layer grid coordinates and invalidates its old and new bounds.
Definition Sprite.cs:306
Implemented by grid-space IMovable objects that belong to a specific Scenes.SceneLayer.
CompositeAnchorMode
Defines the anchor mode for a composite sprite, determining how its position is calculated.
@ Center
The anchor is at the center of the bounding rectangle.
@ TopLeft
The anchor is at the top-left corner of the bounding rectangle.
MovementSpace
Specifies the coordinate system used for movement calculations and updates.