diff --git a/Snake.Avalonia/Views/AnimationHelper.cs b/Snake.Avalonia/Views/AnimationHelper.cs index f11c590..0a5f2ce 100644 --- a/Snake.Avalonia/Views/AnimationHelper.cs +++ b/Snake.Avalonia/Views/AnimationHelper.cs @@ -26,7 +26,16 @@ public static class AnimationHelper } /// - /// Interpolates between two positions with given t value. + /// Ease-in cubic — mirror of EaseOutCubic, for tail interpolation. + /// + public static double EaseInCubic(double t) + { + t = Math.Clamp(t, 0.0, 1.0); + return t * t * t; + } + + /// + /// Interpolates between two positions with given t value and easing mode. /// Returns the visual position for rendering. /// public static (double X, double Y) InterpolatePosition( @@ -34,9 +43,14 @@ public static class AnimationHelper Position to, double t, double cellSize, - bool useEasing = false) + string easing = "linear") { - var eased = useEasing ? EaseOutCubic(t) : t; + var eased = easing switch + { + "easeOut" => EaseOutCubic(t), + "easeIn" => EaseInCubic(t), + _ => t + }; var x = from.X + (to.X - from.X) * eased; var y = from.Y + (to.Y - from.Y) * eased; return (x * cellSize + cellSize * 0.5, y * cellSize + cellSize * 0.5); diff --git a/Snake.Avalonia/Views/SnakeRenderer.cs b/Snake.Avalonia/Views/SnakeRenderer.cs index 06af464..b5b6598 100644 --- a/Snake.Avalonia/Views/SnakeRenderer.cs +++ b/Snake.Avalonia/Views/SnakeRenderer.cs @@ -172,28 +172,6 @@ public sealed class SnakeRenderer : Control context.DrawGeometry(null, innerPen, bodyGeometry); } - // === Fading ghost circle at old tail position === - // Creates the illusion of smooth tail movement without affecting - // the body path geometry (which stays grid-snapped with sharp corners). - if (count >= 2 && PreviousSegments != null && PreviousSegments.Count > 0) - { - var opacity = 0.4 * (1.0 - t); - if (opacity > 0) - { - var oldTailPos = PreviousSegments[PreviousSegments.Count - 1]; - var currentTailPos = segmentsList[count - 1]; - - var (ghostX, ghostY) = AnimationHelper.InterpolatePosition( - oldTailPos, currentTailPos, t, CellSize, useEasing: false); - - context.DrawEllipse( - new SolidColorBrush(SnakeBodyBrush.Color, opacity), - null, - new Point(ghostX, ghostY), - CellSize * 0.35, CellSize * 0.35); - } - } - // Draw head DrawHead(context, t); @@ -379,17 +357,22 @@ public sealed class SnakeRenderer : Control private (double X, double Y) GetVisualPosition(Position pos, int segmentIndex, double t) { - // Head (index 0): interpolated with easing for smooth movement - if (segmentIndex == 0) - { - var from = PreviousSegments != null && PreviousSegments.Count > 0 - ? AnimationHelper.GetPreviousPosition(0, Segments!, PreviousSegments, SnakeDirection) - : pos; + if (Segments == null) + return (pos.X * CellSize + CellSize * 0.5, pos.Y * CellSize + CellSize * 0.5); - return AnimationHelper.InterpolatePosition(from, pos, t, CellSize, useEasing: true); - } + var isHead = segmentIndex == 0; + var isTail = Segments.Count > 1 && segmentIndex == Segments.Count - 1; - // All other segments (body and tail): grid-snapped for sharp corners - return (pos.X * CellSize + CellSize * 0.5, pos.Y * CellSize + CellSize * 0.5); + // Head and tail interpolate smoothly; middle segments stay grid-snapped + if (!isHead && !isTail) + return (pos.X * CellSize + CellSize * 0.5, pos.Y * CellSize + CellSize * 0.5); + + var from = PreviousSegments != null && PreviousSegments.Count > 0 + ? AnimationHelper.GetPreviousPosition(segmentIndex, Segments, PreviousSegments, SnakeDirection) + : pos; + + // Head: ease-out forward. Tail: ease-in backward (mirrored). + var easingMode = isHead ? "easeOut" : "easeIn"; + return AnimationHelper.InterpolatePosition(from, pos, t, CellSize, easing: easingMode); } }