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
Tile.cs
1using System.Drawing;
2using System.Runtime.Serialization;
9using Newtonsoft.Json;
10
11namespace Gondwana.Drawing;
12
17[JsonObject(IsReference = true)]
18public abstract class Tile : IDrawable, ICollisionEntity, IComparable<Tile>, IDisposable
19{
20 #region static members
21
25 public static List<Tile> TilesAnimating { get; } = new();
26
27 #endregion static members
28
29 #region fields
30
34 protected internal int zOrder = 0;
38 protected internal bool visible;
42 protected internal Frame frame;
46 protected internal bool enableFog = false;
50 protected internal Animator? animator;
54 protected bool pauseAnimation;
58 protected ICollider? _collider;
59
60 private CollisionAdjust _adjustCollisionArea = CollisionAdjust.None;
61 private bool _adjustCollisionAreaByFrame;
62 private bool _hasAssignedFrame;
63 private bool _collisionAdjustExplicitlySet;
64 private TileCollisionType _collisionType = TileCollisionType.None;
65 private bool _collisionTypeByFrame;
66 private bool _collisionTypeExplicitlySet;
67 private bool _collisionsEnabled;
68 private string? _collisionProfileName;
69 private long _sortKeyRenderPassId;
70 private TileSortKey _sortKey;
71
72 #endregion fields
73
74 #region abstract properties
75
79 public abstract bool IsPositionFixed { get; }
83 public abstract Rectangle DrawLocationWorld { get; }
87 public abstract PointF SceneLayerCoordinates { get; }
91 public abstract SceneLayer SceneLayer { get; }
92
93 #endregion abstract properties
94
95 #region IDrawable members
96
100 [JsonProperty]
101 public Guid Id { get; internal set; } = Guid.NewGuid();
102
106 [JsonProperty]
107 public string? Nickname { get; set; }
108
112 [JsonProperty]
113 public virtual bool Visible
114 {
115 get => visible;
116 set
117 {
118 visible = value;
119 SceneLayer.RefreshQueue.AddWorldRect(DrawLocationWorld);
120 }
121 }
122
126 [JsonProperty]
127 public virtual int ZOrder
128 {
129 get => zOrder;
130 set
131 {
132 zOrder = value;
133 SceneLayer.RefreshQueue.AddWorldRect(DrawLocationWorld);
134 }
135 }
136
142 public virtual RectangleF GetDrawLocationScreen(View view) =>
143 view.WorldRectToScreenRect(SceneLayer, DrawLocationWorld);
144
150 public virtual RectangleF GetCollisionAreaScreen(View view) =>
151 view.WorldRectToScreenRect(SceneLayer, CollisionArea);
152
158 public virtual void Draw(BackbufferBase backbuffer, RectangleF destRectScreen) =>
159 backbuffer.DrawTileFrame(this, destRectScreen);
160
161 #endregion IDrawable members
162
166 [JsonIgnore]
167 public virtual Spacing Overhang => frame.Overhang;
168
174 [JsonProperty]
175 public virtual Frame CurrentFrame
176 {
177 get => frame;
178 set
179 {
180 // Animation may change tile size, so bitmap rendering must invalidate
181 // the old and new bounds. GPU rendering redraws the full frame and does
182 // not consume RefreshQueue, so avoid calculating those bounds entirely.
183 bool usesDirtyRegionRendering =
184 SceneLayer.Scene?.UsesDirtyRegionRendering ?? true;
185
186 if (usesDirtyRegionRendering)
187 SceneLayer.RefreshQueue.AddWorldRect(DrawLocationWorld);
188
189 frame = value;
190
191 bool hasFrame = value.Tilesheet is not null;
192 if (hasFrame &&
193 (_adjustCollisionAreaByFrame ||
194 (!_hasAssignedFrame && !_collisionAdjustExplicitlySet)))
195 {
196 SetCollisionAdjustFromFrame(value);
197 }
198
199 if (hasFrame &&
200 (_collisionTypeByFrame ||
201 (!_hasAssignedFrame && !_collisionTypeExplicitlySet)))
202 {
203 SetCollisionTypeFromFrame(value);
204 }
205
206 _hasAssignedFrame = hasFrame;
207
208 if (usesDirtyRegionRendering)
209 SceneLayer.RefreshQueue.AddWorldRect(DrawLocationWorld);
210 }
211 }
212
216 [JsonIgnore]
217 public virtual Animator TileAnimator => animator!;
218
222 [JsonIgnore]
223 public virtual bool PauseAnimation { get; set; }
224
228 [JsonIgnore]
229 public virtual ICollider? Collider => _collider;
230
234 [JsonIgnore]
235 public virtual Rectangle CollisionArea =>
237
241 [JsonProperty]
242 public virtual bool EnableFog
243 {
244 get => enableFog;
245 set
246 {
247 enableFog = value;
248 SceneLayer.RefreshQueue.AddWorldRect(DrawLocationWorld);
249 }
250 }
251
255 [JsonIgnore]
256 public virtual Point[] OutlinePointsWorld =>
257 SceneLayer.CoordinateSystem.GetPolygonPts(this, false);
258
267 [JsonProperty]
268 public virtual bool AdjustCollisionAreaByFrame
269 {
270 get => _adjustCollisionAreaByFrame;
271 set
272 {
273 _adjustCollisionAreaByFrame = value;
274
275 if (value && frame.Tilesheet is not null)
276 SetCollisionAdjustFromFrame(frame);
277 }
278 }
279
283 [JsonProperty]
285 {
286 get => _adjustCollisionArea;
287 set
288 {
289 _adjustCollisionArea = value;
290 _collisionAdjustExplicitlySet = true;
291 }
292 }
293
298 [JsonProperty]
300 {
301 get => _collisionType;
302 set
303 {
304 _collisionTypeExplicitlySet = true;
305 SetCollisionTypeCore(value);
306 }
307 }
308
313 [JsonProperty]
314 public virtual bool CollisionTypeByFrame
315 {
316 get => _collisionTypeByFrame;
317 set
318 {
319 _collisionTypeByFrame = value;
320
321 if (value && frame.Tilesheet is not null)
322 SetCollisionTypeFromFrame(frame);
323 }
324 }
325
329 [JsonProperty]
330 public string? CollisionProfileName
331 {
332 get => _collisionProfileName;
333 private set => _collisionProfileName = value;
334 }
335
339 [JsonProperty]
341 {
342 get => _collisionsEnabled;
343 set
344 {
345 _collisionTypeExplicitlySet = true;
346
347 if (value)
348 {
349 var enabledType = _collisionType == TileCollisionType.None
353 : _collisionType;
354
355 SetCollisionTypeCore(enabledType);
356 }
357 else
358 {
359 SetCollisionTypeCore(TileCollisionType.None);
360 }
361 }
362 }
363
367 [JsonIgnore]
368 public TypedValueBag ValueBag { get; } = new();
369
376 public void SetCollisionProfile(string profileName)
377 {
378 if (string.IsNullOrWhiteSpace(profileName))
379 throw new ArgumentException("Collision profile name cannot be empty.", nameof(profileName));
380
381 var scene = SceneLayer.Scene;
382 if (scene is not null)
383 {
384 var profile = scene.CollisionProfiles.Get(profileName);
385 _ = profile.ResolveCollisionGroup(scene.CollisionGroups);
386 _ = profile.ResolveCollidesWith(scene.CollisionGroups);
387 }
388
389 CollisionProfileName = profileName;
390 ApplyCollisionProfileToCollider();
391 }
392
397 internal void RefreshCollisionProfile() => ApplyCollisionProfileToCollider();
398
403 protected void CopyCollisionSettingsFrom(Tile source)
404 {
405 ArgumentNullException.ThrowIfNull(source);
406
407 _adjustCollisionArea = source._adjustCollisionArea;
408 _adjustCollisionAreaByFrame = source._adjustCollisionAreaByFrame;
409 _hasAssignedFrame = frame.Tilesheet is not null;
410 _collisionAdjustExplicitlySet = source._collisionAdjustExplicitlySet;
411 _collisionType = source._collisionType;
412 _collisionTypeByFrame = source._collisionTypeByFrame;
413 _collisionTypeExplicitlySet = source._collisionTypeExplicitlySet;
414 _collisionsEnabled = source._collisionsEnabled;
415 _collisionProfileName = source._collisionProfileName;
416 }
417
418 private void SetCollisionAdjustFromFrame(Frame value)
419 {
420 // Deliberately bypass the public setter: following a frame should not mark
421 // the adjustment as an explicit tile-level override.
422 _adjustCollisionArea = value.CollisionAdjust;
423 }
424
425 private void SetCollisionTypeFromFrame(Frame value)
426 {
427 // Deliberately bypass the public setter: following a frame should not mark
428 // the collision type as an explicit tile-level override.
429 SetCollisionTypeCore(value.CollisionType);
430 }
431
432 private void SetCollisionTypeCore(TileCollisionType collisionType)
433 {
434 _collisionType = collisionType;
435 ApplyCollisionTypeToCollider();
436 SetCollisionsEnabledCore(collisionType != TileCollisionType.None);
437 }
438
439 private void ApplyCollisionTypeToCollider()
440 {
441 if (_collider is null)
442 return;
443
444 switch (_collisionType)
445 {
446 case TileCollisionType.Blocking:
447 _collider.ResponseType = CollisionResponseType.Solid;
448 break;
449 case TileCollisionType.Trigger:
450 _collider.ResponseType = CollisionResponseType.Trigger;
451 break;
452 }
453 }
454
455 private void SetCollisionsEnabledCore(bool enabled)
456 {
457 _collisionsEnabled = enabled;
458 SynchronizeColliderRegistration();
459 }
460
461 private void SynchronizeColliderRegistration()
462 {
463 if (_collider is null)
464 return;
465
466 if (_collisionsEnabled)
467 SceneLayer.ColliderRegistry.Register(_collider);
468 else
469 SceneLayer.ColliderRegistry.Unregister(_collider);
470 }
471
472 private void ApplyCollisionProfileToCollider()
473 {
474 if (_collider is null || string.IsNullOrWhiteSpace(_collisionProfileName))
475 return;
476
477 var scene = SceneLayer.Scene;
478 if (scene is null)
479 return;
480
481 var profile = scene.CollisionProfiles.Get(_collisionProfileName);
482 _collider.CollisionGroup = profile.ResolveCollisionGroup(scene.CollisionGroups);
483 _collider.CollidesWith = profile.ResolveCollidesWith(scene.CollisionGroups);
484 }
485
490 protected void DetachCollider()
491 {
492 if (_collider is null)
493 return;
494
495 SceneLayer.ColliderRegistry.Unregister(_collider);
496 _collider = null;
497 }
498
504 protected void AttachCollider(ICollider collider)
505 {
506 ArgumentNullException.ThrowIfNull(collider);
507
508 if (_collider is not null)
509 SceneLayer.ColliderRegistry.Unregister(_collider);
510
511 _collider = collider;
512 ApplyCollisionProfileToCollider();
513 ApplyCollisionTypeToCollider();
514 SynchronizeColliderRegistration();
515 }
516
517 [OnDeserialized]
518 private void OnTileDeserialized(StreamingContext context)
519 {
520 _hasAssignedFrame = frame.Tilesheet is not null;
521
522 if (_adjustCollisionAreaByFrame && _hasAssignedFrame)
523 SetCollisionAdjustFromFrame(frame);
524
525 if (_collisionTypeByFrame && _hasAssignedFrame)
526 SetCollisionTypeFromFrame(frame);
527 }
528
529 #region IComparable<Tile> Members
530
536 public int CompareTo(Tile? tile)
537 {
538 if (tile is null)
539 return -1;
540
541 var thisKey = GetSortKeyForCompare();
542 var tileKey = tile.GetSortKeyForCompare();
543
544 if (thisKey.IsPositionFixed && !tileKey.IsPositionFixed)
545 return -1;
546
547 if (!thisKey.IsPositionFixed && tileKey.IsPositionFixed)
548 return 1;
549
550 return (thisKey.Location, thisKey.ZOrder, thisKey.SceneLayerX)
551 .CompareTo((tileKey.Location, tileKey.ZOrder, tileKey.SceneLayerX));
552 }
553
554 private TileSortKey GetSortKeyForCompare()
555 {
556 var renderContext = RenderContext.Current;
557
558 // Outside an active render pass preserve the historical live comparison
559 // semantics. During rendering, capture the expensive virtual/property values
560 // once per tile and reuse them for every comparison in this sort pass.
561 if (renderContext is null)
562 return CaptureSortKey();
563
564 long renderPassId = renderContext.PassId;
565 if (_sortKeyRenderPassId != renderPassId)
566 {
567 _sortKey = CaptureSortKey();
568 _sortKeyRenderPassId = renderPassId;
569 }
570
571 return _sortKey;
572 }
573
574 private TileSortKey CaptureSortKey()
575 {
576 bool isPositionFixed = IsPositionFixed;
577 Rectangle drawLocationWorld = DrawLocationWorld;
578 Spacing overhang = Overhang;
579 float location = isPositionFixed
580 ? drawLocationWorld.Top + overhang.Top
581 : drawLocationWorld.Bottom - overhang.Bottom - 1;
582
583 return new TileSortKey(
584 isPositionFixed,
585 location,
586 zOrder,
588 }
589
590 private readonly record struct TileSortKey(
591 bool IsPositionFixed,
592 float Location,
593 int ZOrder,
594 float SceneLayerX);
595
596 #endregion IComparable<Tile> Members
597
598 #region IDisposable Members
599
603 public virtual void Dispose()
604 {
605 if (TilesAnimating.IndexOf(this) != -1)
606 TilesAnimating.Remove(this);
607
608 animator?.Dispose();
609
610 if (_collider is not null)
611 SceneLayer.ColliderRegistry.Unregister(_collider);
612
613 _collider = null;
614 GC.SuppressFinalize(this);
615 }
616
617 #endregion IDisposable Members
618}
Manages animation playback for a Tile, controlling animation cycles, frame timing,...
Definition Animator.cs:9
Represents an abstract base class for drawable tiles in the Gondwana engine. Provides core functional...
Definition Tile.cs:19
bool CollisionsEnabled
Gets or sets whether this tile participates in collision detection.
Definition Tile.cs:341
virtual bool EnableFog
Gets or sets whether scene-layer fog applies to this tile.
Definition Tile.cs:243
virtual bool Visible
Gets or sets whether this drawable is visible.
Definition Tile.cs:114
virtual RectangleF GetCollisionAreaScreen(View view)
Transforms the collision bounds into screen pixels for the specified view.
virtual Frame CurrentFrame
Gets or sets the current frame. The initial frame supplies the tile's default collision adjustment an...
Definition Tile.cs:176
virtual RectangleF GetDrawLocationScreen(View view)
Transforms the drawable bounds into screen pixels for the specified view.
void AttachCollider(ICollider collider)
Attaches this tile's collider and applies any collision state that was set before the collider became...
Definition Tile.cs:504
virtual Rectangle CollisionArea
Gets the effective collision area in world coordinates.
Definition Tile.cs:235
SceneLayer SceneLayer
Gets the scene layer that owns this tile.
Definition Tile.cs:91
virtual void Draw(BackbufferBase backbuffer, RectangleF destRectScreen)
Draws the current content into the backbuffer at the supplied screen-pixel bounds.
virtual ? ICollider Collider
Gets the collision shape associated with this tile.
Definition Tile.cs:229
bool pauseAnimation
The pause animation.
Definition Tile.cs:54
void CopyCollisionSettingsFrom(Tile source)
Copies collision behavior, profile, and effective frame metadata from another tile.
Definition Tile.cs:403
string? Nickname
Gets or sets the optional lookup name.
Definition Tile.cs:107
virtual Point[] OutlinePointsWorld
Gets the tile outline vertices in world pixels.
Definition Tile.cs:256
virtual TileCollisionType CollisionType
Gets or sets the tile's collision behavior. This is initialized from the first assigned frame and may...
Definition Tile.cs:300
virtual void Dispose()
Releases the resources owned by this instance.
Definition Tile.cs:603
virtual bool AdjustCollisionAreaByFrame
Gets or sets whether frame changes replace the tile's collision adjustment with the newly selected fr...
Definition Tile.cs:269
virtual bool PauseAnimation
Gets or sets whether animation is paused.
Definition Tile.cs:223
virtual int ZOrder
Gets or sets the drawing order within the layer.
Definition Tile.cs:128
void SetCollisionProfile(string profileName)
Applies a named profile from this tile's parent scene. If the layer has not yet been attached to a sc...
Definition Tile.cs:376
TypedValueBag ValueBag
Gets the custom typed values associated with this object.
Definition Tile.cs:368
int CompareTo(Tile? tile)
Compares this tile with another tile using their render ordering.
Definition Tile.cs:536
Rectangle DrawLocationWorld
Gets the tile drawing bounds in world pixels.
Definition Tile.cs:83
virtual Spacing Overhang
Gets the current frame's visual overhang beyond its tile bounds.
Definition Tile.cs:167
void DetachCollider()
Detaches the current collider from its scene-layer registry without otherwise changing this tile's se...
Definition Tile.cs:490
string? CollisionProfileName
Gets the scene-level collision profile currently associated with this tile.
Definition Tile.cs:331
ICollider? _collider
The collider.
Definition Tile.cs:58
virtual Animator TileAnimator
Gets the animator associated with this tile.
Definition Tile.cs:217
static List< Tile > TilesAnimating
Gets the collection of all tiles that are currently animating in the scene.
Definition Tile.cs:25
virtual CollisionAdjust AdjustCollisionArea
Gets or sets the collision adjustment used to derive CollisionArea.
Definition Tile.cs:285
Guid Id
Gets the unique identifier.
Definition Tile.cs:101
virtual bool CollisionTypeByFrame
Gets or sets whether frame changes replace this tile's collision type with the newly selected frame's...
Definition Tile.cs:315
bool IsPositionFixed
Gets whether the tile occupies a fixed position in its layer.
Definition Tile.cs:79
PointF SceneLayerCoordinates
Gets the position in scene-layer grid coordinates.
Definition Tile.cs:87
Represents a base class for managing a graphical backbuffer, and is the in-memory surface where rende...
Represents a rendered view of a scene, combining a camera position with a viewport configuration to c...
Definition View.cs:14
A flexible, strongly-typed value container intended for runtime-attached metadata and ephemeral engin...
Represents an object that can be drawn on a visual surface, with properties for visibility and stacki...
Definition IDrawable.cs:14
World-space axis-aligned collider used by the collision system.
Definition ICollider.cs:7
Represents an entity that participates in the collision system and has a defined collision area.
record struct Spacing([property:JsonProperty("left")] int Left, [property:JsonProperty("top")] int Top, [property:JsonProperty("right")] int Right, [property:JsonProperty("bottom")] int Bottom)
Represents dimensions in pixels when spacing Tilesheets.
Definition Spacing.cs:12
CollisionResponseType
Defines how a collider responds to collisions with other colliders.
@ Trigger
Trigger collision response that reports overlaps without applying push-out or blocking movement.
TileCollisionType
Defines the default collision behavior associated with a tilesheet region, frame, or runtime tile.
@ Blocking
Collision is enabled and overlapping solid colliders block movement.
@ Trigger
Collision is enabled and overlaps are reported without blocking movement.
Represents the source Tilesheet and its coordinates to render on a destination.
Definition Frame.cs:12
CollisionAdjust CollisionAdjust
Gets or sets the collision adjustment associated with this frame's region coordinates.
Definition Frame.cs:99
TileCollisionType CollisionType
Gets or sets the effective collision type associated with this frame's region coordinates.
Definition Frame.cs:147
Per-edge inset amounts applied to visual bounds to produce a collision rectangle.
static readonly CollisionAdjust None
Represents an adjustment with no pixel offsets.