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

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

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

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

#include <string>
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/System/Clock.hpp>
#include <SFML/Window/Event.hpp>
#include <SFML/Graphics.hpp>
Вернемся к игроку, я сразу напишу код а потом пошагово все объясню.
Код:
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 с таким содержимым:
Код:
#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 координату на текущий кадр тем самым получая нужный тайл.
Звучит непросто, советую перечитать ещё раз.

Ну и наконец осталась самая простая часть сегодняшнего урока. Движение персонажа Скажу вам по секрету, оно у нас уже реализовано, но не привязано к клавишам. Но перед этим, давайте инициализируем нашего персонажа и заставим его обновляться.
Код:
Image tileset_image;
    tileset_image.loadFromFile("data/images/tileset.png" );
    Texture tileset_texture;
    tileset_texture.loadFromImage(tileset_image);
    CPlayer Hero(tileset_texture, 100, 50);
А следующий код нужно вставить вместо очистки экрана и отрисовки тайлсета:
Код:
Hero.update(Hero.GetAnimationSpeed());
        window.clear(Color::White);
        window.draw(Hero.GetSprite()); // получаем спрайт через геттер
Для привязки клавиш, в главном цикле, после ImGui::SFML::Update() добавим этот код:
Код:
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());
}
Ну и все. Все задачи выполнены, если есть вопросы, задавайте. Спасибо за внимание!
Код:
#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 при закрытии программы
}
 
Последнее редактирование:
Сверху Снизу