Монетка

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

Обратите внимание, что исходная картинка в архиве на сайте имеет некорректный размер 127х16, не забудьте в редакторе подправить его до 128х16, либо взять уже отредактированный мной вариант отсюда

Создадим шаблон с анимацией

Теперь загрузим тайлсет с анимацией монетки в нашу программу, для этого объявим лист 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 из вращающихся монеток таким образом чтобы изначально все они имели разный стартовый кадр анимации