DrawFiddle▶ RunEdit in Fiddle
// Asteroids — arrows turn and thrust, Space fires. Vector paths through a raw Skia hook.
const double W = 380, H = 480;
const double turnRate = 3.4;      // rad/s
const double thrustAcc = 230;     // dip/s^2
const double drag = 0.38;
const double maxSpeed = 270;
const double shotSpeed = 330, shotLife = 0.85, fireCool = 0.22;
const double bigR = 34;

var rnd = new Random();
var rocks = new List<double[]>();  // x, y, vx, vy, r, angle, spin
var edges = new List<float[]>();   // matching polygon for each rock
var shots = new List<double[]>();  // x, y, vx, vy, life

double shipX = W / 2, shipY = H / 2, shipVX = 0, shipVY = 0, aim = -Math.PI / 2;
double cool = 0, invuln = 0, blink = 0;
int score = 0, lives = 3, wave = 0, rot = 0;
bool ptr = false;
double ptrX = 0, ptrY = 0;
bool over = false, thrusting = false;
SkiaLabel hud = null;

float[] RockEdges(double r)
{
    var n = 9;
    var pts = new float[n * 2];
    for (int i = 0; i < n; i++)
    {
        var a = i * Math.PI * 2 / n;
        var rad = r * (0.7 + rnd.NextDouble() * 0.45);
        pts[i * 2] = (float)(Math.Cos(a) * rad);
        pts[i * 2 + 1] = (float)(Math.Sin(a) * rad);
    }
    return pts;
}

void AddRock(double x, double y, double r)
{
    var a = rnd.NextDouble() * Math.PI * 2;
    var sp = 20 + rnd.NextDouble() * 45 + wave * 4;
    rocks.Add(new double[] { x, y, Math.Cos(a) * sp, Math.Sin(a) * sp, r, rnd.NextDouble() * 6.28, (rnd.NextDouble() - 0.5) * 1.8 });
    edges.Add(RockEdges(r));
}

void SpawnWave()
{
    wave++;
    for (int i = 0; i < 3 + wave; i++)
    {
        double x, y;
        do
        {
            x = rnd.NextDouble() * W;
            y = rnd.NextDouble() * H;
        }
        while (Math.Abs(x - shipX) < 100 && Math.Abs(y - shipY) < 100);
        AddRock(x, y, bigR);
    }
}

void Respawn()
{
    shipX = W / 2; shipY = H / 2; shipVX = shipVY = 0; aim = -Math.PI / 2;
    invuln = 2;
}

void Reset()
{
    rocks.Clear(); edges.Clear(); shots.Clear();
    score = 0; lives = 3; wave = 0; cool = 0; rot = 0;
    over = false; thrusting = false;
    Respawn();
    SpawnWave();
}
Reset();

void Fire()
{
    if (over || cool > 0) return;
    cool = fireCool;
    shots.Add(new double[]
    {
        shipX + Math.Cos(aim) * 12,
        shipY + Math.Sin(aim) * 12,
        shipVX + Math.Cos(aim) * shotSpeed,
        shipVY + Math.Sin(aim) * shotSpeed,
        shotLife,
    });
}

void Wrap(double[] o)
{
    if (o[0] < 0) o[0] += W; else if (o[0] > W) o[0] -= W;
    if (o[1] < 0) o[1] += H; else if (o[1] > H) o[1] -= H;
}

void Kill(int i)
{
    var r = rocks[i];
    score += r[4] > 26 ? 20 : r[4] > 16 ? 50 : 100;
    if (r[4] > 18)
    {
        AddRock(r[0], r[1], r[4] * 0.55);
        AddRock(r[0], r[1], r[4] * 0.55);
    }
    rocks.RemoveAt(i);
    edges.RemoveAt(i);
}

void Step(double dt)
{
    blink += dt;
    if (over) return;
    cool -= dt;
    invuln -= dt;

    aim += rot * turnRate * dt;

    // mouse / touch: hold anywhere, the ship turns toward the pointer, thrusts and fires
    if (ptr)
    {
        var want = Math.Atan2(ptrY - shipY, ptrX - shipX);
        var d = want - aim;
        while (d > Math.PI) d -= Math.PI * 2;
        while (d < -Math.PI) d += Math.PI * 2;
        var maxTurn = turnRate * dt;
        aim += Math.Clamp(d, -maxTurn, maxTurn);
        var dist = Math.Sqrt((ptrX - shipX) * (ptrX - shipX) + (ptrY - shipY) * (ptrY - shipY));
        thrusting = dist > 40;
        if (Math.Abs(d) < 0.5) Fire();
    }
    if (thrusting)
    {
        shipVX += Math.Cos(aim) * thrustAcc * dt;
        shipVY += Math.Sin(aim) * thrustAcc * dt;
        var sp = Math.Sqrt(shipVX * shipVX + shipVY * shipVY);
        if (sp > maxSpeed) { shipVX = shipVX / sp * maxSpeed; shipVY = shipVY / sp * maxSpeed; }
    }
    shipVX -= shipVX * drag * dt;
    shipVY -= shipVY * drag * dt;
    shipX += shipVX * dt;
    shipY += shipVY * dt;
    if (shipX < 0) shipX += W; else if (shipX > W) shipX -= W;
    if (shipY < 0) shipY += H; else if (shipY > H) shipY -= H;

    for (int i = 0; i < rocks.Count; i++)
    {
        var r = rocks[i];
        r[0] += r[2] * dt;
        r[1] += r[3] * dt;
        r[5] += r[6] * dt;
        Wrap(r);
    }

    for (int i = shots.Count - 1; i >= 0; i--)
    {
        var b = shots[i];
        b[0] += b[2] * dt;
        b[1] += b[3] * dt;
        b[4] -= dt;
        Wrap(b);
        if (b[4] <= 0) { shots.RemoveAt(i); continue; }
        for (int k = rocks.Count - 1; k >= 0; k--)
        {
            var r = rocks[k];
            var dx = b[0] - r[0];
            var dy = b[1] - r[1];
            if (dx * dx + dy * dy < r[4] * r[4])
            {
                Kill(k);
                shots.RemoveAt(i);
                break;
            }
        }
    }

    if (invuln <= 0)
    {
        for (int k = rocks.Count - 1; k >= 0; k--)
        {
            var r = rocks[k];
            var dx = shipX - r[0];
            var dy = shipY - r[1];
            var hit = r[4] + 8;
            if (dx * dx + dy * dy < hit * hit)
            {
                Kill(k);
                if (--lives <= 0) over = true; else Respawn();
                break;
            }
        }
    }

    if (rocks.Count == 0) SpawnWave();

    if (hud != null)
        hud.Text = over
            ? $"GAME OVER   {score}   —   SPACE"
            : $"{score}    {new string('^', Math.Max(0, lives))}    wave {wave}    —   hold the mouse to fly";
}

return new SkiaShape
{
    CornerRadius = 10,
    HorizontalOptions = LayoutOptions.Center,
    VerticalOptions = LayoutOptions.Center,
    BackgroundColor = Color.Parse("#05070D"),
    WidthRequest = W,
    HeightRequest = H,
    Children = new List<SkiaControl>
    {
        new SkiaLayer()
            .Fill()
            .WhenPaint((me, ctx) =>
            {
                var canvas = ctx.Context.Canvas;
                var s = (float)ctx.Scale;
                var ox = ctx.Destination.Left;
                var oy = ctx.Destination.Top;
                canvas.Save();
                canvas.ClipRect(ctx.Destination);

                using var line = new SKPaint
                {
                    IsAntialias = true,
                    Style = SKPaintStyle.Stroke,
                    StrokeWidth = 1.6f * s,
                    StrokeCap = SKStrokeCap.Round,
                    Color = SKColors.White,
                };
                using var dot = new SKPaint { IsAntialias = true, Color = SKColors.White };

                for (int i = 0; i < rocks.Count; i++)
                {
                    var r = rocks[i];
                    var p = edges[i];
                    using var path = new SKPath();
                    for (int k = 0; k < p.Length; k += 2)
                    {
                        if (k == 0) path.MoveTo(p[0] * s, p[1] * s);
                        else path.LineTo(p[k] * s, p[k + 1] * s);
                    }
                    path.Close();
                    line.Color = r[4] > 26 ? SKColors.White : r[4] > 16 ? SKColor.Parse("#B9D4FF") : SKColor.Parse("#7FA8E8");
                    // draw the wrapped copies too, so a rock crossing an edge is whole on both sides
                    for (int wx = -1; wx <= 1; wx++)
                        for (int wy = -1; wy <= 1; wy++)
                        {
                            var px = r[0] + wx * W;
                            var py = r[1] + wy * H;
                            if (px < -r[4] || px > W + r[4] || py < -r[4] || py > H + r[4]) continue;
                            canvas.Save();
                            canvas.Translate((float)(ox + px * s), (float)(oy + py * s));
                            canvas.RotateDegrees((float)(r[5] * 180 / Math.PI));
                            canvas.DrawPath(path, line);
                            canvas.Restore();
                        }
                }

                foreach (var b in shots)
                    canvas.DrawCircle((float)(ox + b[0] * s), (float)(oy + b[1] * s), 1.8f * s, dot);

                var visible = !over && (invuln <= 0 || (int)(blink * 12) % 2 == 0);
                if (visible)
                {
                    using var ship = new SKPath();
                    ship.MoveTo(14 * s, 0);
                    ship.LineTo(-9 * s, -8 * s);
                    ship.LineTo(-5 * s, 0);
                    ship.LineTo(-9 * s, 8 * s);
                    ship.Close();
                    using var flame = new SKPath();
                    flame.MoveTo(-6 * s, -4 * s);
                    flame.LineTo(-15 * s, 0);
                    flame.LineTo(-6 * s, 4 * s);
                    for (int wx = -1; wx <= 1; wx++)
                        for (int wy = -1; wy <= 1; wy++)
                        {
                            var px = shipX + wx * W;
                            var py = shipY + wy * H;
                            if (px < -16 || px > W + 16 || py < -16 || py > H + 16) continue;
                            canvas.Save();
                            canvas.Translate((float)(ox + px * s), (float)(oy + py * s));
                            canvas.RotateDegrees((float)(aim * 180 / Math.PI));
                            line.Color = SKColor.Parse("#63D7FF");
                            canvas.DrawPath(ship, line);
                            if (thrusting)
                            {
                                line.Color = SKColor.Parse("#FF8A3D");
                                canvas.DrawPath(flame, line);
                            }
                            canvas.Restore();
                        }
                }
                canvas.Restore();
            })
            .WithGestures((me, args, apply) =>
            {
                var px = (args.Event.Location.X - me.DrawingRect.Left) / me.RenderingScale;
                var py = (args.Event.Location.Y - me.DrawingRect.Top) / me.RenderingScale;
                if (args.Type == TouchActionResult.Down)
                {
                    if (over) { Reset(); return me; }
                    ptr = true; ptrX = px; ptrY = py;
                    Fire();
                    return me;
                }
                if (args.Type == TouchActionResult.Panning)
                {
                    ptrX = px; ptrY = py;
                    return me;
                }
                if (args.Type == TouchActionResult.Up)
                {
                    ptr = false;
                    thrusting = false;
                    return me;
                }
                return null;
            })
            .Animate(1, (me, animator, value, dt) =>
            {
                Step(Math.Min(dt, 0.05));
                me.Update();
            }, repeat: -1),

        new SkiaLabel("0")
        {
            FontSize = 13,
            TextColor = Color.Parse("#8AA0C0"),
            Margin = new Thickness(0, 10, 0, 0),
        }.CenterX().Assign(out hud),
    }
}
.OnKeyDown((me, key) =>
{
    if (over)
    {
        if (key == InputKey.Space || key == InputKey.Enter) Reset();
        return;
    }
    if (key == InputKey.ArrowLeft) rot = -1;
    else if (key == InputKey.ArrowRight) rot = 1;
    else if (key == InputKey.ArrowUp) thrusting = true;
    else if (key == InputKey.Space || key == InputKey.Enter) Fire();
})
.OnKeyUp((me, key) =>
{
    if (key == InputKey.ArrowLeft && rot < 0) rot = 0;
    else if (key == InputKey.ArrowRight && rot > 0) rot = 0;
    else if (key == InputKey.ArrowUp) thrusting = false;
});