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
Scene.cs
1using System.Collections;
2using System.Collections.ObjectModel;
3using System.Drawing;
8using Newtonsoft.Json;
9
10namespace Gondwana.Scenes;
11
32[JsonObject(IsReference = true)]
33public class Scene : IEnumerable<SceneLayer>, IDisposable
34{
35 [Newtonsoft.Json.JsonIgnore]
36 internal DefinitionProvenance? DefinitionProvenance { get; set; }
37
38 [JsonProperty("SceneLayers")]
39 private readonly List<SceneLayer> _sceneLayers = [];
40
41 // Read legacy state files that used the private field name without
42 // continuing to emit that implementation detail in new JSON.
43#pragma warning disable IDE0051 // Used by Newtonsoft.Json via reflection for legacy state.
44 [JsonProperty("_sceneLayers")]
45 private List<SceneLayer>? LegacySceneLayers
46 {
47 set
48 {
49 if (value is null || _sceneLayers.Count != 0)
50 return;
51
52 foreach (var sceneLayer in value)
53 {
54 _sceneLayers.Add(sceneLayer);
55 OnSceneLayerAdded(sceneLayer);
56 }
57 }
58 }
59
60#pragma warning restore IDE0051
61
62 [JsonIgnore]
63 private readonly object _renderSurfaceHostSync = new();
64
65 [JsonIgnore]
66 private RenderSurfaceHostBase? _boundRenderSurfaceHost;
67
68 #region Scene events
69
78 public event Action<SceneLayer>? SceneLayerAdded;
79
88 public event Action<SceneLayer>? SceneLayerRemoved;
89
98 public event Action<Scene>? SceneDisposing;
99
100 #endregion Scene events
101
102 #region constructors / finalizer
103
112 public Scene()
113 {
114 _sceneLayers = new List<SceneLayer>();
115 Init();
116 }
117
125 [JsonConstructor]
126 protected Scene(List<SceneLayer>? sceneLayers,
127 string? id,
128 CollisionGroupRegistry? collisionGroups,
129 CollisionProfileRegistry? collisionProfiles)
130 {
131 _sceneLayers = sceneLayers ?? new List<SceneLayer>();
132 ID = string.IsNullOrWhiteSpace(id) ? Guid.NewGuid().ToString() : id;
133 CollisionGroups = collisionGroups ?? new CollisionGroupRegistry();
134 CollisionProfiles = collisionProfiles ?? new CollisionProfileRegistry();
135 ValueBag = new TypedValueBag();
136
137 Init();
138 }
139
149 ~Scene()
150 {
151 Dispose();
152 }
153
154 private void Init()
155 {
156 SetSceneLayerEventDelegates();
157
158 foreach (var sceneLayer in _sceneLayers)
159 OnSceneLayerAdded(sceneLayer);
160
161 // During static initialization the Empty property has not yet been assigned
162 // while the EmptyScene base constructor is running, so ReferenceEquals(this, Empty)
163 // is not a reliable discriminator here. Use the runtime type instead.
164 if (this is not EmptyScene)
165 _allScenes.Add(this);
166 }
167
168 #endregion constructors / finalizer
169
170 #region public properties
171
183 [JsonIgnore]
184 public TypedValueBag ValueBag { get; private set; } = new();
185
195 [JsonProperty]
196 public string ID { get; protected internal set; } = Guid.NewGuid().ToString();
197
206 [JsonIgnore]
207 public int Count => _sceneLayers?.Count ?? 0;
208
216 [JsonIgnore]
217 internal RenderSurfaceHostBase? BoundRenderSurfaceHost
218 {
219 get
220 {
221 lock (_renderSurfaceHostSync)
222 return _boundRenderSurfaceHost;
223 }
224 }
225
230 [JsonIgnore]
231 internal bool UsesDirtyRegionRendering => BoundRenderSurfaceHost?.Backbuffer.IsGlThreadRendered != true;
232
251 [JsonIgnore]
252 public bool FullRefreshNeeded { get; set; }
253
266 [JsonIgnore]
267 public ReadOnlyCollection<SceneLayer> SceneLayers => _sceneLayers.AsReadOnly();
268
269 private ReadOnlyCollection<SceneLayer>? _visibleSortedCache;
270 private bool _visibleSortedDirty = true;
271
291 [JsonIgnore]
292 public ReadOnlyCollection<SceneLayer> VisibleSceneLayers
293 {
294 get
295 {
296 if (_visibleSortedDirty || _visibleSortedCache == null)
297 {
298 _visibleSortedCache = _sceneLayers
299 .Where(sl => sl.Visible)
300 .OrderBy(sl => sl.ZOrder)
301 .ToList()
302 .AsReadOnly();
303 _visibleSortedDirty = false;
304 }
305 return _visibleSortedCache;
306 }
307 }
308
318 [JsonIgnore]
319 public int CountOfVisibleLayers => VisibleSceneLayers?.Count ?? 0;
320
327 [JsonProperty]
328 public CollisionGroupRegistry CollisionGroups { get; private set; } = new();
329
334 [JsonProperty]
335 public CollisionProfileRegistry CollisionProfiles { get; private set; } = new();
336
337 #endregion public properties
338
339 #region internal properties and methods
340
348 internal void BindRenderSurfaceHost(RenderSurfaceHostBase host)
349 {
350 ArgumentNullException.ThrowIfNull(host);
351
352 // Scene.Empty is a shared null-object placeholder and is never treated as an
353 // actively rendered scene for binding ownership purposes.
354 if (ReferenceEquals(this, Empty))
355 return;
356
357 lock (_renderSurfaceHostSync)
358 {
359 if (_boundRenderSurfaceHost is not null &&
360 !ReferenceEquals(_boundRenderSurfaceHost, host))
361 {
362 throw new InvalidOperationException(
363 $"Scene '{ID}' is already bound to another RenderSurfaceHost.");
364 }
365
366 _boundRenderSurfaceHost = host;
367 }
368
369 // A GPU host redraws the full frame and never consumes RefreshQueue. Remove
370 // anything accumulated before binding; future additions are rejected by the
371 // queue's scene-policy callback.
372 if (!UsesDirtyRegionRendering)
373 {
374 foreach (var layer in _sceneLayers)
375 layer.RefreshQueue.ClearRefreshQueue();
376 }
377 }
378
384 internal void UnbindRenderSurfaceHost(RenderSurfaceHostBase host)
385 {
386 ArgumentNullException.ThrowIfNull(host);
387
388 if (ReferenceEquals(this, Empty))
389 return;
390
391 lock (_renderSurfaceHostSync)
392 {
393 if (ReferenceEquals(_boundRenderSurfaceHost, host))
394 _boundRenderSurfaceHost = null;
395 }
396 }
397
410 internal bool IsDirty
411 {
412 get
413 {
414 for (int i = 0; i < CountOfVisibleLayers; i++)
415 {
416 if (VisibleSceneLayers[i].RefreshQueue.IsDirty)
417 return true;
418 }
419
420 return false;
421 }
422 }
423
424 #endregion internal properties and methods
425
426 #region public methods
427
466 public SceneLayer AddLayer(int columnCount,
467 int rowCount,
468 int width = 32,
469 int height = 32,
470 int zOrder = 0,
471 float parallax = 1f,
472 CoordinateSystemTypes coordinateSystem = CoordinateSystemTypes.Orthogonal)
473 {
474 var sceneLayer = new SceneLayer(columnCount, rowCount, width, height, parallax, coordinateSystem);
475 sceneLayer.ZOrder = zOrder;
476
477 _sceneLayers.Add(sceneLayer);
478 OnSceneLayerAdded(sceneLayer);
479
480 FullRefreshNeeded = true;
481
482 return sceneLayer;
483 }
484
496 public SceneLayer AddLayer(SceneLayer sceneLayer)
497 {
498 _sceneLayers.Add(sceneLayer);
499 OnSceneLayerAdded(sceneLayer);
500 FullRefreshNeeded = true;
501 return sceneLayer;
502 }
503
518 public void RemoveAllLayers()
519 {
520 // raise "remove" event for each SceneLayer
521 foreach (SceneLayer sceneLayer in this)
522 OnSceneLayerRemoved(sceneLayer);
523
524 _sceneLayers.Clear();
525
526 FullRefreshNeeded = true;
527 }
528
543 public void RemoveLayer(SceneLayer sceneLayer)
544 {
545 _sceneLayers.Remove(sceneLayer);
546 OnSceneLayerRemoved(sceneLayer);
547
548 FullRefreshNeeded = true;
549 }
550
563 public SceneLayer? GetSceneLayerByID(string id)
564 {
565 foreach (var sceneLayer in _sceneLayers)
566 {
567 if (sceneLayer.ID == id)
568 return sceneLayer;
569 }
570
571 return null;
572 }
573
586 public RectangleF GetWorldBoundsPx()
587 {
588 if (_sceneLayers.Count == 0)
589 return RectangleF.Empty;
590
591 RectangleF result = RectangleF.Empty;
592 bool hasBounds = false;
593
594 foreach (var layer in _sceneLayers)
595 {
596 var lb = layer.GetLayerBoundsPx();
597 if (lb.IsEmpty)
598 continue;
599
600 if (!hasBounds)
601 {
602 result = lb;
603 hasBounds = true;
604 }
605 else
606 {
607 result = RectangleF.Union(result, lb);
608 }
609 }
610
611 return result;
612 }
613
614 #endregion public methods
615
616 #region raise Scene events
617
638 protected virtual void OnSceneLayerAdded(SceneLayer sceneLayer)
639 {
640 sceneLayer.Scene = this;
641 sceneLayer.ApplyDefaultTileCollisionProfile();
642 SpriteManager.Instance.RefreshCollisionProfiles(sceneLayer);
643
644 sceneLayer.Disposing += sceneLayerDisposing;
645 sceneLayer.VisibleChanged += visChgDel;
646 sceneLayer.SceneLayerTileSizeChanged += sceneLayerTileSizeDel;
647 sceneLayer.WrappingChanged += wrappingDel;
648 sceneLayer.ShowGridLinesChanged += gridLinesShowChanged;
649 sceneLayer.ShowCollisionBoxesChanged += showCollisionBoxesChanged;
650 sceneLayer.ZOrderChanged += zOrderChangedDel;
651 sceneLayer.ParallaxChanged += parallaxChangedDel;
652 sceneLayer.OriginPxChanged += originPxChangedDel;
653
654 _visibleSortedDirty = true;
655 SceneLayerAdded?.Invoke(sceneLayer);
656 }
657
678 protected virtual void OnSceneLayerRemoved(SceneLayer sceneLayer)
679 {
680 sceneLayer.Scene = null;
681
682 sceneLayer.Disposing -= sceneLayerDisposing;
683 sceneLayer.VisibleChanged -= visChgDel;
684 sceneLayer.SceneLayerTileSizeChanged -= sceneLayerTileSizeDel;
685 sceneLayer.WrappingChanged -= wrappingDel;
686 sceneLayer.ShowGridLinesChanged -= gridLinesShowChanged;
687 sceneLayer.ShowCollisionBoxesChanged -= showCollisionBoxesChanged;
688 sceneLayer.ZOrderChanged -= zOrderChangedDel;
689 sceneLayer.ParallaxChanged -= parallaxChangedDel;
690 sceneLayer.OriginPxChanged -= originPxChangedDel;
691
692 _visibleSortedDirty = true;
693 SceneLayerRemoved?.Invoke(sceneLayer);
694 }
695
704 protected virtual void OnSceneDisposing() => SceneDisposing?.Invoke(this);
705
706 #endregion raise handle Scene events
707
708 #region handle SceneLayer events
709
710 private Action<SceneLayer> sceneLayerDisposing;
711 private Action<SceneLayer> visChgDel;
712 private Action<SceneLayer> sceneLayerTileSizeDel;
713 private Action<SceneLayer> wrappingDel;
714 private Action<SceneLayer> gridLinesShowChanged;
715 private Action<SceneLayer> showCollisionBoxesChanged;
716 private Action<SceneLayer> zOrderChangedDel;
717 private Action<SceneLayer> parallaxChangedDel;
718 private Action<SceneLayer> originPxChangedDel;
719
720 private void SetSceneLayerEventDelegates()
721 {
722 sceneLayerDisposing = (sceneLayer) => RemoveLayer(sceneLayer);
723 visChgDel = (sceneLayer) => _SceneLayerVisibleChanged();
724 sceneLayerTileSizeDel = (sceneLayer) => _SceneLayerTileSizeChanged();
725 wrappingDel = (sceneLayer) => _SceneLayerWrappingChanged();
726 gridLinesShowChanged = (sceneLayer) => _SceneLayerGridLinesShowChanged();
727 showCollisionBoxesChanged = (sceneLayer) => _SceneLayerShowCollisionBoxChanged();
728 zOrderChangedDel = (sceneLayer) => _SceneLayerZOrderChanged();
729 parallaxChangedDel = (sceneLayer) => _SceneLayerParallaxChanged();
730 originPxChangedDel = (sceneLayer) => _SceneLayerZeroPixelChanged();
731 }
732
733 private void _SceneLayerVisibleChanged()
734 {
735 _visibleSortedDirty = true;
736 FullRefreshNeeded = true;
737 }
738
739 private void _SceneLayerTileSizeChanged() => FullRefreshNeeded = true;
740
741 private void _SceneLayerWrappingChanged() => FullRefreshNeeded = true;
742
743 private void _SceneLayerGridLinesShowChanged() => FullRefreshNeeded = true;
744
745 private void _SceneLayerShowCollisionBoxChanged() => FullRefreshNeeded = true;
746
747 private void _SceneLayerZOrderChanged() => FullRefreshNeeded = true;
748
749 private void _SceneLayerParallaxChanged() => FullRefreshNeeded = true;
750
751 private void _SceneLayerZeroPixelChanged() => FullRefreshNeeded = true;
752
753 #endregion handle SceneLayer events
754
755 #region indexers
756
769 public SceneLayer? this[int i] => (i >= 0 && i < _sceneLayers.Count) ? _sceneLayers[i] : null;
770
782 public SceneLayer? this[string id] => GetSceneLayerByID(id);
783
784 #endregion indexers
785
786 #region enumerable code
787
797 public IEnumerator GetEnumerator() => ((IEnumerable<SceneLayer>)this).GetEnumerator();
798
810 IEnumerator<SceneLayer> IEnumerable<SceneLayer>.GetEnumerator()
811 {
812 for (int i = 0; i < _sceneLayers.Count; i++)
813 {
814 yield return _sceneLayers[i];
815 }
816 }
817
818 #endregion enumerable code
819
820 #region IDisposable Members
821
845 public virtual void Dispose()
846 {
847 GC.SuppressFinalize(this);
848
850
852
853 _allScenes.Remove(this);
854
855 // cancel all subscriptions to this object
856 SceneLayerAdded = null;
857 SceneLayerRemoved = null;
858 SceneDisposing = null;
859 }
860
861 #endregion IDisposable Members
862
863 #region static helpers
864
865 internal readonly static List<Scene> _allScenes = [];
866
878 public static Scene? GetSceneByID(string id) => _allScenes.Find(s => s.ID == id);
879
888 public static List<string> GetAllSceneIDs() => _allScenes.FindAll(s => s != null).ConvertAll(s => s.ID);
889
902 public static ReadOnlyCollection<Scene> GetAllScenes() => _allScenes.AsReadOnly();
903
918 public static void ClearAllScenes()
919 {
920 var tmp = new List<Scene>(_allScenes);
921 foreach (var scene in tmp)
922 scene.Dispose();
923 }
924
925 #endregion static helpers
926
927 #region empty Scene
928
948 public static Scene Empty { get; } = new EmptyScene();
949
950 private sealed class EmptyScene : Scene
951 {
952 internal EmptyScene()
953 {
954 _sceneLayers.Clear();
955
956 // Attach the singleton empty layer
957 _sceneLayers.Add(SceneLayer.Empty);
958 SceneLayer.Empty.Scene = this;
959
960 FullRefreshNeeded = false;
961 }
962
967 protected override void OnSceneLayerAdded(SceneLayer sceneLayer)
968 => throw new InvalidOperationException("Cannot add layers to Scene.Empty");
969
974 protected override void OnSceneLayerRemoved(SceneLayer sceneLayer)
975 {
976 // no-op
977 }
978
982 public override void Dispose()
983 {
984 // Intentionally empty - singleton
985 }
986 }
987
988 #endregion empty Scene
989}
Manages the lifecycle, lookup, and per-frame updates of all active Sprite instances.
static SpriteManager Instance
Gets the singleton instance of the sprite manager.
Manages collision group definitions and provides bitwise mask values for collision filtering....
Stores named collision-filtering profiles for a scene. Profiles resolve their group names through the...
Represents a base class for hosting a render surface, providing functionality for managing rendering ...
BackbufferBase Backbuffer
Gets the in-memory BackbufferBase associated with the current rendering context.
Represents a single layer within a Scene, containing a grid of tiles and supporting various coordinat...
Definition SceneLayer.cs:40
string ID
Gets or sets the unique identifier for this layer.
static SceneLayer Empty
Gets the singleton empty scene layer instance.
Represents a game scene containing multiple layers of tiles, sprites, and game objects....
Definition Scene.cs:34
SceneLayer? GetSceneLayerByID(string id)
Retrieves a layer from this scene by its unique identifier.
Definition Scene.cs:563
virtual void OnSceneLayerAdded(SceneLayer sceneLayer)
Raises the SceneLayerAdded event and performs internal setup when a layer is added to the scene.
Definition Scene.cs:638
Scene()
Initializes a new instance of the Scene class with an empty layer collection.
Definition Scene.cs:112
Action< SceneLayer >? SceneLayerRemoved
Occurs when a SceneLayer is removed from this scene.
Definition Scene.cs:88
SceneLayer AddLayer(SceneLayer sceneLayer)
Adds an existing SceneLayer to this scene.
Definition Scene.cs:496
CollisionGroupRegistry CollisionGroups
Gets the registry of collision groups used to organize and manage collision detection within the scen...
Definition Scene.cs:328
static ? Scene GetSceneByID(string id)
Retrieves a scene from the global scene collection by its unique identifier.
TypedValueBag ValueBag
Gets the extensible value bag for storing arbitrary scene-specific metadata.
Definition Scene.cs:184
bool FullRefreshNeeded
Gets or sets a value indicating whether the entire scene needs to be refreshed on the next render.
Definition Scene.cs:252
static List< string > GetAllSceneIDs()
Gets a list of unique identifiers for all active scenes in the global scene collection.
Action< SceneLayer >? SceneLayerAdded
Occurs when a SceneLayer is added to this scene.
Definition Scene.cs:78
ReadOnlyCollection< SceneLayer > SceneLayers
Gets a read-only collection of all layers in this scene, in their current order.
Definition Scene.cs:267
ReadOnlyCollection< SceneLayer > VisibleSceneLayers
Gets a read-only collection of visible layers sorted by z-order for rendering.
Definition Scene.cs:293
virtual void OnSceneLayerRemoved(SceneLayer sceneLayer)
Raises the SceneLayerRemoved event and performs internal cleanup when a layer is removed from the sce...
Definition Scene.cs:678
static void ClearAllScenes()
Disposes all active scenes in the global scene collection, releasing all resources.
Definition Scene.cs:918
string ID
Gets or sets the unique identifier for this scene.
Definition Scene.cs:196
virtual void Dispose()
Releases all resources used by the Scene and removes it from the global scene collection.
Definition Scene.cs:845
static ReadOnlyCollection< Scene > GetAllScenes()
Gets a read-only collection of all active scenes in the global scene collection.
int CountOfVisibleLayers
Gets the number of currently visible layers in this scene.
Definition Scene.cs:319
static Scene Empty
Gets the singleton empty scene instance.
Definition Scene.cs:948
virtual void OnSceneDisposing()
Raises the SceneDisposing event when the scene is being disposed.
Scene(List< SceneLayer >? sceneLayers, string? id, CollisionGroupRegistry? collisionGroups, CollisionProfileRegistry? collisionProfiles)
Initializes a new instance of the Scene class from serialized state.
Definition Scene.cs:126
CollisionProfileRegistry CollisionProfiles
Gets the named collision-filtering profiles used by layers and sprites in this scene....
Definition Scene.cs:335
SceneLayer AddLayer(int columnCount, int rowCount, int width=32, int height=32, int zOrder=0, float parallax=1f, CoordinateSystemTypes coordinateSystem=CoordinateSystemTypes.Orthogonal)
Creates and adds a new SceneLayer to this scene with the specified properties.
Definition Scene.cs:466
void RemoveLayer(SceneLayer sceneLayer)
Removes the specified layer from this scene.
Definition Scene.cs:543
Action< Scene >? SceneDisposing
Occurs when this scene is being disposed.
Definition Scene.cs:98
IEnumerator GetEnumerator()
Returns an enumerator that iterates through the scene's layers.
RectangleF GetWorldBoundsPx()
Computes a world-space pixel bounding rectangle that encloses all layers in the scene.
Definition Scene.cs:586
void RemoveAllLayers()
Removes all layers from this scene.
Definition Scene.cs:518
int Count
Gets the total number of layers in this scene, including hidden layers.
Definition Scene.cs:207
A flexible, strongly-typed value container intended for runtime-attached metadata and ephemeral engin...
CoordinateSystemTypes
Identifies the layout and math rules used to map tiles to world pixels and world pixels back to grid ...