🎨 Графическое программирование на C#

← Вернуться к модулю 4

Урок 4.5: Проект: Простая игра

Модуль 4 ⏱️ ~3 часа

📌 Ключевые моменты этого урока:

  • Игровой цикл: UpdateGame() → Invalidate() → Paint() → UI обновляется на экране
  • Спрайт - это просто объект с координатами (x, y) и методом Draw()
  • Столкновения проверяются через Rectangle.IntersectsWith()
  • Игра работает в режиме Timer с Interval ~16-50 мс (зависит от сложности)
  • Всегда используйте DoubleBuffered = true и KeyPreview = true для обработки клавиш

Архитектура игры

СТРУКТУРА: Player (спрайт) → Враги/Препятствия → Игровой цикл (Timer) → Столкновения → Score

  • Игровой объект (управляемый игроком)
  • Враги или препятствия
  • Игровой цикл (таймер)
  • Обработка столкновений
  • Система очков

Игровой цикл

private Timer gameTimer;
private bool isGameRunning = false;

public Form1()
{
    InitializeComponent();
    this.DoubleBuffered = true;
    
    gameTimer = new Timer();
    gameTimer.Interval = 50; // 20 FPS
    gameTimer.Tick += GameLoop;
}

private void GameLoop(object sender, EventArgs e)
{
    UpdateGame(); // Обновление логики
    this.Invalidate(); // Перерисовка
}

private void UpdateGame()
{
    // Обновление позиций объектов
    // Проверка столкновений
    // Обновление счета
}

Обработка столкновений

private bool CheckCollision(Rectangle rect1, Rectangle rect2)
{
    return rect1.IntersectsWith(rect2);
}

private void UpdateGame()
{
    Rectangle playerRect = new Rectangle(playerX, playerY, 20, 20);
    
    foreach (var enemy in enemies)
    {
        Rectangle enemyRect = new Rectangle(enemy.X, enemy.Y, 20, 20);
        if (CheckCollision(playerRect, enemyRect))
        {
            // Столкновение!
            GameOver();
            return;
        }
    }
}

Завершенная мини-игра

Создайте игру со следующими элементами:

  • Управляемый объект (стрелками или WASD)
  • Движущиеся враги
  • Система очков
  • Экран начала игры
  • Экран окончания игры
  • Возможность перезапуска

📝 Пример задания — отскакивающий шар

Шар движется с начальной скоростью (4, 3) и отражается от стенок формы. Таймер 16 мс обеспечивает ~60 FPS:

using System;
using System.Drawing;
using System.Windows.Forms;

namespace CSharpGraphicsApp
{
    public class Task16_BouncingBall : Form
    {
        private PointF position = new PointF(100, 100);
        private PointF velocity = new PointF(4, 3);
        private readonly Timer timer = new Timer();

        public Task16_BouncingBall()
        {
            Text = "Задание 18 — Прыгающий шар";
            Size = new Size(600, 400);
            BackColor = Color.White;
            DoubleBuffered = true;
            Paint += Form1_Paint;

            timer.Interval = 16;
            timer.Tick += Timer_Tick;
            timer.Start();
            FormClosed += (s, e) => timer.Stop();
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            position.X += velocity.X;
            position.Y += velocity.Y;

            if (position.X < 0 || position.X > ClientSize.Width - 50)
                velocity.X = -velocity.X;
            if (position.Y < 0 || position.Y > ClientSize.Height - 50)
                velocity.Y = -velocity.Y;

            position.X = Math.Max(0, Math.Min(position.X, ClientSize.Width - 50));
            position.Y = Math.Max(0, Math.Min(position.Y, ClientSize.Height - 50));

            Invalidate();
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.SmoothingMode =
                System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            e.Graphics.FillEllipse(Brushes.Red,
                position.X, position.Y, 50, 50);
            e.Graphics.DrawString("Отскоки от стенок",
                SystemFonts.DefaultFont, Brushes.Black, 10, 10);
        }
    }
}

Практическое задание

Задание: Простая игра

  1. Создайте игру "Ловец шариков"
  2. Реализуйте игровой цикл с таймером
  3. Добавьте обработку столкновений
  4. Реализуйте систему очков

Задание по аналогии: Отскакивающий шар

По аналогии с Task16_BouncingBall реализуйте шар, отражающийся от границ окна.

  1. Добавьте поля PointF position и PointF velocity с начальными значениями.
  2. В Timer_Tick обновляйте позицию: position.X += velocity.X, position.Y += velocity.Y.
  3. При касании левой/правой границы инвертируйте velocity.X, при касании верхней/нижней — velocity.Y.
  4. Зафиксируйте позицию через Math.Clamp, чтобы шар не вышел за пределы.
  5. Дополнительно: увеличивайте скорость на 0.1% после каждого отскока.
💡 Совет: Проверяйте границу не > Width, а > Width - ballSize, иначе шар будет «застревать» в стенке.

Проверка знаний

Ответьте на вопросы, чтобы проверить, насколько хорошо вы усвоили материал:

1. Усвоили ли вы основные концепции урока?
  • Да, материал понятен
  • Частично понятен
  • Нужно повторить
  • Не понял
2. Готовы ли вы применить изученное на практике?
  • Нет, нужно еще изучить
  • Да, готов попробовать
  • Возможно
  • Не уверен
3. Что такое игровой цикл?
  • Цикл обновления состояния игры и отрисовки
  • Цикл обработки событий
  • Цикл загрузки ресурсов
  • Цикл инициализации