DrawFiddle▶ RunEdit in Fiddle
// Space Invaders — tiny proto. Arrows or drag to move, Space or tap to fire.
const int cols = 9, rows = 4;
const double fieldW = 360, fieldH = 500;
const double invW = 20, invH = 13, gapX = 10, gapY = 12;
const double marchX = 10, marchTop = 44;   // swarm origin inside the field
const double dropStep = 10;                // dip gained every time the swarm turns
const double baseMarch = 26;               // dip/s with a full swarm, faster as they die
const double shipW = 28, shipH = 10, shipY = 468;
const double shipSpeed = 300;
const double shotSpeed = 430, bombSpeed = 180;
const double bombRate = 0.9;               // bombs per second for the whole swarm
const double fireDelay = 0.3;
const int maxShots = 3, maxBombs = 7;

var rnd = new Random();
var alive = new bool[cols, rows];
var shots = new List<double[]>();          // x, y
var bombs = new List<double[]>();
double swarmX = 0, swarmY = 0, shipX = fieldW / 2, shipTo = fieldW / 2, cool = 0;
int dir = 1, left = 0, keyDir = 0, score = 0, lives = 3;
bool over = false, won = false;
SkiaLabel hud = null;

double InvX(int c) => marchX + swarmX + c * (invW + gapX);
double InvY(int r) => marchTop + swarmY + r * (invH + gapY);

void Reset()
{
    for (int c = 0; c < cols; c++)
        for (int r = 0; r < rows; r++)
            alive[c, r] = true;
    left = cols * rows;
    swarmX = 0; swarmY = 0; dir = 1;
    shots.Clear(); bombs.Clear();
    shipX = shipTo = fieldW / 2;
    score = 0; lives = 3; cool = 0;
    over = false; won = false;
}
Reset();

void Fire()
{
    if (over || won || cool > 0 || shots.Count >= maxShots) return;
    cool = fireDelay;
    shots.Add(new double[] { shipX, shipY });
}

void Step(double dt)
{
    if (over || won) return;
    cool -= dt;

    // ship: keyboard holds a direction, the pointer chases a target
    if (keyDir != 0) shipTo = shipX + keyDir * shipSpeed * dt;
    shipTo = Math.Clamp(shipTo, shipW / 2, fieldW - shipW / 2);
    shipX += (shipTo - shipX) * Math.Min(1.0, dt * 22);

    // swarm marches, turns and drops at the walls, speeds up as it thins out
    var speed = baseMarch * (1 + 2.0 * (1 - (double)left / (cols * rows)));
    swarmX += dir * speed * dt;
    var span = cols * (invW + gapX) - gapX;
    var maxX = fieldW - marchX * 2 - span;
    if (swarmX < 0 || swarmX > maxX)
    {
        swarmX = Math.Clamp(swarmX, 0, maxX);
        dir = -dir;
        swarmY += dropStep;
    }

    // bombs fall from the lowest invader of a random column
    if (bombs.Count < maxBombs && rnd.NextDouble() < bombRate * dt)
    {
        var c = rnd.Next(cols);
        for (int r = rows - 1; r >= 0; r--)
            if (alive[c, r]) { bombs.Add(new double[] { InvX(c) + invW / 2, InvY(r) + invH }); break; }
    }

    for (int i = shots.Count - 1; i >= 0; i--)
    {
        var s = shots[i];
        s[1] -= shotSpeed * dt;
        if (s[1] < 0) { shots.RemoveAt(i); continue; }
        var gone = false;
        for (int c = 0; c < cols && !gone; c++)
            for (int r = 0; r < rows; r++)
            {
                if (!alive[c, r]) continue;
                var x = InvX(c); var y = InvY(r);
                if (s[0] >= x && s[0] <= x + invW && s[1] >= y && s[1] <= y + invH)
                {
                    alive[c, r] = false;
                    left--; score += 10;
                    gone = true;
                    break;
                }
            }
        if (gone) shots.RemoveAt(i);
    }
    if (left == 0) won = true;

    for (int i = bombs.Count - 1; i >= 0; i--)
    {
        var b = bombs[i];
        b[1] += bombSpeed * dt;
        if (b[1] > shipY + shipH) { bombs.RemoveAt(i); continue; }
        if (b[1] >= shipY && Math.Abs(b[0] - shipX) < shipW / 2)
        {
            if (--lives <= 0) over = true;
            bombs.Clear();
            break;
        }
    }

    // swarm landed on the ship line
    for (int c = 0; c < cols && !over; c++)
        for (int r = rows - 1; r >= 0; r--)
            if (alive[c, r]) { if (InvY(r) + invH >= shipY) over = true; break; }

    if (hud != null)
        hud.Text = over ? $"GAME OVER  {score}  —  SPACE"
            : won ? $"CLEARED  {score}  —  SPACE"
            : $"{score}   {new string('|', lives)}";
}

return new SkiaShape
{
    CornerRadius = 10,
    BackgroundColor = Color.Parse("#080B12"),
    WidthRequest = fieldW,
    HeightRequest = fieldH,
    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;
                using var paint = new SKPaint { IsAntialias = false };

                void Box(double x, double y, double w, double h, string col)
                {
                    paint.Color = SKColor.Parse(col);
                    canvas.DrawRect(new SKRect(
                        (float)(ox + x * s), (float)(oy + y * s),
                        (float)(ox + (x + w) * s), (float)(oy + (y + h) * s)), paint);
                }

                for (int c = 0; c < cols; c++)
                    for (int r = 0; r < rows; r++)
                    {
                        if (!alive[c, r]) continue;
                        var x = InvX(c); var y = InvY(r);
                        var col = r == 0 ? "#FF4D6D" : r == 1 ? "#FFC65C" : "#5CE1A0";
                        Box(x + 3, y, invW - 6, invH, col);            // body
                        Box(x, y + 3, invW, invH - 7, col);            // arms
                        Box(x + 4, y + 4, 3, 3, "#080B12");            // eyes
                        Box(x + invW - 7, y + 4, 3, 3, "#080B12");
                        Box(x, y + invH, 4, 3, col);                   // legs
                        Box(x + invW - 4, y + invH, 4, 3, col);
                    }

                foreach (var b in bombs) Box(b[0] - 1.5, b[1], 3, 8, "#FF8A3D");
                foreach (var sh in shots) Box(sh[0] - 1.5, sh[1], 3, 10, "#FFFFFF");

                if (!over)
                {
                    Box(shipX - shipW / 2, shipY, shipW, shipH, "#63D7FF");
                    Box(shipX - 2, shipY - 5, 4, 5, "#63D7FF");
                }
            })
            .WithGestures((me, args, apply) =>
            {
                if (args.Type == TouchActionResult.Down || args.Type == TouchActionResult.Panning)
                {
                    shipTo = (args.Event.Location.X - me.DrawingRect.Left) / me.RenderingScale;
                    if (args.Type == TouchActionResult.Down)
                    {
                        if (over || won) Reset(); else Fire();
                    }
                    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),
    }
}
.CenterX()
.OnKeyDown((me, key) =>
{
    if (over || won)
    {
        if (key == InputKey.Space || key == InputKey.Enter) Reset();
        return;
    }
    if (key == InputKey.Space || key == InputKey.Enter || key == InputKey.ArrowUp) Fire();
    if (key == InputKey.ArrowLeft) keyDir = -1;
    else if (key == InputKey.ArrowRight) keyDir = 1;
})
.OnKeyUp((me, key) =>
{
    if (key == InputKey.ArrowLeft && keyDir < 0) keyDir = 0;
    else if (key == InputKey.ArrowRight && keyDir > 0) keyDir = 0;
});