Монетка
Для начала скачаем бесплатный тайлсет с вращающейся монеткой, например отсюда https://opengameart.org/content/rotating-coin

Создадим шаблон с анимацией
Теперь загрузим тайлсет с анимацией монетки в нашу программу, для этого объявим лист frames где будут храниться все кадры анимации. При загрузке нарежем исходный тайлсет на отделньные кадры
Создадим конструктор
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
StartPosition = FormStartPosition.CenterScreen;
ClientSize = new Size(300, 300);
MaximizeBox = false;
MinimizeBox = false;
// загружаем тайлсет
LoadTileSet();
Paint += Form1_Paint;
}
//объявляем глобальную переменную в форме
List<Bitmap> frames = new List<Bitmap>();
public void LoadTileSet() { //загружаем и нарезаем тайлсет
var tileset = Bitmap.FromFile("Coins.png") as Bitmap;
for (int i = 0; i < 8; i++)
{
frames.Add(tileset.Clone(new Rectangle(i * 16, 0, 16, 16), System.Drawing.Imaging.PixelFormat.Format32bppArgb));
}
}
Для того чтобы контроллировать скорость переключения кадров (скорость анимации) введем переменную хранящую время, и будем накапливать миллисекунды между рендерингами. Скорость переключения на следующий кадр зададим в миллисекундах (это позволяет привязать анимацию напрямую ко времени, а не к таймеру отрисовки)
int frameIdx = 0; // индекс текущего кадра анимации
int speedMs = 120; // скрость перекелючения кадров в миллисекундах
DateTime last = DateTime.Now; // timestamp предыдущего рендеринга
double animMsAccum = 0; // аккумулятор миллисекунд
private void Form1_Paint(object? sender, PaintEventArgs e)
{
var now = DateTime.Now; // сохраняем timestamp текущего кадра
var diff = now.Subtract(last); // вычисляем разницу времени с предыдущим рендерингом
last = now; // обновляем last
animMsAccum += diff.TotalMilliseconds; // добавляем разницу миллисекунд в аккумулятор
//обновление сцены
if (animMsAccum > speedMs) // если пора переключить кадр
{
frameIdx++; // увеличиваем индекс кадра (берем следующий)
frameIdx %= frames.Count; // контролируем чтобы frameIdx всегда был в допустимом диапазоне
animMsAccum %= speedMs; // вычитаем полные кадры из аккумулятора
}
var frame = frames[frameIdx]; // берем фрейм для отрисовки
e.Graphics.Clear(Color.Black); // очищаем экран
e.Graphics.DrawImage(frame, 0, 0); // рисуем кадр в позиции 0, 0
}
Запусив вы увидите вращающуюся монетку размером 16х16 в левом верхнем углу экрана.
Теперь давайте попробуем вывести монетку увеличенную в 3 раза по центру окна.
Для того тобы этого сделать нам понадобится другая перегрузка функции DrawImage, которая принимает на вход два прямоугольника , один это целевой прямоугольник на целевой картинке (на которую мы рисуем и который будет увеличен в 3 раза), а второй прямогольник это облась на исходной картинке (по-скольку мы копируем кадр целиком то он будет от (0;0) до размеров фрейма)
int scale = 3; // коэффициент масштабирования картинки
var frame = frames[frameIdx];
e.Graphics.Clear(Color.Black);
e.Graphics.DrawImage(frame,
// первый целевой прямоугольник получается вычитанием из центра окна половину рисуемого кадра (с учетом масштаба)
new RectangleF(ClientRectangle.Width / 2 - scale * frame.Width / 2, ClientRectangle.Height / 2 - scale * frame.Height / 2, frame.Width * scale, frame.Height * scale),
// исходный прямоугольгик просто размер фрейма
new RectangleF(0, 0, frame.Width, frame.Height),
GraphicsUnit.Pixel);
Тепер у нас должен получиться вот такой результат:

Исходный код пример можно посмотреть здесь
Задания:
1. Создайте класс анимированной монетки, а затем выведете сетку 3х3 из вращающихся монеток таким образом чтобы изначально все они имели разный стартовый кадр анимации