Evgenii Legotckoi
Evgenii LegotckoiҚыр. 19, 2015, 11:33 Т.Ж.

Qt тілінде ойынды қалай жазу керек - 2-сабақ. Ойын кейіпкерінің анимациясы (2D)

Екінші сабақта ойын жазудың келесі аспектісіне көшейік - Ойын кейіпкерінің анимациясы. Басты кейіпкер қызыл үшбұрыш болды, бірақ бұл өте қызықты емес. Сондықтан үшбұрышты анимациялық нысанға, атап айтқанда пернетақтаның басқаруымен жорғалап, қозғалған кезде аяқтарын қозғалтатын Ұшуға айналдырайық.

Бұл сабақта бағдарлама коды негізінен triangle.h, triangle.cpp файлдарында және widget.cpp. файлында өте аз түзетілді.

Анимацияны кезең-кезеңімен орындаңыз

Fly сызылатын алгоритмді сипаттайық:

  1. Біз класс конструкторында аяқтардың позиция нөміріне жауап беретін айнымалыны инициализациялаймыз. Барлығы шыбынның аяқтарының үш позициясы болады.
  2. Класс конструкторында ойын таймерінің пайдалы белгілерін, содан кейін Fly жылжытылған белгілерді санайтын айнымалы мәнді инициализациялаймыз. Бұл Шыбын қозғалысы болмаған кенелерді жою үшін қажет, әйтпесе Шыбын басқармасақ та, Шыбын үнемі аяқтарын жылжытады.
  3. Шыбынның аяқтарының қайсысы сызылатынын таңдап, бояу, әдісімен Шыбын суретін сал.
  4. Ойын таймерінің кері санақ оқиғасын өңдейтін ұяшықта ол ойын таймерінің пайдалы кенелерінің есептегішін жинақтайды және қалпына келтіреді және оның мәніне байланысты Ұшудың аяқтарының орнын орнатады, атап айтқанда , аяқтың қалаған күйімен Fly -ді толығымен қайта сызады.

үшбұрыш.сағ

Бұл файлға тек екі бүтін айнымалыны қосамыз:

  • қадамдар - шыбынның аяқтарының позициясының нөмірі;
  • countForSteps - пернетақта пернелері басылған таймер белгілерін санауға арналған есептегіш.
#ifndef TRIANGLE\_H
#define TRIANGLE\_H

#include <QObject>
#include <QGraphicsItem>
#include <QPainter>
#include <QGraphicsScene>

/* Подключаем библиотеку, отвечающую за использование WinAPI
 * Данная библиотека необходима для асинхронной проверки состояния клавиш
 * */
#include <windows.h>

class Triangle : public QObject, public QGraphicsItem
{
    Q\_OBJECT
public:
    explicit Triangle(QObject *parent = 0);
    ~Triangle();

signals:

public slots:
    void slotGameTimer(); /// Слот, который отвечает за обработку перемещения треугольника

protected:
    QRectF boundingRect() const;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);

private:
    qreal angle;        /// Угол поворота графического объекта
    int steps;          // Номер положения ножек мухи
    int countForSteps;  // Счётчик для отсчета тиков таймера, при которых мы нажимали на кнопки

};

#endif // TRIANGLE\_H

triangle.cpp

Жалпы, өзгерістер бүкіл файлға әсер етті. Класс конструкторы екі жаңа айнымалыға мәндер тағайындайды. Ал бояу әдісінде бүкіл Шыбын аяқтың үш ықтимал позициясымен толығымен сызылады. Сурет салу үшін эллипс, сызықтар және QPainterPath. класының объектілері сияқты нысандар пайдаланылады.

Шыбынның сәйкес келбеті бар болуы үшін координаталар бойынша сабақтың бағдарламалық кодына көшірілген Шыбынның эскизі жасалды.

Ең қызығы slotGameTimer() әдісінде бар, бұл әдісте түймелердің күйін бақылау кезінде пайдалы белгілер есептеледі және countForSteps айнымалысы 4, 8, 12-ге тең болса. және 16, аяқтардың позицияларының бірі таңдалады Шыбындар және аяқтардың осы қалпымен Шыбындардың қайта сызуын бастайды.

#include "triangle.h"

Triangle::Triangle(QObject *parent) :
    QObject(parent), QGraphicsItem()
{
    angle = 0;      // Задаём угол поворота графического объекта
    steps = 1;      // Задаём стартовое положение ножек мухи
    countForSteps = 0;      // Счётчик для отсчета тиков таймера, при которых мы нажимали на кнопки
    setRotation(angle);     // Устанавливаем угол поворота графического объекта
}

Triangle::~Triangle()
{

}

QRectF Triangle::boundingRect() const
{
    return QRectF(-40,-50,80,100);   /// Ограничиваем область, в которой лежит треугольник
}

void Triangle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    // Рисуем ножки, без ножек же муха не сможет ползать
    painter->setPen(QPen(Qt::black, 2));
    if(steps == 0){     // Первое положение ножек
        // Left 1
        painter->drawLine(-24,-37,-22,-25);
        painter->drawLine(-22,-25,-17,-15);
        painter->drawLine(-17,-15,-10,-5);
        // Right 1
        painter->drawLine(37,-28,28,-18);
        painter->drawLine(28,-18,24,-8);
        painter->drawLine(24,-8,10,-5);

        // Left 2
        painter->drawLine(-35,-20,-25,-11);
        painter->drawLine(-25,-11,-14,-5);
        painter->drawLine(-14,-5,0,5);
        // Right 2
        painter->drawLine(37,-12,32,-4);
        painter->drawLine(32,-4,24,2);
        painter->drawLine(24,2,0,5);

        // Left 3
        painter->drawLine(-35,35,-26,24);
        painter->drawLine(-26,24,-16,16);
        painter->drawLine(-16,16,0,0);
        // Right 3
        painter->drawLine(37,26,32,17);
        painter->drawLine(32,17,24,8);
        painter->drawLine(24,8,0,0);
    } else if (steps == 1){     // Второе положение ножек
        // Left 1
        painter->drawLine(-32,-32,-25,-22);
        painter->drawLine(-25,-22,-20,-12);
        painter->drawLine(-20,-12,-10,-5);
        // Right 1
        painter->drawLine(32,-32,25,-22);
        painter->drawLine(25,-22,20,-12);
        painter->drawLine(20,-12,10,-5);

        // Left 2
        painter->drawLine(-39,-15,-30,-8);
        painter->drawLine(-30,-8,-18,-2);
        painter->drawLine(-18,-2,0,5);
        // Right 2
        painter->drawLine(39,-15,30,-8);
        painter->drawLine(30,-8,18,-2);
        painter->drawLine(18,-2,0,5);

        // Left 3
        painter->drawLine(-39,30,-30,20);
        painter->drawLine(-30,20,-20,12);
        painter->drawLine(-20,12,0,0);
        // Right 3
        painter->drawLine(39,30,30,20);
        painter->drawLine(30,20,20,12);
        painter->drawLine(20,12,0,0);
    } else if (steps == 2){     // Третье положение ножек
        // Left 1
        painter->drawLine(-37,-28,-28,-18);
        painter->drawLine(-28,-18,-24,-8);
        painter->drawLine(-24,-8,-10,-5);
        // Right 1
        painter->drawLine(24,-37,22,-25);
        painter->drawLine(22,-25,17,-15);
        painter->drawLine(17,-15,10,-5);

        // Left 2
        painter->drawLine(-37,-12,-32,-4);
        painter->drawLine(-32,-4,-24,2);
        painter->drawLine(-24,2,0,5);
        // Right 2
        painter->drawLine(35,-20,25,-11);
        painter->drawLine(25,-11,14,-5);
        painter->drawLine(14,-5,0,5);

        // Left 3
        painter->drawLine(-37,26,-32,17);
        painter->drawLine(-32,17,-24,8);
        painter->drawLine(-24,8,0,0);
        // Right 3
        painter->drawLine(35,35,26,24);
        painter->drawLine(26,24,16,16);
        painter->drawLine(16,16,0,0);
    }
    // Усики
    QPainterPath path(QPointF(-5,-34));
    path.cubicTo(-5,-34, 0,-36,0,-30);
    path.cubicTo(0,-30, 0,-36,5,-34);
    painter->setBrush(Qt::NoBrush);
    painter->drawPath(path);

    painter->setPen(QPen(Qt::black, 1));
    // Тушка
    painter->setBrush(Qt::black);
    painter->drawEllipse(-15, -20, 30, 50);
    // Голова
    painter->drawEllipse(-15, -30, 30, 20);
    // Глазища
    painter->setBrush(Qt::green);
    painter->drawEllipse(-15, -27, 12, 15);
    painter->drawEllipse(3, -27, 12, 15);

    // Левое крылище
    QPainterPath path2(QPointF(-10, -10));
    path2.cubicTo(-18, -10, -30, 10, -25, 35);
    path2.cubicTo(-25,35,-20,50,-15,40);
    path2.cubicTo(-15,40,0,20,-3,5 );
    path2.cubicTo(-3,5, -8,8,-10,-10);
    painter->setBrush(Qt::white);
    painter->drawPath(path2);

    // Правое крылище
    QPainterPath path3(QPointF(10, -10));
    path3.cubicTo(18, -10, 30, 10, 25, 35);
    path3.cubicTo(25,35,20,50,15,40);
    path3.cubicTo(15,40,0,20,3,5 );
    path3.cubicTo(3,5, 8,8,10,-10);
    painter->setBrush(Qt::white);
    painter->drawPath(path3);

    Q\_UNUSED(option);
    Q\_UNUSED(widget);
}

void Triangle::slotGameTimer()
{
    /* Проверяем, нажата ли была какая-либо из кнопок управления объектом.
     * Прежде чем считать шажки
     * */
    if(GetAsyncKeyState(VK\_LEFT) ||
       GetAsyncKeyState(VK\_RIGHT) ||
       GetAsyncKeyState(VK\_UP) ||
       GetAsyncKeyState(VK\_DOWN))
    {
        /* Поочерёдно выполняем проверку на нажатие клавиш
         * с помощью функции асинхронного получения состояния клавиш,
         * которая предоставляется WinAPI
         * */
        if(GetAsyncKeyState(VK\_LEFT)){
            angle -= 5;        // Задаём поворот на 5 градусов влево
            setRotation(angle); // Поворачиваем объект
        }
        if(GetAsyncKeyState(VK\_RIGHT)){
            angle += 5;        // Задаём поворот на 5 градусов вправо
            setRotation(angle); // Поворачиваем объект
        }
        if(GetAsyncKeyState(VK\_UP)){
            setPos(mapToParent(0, -2));     /* Продвигаем объект на 5 пискселей вперёд
                                             * перетранслировав их в координатную систему
                                             * графической сцены
                                             * */
        }
        if(GetAsyncKeyState(VK\_DOWN)){
            setPos(mapToParent(0, 2));      /* Продвигаем объект на 5 пискселей назад
                                             * перетранслировав их в координатную систему
                                             * графической сцены
                                             * */
        }

        // Двигаем ножками, Dance, dance, Baby !!!
        countForSteps++;
        if(countForSteps == 4){
            steps = 2;
            update(QRectF(-40,-50,80,100));
        } else if (countForSteps == 8){
            steps = 1;
            update(QRectF(-40,-50,80,100));
        } else if (countForSteps == 12){
            steps = 0;
            update(QRectF(-40,-50,80,100));
        } else if (countForSteps == 16) {
            steps = 1;
            update(QRectF(-40,-50,80,100));
            countForSteps = 0;
        }
    }

    /* Проверка выхода за границы поля
     * Если объект выходит за заданные границы, то возвращаем его назад
     * */
    if(this->x() - 10 < -250){
        this->setX(-240);       // слева
    }
    if(this->x() + 10 > 250){
        this->setX(240);        // справа
    }

    if(this->y() - 10 < -250){
        this->setY(-240);       // сверху
    }
    if(this->y() + 10 > 250){
        this->setY(240);        // снизу
    }
}

widget.cpp

Көрсетудің бірқалыптылығын жақсарту үшін таймерден сигнал жиілігі ұлғайтылды.

timer->start(1000 / 100);

Барлығы

Нәтижесінде сіз қозғалған кезде табандарына тиетін Шыбын алуыңыз керек. Сондай-ақ осы мақаладағы бейне оқулықта осы сабақтың нәтижесінің демонстрациясын көре аласыз.

Осы сериядағы мақалалардың толық тізімі:

Бейне оқулық

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Ол саған ұнайды ма? Әлеуметтік желілерде бөлісіңіз!

M
  • Сәуір 25, 2018, 5:16 Т.Ж.

Здравствуйте,
Подскажите, почему муха может оставлять следы на игровой сцене?

Evgenii Legotckoi
  • Сәуір 25, 2018, 5:51 Т.Ж.

Добрый день. Артефакты обычно остаются в том случае, если не обновляется полностью тот участок графической сцены, в котором находится муха. Скорее возможно boundingRect() написали не совсем правильно, если писали его самостоятельно для своего собственного варианта. Также как вариант можете запускать метод update() на графической сцене с указание того пространства, которое требуется перерисовать.

Илья Некрасов
  • Наурыз 27, 2020, 2:28 Т.Қ.
  • (өңделген)

Здравствуйте, а можно, пожалуйста, ссылку на целые исходники, если есть?

Evgenii Legotckoi
  • Наурыз 27, 2020, 2:40 Т.Қ.

Добрый день. В конце пятой статьи скачать можете.

J
  • Мамыр 20, 2023, 11:27 Т.Ж.

Евгений, здравствуйте! Подскажите, а почему при нажатии одной клавиши переменная countForSteps увеличивается не на 1, на 4, ведь одно действие даёт увеличение этой переменной только на единицу? (стр.184 в файле triangle.cpp)

Evgenii Legotckoi
  • Мамыр 21, 2023, 5:57 Т.Ж.

Добрый день. slotGameTimer срабатывает по таймеру и при каждой сработке countForSteps увеличивается на 1, это не зависит от нажатия клавиш, нажатая клавиша лишь определяет положение ножек, которое отрисовывается у игрового персонажа. В коде есть часть, которая отвечает за отрисовку и эта часть делает проверку на число, кратное 4, поэтому вам и показалось, что countForSteps увеличивается на 4, а на самом деле во время нажатя клавиши слот успевает вызываться 4 раза.

J
  • Мамыр 21, 2023, 10:49 Т.Ж.
  • (өңделген)

Евгений, благодарю! Всё равно не совсем понимаю :( Если муха двигает ножками только при нажатии клавиш перемещение, то что, собственно, делает код со строк 184-198 в triangle.cpp? В этих строчках код опеределяет "характер" движения ножек в зависимости от направления движения мухи. Но я не очень понимаю как эта часть кода связана с разными нажатиями кнопок игроком, которые, собственно, и задают "характер" движения ножек. Ведь, как я понял, таймер работает не только во время нажатия клавиш, так? Плюс, не понимаю, почему слот за одно движение срабатывает 4 раза... Прошу прощение, немного запутался :(

Evgenii Legotckoi
  • Мамыр 25, 2023, 4:49 Т.Ж.

Код на строчка 184-198 вызывает перерисовку области на каждый 4-й такт счётчика. По той логике не нужно перерисовывать объект постоянно, достаточно реже, чем выполняется игровой слот. А слот выполняется постоянно по таймеру. Потому, что как вы правильно поняли, таймер работает и тогда, когда клавиши не нажимаются. А сработка 4 раза или даже чаще - это потому что нажатие обычно дольше, чем длительность отсчёта таймера. Слот срабатывает по таймеру, таймер срабатывает постоянно. Почему именно 4 раза? - Потому, что это эмпирически подобранная величина.

J
  • Мамыр 25, 2023, 2:24 Т.Қ.

Евгений, благодарю!

J
  • Там. 3, 2023, 3:56 Т.Ж.

Евгейни, скажите: а почему вы не использовали в этом проекте класс событий нажатия клавиш QKeyPressEvent? Это менее целесообразно в данном случае было?

Evgenii Legotckoi
  • Там. 3, 2023, 4:43 Т.Ж.

Да, менее целесообразно. Не подходит, поскольку не даёт обрабатывать постоянное нажатие кнопки достаточно простым способом.
Во всяком случае это первая причина, которая мне вспоминается. Я уже почти ничего не помню с момента написания этого поста, касательно этого примера.

J
  • Там. 3, 2023, 5:19 Т.Ж.
  • (өңделген)

Евгений, я новичок в Qt и решил попрактиковать в создании какой-нибудь простенькой игры (змейка, тетрис, морской бой), где присутствует постоянное нажатие клавиш. На ваш опытный взгляд, можно ли сказать, что в таких и похожих ситуациях лучше не влезать в класс обработки событий клавиатуры из-за его насыщенности, а просто прибегнуть к подключению возможностей Win32 как вы сделали в этой игре?

Evgenii Legotckoi
  • Там. 8, 2023, 2:13 Т.Ж.

События нажатий кнопок в Qt одноразовые. То есть пришло событие и вам нужно куда-то сохранить информацию об этом.
А лично на мой взгляд гораздо проще проверять нажата ли кнопка через Win Api в тот момент времени, когда это понадобится.

Пікірлер

Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
Кіріңіз немесе Тіркеліңіз
OI
  • Ora Iro
  • Жел. 24, 2024, 6:38 Т.Ж.

C++ - Тест 001. Первая программа и типы данных

  • Нәтиже:40ұпай,
  • Бағалау ұпайлары-8
AD

C++ - Тест 004. Указатели, Массивы и Циклы

  • Нәтиже:50ұпай,
  • Бағалау ұпайлары-4
m
  • molni99
  • Қаз. 26, 2024, 1:37 Т.Ж.

C++ - Тест 004. Указатели, Массивы и Циклы

  • Нәтиже:80ұпай,
  • Бағалау ұпайлары4
Соңғы пікірлер
ИМ
Игорь МаксимовҚар. 22, 2024, 11:51 Т.Ж.
Django - Оқулық 017. Теңшелген Django кіру беті Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii LegotckoiҚаз. 31, 2024, 2:37 Т.Қ.
Django - Сабақ 064. Python Markdown кеңейтімін қалай жазуға болады Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZEҚаз. 19, 2024, 8:19 Т.Ж.
Qt Creator көмегімен fb3 файл оқу құралы Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь МаксимовҚаз. 5, 2024, 7:51 Т.Ж.
Django - Сабақ 064. Python Markdown кеңейтімін қалай жазуға болады Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas5Шілде 5, 2024, 11:02 Т.Ж.
QML - Сабақ 016. SQLite деректер қоры және онымен QML Qt-та жұмыс істеу Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Енді форумда талқылаңыз
Evgenii Legotckoi
Evgenii LegotckoiМаусым 24, 2024, 3:11 Т.Қ.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey1Қар. 15, 2024, 6:04 Т.Ж.
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProjectМаусым 4, 2022, 3:49 Т.Ж.
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9AnonimҚаз. 25, 2024, 9:10 Т.Ж.
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Бізді әлеуметтік желілерде бақылаңыз