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
MovementController.Follow.cs
1using System.Drawing;
2using System.Numerics;
5
7
11public sealed partial class MovementController
12{
13 // --- Pixel-follow (DirectDrawing → DirectDrawing)
14 private Func<Vector2>? _followPixel; // returns current target pixel position each frame
15 private Vector2 _followOffsetPx;
16 private bool _followHard;
17 private float _followSpeedPxPerSec;
18 private float _followSnapPx;
19
20 // Grid-follow (Scene-layer → this DirectDrawing in pixels)
21 private IMovableOnSceneLayer? _followGridTarget;
22 private Vector2 _followGridOffset;
23 private float _followSpeedTilesPerSec;
24 private float _followSnapTiles;
25
26 // Easing follow
27 private Func<float, float>? _followEasing; // optional easing curve
28 private float _followDurationSec; // duration for easing-based follow
29
45 public void FollowPixelSoft(Func<Vector2> getPixelPos,
46 float speed,
47 float snap = 0.5f,
48 Vector2 offsetPx = default)
49 {
50 if (speed <= 0)
51 throw new ArgumentOutOfRangeException(nameof(speed));
52
53 _followPixel = getPixelPos ?? throw new ArgumentNullException(nameof(getPixelPos));
54 _followGridTarget = null;
55 _followHard = false;
56
57 if (_mover.PositionSpace == MovementSpace.Pixel)
58 {
59 _followOffsetPx = offsetPx;
60 _followSpeedPxPerSec = speed;
61 _followSnapPx = MathF.Max(0f, snap);
62 _followSpeedTilesPerSec = 0f;
63 _followSnapTiles = 0f;
64 }
65 else
66 {
67 // grid follower chasing a pixel target: interpret speed/snap as tiles-based
68 _followOffsetPx = Vector2.Zero; // pixel offset not used for grid follower
69 _followSpeedTilesPerSec = speed;
70 _followSnapTiles = MathF.Max(0f, snap);
71 _followSpeedPxPerSec = 0f;
72 _followSnapPx = 0f;
73 }
74
75 // clear tween state
76 _followEasing = null;
77 _followDurationSec = 0f;
78 }
79
96 public void FollowPixelSoft(Func<Vector2> getPixelPos,
97 float durationSec,
98 Func<float, float>? easing = null,
99 float snap = 0.5f,
100 Vector2 offsetPx = default)
101 {
102 if (durationSec <= 0f)
103 throw new ArgumentOutOfRangeException(nameof(durationSec));
104
105 _followPixel = getPixelPos ?? throw new ArgumentNullException(nameof(getPixelPos));
106 _followGridTarget = null;
107 _followHard = false;
108
109 _followOffsetPx = offsetPx; // only applied for pixel followers
110 _followEasing = easing ?? EasingFunctions.Linear;
111 _followDurationSec = durationSec;
112
113 // Interpret snap in the follower’s space
114 if (_mover.PositionSpace == MovementSpace.Pixel)
115 {
116 _followSnapPx = MathF.Max(0f, snap);
117 _followSnapTiles = 0f;
118 }
119 else
120 {
121 _followSnapTiles = MathF.Max(0f, snap);
122 _followSnapPx = 0f;
123 }
124
125 // neutralize speed fields
126 _followSpeedPxPerSec = 0f;
127 _followSpeedTilesPerSec = 0f;
128 }
129
146 public void FollowPixelSoft(Func<Vector2> getPixelPos,
147 float durationSec,
148 EasingKind easingKind,
149 float snap = 0.5f,
150 Vector2 offsetPx = default)
151 {
152 var easing = EasingFunctions.From(easingKind);
153 FollowPixelSoft(getPixelPos, durationSec, easing, snap, offsetPx);
154 }
155
163 public void FollowPixelHard(Func<Vector2> getPixelPos, Vector2 offsetPx = default)
164 {
165 _followPixel = getPixelPos ?? throw new ArgumentNullException(nameof(getPixelPos));
166 _followGridTarget = null;
167 _followHard = true;
168
169 _followOffsetPx = (_mover.PositionSpace == MovementSpace.Pixel) ? offsetPx : Vector2.Zero;
170
171 // neutralize speed/tween fields
172 _followSpeedPxPerSec = 0f;
173 _followSnapPx = 0f;
174 _followSpeedTilesPerSec = 0f;
175 _followSnapTiles = 0f;
176 _followEasing = null;
177 _followDurationSec = 0f;
178 }
179
191 public void FollowTileSoft(IMovableOnSceneLayer tileTarget,
192 float speedTilesPerSec,
193 float snapTiles = 0.25f,
194 Vector2 gridOffset = default,
195 Vector2 pixelOffset = default)
196 {
197 if (speedTilesPerSec <= 0)
198 throw new ArgumentOutOfRangeException(nameof(speedTilesPerSec));
199
200 _followGridTarget = tileTarget ?? throw new ArgumentNullException(nameof(tileTarget));
201 _followGridOffset = gridOffset;
202 _followPixel = null;
203 _followHard = false;
204
205 _followSpeedTilesPerSec = speedTilesPerSec;
206 _followSnapTiles = MathF.Max(0f, snapTiles);
207 _followSpeedPxPerSec = 0f;
208 _followSnapPx = 0f;
209
210 _followOffsetPx = (_mover.PositionSpace == MovementSpace.Pixel) ? pixelOffset : Vector2.Zero;
211
212 // clear tween state
213 _followEasing = null;
214 _followDurationSec = 0f;
215 }
216
230 public void FollowTileSoft(IMovableOnSceneLayer tileTarget,
231 float durationSec,
232 Func<float, float>? easing = null,
233 float snap = 0.5f,
234 Vector2 gridOffset = default,
235 Vector2 pixelOffset = default)
236 {
237 if (durationSec <= 0f)
238 throw new ArgumentOutOfRangeException(nameof(durationSec));
239
240 _followGridTarget = tileTarget ?? throw new ArgumentNullException(nameof(tileTarget));
241 _followGridOffset = gridOffset;
242 _followPixel = null;
243 _followHard = false;
244
245 _followOffsetPx = (_mover.PositionSpace == MovementSpace.Pixel) ? pixelOffset : Vector2.Zero;
246
247 _followEasing = easing ?? EasingFunctions.Linear;
248 _followDurationSec = durationSec;
249
250 // snap in follower’s space at runtime:
251 // - pixel follower uses _followSnapPx
252 // - grid follower uses _followSnapTiles
253 // so just store both clamped and let the branch pick the right one:
254 _followSnapPx = MathF.Max(0f, snap);
255 _followSnapTiles = MathF.Max(0f, snap);
256
257 // neutralize speed fields
258 _followSpeedPxPerSec = 0f;
259 _followSpeedTilesPerSec = 0f;
260 }
261
274 public void FollowTileSoft(IMovableOnSceneLayer tileTarget,
275 float durationSec,
276 EasingKind easingKind,
277 float snap = 0.5f,
278 Vector2 gridOffset = default,
279 Vector2 pixelOffset = default)
280 {
281 var easing = EasingFunctions.From(easingKind);
282 FollowTileSoft(tileTarget, durationSec, easing, snap, gridOffset, pixelOffset);
283 }
284
294 public void FollowTileHard(IMovableOnSceneLayer tileTarget,
295 Vector2 gridOffset = default,
296 Vector2 pixelOffset = default)
297 {
298 _followGridTarget = tileTarget ?? throw new ArgumentNullException(nameof(tileTarget));
299 _followGridOffset = gridOffset;
300 _followPixel = null;
301 _followHard = true;
302
303 _followOffsetPx = (_mover.PositionSpace == MovementSpace.Pixel) ? pixelOffset : Vector2.Zero;
304
305 // neutralize speed/tween fields
306 _followSpeedTilesPerSec = 0f;
307 _followSnapTiles = 0f;
308 _followSpeedPxPerSec = 0f;
309 _followSnapPx = 0f;
310 _followEasing = null;
311 _followDurationSec = 0f;
312 }
313
318 public void Unfollow()
319 {
320 _followPixel = null;
321 _followGridTarget = null;
322 _followEasing = null;
323 _followDurationSec = 0f;
324 _followHard = false;
325
326 _followSpeedPxPerSec = 0f;
327 _followSnapPx = 0f;
328 _followSpeedTilesPerSec = 0f;
329 _followSnapTiles = 0f;
330 _followOffsetPx = Vector2.Zero;
331 _followGridOffset = Vector2.Zero;
332
333 CancelScript();
334 }
335
336 private bool AdvanceFollow()
337 {
338 // --- Pixel-follow ---
339 if (_followPixel is not null)
340 {
341 var goalPx = _followPixel() + _followOffsetPx;
342
343 if (_mover.PositionSpace == MovementSpace.Grid)
344 {
345 // grid-space mover following pixel goal...
346 return Advance_TargetPixel_MoverGrid(goalPx);
347 }
348 else
349 {
350 // pixel mover following pixel goal...
351 return Advance_TargetPixel_MoverPixel(goalPx);
352 }
353 }
354
355 // --- Grid-follow (target provides GRID coords each frame) ---
356 if (_followGridTarget is not null)
357 {
358 var layer = _followGridTarget.SceneLayer;
359
360 // target grid coordinate (+ any grid offset)
361 var goalCoordinates = _followGridTarget.GetPosition() + _followGridOffset;
362
363 if (_mover.PositionSpace == MovementSpace.Grid)
364 {
365 // GRID follower -> set/use GRID directly (no conversion)
366 return Advance_TargetGrid_MoverGrid(goalCoordinates);
367 }
368 else
369 {
370 // PIXEL follower -> convert GRID -> PIXEL, then apply optional pixel offset
371 return Advance_TargetGrid_MoverPixel(goalCoordinates, layer);
372 }
373 }
374
375 return false; // not following this frame
376 }
377
378 private bool Advance_TargetPixel_MoverGrid(Vector2 goalPx)
379 {
380 // follower is grid-space: convert pixel -> grid
381 if (_sceneLayer is null || _coords is null)
382 throw new InvalidOperationException("Pixel->Grid follow requires scene layer/coords.");
383
384 var gridPt = _coords.GetSceneLayerCoordinatesAtPixel(_sceneLayer, new PointF(goalPx.X, goalPx.Y));
385 var goalGrid = new Vector2(gridPt.X, gridPt.Y);
386
387 if (_followHard)
388 {
389 _mover.SetPosition(goalGrid);
390 return true;
391 }
392
393 if (_followEasing is not null && _followDurationSec > 0f)
394 {
395 MoveTo(goalGrid, _followDurationSec, _followEasing, _followSnapTiles);
396 return false;
397 }
398
399 MoveToward(goalGrid, _followSpeedTilesPerSec, _followSnapTiles);
400 return false;
401 }
402
403 private bool Advance_TargetPixel_MoverPixel(Vector2 goalPx)
404 {
405 if (_followHard)
406 {
407 _mover.SetPosition(goalPx);
408 return true;
409 }
410
411 // tweened follow
412 if (_followEasing is not null && _followDurationSec > 0f)
413 {
414 MoveTo(goalPx, _followDurationSec, _followEasing, _followSnapPx);
415 }
416 else
417 {
418 // constant-speed pursue
419 MoveToward(goalPx, _followSpeedPxPerSec, _followSnapPx);
420 }
421
422 return false; // we scheduled a script; let scripted stage consume this frame
423 }
424
425 private bool Advance_TargetGrid_MoverGrid(Vector2 goalCoordinates)
426 {
427 if (_followHard)
428 {
429 _mover.SetPosition(goalCoordinates);
430 return true;
431 }
432
433 if (_followEasing is not null && _followDurationSec > 0f)
434 {
435 MoveTo(goalCoordinates, _followDurationSec, _followEasing, _followSnapTiles);
436 return false;
437 }
438
439 // soft follow in tiles/sec and snap in tiles
440 MoveToward(goalCoordinates, _followSpeedTilesPerSec, _followSnapTiles);
441 return false;
442 }
443
444 private bool Advance_TargetGrid_MoverPixel(Vector2 goalCoordinates, SceneLayer sceneLayer)
445 {
446 var targetPx = sceneLayer.CoordinateSystem.GetAnchorPixelAtSceneLayerCoordinates(sceneLayer, new PointF(goalCoordinates.X, goalCoordinates.Y));
447 var goalPx = new Vector2(targetPx.X, targetPx.Y) + _followOffsetPx;
448
449 if (_followHard)
450 {
451 _mover.SetPosition(goalPx);
452 return true;
453 }
454
455 if (_followEasing is not null && _followDurationSec > 0f)
456 {
457 MoveTo(goalPx, _followDurationSec, _followEasing, _followSnapPx);
458 return false;
459 }
460
461 // tiles/sec → px/sec (sample +X to estimate px-per-tile length)
462 var pxRight = sceneLayer.CoordinateSystem.GetAnchorPixelAtSceneLayerCoordinates(sceneLayer, new PointF(goalCoordinates.X + 1f, goalCoordinates.Y));
463 float pxPerTile = MathF.Max(1f, new Vector2(pxRight.X - targetPx.X, pxRight.Y - targetPx.Y).Length());
464 float speedPxPerSec = _followSpeedTilesPerSec * pxPerTile;
465 float snap = _followSnapTiles * pxPerTile;
466
467 MoveToward(goalPx, speedPxPerSec, snap);
468 return false;
469 }
470}
Central controller responsible for managing all movement modes for a single IMovable object....
void FollowTileSoft(IMovableOnSceneLayer tileTarget, float durationSec, Func< float, float >? easing=null, float snap=0.5f, Vector2 gridOffset=default, Vector2 pixelOffset=default)
Start following a grid (tile) target using a duration-based tween (easing). When easing is active,...
void FollowPixelSoft(Func< Vector2 > getPixelPos, float speed, float snap=0.5f, Vector2 offsetPx=default)
Start following a pixel-space target at a constant speed. The speed and snap units are interpreted ...
MovementController MoveToward(Vector2 target, float speedPerSec, float snapEpsilon=0.5f)
Begins scripted motion toward the specified target position at a constant speed.
void FollowTileSoft(IMovableOnSceneLayer tileTarget, float durationSec, EasingKind easingKind, float snap=0.5f, Vector2 gridOffset=default, Vector2 pixelOffset=default)
Convenience overload of FollowTileSoft(IMovableOnSceneLayer, float, Func<float, float>?...
void FollowPixelSoft(Func< Vector2 > getPixelPos, float durationSec, EasingKind easingKind, float snap=0.5f, Vector2 offsetPx=default)
Start following a pixel-space target using a duration-based tween (easing). When easing is active,...
void CancelScript()
Cancels any active scripted movement and clears the current script state. Raises ScriptedMovementStop...
void FollowPixelHard(Func< Vector2 > getPixelPos, Vector2 offsetPx=default)
Hard-follow a pixel-space target. The follower snaps directly to the target every update (no tweening...
void FollowPixelSoft(Func< Vector2 > getPixelPos, float durationSec, Func< float, float >? easing=null, float snap=0.5f, Vector2 offsetPx=default)
Start following a pixel-space target using a duration-based tween (easing). When easing is active,...
void FollowTileSoft(IMovableOnSceneLayer tileTarget, float speedTilesPerSec, float snapTiles=0.25f, Vector2 gridOffset=default, Vector2 pixelOffset=default)
Start following a grid (tile) target at a constant speed (tiles/sec). If the follower is pixel-space,...
MovementController MoveTo(Vector2 target, float durationSec, Func< float, float >? easing=null, float snapEpsilon=0.5f)
Begins a scripted tween toward the specified target position over a fixed duration....
void FollowTileHard(IMovableOnSceneLayer tileTarget, Vector2 gridOffset=default, Vector2 pixelOffset=default)
Hard-follow a grid (tile) target. The follower snaps directly to the target grid coordinate each upda...
void Unfollow()
Stop following any target and clear all follow/tween state (targets, speeds, offsets,...
Implemented by grid-space IMovable objects that belong to a specific Scenes.SceneLayer.
MovementSpace PositionSpace
Which unit system this mover uses for its position.
Definition IMovable.cs:14
EasingKind
Enumeration of all supported easing functions for animation and movement interpolation....
Definition EasingKind.cs:9
MovementSpace
Specifies the coordinate system used for movement calculations and updates.