Часть №2: Создание игры «Тетрис» на C++/SFML

  Дмитрий Бушуев  | 

  Обновл. 26 Ноя 2020  | 

 7793

 ǀ   17 

Вот и подоспела вторая часть туториала по созданию тетриса на C++/SFML (первая часть здесь). Сегодня мы рассмотрим вопросы реализации механизма вращения фигурок тетрамино, а также их горизонтальные и вертикальные перемещения по игровому полю.

Горизонтальное перемещение тетрамино

Предлагаю начать с самой легкой части, а именно с реализации перемещения тетрамино вдоль горизонтали. Управлять данным действием мы будем при помощи стрелок на клавиатуре. Для этого нам понадобится добавить обработчики событий на нажатия соответствующих клавиш на клавиатуре:

Данный код очень простой. Сначала мы отлавливаем событие нажатия клавиши на клавиатуре (Event::KeyPressed), а затем проверяем, была ли это «стрелка влево» (Keyboard::Left) или «стрелка вправо» (Keyboard::Right). В зависимости от нажатой клавиши мы делаем шаг влево (dx = -1) или же шаг вправо (dx = 1). Далее, при помощи цикла for, мы сдвигаем все части тетрамино в нужном направлении. И в конце мы присваивем ноль для переменной, обозначающей горизонтальное перемещение (dx = 0;).

Рассмотрим детально следующий код:

Здесь мы задаем первоначальные координаты для тетрамино. Строка if(a[0].x == 0) является проверкой, убрав которую мы будем постоянно затирать изменяющиеся в результате горизонтального перемещения координаты тетрамино первоначальными координатами. Исходя из этого наши фигуры будут постоянно отрисовываться в верхнем левом углу игрового поля. Но благодаря проверке выше, код, отвечающий за первоначальное размещение тетрамино на игровом поле, будет выполняться только тогда, когда наша фигура находится в «стартовом» положении и будет пропускаться тогда, когда тетрамино совершило перемещение вдоль оси.

В результате у нас получится что-то вроде следующего:


Вращение тетрамино


Теперь мы переходим к чуть более сложной части нашего туториала. Как вы наверняка помните, в тетрисе, помимо перемещения фигурок тетрамино вдоль горизонтальной оси, фигурки можно вращать. Для реализации данного функционала нам понадобятся знания линейной алгебры, а именно то, что вращение спрайта вокруг заданной точки с координатами (x_0; y_0) описывается уравнениями следующего вида:

X = x_0 + (x − x_0) * cos⁡(a) − (y − y_0 ) * sin⁡(a);
Y = y_0 + (y − y_0) * cos⁡(a) + (x − x_0 ) * sin⁡(a);

где (x; y) — это старые (исходные) координаты точки, (X; Y) — это новые координаты (после вращения), а а — это угол поворота. Так как все повороты у нас идут исключительно на 90°, а из школьного курса алгебры мы знаем, что:

sin⁡(90°) = 1
cos⁡(90°) = 0

то подставляя соответствующие значения синуса и косинуса, исходные уравнения упрощаются до следующего вида:

X = x_0 − (y − y_0);
Y = y_0 + (x − x_0);

Картинка для наглядности:

Примечание: Не забудьте раскомментировать следующую строку кода:

А теперь на практике:

Результат:


Вертикальное перемещение тетрамино

Осталось реализовать падение тетрамино вниз. Для этого мы создадим таймер при помощи класса Clock и по истечению 0.3 секунды с начала отсчета времени мы будем сдвигать все части тетрамино на 1 позицию вниз. Для получения времени, прошедшего с момента старта таймера, мы будем использовать метод getElapsedTime(), а для перевода времени в секунды — метод asSeconds():

Как вы можете заметить, код довольно простой. В результате мы получим следующее:

  GitHub / Часть №2: Создание игры «Тетрис» на C++/SFML — Исходный код

Заключение


Мы прошли уже больше половины пути к заветной цели. Осталось совсем немного: сделать рандомный выбор типа фигурок тетрамино, раскрасить их в разные цвета, реализовать механизм уничтожения при получении ровных линий с тетрамино, и установить красивую картинку в качестве фона. Всё это будет рассмотрено в следующей (заключительной) части нашего туториала.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (22 оценок, среднее: 4,86 из 5)
Загрузка...

Комментариев: 17

  1. Аватар Тимофей:

    Здравствуйте! Очень благодарен вам за уроки. Возник вопрос. Почему мы rotate пишем выше до заполнения a[i].
    Мы же пользуемся a[i].x и a[i].y, но заполняем мы их позже.

  2. Аватар Viktor:

    Здравствуйте. Обьясните пожалуйста чем мы руководствуемся когда в части кода для вращения выставляем кординаты точки относительно которых происходит вращение на координаты а[1] тетрамино. Конкретно в этой строчке:

    У нас тетрамино состоит всего из 4х спрайтов. Почему тогда именно второй из них мы выбираем центральным а не третий, или от этого ничего не меняется. Я пытаюсь это как то визуализировать но у меня не выходит. Хелп ми плиз)

    1. Дмитрий Бушуев Дмитрий Бушуев:

      Как гласит известная пословица: "Лучше 1 раз увидеть, чем 100 раз услышать". Попробуйте задать в качестве вращения не второй спрайт, а третий, и посмотреть, что из этого получится 🙂

  3. Аватар Анастасия:

    Здравствуйте.
    В первом куске кода, где мы проверяем, какие стрелки нажал пользователь, мы также проверяем стрелку вверх. Об этом нет пояснения в абзаце ниже. И если она нажата, то присваиваем неизвестной на тот момент булевой переменной rotate значение true. Нехорошо. Компилятор ругается, что rotate он не знает. Понятно, что в следующем куске кода rotate уже объясняется и инициализируется. Но в первом куске следует внести соответствующие комментарии или убрать часть про rotate.
    Для тех, кому уравнения координат точки при вращении относительно заданного центра — как кантонский диалект китайского языка, можно было бы дать ссылку на годную статью или видео-объяснение по этой теме.

    1. Дмитрий Бушуев Дмитрий Бушуев:

      Спасибо за замечания. В ближайшее время мы внесем в статью необходимые коррективы. 🙂

      1. Аватар AndreyOlegovich.ru:

        Здравствуйте! Если я правильно всё понял — в ответ на предыдущий комментарий была закомментирована строчка if (event.key.code == Keyboard::Up …
        После этого нужно заменить else if для следующего условия на if. Но тогда в следующей части урока придется снова менять на else if. Как Вам вариант сделать Keyboard::Up последним ?

        1. Дмитрий Бушуев Дмитрий Бушуев:

          Эта строчка попала в код раньше, чем должна была 🙂
          Вы правильно подметили, что в таком случае нарушается структура "else if" и из-за этого требуется внести в код некоторые исправления, а именно — убрать первый else. Т.е., код:

          // Или может стрелка влево?
          else if (event.key.code == Keyboard::Left) dx = -1;
          // Или стрелка вправо?
          else if (event.key.code == Keyboard::Right) dx = 1;

          заменить на:
          // Или может стрелка влево?
          if (event.key.code == Keyboard::Left) dx = -1;
          // Или стрелка вправо?
          else if (event.key.code == Keyboard::Right) dx = 1;

          Спасибо, что напомнили об этом, постараюсь сегодня исправить этот недочет! 🙂

  4. Аватар Александр:

    Странно. Нифига не получилось Скачал исходники — открыл — тоже не получилось =)
    Пустое белое игровое поле..

    1. Дмитрий Бушуев Дмитрий Бушуев:

      >>"Пустое белое игровое поле.."
      Только что проверил — всё работает. Скорее всего ваша проблема в том, что неправильно указан путь до текстуры. Обратите внимание на строчку:

      У меня путь — это "C:\dev\SFML_Tutorial\Images\tiles.png"
      Для вас 2 варианта решения:
      1 Вариант.
      Сделать путь относительным. Для этого замените строчку:

      на

      2. Вариант.
      Распакуйте архив с исходниками. Найдите в нем папку:
      "SFML — Tetris. Part2 — Sources"
      Скопируйте всё её содержимое в "С:\dev\SFML_Tutorial" (предварительно создав на диске С: папку "dev", а в ней — папку "SFML_Tutorial"). Таким образом, картинки с текстурами должны будут лежать в "С:\dev\SFML_Tutorial\images"

      1. Аватар Александр:

        С путем все ок.
        Показывает тетрамино N=3 если закомментарю строку

      2. Аватар Александр:

        Вот мой код если что
        https://onlinegdb.com/ryid1CIoH

        1. Дмитрий Бушуев Дмитрий Бушуев:

          А зачем же вы все глобальные переменные перенесли внутрь функции main()? В особенности эти:

          Ведь пока эти массивы структур были объявлены вне функции main() (т.е. как глобальные), то их элементы автоматически инициализировались нулями, благодаря чему проходилась проверка if (a[0].x == 0) и происходило отрисовывание тетрамино.

          А вот когда вы эти массивы переместили внутрь функции main(), то они стали инициализироваться "мусором", отличным от нуля. Именно поэтому, проверка не проходила и тетрамино не отрисовывались. И именно же поэтому тетрамино снова отображались, когда вы закомментировали строчку с проверкой.

          Картинка для наглядности:
          https://ibb.co/0QbCGhY

        2. Аватар Анастасия:

          Я немного по-другому сделала и работает без проверки нулевых координат:

  5. Аватар Виктор:

    Шикарно! Большое спасибо за ваш разбор:) В следующий раз, пожалуйста, разберите, как реализованы шахматы.

    1. Дмитрий Бушуев Дмитрий Бушуев:

      Пожалуйста. Постараюсь сделать и про шахматы 🙂

      1. Аватар Elly:

        Огромное спасибо за разбор! все очень понятно и толково разъяснено. При возможности доработать и сделать более развернутую реализацию со всем функционалом прямо до готового продукта было бы очень круто! а еще было бы классно если показали похожую игру в ооп.

        1. Дмитрий Бушуев Дмитрий Бушуев:

          Всегда пожалуйста.
          P.S.: Примем к сведению ваше пожелание 🙂

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *