• Ищем качественного (не новичок) разработчиков Xenforo для этого форума! В идеале, чтобы ты был фулл стек программистом. Если у тебя есть что показать, то свяжись с нами по контактным данным: https://t.me/DREDD

Гайд [GameDev] Урок 3. Движение и анимация персонажа. Класс CPlayer.

Участник
Участник
Статус
Оффлайн
Регистрация
4 Авг 2017
Сообщения
491
Реакции
212
Всем привет. Продолжаем разрабатывать свою игру, но перед этим, советую ознакомиться с предыдущей статьей. Сегодня мы:
  • освоим принцип работы с тайлсетами
  • создадим класс игрока
  • заставим нашего персонажа двигаться
  • попытаемся анимировать персонажа
Начнем с самого сложного. В прошлом уроке мы работали с изображением, а также узнали, что такое тайлсет. Теперь наша задача заключается в том, чтобы вырезать из тайлсета тот тайл, который нам нужен в данный момент. Но пока давайте попробуем просто вырезать тайл. Для этого мы будем использовать функцию SetTextureRect объекта Sprite, в параметрах которой, будет указан прямоугольник, такой тип называется IntRect или FloatRect. Посмотрим как это выглядит:
Код:
Expand Collapse Copy
Sprite tileset;
tileset.loadFromImage(tileset_texture); // инициализация
tileset.SetTextureRect(IntRect(46, 140, 46, 50)); // "вырезаем" прямоугольник
Если вы используете один из тех тайлсетов, которые я оставил в прошлом уроке, то вы должны увидеть что-то вроде этого:
K6c9XLf.png
Наш герой смотрит на нас. Кстати, расскажу немного об прямоугольниках. Конструктор Int/FloatRect принимает x и y координаты точки, откуда мы будем создавать квадрат, а также длину и ширину квадрата, то есть его размеры. В итоге мы имеем такие параметры конструктора:

IntRect(x-координата, y-координата, длина, ширина)
Причем вся наша игра, точнее её визуализация, строится на этом принципе. Когда мы будем работать с картой нашей игры, мы будем пользоваться этим же принципом.

Теперь давайте займемся движением персонажа, но перед этим мы создадим его класс. Создайте заголовочный файл "Player.h" в котором нужно подключить заголовочные файлы SFML, я для этого создал файл "include.h" и поместил туда это:
Код:
Expand Collapse Copy
#pragma once

#include <string>
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/System/Clock.hpp>
#include <SFML/Window/Event.hpp>
#include <SFML/Graphics.hpp>
Вернемся к игроку, я сразу напишу код а потом пошагово все объясню.
Код:
Expand Collapse Copy
class CPlayer
{
public:
    CPlayer(Texture &image, int PosX, int PosY);

    void update(float time);

    Sprite GetSprite();
    float GetDX() const;
    float GetDY() const;
    float GetAnimationSpeed();
    float GetHeroSpeed();
    float GetCurrentFrame();

    void SetDX(float dx);
    void SetDY(float dy);
    void SetAnimationSpeed(float speed);
    void SetHeroSpeed(float speed);
    void SetCurrentFrame(float frame);
    void SetSpriteTexture(Texture texture);

private:
    float m_dx;
    float m_dy;

    float animation_speed = 1;
    float hero_speed = 1.5;

    float currentFrame;

    FloatRect rect;
    Sprite sprite;
};

Начнем с членов класса m_dx и m_dy. Это то расстояние, которое будет проходить наш игрок за время обновления, о котором я расскажу чуть позже. Изменяя эти переменные, наш персонаж будет двигаться, в зависимости от их значения.
Следующие две переменные отвечают за скорость анимации и за скорость обновления персонажа, я думаю здесь все понятно.
А вот про currentFrame я сейчас расскажу. Данная переменная нам поможет при реализации анимации, то есть при переключении с одного тайла, на другой, тем самым создавая анимацию.
Скорее всего, вам сложновато это понять, но поверьте, когда мы это реализуем, все будет гораздо проще.
Оставшиеся члены класса говорят сами за себя. rect - это прямоугольник, который будет использоваться при создании персонажа. sprite - это основа нашего пероснажа, его картинка.
Для каждого члена класса у нас присутствуют сеттеры и геттеры, для работы с ними вне класса. Но кроме них ещё присутствует конструктор CPlayer, в параметры которого входят текстура (тайлсет с которым работаем) и координаты появления игрока в самом начале, а также функция update, которая будет обновлять нашего игрока через определенное количество времени.
Перейдем к реализации всех функций, думаю объяснять не придется, ибо все достаточно просто. Единственное что я объясню - как работает функция update, в которой реализована анимация.
Создайте файл Player.cpp с таким содержимым:
Код:
Expand Collapse Copy
#include "Player.h"

CPlayer::CPlayer(Texture &image, int PosX, int PosY)
{
    sprite.setTexture(image);
    sprite.setPosition(PosX, PosY);
    IntRect rect = IntRect(46, 140, 46, 50);
    sprite.setTextureRect(rect);

    m_dx = 0;
    m_dy = 0;
    currentFrame = 0;
}

void CPlayer::update(float time)
{
    rect.left += m_dx * time;
    rect.top += m_dy*time;

    currentFrame += time * 0.07;
    if (currentFrame > 3)
    {
        currentFrame -= 3;
    }
    if (m_dx > 0)
    {
        sprite.setTextureRect(IntRect(46 * int(currentFrame), 80, 46, 56));
    }
    if (m_dx < 0)
    {
        sprite.setTextureRect(IntRect(46 * int(currentFrame), 200, 46, 56));
    }
    if (m_dy > 0)
    {
        sprite.setTextureRect(IntRect(46 * int(currentFrame), 135, 46, 60));
    }
    if (m_dy < 0)
    {
        sprite.setTextureRect(IntRect(46 * int(currentFrame), 0, 46, 65));
    }
 
    sprite.setPosition(rect.left, rect.top);

    m_dx = 0;
    m_dy = 0;
}

float CPlayer::GetDX() const
{
    return m_dx;
}

float CPlayer::GetDY() const
{
    return m_dy;
}

void CPlayer::SetDX(float dx)
{
    m_dx = dx;
}

void CPlayer::SetDY(float dy)
{
    m_dy = dy;
}

float CPlayer::GetAnimationSpeed()
{
    return animation_speed;
}

float CPlayer::GetHeroSpeed()
{
    return hero_speed;
}

void CPlayer::SetAnimationSpeed(float speed)
{
    animation_speed = speed;
}

void CPlayer::SetHeroSpeed(float speed)
{
    hero_speed = speed;
}

Sprite CPlayer::GetSprite()
{
    return sprite;
}

void CPlayer::SetSpriteTexture(Texture texture)
{
    sprite.setTexture(texture);
}

float CPlayer::GetCurrentFrame()
{
    return currentFrame;
}

void CPlayer::SetCurrentFrame(float frame)
{
    currentFrame = frame;
}

Функция update работает так:

Мы двигаем персонажа на расстояние, на которое двигается герой, умноженное на время обновления персонажа, тем самым получая расстояние. И все это происходит вне зависимости от мощности процессора.
С анимацией же сложнее. У нас есть по 3 тайла (в моем случае) на определенное движение. Если у вас качественный тайлсет, то у вас все тайлы одной анимации будут в одном столбце или в одной строке => мы должны вырезать прямоугольник одинаковых размеров, но с разными x или y координатами. Из этого мы можем сделать вывод, что мы будем умножать x или y координату на текущий кадр тем самым получая нужный тайл.
Звучит непросто, советую перечитать ещё раз.

Ну и наконец осталась самая простая часть сегодняшнего урока. Движение персонажа Скажу вам по секрету, оно у нас уже реализовано, но не привязано к клавишам. Но перед этим, давайте инициализируем нашего персонажа и заставим его обновляться.
Код:
Expand Collapse Copy
Image tileset_image;
    tileset_image.loadFromFile("data/images/tileset.png" );
    Texture tileset_texture;
    tileset_texture.loadFromImage(tileset_image);
    CPlayer Hero(tileset_texture, 100, 50);
А следующий код нужно вставить вместо очистки экрана и отрисовки тайлсета:
Код:
Expand Collapse Copy
Hero.update(Hero.GetAnimationSpeed());
        window.clear(Color::White);
        window.draw(Hero.GetSprite()); // получаем спрайт через геттер
Для привязки клавиш, в главном цикле, после ImGui::SFML::Update() добавим этот код:
Код:
Expand Collapse Copy
if ((Keyboard::isKeyPressed(Keyboard::Left) || (Keyboard::isKeyPressed(Keyboard::A))))
{
Hero.SetDX(-Hero.GetHeroSpeed());
}
else if ((Keyboard::isKeyPressed(Keyboard::Right) || (Keyboard::isKeyPressed(Keyboard::D))))
{
Hero.SetDX(Hero.GetHeroSpeed());
}
else if ((Keyboard::isKeyPressed(Keyboard::Up) || (Keyboard::isKeyPressed(Keyboard::W))))
{
Hero.SetDY(-Hero.GetHeroSpeed());
}
else if ((Keyboard::isKeyPressed(Keyboard::Down) || (Keyboard::isKeyPressed(Keyboard::S))))
{
Hero.SetDY(Hero.GetHeroSpeed());
}

Ну и все. Все задачи выполнены, если есть вопросы, задавайте. Спасибо за внимание!
Код:
Expand Collapse Copy
#include "imgui.h"
#include "imgui-sfml.h"

#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/System/Clock.hpp>
#include <SFML/Window/Event.hpp>

using namespace sf;

int main()
{
    sf::RenderWindow window(sf::VideoMode(640, 480), "Window"); // создаем окно с названием "Window" и разрешением 640X480
    window.setVerticalSyncEnabled(true); //устанавливаем вертикальную синхронизацию для плавности картинки
   
    ImGui::SFML::Init(window); //инициализируем окно ImGui с нашим омновным окном
   
    Image tileset_image;
    tileset_image.loadFromFile("data/images/tileset.png" );
    Texture tileset_texture;
    tileset_texture.loadFromImage(tileset_image);
    CPlayer Hero(tileset_texture, 100, 50);
    sf::Clock deltaClock; //создаем "таймер" который будет обновлять окно
   
    while (window.isOpen())  //основной цикл программы, работающий, пока окно открыто
    {
        sf::Event event; //событие, может быть равно "закрытию программы", "нажатию клавиши" и др.
        while (window.pollEvent(event))  //обработчик события
        {
            ImGui::SFML::ProcessEvent(event); // программа принимает на себя событие

            if (event.type == sf::Event::Closed)   // обработчки события, в данном случае, закрытия окна
            {
                window.close();
            }
        }

        ImGui::SFML::Update(window, deltaClock.restart()); // обновляем окно

        if ((Keyboard::isKeyPressed(Keyboard::Left) || (Keyboard::isKeyPressed(Keyboard::A))))
        {
            Hero.SetDX(-Hero.GetHeroSpeed());
        }
        else if ((Keyboard::isKeyPressed(Keyboard::Right) || (Keyboard::isKeyPressed(Keyboard::D))))
        {
            Hero.SetDX(Hero.GetHeroSpeed());      
        }
        else if ((Keyboard::isKeyPressed(Keyboard::Up) || (Keyboard::isKeyPressed(Keyboard::W))))
        {
            Hero.SetDY(-Hero.GetHeroSpeed());
        }
        else if ((Keyboard::isKeyPressed(Keyboard::Down) || (Keyboard::isKeyPressed(Keyboard::S))))
        {
            Hero.SetDY(Hero.GetHeroSpeed());      
        }
       
        ImGui::Begin("ImGui Window"); // создаём окно ImGui
     
        ImGui::End(); // end window
       
        Hero.update(Hero.GetAnimationSpeed());
        window.clear(Color::White); // заполняем окно заданным цветом
        window.draw(Hero.GetSprite());
        ImGui::SFML::Render(window);// отображаем окно ImGui в основном окне
        window.display(); // отображаем все это на экране
    }

    ImGui::SFML::Shutdown(); // завершаем работу ImGui при закрытии программы
}
 
Последнее редактирование:
Спасибо большое. Обширно. Будем пробовать.
 
Назад
Сверху Снизу