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
DirectDrawingBase.cs
1using System.Drawing;
8using SkiaSharp;
9
11
38public abstract class DirectDrawingBase : IDirectDrawable, IComparable<DirectDrawingBase>
39{
47 public event EventHandler<IDirectDrawable>? Disposing;
48
57 public event EventHandler<DirectDrawingBase>? FadeToCompleted;
58
63
67 protected Rectangle _screenBounds;
68
72 protected Rectangle _worldBounds;
73
77 protected int _zOrder;
78
82 protected bool _visible;
83
87 protected long _lastTick = HighResTimer.GetCurrentTick();
88
89 private bool _disposed = false;
90
91 // Fade/opacity state
92 private float _opacity = 1f; // 0..1
93 private float _fadeFrom, _fadeTo;
94 private float _fadeDurationSec, _fadeElapsedSec;
95 private bool _isFading;
96
110 public bool HideWhenFullyTransparent { get; set; } = true;
111
112 // Reveal state
113 private float _revealT = 1f; // 0 = hidden, 1 = fully shown
114 private RevealDirection _revealDir = RevealDirection.LeftToRight;
115
116 // optional tween state
117 private bool _revealAnimating;
118 private float _revealElapsedSec, _revealDurationSec;
119 private Func<float, float>? _revealEasing;
120 private float _revealStart = 1f, _revealTarget = 1f;
121
147 protected abstract void OnDraw(BackbufferBase backbuffer, RectangleF destRectScreen);
148
173 protected DirectDrawingBase(RenderSurfaceHostBase renderSurfaceHost,
175 SceneLayer? sceneLayer,
176 View? view,
177 Rectangle? screenBounds,
178 Rectangle? worldBounds,
179 string? nickname = null)
180 {
181 if (renderSurfaceHost is null)
182 throw new ArgumentNullException(nameof(renderSurfaceHost));
183
184 if (mode == DirectDrawingMode.SceneLayer && sceneLayer is null)
185 throw new ArgumentException("SceneLayer cannot be null when using DirectDrawingMode.SceneLayer", nameof(sceneLayer));
186
187 if (mode == DirectDrawingMode.View && view is null)
188 throw new ArgumentException("View cannot be null when using DirectDrawingMode.View", nameof(view));
189
190 if (mode == DirectDrawingMode.SceneLayer && worldBounds is null)
191 throw new ArgumentException("worldBounds cannot be null when using DirectDrawingMode.SceneLayer", nameof(worldBounds));
192
193 if (mode == DirectDrawingMode.View && screenBounds is null)
194 throw new ArgumentException("screenBounds cannot be null when using DirectDrawingMode.View", nameof(screenBounds));
195
196 _renderSurfaceHost = renderSurfaceHost;
197 _zOrder = 0;
198 _visible = true;
199 Mode = mode;
200 SceneLayer = sceneLayer;
201 View = view;
202 Nickname = nickname ?? Id.ToString();
203
204 if (mode == DirectDrawingMode.SceneLayer)
205 {
206 _worldBounds = worldBounds!.Value;
207 _screenBounds = Rectangle.Empty;
208 }
209 else // View
210 {
211 _worldBounds = Rectangle.Empty;
212 _screenBounds = screenBounds!.Value;
213 _screenBounds.Intersect(view!.Viewport.TargetRectPx);
214 }
215
216 DirectDrawingManager.Instance.AddOrReplace(this);
217 ForceRefresh();
218 }
219
220 ~DirectDrawingBase() => Dispose(false);
221
230
243 public DirectDrawingMode Mode { get; }
244
257 public SceneLayer? SceneLayer { get; private set; }
258
271 public View? View { get; private set; }
272
292 public Rectangle ScreenBounds
293 {
294 get => _screenBounds;
295 set
296 {
297 if (Mode != DirectDrawingMode.View)
298 return;
299
300 ForceRefresh();
301 _screenBounds = value;
302 ForceRefresh();
303 }
304 }
305
325 public Rectangle WorldBounds
326 {
327 get => _worldBounds;
328 set
329 {
330 if (Mode != DirectDrawingMode.SceneLayer)
331 return;
332
333 ForceRefresh();
334 _worldBounds = value;
335 ForceRefresh();
336 }
337 }
338
349 public TypedValueBag ValueBag { get; } = new();
350
351 #region IDrawable members
352
363 public Guid Id { get; } = Guid.NewGuid();
364
375 public string? Nickname { get; private set; }
376
389 public int ZOrder
390 {
391 get => _zOrder;
392 set
393 {
394 if (_zOrder != value)
395 {
396 _zOrder = value;
397 ForceRefresh();
398 }
399 }
400 }
401
419 public bool Visible
420 {
421 get => _visible;
422 set
423 {
424 if (_visible != value)
425 {
426 _visible = value;
427 ForceRefresh();
428 }
429 }
430 }
431
446 public RectangleF GetDrawLocationScreen(View view)
447 {
448 // View-mode drawings participate in View-level presentation transforms.
449 if (Mode == DirectDrawingMode.View)
450 {
451 PointF offset = view.GetEffectOffsetPx(layer: null);
452 return new RectangleF(
453 _screenBounds.Left + offset.X,
454 _screenBounds.Top + offset.Y,
455 _screenBounds.Width,
456 _screenBounds.Height);
457 }
458
459 // translate world bounds to screen via view transform
461 }
462
489 public void Draw(BackbufferBase backbuffer, RectangleF destRectScreen)
490 {
491 if (!Visible)
492 return;
493
494 var canvas = backbuffer.Canvas;
495
496 // Compute reveal clip rect (screen pixel space) from bounds
497 bool useClip = _revealT < 0.999f;
498
499 if (useClip)
500 {
501 var r = new SKRect(destRectScreen.Left, destRectScreen.Top, destRectScreen.Right, destRectScreen.Bottom);
502
503 SKRect clipRect = _revealDir switch
504 {
505 RevealDirection.LeftToRight => new SKRect(r.Left, r.Top, r.Left + r.Width * _revealT, r.Bottom),
506 RevealDirection.RightToLeft => new SKRect(r.Right - r.Width * _revealT, r.Top, r.Right, r.Bottom),
507 RevealDirection.TopToBottom => new SKRect(r.Left, r.Top, r.Right, r.Top + r.Height * _revealT),
508 RevealDirection.BottomToTop => new SKRect(r.Left, r.Bottom - r.Height * _revealT, r.Right, r.Bottom),
509 _ => r
510 };
511
512 // Early-out if reveal window is empty
513 if (clipRect.Width <= 0f || clipRect.Height <= 0f)
514 return;
515
516 // Outer save owns the clip lifetime
517 canvas.Save();
518
519 // Capture current matrix
520 var m = canvas.TotalMatrix;
521
522 canvas.ResetMatrix();
523 canvas.ClipRect(clipRect, SKClipOperation.Intersect, antialias: false);
524
525 // Put the prior matrix back so Draw() sees the same transform state
526 canvas.SetMatrix(m);
527 }
528
529 if (_opacity >= 0.999f)
530 {
531 OnDraw(backbuffer, destRectScreen);
532 }
533 else
534 {
535 using var layerPaint = new SKPaint
536 {
537 Color = new SKColor(255, 255, 255, (byte)(_opacity * 255))
538 };
539
540 canvas.SaveLayer(destRectScreen.ToSKRect(), layerPaint);
541 OnDraw(backbuffer, destRectScreen);
542 canvas.Restore(); // end SaveLayer
543 }
544
545 if (useClip)
546 canvas.Restore(); // end outer clip Save
547 }
548
549 #endregion IDrawable members
550
574 public float Opacity
575 {
576 get => _opacity;
577 set
578 {
579 var clamped = Math.Clamp(value, 0f, 1f);
580 if (Math.Abs(clamped - _opacity) < 0.0001f)
581 return;
582
583 _opacity = clamped;
584
585 if (HideWhenFullyTransparent && _opacity <= 0f)
586 Visible = false;
587 else if (_opacity > 0f)
588 Visible = true;
589
590 ForceRefresh();
591 }
592 }
593
603 public DirectDrawingBase SetOpacity(float opacity)
604 {
605 Opacity = opacity;
606 return this;
607 }
608
631 public DirectDrawingBase FadeTo(float targetOpacity, float durationSec)
632 {
633 _fadeFrom = _opacity;
634 _fadeTo = Math.Clamp(targetOpacity, 0f, 1f);
635 _fadeDurationSec = Math.Max(0.0001f, durationSec);
636 _fadeElapsedSec = 0f;
637 _isFading = true;
638
639 if (_fadeTo > 0f)
640 Visible = true; // ensure we draw during fade-in
641
642 ForceRefresh();
643
644 return this;
645 }
646
657 public DirectDrawingBase FadeIn(float durationSec)
658 {
659 if (_opacity <= 0f)
660 Opacity = 0f;
661
662 return FadeTo(1f, durationSec);
663 }
664
681 public DirectDrawingBase FadeOut(float durationSec)
682 {
683 return FadeTo(0f, durationSec);
684 }
685
696 {
697 _isFading = false;
698 return this;
699 }
700
717 public DirectDrawingBase SetReveal(float t01)
718 {
719 _revealT = Math.Clamp(t01, 0f, 1f);
720 ForceRefresh();
721 return this;
722 }
723
735 {
736 _revealDir = dir;
737 ForceRefresh();
738 return this;
739 }
740
762 public DirectDrawingBase RevealTo(float t01, float durationSec, Func<float, float>? easing = null)
763 {
764 _revealAnimating = true;
765 _revealElapsedSec = 0f;
766 _revealDurationSec = Math.Max(0.0001f, durationSec);
767 _revealEasing = easing;
768 // target is t01; we'll lerp in Update
769 _revealTarget = Math.Clamp(t01, 0f, 1f);
770 _revealStart = _revealT;
771 return this;
772 }
773
777 {
778 _revealAnimating = false;
779 return this;
780 }
781
801 protected internal void ForceRefresh()
802 {
803 switch (Mode)
804 {
805 case DirectDrawingMode.SceneLayer:
806 // bounds is WORLD-space
807 // there will only be one SceneLayer per DirectDrawing in this mode
808 SceneLayer!.RefreshQueue.AddWorldRect(_worldBounds);
809 break;
810
811 case DirectDrawingMode.View:
812 // bounds is SCREEN-space
813 // need to cycle through all SceneLayers for the View to which this DirectDrawing belongs
814 foreach (var sceneLayer in RenderSurfaceHost.Scene.SceneLayers)
815 {
816 sceneLayer.RefreshQueue.AddViewScreenRect(View!, sceneLayer, _screenBounds);
817 }
818 break;
819
820 default:
821 break;
822 }
823 }
824
846 public virtual void Update(long tick)
847 {
848 if (tick <= _lastTick)
849 return;
850
851 // Advance fade tween
852 if (_isFading)
853 {
854 float dt = HighResTimer.GetDuration(_lastTick, tick);
855
856 _fadeElapsedSec += dt;
857 float timeElapsed = Math.Clamp(_fadeElapsedSec / _fadeDurationSec, 0f, 1f);
858 // linear; swap in easing if you like
859 _opacity = _fadeFrom + (_fadeTo - _fadeFrom) * timeElapsed;
860
862 Visible = _opacity > 0f; // hides when hit zero
863
864 ForceRefresh();
865
866 if (timeElapsed >= 1f)
867 {
868 _isFading = false;
869 FadeToCompleted?.Invoke(this, this);
870 }
871 }
872
873 // Advance reveal tween
874 if (_revealAnimating)
875 {
876 float dt = HighResTimer.GetDuration(_lastTick, tick);
877 _revealElapsedSec = Math.Min(_revealElapsedSec + dt, _revealDurationSec);
878
879 float u = _revealElapsedSec / _revealDurationSec;
880 _revealT = (_revealEasing is null ? u : _revealEasing(u));
881 _revealT = _revealStart + (_revealTarget - _revealStart) * _revealT;
882
883 ForceRefresh();
884
885 if (_revealElapsedSec >= _revealDurationSec)
886 _revealAnimating = false;
887 }
888
889 _lastTick = tick;
890 }
891
905 public int CompareTo(DirectDrawingBase? other) => _zOrder.CompareTo(other?._zOrder ?? 0);
906
907 #region IDisposable members
908
922 public void Dispose()
923 {
924 lock (RenderStateSynchronization.SyncRoot)
925 {
926 Dispose(true);
927 GC.SuppressFinalize(this);
928 }
929 }
930
952 protected virtual void Dispose(bool disposing)
953 {
954 if (_disposed)
955 return;
956
957 if (disposing)
958 {
959 ForceRefresh();
960 Disposing?.Invoke(this, this);
961 Disposing = null;
962 }
963
964 _disposed = true;
965 }
966
967 #endregion IDisposable members
968
969 #region Equality & Operators
970
983 public override bool Equals(object? obj) => ReferenceEquals(this, obj);
984
994 public override int GetHashCode() => HashCode.Combine(Nickname);
995
1005 public static bool operator ==(DirectDrawingBase? left, DirectDrawingBase? right) =>
1006 ReferenceEquals(left, null) ? ReferenceEquals(right, null) : left.Equals(right);
1007
1017 public static bool operator !=(DirectDrawingBase? left, DirectDrawingBase? right) => !(left == right);
1018
1028 public static bool operator <(DirectDrawingBase? left, DirectDrawingBase? right) =>
1029 ReferenceEquals(left, null) ? !ReferenceEquals(right, null) : left.CompareTo(right) < 0;
1030
1040 public static bool operator <=(DirectDrawingBase? left, DirectDrawingBase? right) =>
1041 ReferenceEquals(left, null) || left.CompareTo(right) <= 0;
1042
1052 public static bool operator >(DirectDrawingBase? left, DirectDrawingBase? right) =>
1053 !ReferenceEquals(left, null) && left.CompareTo(right) > 0;
1054
1064 public static bool operator >=(DirectDrawingBase? left, DirectDrawingBase? right) =>
1065 ReferenceEquals(left, null) ? ReferenceEquals(right, null) : left.CompareTo(right) >= 0;
1066
1067 #endregion Equality & Operators
1068
1099}
Provides the base implementation for direct drawing objects that can be rendered to the backbuffer wi...
void OnDraw(BackbufferBase backbuffer, RectangleF destRectScreen)
Performs the concrete drawing for this direct drawing to the backbuffer.
DirectDrawingBase SetRevealDirection(RevealDirection dir)
Sets the direction from which the reveal animation progresses.
DirectDrawingBase CancelReveal()
Stops an active reveal animation at its current progress.
int ZOrder
Gets or sets the Z-order (depth) used for sorting this direct drawing relative to other drawables.
static bool operator>(DirectDrawingBase? left, DirectDrawingBase? right)
Determines whether the Z-order of the left direct drawing is greater than the right.
DirectDrawingBase RevealTo(float t01, float durationSec, Func< float, float >? easing=null)
Initiates a smooth reveal animation to the specified target progress over the given duration,...
int _zOrder
The Z-order used for draw sorting. Higher values draw later (on top).
static bool operator==(DirectDrawingBase? left, DirectDrawingBase? right)
Determines whether two direct drawing instances are equal using reference equality.
DirectDrawingBase FadeOut(float durationSec)
Fades this direct drawing from its current opacity to fully transparent over the specified duration.
void Dispose()
Releases all resources used by this DirectDrawingBase instance.
bool HideWhenFullyTransparent
Gets or sets a value indicating whether this direct drawing should be automatically hidden when its o...
override int GetHashCode()
Returns a hash code for this direct drawing.
View? View
Gets the view to which this direct drawing is attached, or null if the drawing is in scene-layer mode...
long _lastTick
The last engine tick value used for delta-time calculations in Update.
TypedValueBag ValueBag
Gets the extensible value bag for storing arbitrary key-value data associated with this direct drawin...
bool _visible
Indicates whether this direct drawing is currently visible and should be rendered.
virtual void Update(long tick)
Performs per-frame update logic for this direct drawing, including fade and reveal animations.
int CompareTo(DirectDrawingBase? other)
Compares this direct drawing to another for sorting by ZOrder.
bool Visible
Gets or sets a value indicating whether this direct drawing is visible and should be rendered.
RectangleF GetDrawLocationScreen(View view)
Gets the screen-space drawing location for this direct drawing as seen from the specified view.
float Opacity
Gets or sets the current opacity of this direct drawing.
static bool operator>=(DirectDrawingBase? left, DirectDrawingBase? right)
Determines whether the Z-order of the left direct drawing is greater than or equal to the right.
static bool operator!=(DirectDrawingBase? left, DirectDrawingBase? right)
Determines whether two direct drawing instances are not equal using reference equality.
DirectDrawingBase FadeTo(float targetOpacity, float durationSec)
Initiates a smooth fade transition to the specified target opacity over the given duration.
static bool operator<(DirectDrawingBase? left, DirectDrawingBase? right)
Determines whether the Z-order of the left direct drawing is less than the right.
readonly RenderSurfaceHostBase _renderSurfaceHost
The render surface host that manages this direct drawing's rendering pipeline.
string? Nickname
Gets the optional human-readable name for this direct drawing.
EventHandler< IDirectDrawable >? Disposing
Occurs when this direct drawing is being disposed.
DirectDrawingBase SetReveal(float t01)
Instantly sets the reveal progress to the specified value without animation.
Guid Id
Gets the unique identifier for this direct drawing.
RenderSurfaceHostBase RenderSurfaceHost
Gets the render surface host that manages this direct drawing's rendering pipeline.
override bool Equals(object? obj)
Determines whether the specified object is equal to the current direct drawing.
DirectDrawingBase FadeIn(float durationSec)
Fades this direct drawing from its current opacity to fully opaque over the specified duration.
Rectangle _worldBounds
The current world-space bounds for scene-layer-mode direct drawings, in pixels.
SceneLayer? SceneLayer
Gets the scene layer to which this direct drawing is attached, or null if the drawing is in view mode...
static bool operator<=(DirectDrawingBase? left, DirectDrawingBase? right)
Determines whether the Z-order of the left direct drawing is less than or equal to the right.
Rectangle _screenBounds
The current screen-space bounds for view-mode direct drawings, in pixels.
DirectDrawingBase CancelFade()
Cancels any active fade transition, leaving the opacity at its current value.
Rectangle WorldBounds
Gets or sets the world-space bounds of this direct drawing in pixels.
DirectDrawingMode Mode
Gets the drawing mode that determines how this direct drawing is positioned and transformed.
void Draw(BackbufferBase backbuffer, RectangleF destRectScreen)
Renders this direct drawing to the backbuffer at the specified screen location.
virtual void Dispose(bool disposing)
Releases resources used by this DirectDrawingBase instance.
RevealDirection
Defines the direction from which a reveal animation progressively displays the direct drawing.
@ BottomToTop
Reveals the drawing from the bottom edge toward the top edge.
@ RightToLeft
Reveals the drawing from the right edge toward the left edge.
@ LeftToRight
Reveals the drawing from the left edge toward the right edge.
@ TopToBottom
Reveals the drawing from the top edge toward the bottom edge.
EventHandler< DirectDrawingBase >? FadeToCompleted
Occurs when a fade transition initiated by FadeTo, FadeIn, or FadeOut completes.
DirectDrawingBase SetOpacity(float opacity)
Instantly sets the opacity to the specified value without animation.
DirectDrawingBase(RenderSurfaceHostBase renderSurfaceHost, DirectDrawingMode mode, SceneLayer? sceneLayer, View? view, Rectangle? screenBounds, Rectangle? worldBounds, string? nickname=null)
Initializes a new instance of the DirectDrawingBase class.
Rectangle ScreenBounds
Gets or sets the screen-space bounds of this direct drawing in pixels.
Manages the lifecycle, registration, and retrieval of all direct drawing objects in the Gondwana engi...
static DirectDrawingManager Instance
Gets the singleton instance of the direct drawing manager.
Represents a base class for managing a graphical backbuffer, and is the in-memory surface where rende...
SKCanvas Canvas
Gets the SkiaSharp canvas used for drawing operations on this backbuffer.
Represents a base class for hosting a render surface, providing functionality for managing rendering ...
Scene Scene
Gets the source Scenes.Scene used for rendering operations.
RectangleF WorldRectToScreenRect(SceneLayer layer, RectangleF worldRect)
Converts a world-space pixel rectangle into a screen-space rectangle for this View,...
Definition View.cs:324
A rectangular window on the render target with its own zoom and placement. Viewport never moves the w...
Definition Viewport.cs:10
Rectangle TargetRectPx
Screen-space rectangle (in SCREEN / RenderSurface pixels) where this view is drawn....
Definition Viewport.cs:35
ReadOnlyCollection< SceneLayer > SceneLayers
Gets a read-only collection of all layers in this scene, in their current order.
Definition Scene.cs:267
A flexible, strongly-typed value container intended for runtime-attached metadata and ephemeral engin...
Represents any drawable object (movable or static) that can render directly to a RenderSurfaceHost,...
DirectDrawingMode
Specifies where direct drawing operations are applied within the rendering pipeline.
@ View
Performs direct drawing at the view level using screen-space coordinates.
@ SceneLayer
Performs direct drawing within a scene layer using world-space coordinates.