-
Автор темы
- #1
Всем привет. Продолжаем разрабатывать свою игру, но перед этим, советую ознакомиться с предыдущей статьей. Сегодня мы:
Если вы используете один из тех тайлсетов, которые я оставил в прошлом уроке, то вы должны увидеть что-то вроде этого:
Наш герой смотрит на нас. Кстати, расскажу немного об прямоугольниках. Конструктор Int/FloatRect принимает x и y координаты точки, откуда мы будем создавать квадрат, а также длину и ширину квадрата, то есть его размеры. В итоге мы имеем такие параметры конструктора:
Теперь давайте займемся движением персонажа, но перед этим мы создадим его класс. Создайте заголовочный файл "Player.h" в котором нужно подключить заголовочные файлы SFML, я для этого создал файл "include.h" и поместил туда это:
Вернемся к игроку, я сразу напишу код а потом пошагово все объясню.
Начнем с членов класса m_dx и m_dy. Это то расстояние, которое будет проходить наш игрок за время обновления, о котором я расскажу чуть позже. Изменяя эти переменные, наш персонаж будет двигаться, в зависимости от их значения.
Следующие две переменные отвечают за скорость анимации и за скорость обновления персонажа, я думаю здесь все понятно.
А вот про currentFrame я сейчас расскажу. Данная переменная нам поможет при реализации анимации, то есть при переключении с одного тайла, на другой, тем самым создавая анимацию.
Скорее всего, вам сложновато это понять, но поверьте, когда мы это реализуем, все будет гораздо проще.
Оставшиеся члены класса говорят сами за себя. rect - это прямоугольник, который будет использоваться при создании персонажа. sprite - это основа нашего пероснажа, его картинка.
Для каждого члена класса у нас присутствуют сеттеры и геттеры, для работы с ними вне класса. Но кроме них ещё присутствует конструктор CPlayer, в параметры которого входят текстура (тайлсет с которым работаем) и координаты появления игрока в самом начале, а также функция update, которая будет обновлять нашего игрока через определенное количество времени.
Перейдем к реализации всех функций, думаю объяснять не придется, ибо все достаточно просто. Единственное что я объясню - как работает функция update, в которой реализована анимация.
Создайте файл Player.cpp с таким содержимым:
Функция update работает так:
Мы двигаем персонажа на расстояние, на которое двигается герой, умноженное на время обновления персонажа, тем самым получая расстояние. И все это происходит вне зависимости от мощности процессора.
С анимацией же сложнее. У нас есть по 3 тайла (в моем случае) на определенное движение. Если у вас качественный тайлсет, то у вас все тайлы одной анимации будут в одном столбце или в одной строке => мы должны вырезать прямоугольник одинаковых размеров, но с разными x или y координатами. Из этого мы можем сделать вывод, что мы будем умножать x или y координату на текущий кадр тем самым получая нужный тайл.
Звучит непросто, советую перечитать ещё раз.
Ну и наконец осталась самая простая часть сегодняшнего урока. Движение персонажа Скажу вам по секрету, оно у нас уже реализовано, но не привязано к клавишам. Но перед этим, давайте инициализируем нашего персонажа и заставим его обновляться.
А следующий код нужно вставить вместо очистки экрана и отрисовки тайлсета:
Для привязки клавиш, в главном цикле, после ImGui::SFML::Update() добавим этот код:
- освоим принцип работы с тайлсетами
- создадим класс игрока
- заставим нашего персонажа двигаться
- попытаемся анимировать персонажа
Код:
Sprite tileset;
tileset.loadFromImage(tileset_texture); // инициализация
tileset.SetTextureRect(IntRect(46, 140, 46, 50)); // "вырезаем" прямоугольник
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;
};
Следующие две переменные отвечают за скорость анимации и за скорость обновления персонажа, я думаю здесь все понятно.
А вот про 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;
}
Мы двигаем персонажа на расстояние, на которое двигается герой, умноженное на время обновления персонажа, тем самым получая расстояние. И все это происходит вне зависимости от мощности процессора.
С анимацией же сложнее. У нас есть по 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()); // получаем спрайт через геттер
Код:
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 при закрытии программы
}
Последнее редактирование: