Шутер “Проект Кощей”, разработка игр на SFML C++

Предыдущая тема

7a2e4890401f1075d3dd5968093c855c.png

Шутеры — это один из самых популярных жанров видеоигр, который появился в начале 90-х годов. Первые шутеры были созданы для персональных компьютеров и имели простейшую графику и управление. Однако, они были невероятно популярны, и в скором времени стали самыми продаваемыми играми.

Первая игра, которую можно отнести к жанру шутеров, была Wolfenstein 3D, созданная компанией id Software в 1992 году. Она является первой игрой, в которой игрок управляет персонажем от первого лица, противостоящим врагам на экране. Wolfenstein 3D также стала первой игрой, которая использовала 3D-графику в реальном времени. Она была невероятно популярна и стала основой для многих последующих шутеров.

В 1993 году id Software выпустила игру Doom, которая стала еще более популярной, чем Wolfenstein 3D. Doom была первой игрой, которая использовала сетевой мультиплеер, что позволило игрокам из разных частей мира сражаться друг с другом через интернет.

В 1996 году компания Valve выпустила игру Half-Life, которая стала новой эволюцией жанра. Она представила новые элементы, такие как непрерывный геймплей, сюжет и персонажи, а также новую графику и физический движок.

Шутеры продолжают развиваться и совершенствоваться по сей день, и их популярность не угасает. Они стали неотъемлемой частью игровой индустрии и позволяют игрокам окунуться в захватывающий мир сражений и экшена.

Шутер «Проект Кощей»

83a82f15ba76630098504170ab24948e.gif9d29fa63f201e0ea77a37b733b225e31.png

Класс Player

player.h

#pragma once
#include"Animator.h"

class Player
{
public:
	Player();
	// направления движения игрока
	enum class playermove { UpPressed, UpRg, UpLf, DownPressed, 
		DownRg, DownLf, LeftPressed, RightPressed, Stop };
	// метод появление игрока на игровом поле
	void spawn(sf::IntRect planet, sf::Vector2f resolution, int tileSize);
	// метод рестарт параметров игрока
	void resetPlayerStats();
	// возвращает состояние жизни игрока
	bool getLive() const;
	// метод получения урона игроком
	bool hit(sf::Time timeHit);
	// возвращает время как давно был последний удар по игроку
	sf::Time getLastHitTime() const;
	// возвращает координаты игрока
	sf::FloatRect getPosition() const;
	// возвращает центральные координаты игрока
	sf::Vector2f getCenter() const;
	// возвращает угол поворота игрока
	float getRotation() const;
	// возвращает копию спрайта игрока
	sf::Sprite getSprite() const;
	// рисуем игрока
	void draw(sf::RenderWindow& win) const;
	// перемещаем игрока
	void move(playermove mov);
	// обновление игровой логики 
	void update(sf::Time deltaTime, sf::Vector2i mousePosition);
	// увеличиваем максимальное количество здоровья 
	void upgradeHealth(float heal);
	// пополняем здоровье игрока
	void increaseHealthLevel(float amount);
	// возвращает сколько здоровья у игрока на данный момент?
	float getHealth() const;
	// возвращает максимальное здоровье игрока
	float getMaxHealth() const;

private:
	// стартовая жизнь игрока
	const float START_HEALTH = 200;
	// позиция игрока
	sf::Vector2f m_Position;
	// отображение игрока спрайт и  объект анимации
	sf::Sprite m_Sprite;
	Animator m_AnimPlayer = Animator(m_Sprite);
	// разрешение анимировать игрока
	bool m_animMove = false;
	// разрешение экрана
	sf::Vector2f m_Resolution;
	// размер игрового поля 
	sf::IntRect m_planet;
	// размер текстур игрового поля 
	int m_TileSize;
	// в каком направлении(ях) движется игрок в данный момент
	playermove m_move;
	// живой ли игрок
	bool m_live = true;
	// здоровье игрока
	float m_Health;
	// максимальное здоровье игрока
	float m_MaxHealth;
	// время нанесения последнего удара по игроку 
	sf::Time m_LastHit;
	// частота перемещения игрока
	sf::Time m_time_moving;
	// скорость перемещения игрока в пикселях в секунду
	float m_Speed;
	
};

player.cpp

#include "Player.h"

Player::Player() {
		// задаём начальные значения свойствам игрока
		m_Speed = 2;
		m_Health = START_HEALTH;
		m_MaxHealth = START_HEALTH;
		m_move = playermove::Stop;
		m_TileSize = 0;
		// анимация игрока
		auto& idleForward = m_AnimPlayer.CreateAnimation("idleForward", "graphics/player.png", sf::seconds(0.5), true);
		idleForward.AddFrames(sf::Vector2i(0, 0), sf::Vector2i(135, 105), 4, 1);
		auto& dead = m_AnimPlayer.CreateAnimation("dead", "graphics/player.png", sf::seconds(0.5), false);
		dead.AddFrames(sf::Vector2i(405, 0), sf::Vector2i(135, 105), 4, 1);
		m_AnimPlayer.SwitchAnimation("idleForward");
		m_AnimPlayer.Update(sf::seconds(0));
		// устанавливаем координаты спрайта в центр 
		m_Sprite.setOrigin(m_Sprite.getGlobalBounds().width / 2, m_Sprite.getGlobalBounds().height / 2);		
}

В конструкторе класса player устанавливаем начальные значения свойствам и создаём анимацию перемещения и гибели персонажа. Картинки для анимации берём из файла player.png.

7200aedd2af7f1dc6bc9a08b6355d256.png

Как работать с методами анимации, подробно описано в статье «Покадровая анимация».

С помощью метода setOrigin (m_Sprite.getGlobalBounds ().width / 2, m_Sprite.getGlobalBounds ().height / 2) выставляем координаты положения спрайта в центр спрайта.

cbaec5d917af4148a312ea6450d683df.png

// появление игрока в мире
void Player::spawn(sf::IntRect planet, sf::Vector2f resolution, int tileSize) {
	
	// поместите игрока в центр игрового поля
	m_Position.x = static_cast(planet.width / 2);
	m_Position.y = static_cast(planet.height / 2);
	// копируем детали игрового поля
	m_planet.left = planet.left;
	m_planet.width = planet.width;
	m_planet.top = planet.top;
	m_planet.height = planet.height;
	// сохраняем размер плиток
	m_TileSize = tileSize;
	// сохраняем разрешение экрана
	m_Resolution.x = resolution.x;
	m_Resolution.y = resolution.y;
}

Метод void spawn (sf: IntRect planet, sf: Vector2f resolution, int tileSize), появления плеера в игровом мире, в параметрах принимает: размер игрового мира planet, разрешение экрана resolution, размер плиток текстуры tileSize, из которых строится игровой мир.

// сброс свойств игрока
void Player::resetPlayerStats() {

	m_Health = START_HEALTH;
	m_MaxHealth = START_HEALTH;
	m_live = true;
	m_AnimPlayer.SwitchAnimation("idleForward");
	m_AnimPlayer.Update(sf::seconds(0));
}

// состояние игрока жив мёртв
bool Player::getLive() const {

	return m_live;
}

// время получения урона
sf::Time Player::getLastHitTime() const {

	return m_LastHit;
}

// получение урона игроком
bool Player::hit(sf::Time timeHit) {

	if (timeHit.asMilliseconds()- m_LastHit.asMilliseconds() > 200 && m_Health>0) {
		
		m_LastHit = timeHit;
		if (m_Health>100) m_Health -= 10; else m_Health -= 5;
		if (m_Health < 0) m_Health = 0;
		return true;
	}
	else {

		return false;
	}
}

Метод bool hit (sf: Time timeHit) наносит урон игроку при условии, что его жизнь m_Health не равна нулю и время предыдущего получения урона timeHit.asMilliseconds ()- m_LastHit.asMilliseconds () не меньше 200 миллисекунд. Метод возвращает состояние после нанесения урона — урон получен или нет. В параметрах метода передаётся время нанесения урона timeHit.

// область нахождения игрока
sf::FloatRect Player::getPosition() const {

	auto myGlobalBounds = sf::FloatRect(m_Sprite.getGlobalBounds().left+20,
	m_Sprite.getGlobalBounds().top+20, m_Sprite.getGlobalBounds().width - 40, m_Sprite.getGlobalBounds().height - 40);

	return myGlobalBounds;
}

Метод FloatRect getPosition () возвращающий границы области, занимаемые плеером.  Данный метод предназначен для вычисления столкновения плеера с другими объектами. В этот метод внесены поправки  границ с учётом прозрачных полей анимации.

47734dc5d6395b4e0aadbde0f0015627.png

// координаты расположения игрока
sf::Vector2f Player::getCenter() const {

	return m_Position;
}

// угол поворота игрока
float Player::getRotation() const {

	return m_Sprite.getRotation();
}

// спрайт игрока
sf::Sprite Player::getSprite() const {

	return m_Sprite;
}

// количество жизни игрока
float Player::getHealth() const {

	return m_Health;
}

// максимальное количество жизни игрока
float Player::getMaxHealth() const
{
	return m_MaxHealth;
}

// рисуем игрока в графическом окне
void Player::draw(sf::RenderWindow& win) const {

	win.draw(m_Sprite);
}

Метод void draw (sf: RenderWindow& win) const рисующий игрока в графическом окне, в параметрах принимает ссылку на графическое окно win. 

// перемещение игрока
void Player::move(playermove mov) {

	m_move = mov;
}

Метод void move (playermove mov) меняет вектор направления движения плеера. В параметрах принимает выбранное направление движения mov.

// увеличение максимального количества жизни
void Player::upgradeHealth(float heal) {

	if (m_Health!=0) m_MaxHealth += heal;
}

В параметрах метода увеличивающего максимальный размер жизни void upgradeHealth (float heal), передаём вещественное значение heal, на которое увеличиваем максимальный размер жизни. 

// восстановление жизни игрока
void Player::increaseHealthLevel(float amount) {

	if (m_Health > 0 && m_Health< m_MaxHealth) {
		m_Health += amount;
		// Но не выше максимума
		if (m_Health > m_MaxHealth)	{

			m_Health = m_MaxHealth;
		}
	}
}

В параметрах метода восстанавливающего здоровье void increaseHealthLevel (float amount), передаём количество единиц amount, для восстановления жизни игрока m_Health. 

// обновление свойств игрока
void Player::update(sf::Time deltaTime, sf::Vector2i mousePosition)
{
	m_time_moving += deltaTime;
	// анимация перемещения игрока
	if (m_animMove && m_Health>0) m_AnimPlayer.Update(deltaTime);
	// если игрок потерял здоровье
	if (m_Health <= 0) 
	{
		if (m_AnimPlayer.GetCurrentAnimationName() != "dead") m_AnimPlayer.SwitchAnimation("dead");
		
		m_AnimPlayer.Update(deltaTime);

		if (m_AnimPlayer.getEndAnim()) {
		m_live = false;
		}
	}
	// начало раздела если игрок полон сил
	else {

		if (m_time_moving> sf::microseconds(5000)) {

			m_time_moving = sf::microseconds(0);

			switch (m_move)	{

				case Player::playermove::UpPressed:
				m_Position.y -= m_Speed;
				m_animMove = true;
				break;
				case Player::playermove::UpRg:
				m_Position.y -= m_Speed / (100 / (m_Resolution.y / (m_Resolution.x / 100)));
				m_Position.x += m_Speed;
				m_animMove = true;
				break;
				case Player::playermove::UpLf:
				m_Position.y -= m_Speed / (100 / (m_Resolution.y / (m_Resolution.x / 100)));
				m_Position.x -= m_Speed;
				m_animMove = true;
				break;
				case Player::playermove::DownPressed:
				m_Position.y += m_Speed;
				m_animMove = true;
				break;
				case Player::playermove::DownRg:
				m_Position.y += m_Speed / (100 / (m_Resolution.y / (m_Resolution.x / 100)));
				m_Position.x += m_Speed;
				m_animMove = true;
				break;
				case Player::playermove::DownLf:
				m_Position.y += m_Speed / (100 / (m_Resolution.y / (m_Resolution.x / 100)));
				m_Position.x -= m_Speed;
				m_animMove = true;
				break;
				case Player::playermove::LeftPressed:
				m_Position.x -= m_Speed;
				m_animMove = true;
				break;
				case Player::playermove::RightPressed:
				m_animMove = true;
				m_Position.x += m_Speed;
				break;
				default:
				m_animMove = false;
				break;
			}
			
			// Держите игрока на арене
			if (m_Position.x > static_cast((m_planet.width - m_TileSize)-(m_Sprite.getGlobalBounds().width/2))) {

				m_Position.x = static_cast((m_planet.width - m_TileSize) - (m_Sprite.getGlobalBounds().width / 2));
			}
			if (m_Position.x < static_cast((m_planet.left + m_TileSize)+ (m_Sprite.getGlobalBounds().width / 2))) {

				m_Position.x = static_cast((m_planet.left + m_TileSize) + (m_Sprite.getGlobalBounds().width / 2));
			}
			if (m_Position.y > static_cast((m_planet.height - m_TileSize) - (m_Sprite.getGlobalBounds().height / 2))) {

				m_Position.y = static_cast((m_planet.height - m_TileSize) - (m_Sprite.getGlobalBounds().height / 2));
			}
			if (m_Position.y < static_cast((m_planet.top + m_TileSize)+(m_Sprite.getGlobalBounds().height / 2)))	{

				m_Position.y = static_cast((m_planet.top + m_TileSize)+(m_Sprite.getGlobalBounds().height / 2));
			}
			// Вычислить угол, на который смотрит игрок
			auto angle = static_cast((atan2(static_cast(mousePosition.y) - m_Resolution.y / 2, 
			static_cast(mousePosition.x) - m_Resolution.x / 2)* 180) / 3.141);
			std::cout << angle << "\n";
			m_Sprite.setPosition(m_Position);
			m_Sprite.setRotation(angle);
		}
	}  // конец раздела если игрок полон сил
}

В параметрах метода  void update (sf: Time deltaTime, sf: Vector2i mousePosition), передаём единицу времени deltaTime и положения курсора мышки mousePosition.  С помощью свойства m_time_moving подсчитываем интервал времени.

if (m_animMove && m_Health>0) m_AnimPlayer.Update(deltaTime);

Если плеер  в движении m_animMove и его жизнь m_Health больше нуля, проигрываем анимацию перемещения.

if (m_Health <= 0) 
	{
		if (m_AnimPlayer.GetCurrentAnimationName() != "dead") m_AnimPlayer.SwitchAnimation("dead");
		
		m_AnimPlayer.Update(deltaTime);

		if (m_AnimPlayer.getEndAnim()) {
		m_live = false;
		}
	}

Если жизнь игрока m_Health равна нулю или меньше нуля,    меняем анимацию на анимацию смерти и проигрываем её.

if (m_AnimPlayer.getEndAnim()) {
		m_live = false;
		}

Если проигрыш анимации завершён getEndAnim (), устанавливаем статус игрока m_live мёртв.

else {

		if (m_time_moving> sf::microseconds(5000)) {

			m_time_moving = sf::microseconds(0);

			switch (m_move)	{

				case Player::playermove::UpPressed:
				m_Position.y -= m_Speed;
				m_animMove = true;
				break;
				case Player::playermove::UpRg:
				m_Position.y -= m_Speed / (100 / (m_Resolution.y / (m_Resolution.x / 100)));
				m_Position.x += m_Speed;
				m_animMove = true;
				break;
                // дальше продолжается код 

В данном разделе кода, с интервалом 5000 микросекунд, перемещаем персонажа согласно установленного вектора перемещения m_move.    

if (m_Position.x > static_cast((m_planet.width - m_TileSize)-(m_Sprite.getGlobalBounds().width/2))) {

m_Position.x = static_cast((m_planet.width - m_TileSize) - (m_Sprite.getGlobalBounds().width / 2));
			}
if (m_Position.x < static_cast((m_planet.left + m_TileSize)+ (m_Sprite.getGlobalBounds().width / 2))) {

m_Position.x = static_cast((m_planet.left + m_TileSize) + (m_Sprite.getGlobalBounds().width / 2));
			}
          // дальше продолжается код 

 Делаем проверку выхода персонажа за границы игрового поля и возвращаем его обратно если это произошло. 

auto angle = static_cast((atan2(static_cast(mousePosition.y) - m_Resolution.y / 2, 
			static_cast(mousePosition.x) - m_Resolution.x / 2)* 180) / 3.141);

9e8f6337d83ec3b1038dc6eea580893b.png

По формуле вычисляем угол angle, на который смотрит плеер.

m_Sprite.setPosition(m_Position);
m_Sprite.setRotation(angle);

Устанавливаем новые координаты спрайта, и поворачиваем спрайт на ранее вычисленный угол angle.  

MonsterPlanet.h

#pragma once
#include 
#include 

int createBackground(sf::VertexArray& rVA, sf::IntRect planet, int index);

Создаём заголовочный файл планета монстров.  Объявляем функцию построения игрового фона int createBackground (sf: VertexArray& rVA, sf: IntRect planet, int index). 

В параметрах передаём ссылку на массив вершин rVA, размер игрового поля planet, номер игрового уровня index.

#include "MonsterPlanet.h"

int createBackground(sf::VertexArray& rVA, sf::IntRect planet, int index)
{
    // будет использоваться для получения начального числа для механизма случайных чисел
    std::random_device rd;
    std::mt19937 gen(rd());
    // размер плитки
    const int TILE_SIZE = 200;
    // количество вариантов плиток
    const int TILE_TYPES = 9;
    // количество вершин
    const int VERTS_IN_QUAD = 4;
    // ширина мира
    int worldWidth = planet.width / TILE_SIZE;
    // высота мира
    int worldHeight = planet.height / TILE_SIZE;
    // тип примитива
    rVA.setPrimitiveType(sf::Quads);
    // установить размер массива вершин
    rVA.resize(worldWidth * worldHeight * VERTS_IN_QUAD);
    // начать с начала массива вершин
    int currentVertex = 0;

    for (int w = 0; w < worldWidth; w++)
    {
        for (int h = 0; h < worldHeight; h++)
        {
            // позиционируйте каждую вершину в текущем четырехугольнике
            rVA[currentVertex + 0].position = sf::Vector2f(static_cast(w * TILE_SIZE), static_cast(h * TILE_SIZE));
            rVA[currentVertex + 1].position = sf::Vector2f(static_cast((w * TILE_SIZE) + TILE_SIZE), static_cast(h * TILE_SIZE));
            rVA[currentVertex + 2].position = sf::Vector2f(static_cast((w * TILE_SIZE) + TILE_SIZE), static_cast((h * TILE_SIZE) + TILE_SIZE));
            rVA[currentVertex + 3].position = sf::Vector2f(static_cast(w * TILE_SIZE), static_cast((h * TILE_SIZE) + TILE_SIZE));

            // определяем позицию в текстуре для текущего четырехугольника
            // трава, камень, куст или стена
            if (h == 0 || h == worldHeight - 1 ||
                w == 0 || w == worldWidth - 1)
            {
                // Используем текстуру стены
                rVA[currentVertex + 0].texCoords = sf::Vector2f(TILE_SIZE * TILE_TYPES, TILE_SIZE*(index-1));
                rVA[currentVertex + 1].texCoords = sf::Vector2f(TILE_SIZE * TILE_TYPES + TILE_SIZE, TILE_SIZE*(index-1));
                rVA[currentVertex + 2].texCoords = sf::Vector2f(TILE_SIZE * TILE_TYPES + TILE_SIZE, TILE_SIZE*index);
                rVA[currentVertex + 3].texCoords = sf::Vector2f(TILE_SIZE * TILE_TYPES, TILE_SIZE*index);
            }
            else
            {
                std::uniform_int_distribution<> dis(0, TILE_TYPES - 1); 
                // использовать случайную текстуру пола
                int mOrG = dis(gen);
                int verticalOffset = mOrG * TILE_SIZE;
                rVA[currentVertex + 0].texCoords = sf::Vector2f(static_cast(verticalOffset), TILE_SIZE * (index - 1));
                rVA[currentVertex + 1].texCoords = sf::Vector2f(static_cast(verticalOffset + TILE_SIZE), TILE_SIZE * (index - 1));
                rVA[currentVertex + 2].texCoords = sf::Vector2f(static_cast(verticalOffset + TILE_SIZE), TILE_SIZE * index);
                rVA[currentVertex + 3].texCoords = sf::Vector2f(static_cast(verticalOffset), TILE_SIZE * index);
            }
            // позиция готова для следующих четырех вершин
            currentVertex = currentVertex + VERTS_IN_QUAD;
        }
    }
    return TILE_SIZE;
}

В определении функции запускаем генератор случайных чисел. Инициализируем переменные.

rVA[currentVertex + 0].position = sf::Vector2f(static_cast(w * TILE_SIZE), static_cast(h * TILE_SIZE));
            rVA[currentVertex + 1].position = sf::Vector2f(static_cast((w * TILE_SIZE) + TILE_SIZE), static_cast(h * TILE_SIZE));
            rVA[currentVertex + 2].position = sf::Vector2f(static_cast((w * TILE_SIZE) + TILE_SIZE), static_cast((h * TILE_SIZE) + TILE_SIZE));
            rVA[currentVertex + 3].position = sf::Vector2f(static_cast(w * TILE_SIZE), static_cast((h * TILE_SIZE) + TILE_SIZE));

202d0d4fc6b315cadf3da2b72d8ff7c3.png

Устанавливаем координаты для каждой вершины четырёхугольника.

if (h == 0 || h == worldHeight - 1 ||
                w == 0 || w == worldWidth - 1)
            {
                // Используем текстуру стены
                rVA[currentVertex + 0].texCoords = sf::Vector2f(TILE_SIZE * TILE_TYPES, TILE_SIZE*(index-1));
                rVA[currentVertex + 1].texCoords = sf::Vector2f(TILE_SIZE * TILE_TYPES + TILE_SIZE, TILE_SIZE*(index-1));
                rVA[currentVertex + 2].texCoords = sf::Vector2f(TILE_SIZE * TILE_TYPES + TILE_SIZE, TILE_SIZE*index);
                rVA[currentVertex + 3].texCoords = sf::Vector2f(TILE_SIZE * TILE_TYPES, TILE_SIZE*index);
            }
            else
            {
                std::uniform_int_distribution<> dis(0, TILE_TYPES - 1); 
                // использовать случайную текстуру пола
                int mOrG = dis(gen);
                int verticalOffset = mOrG * TILE_SIZE;
                rVA[currentVertex + 0].texCoords = sf::Vector2f(static_cast(verticalOffset), TILE_SIZE * (index - 1));
                rVA[currentVertex + 1].texCoords = sf::Vector2f(static_cast(verticalOffset + TILE_SIZE), TILE_SIZE * (index - 1));
                rVA[currentVertex + 2].texCoords = sf::Vector2f(static_cast(verticalOffset + TILE_SIZE), TILE_SIZE * index);
                rVA[currentVertex + 3].texCoords = sf::Vector2f(static_cast(verticalOffset), TILE_SIZE * index);
            }

42aed9c17926357f19535ee80c2525b9.png6217f2ae5a7f4c6d30c0768b8e86ad7f.png

Для границы игрового поля используем текстуру стены, остальную часть игрового поля заставляем текстурой ландшафта в случайном порядке.

// позиция готова для следующих четырех вершин
            currentVertex = currentVertex + VERTS_IN_QUAD;
        }
    }
    return TILE_SIZE;
}

5519f0b8368951405f544fedc702859d.png

Меняем позицию перебора массива вершин currentVertex. Возвращаем размер текстуры TILE_SIZE.    

Класс GameEngine

GameEngine.h

#pragma once
#include
#include"AssetManager.h"
#include "Player.h"
#include "MonsterPlanet.h"

class GameEngine
{
public:
	// конструктор
	GameEngine();
	// метод запуска игрового цикла
	void run();
private:
	// менеджер ресурсов
	AssetManager m_manager; 
	// разрешение экрана
	sf::Vector2f m_resolution = sf::Vector2f(static_cast(sf::VideoMode::getDesktopMode().width), 
		static_cast(sf::VideoMode::getDesktopMode().height));
	// графическое окно 
	sf::RenderWindow m_window; 
	// игра всегда будет в одном из перечисленных состояний
	enum class State { paused, level, level_up, game_over, playing, 
		game_victory, game_load, splash_screen, transition, help };
	// cостояние игры
	State m_state;
	// иконка
	sf::Image m_icon;	
	// окно mainView
	sf::View m_mainView = sf::View(sf::FloatRect(0, 0, 1920, 1080));
	// окно HUD
	sf::View m_hudView = sf::View(sf::FloatRect(0, 0, 1920, 1080));
	// oбщее игровое время
	sf::Time m_gameTimeTotal;
	// интервал стрельбы
	sf::Time m_lastPressed;
	// мировые координаты мышки
	sf::Vector2f m_mouseWorldPosition;
	// координаты мышки в окне
	sf::Vector2i m_mouseScreenPosition;
	// игрок
	Player m_player;
	// размер игрового уровня
	sf::IntRect m_planet;
	// фон игрового уровня
	sf::VertexArray m_background;
	int m_level = 0;
	// Метод обработки событий 
	void input();
	// метод обновления переменных, свойств и методов 
	void update(sf::Time const& deltaTime);
	// метод отрисовки объектов в графическом окне
	void draw();
	// рестарт игры
	void restart();
	// новый уровень
	void newLevel();
};

GameEngine.cpp

#include "GameEngine.h"

GameEngine::GameEngine() {
	// параметры игрового окна
	m_window.create(sf::VideoMode(m_resolution.x, m_resolution.y), L"Проект Кощей", sf::Style::Default);
	// этап загрузки игры
	m_state = State::game_load;
	// размер вида
	m_mainView.setSize(m_resolution.x, m_resolution.y);
	// начальные координаты игрового поля
	m_planet.left = 0;
	m_planet.top = 0;
	// иконка игрового окна
	if (!m_icon.loadFromFile("game.png")) exit(3); 
	m_window.setIcon(194, 256, m_icon.getPixelsPtr());
}

В конструкторе устанавливаем параметры графического окна m_window, состояние игры m_state, размер вида m_mainView и координаты игрового поля m_planet.

void GameEngine::input() {
	sf::Event event;
	while (m_window.pollEvent(event))
	{
		if (event.type == sf::Event::KeyPressed) {		
			// Загрузка игры
			if (m_state == State::game_load) {
				if ((event.key.code == sf::Keyboard::Space)) {
					m_state = State::splash_screen;
					return;
				}
			}
          // Заставка игры
			if (m_state == State::splash_screen) {
				if ((event.key.code == sf::Keyboard::Space)) {
					m_state = State::level_up;
					return;
				}
			}
           // Начать игру
			if (m_state == State::level) {
				if ((event.key.code == sf::Keyboard::Space)) {
					m_state = State::playing;
				}
			}
			// Выход из игры
			if ((event.key.code == sf::Keyboard::Escape) || (event.type == sf::Event::Closed)) {	
				m_window.close();
			}
			if (m_state == State::transition) {

				if ((event.key.code == sf::Keyboard::Space))
				{
					m_state = State::level_up;
					return;
				}
			}

			// Игра
			if (m_state == State::playing || m_state == State::transition) {
				// перемещение игрока
				if (event.key.code==sf::Keyboard::W) {

					m_player.move(Player::playermove::UpPressed);
				}			
				if (event.key.code == sf::Keyboard::E) {

					m_player.move(Player::playermove::UpRg);
				}				
				if (event.key.code == sf::Keyboard::C) {

					m_player.move(Player::playermove::DownRg);
					
				}			
				if (event.key.code == sf::Keyboard::S) {

					m_player.move(Player::playermove::DownPressed);					
				}
				
				if (event.key.code == sf::Keyboard::Z) {

					m_player.move(Player::playermove::DownLf);
				}
				
				if (event.key.code == sf::Keyboard::Q) {

					m_player.move(Player::playermove::UpLf);
				}
				
				if (event.key.code == sf::Keyboard::A) {

					m_player.move(Player::playermove::LeftPressed);
					
				}			
				if (event.key.code == sf::Keyboard::D) {

					m_player.move(Player::playermove::RightPressed);
				}
				
				if (event.key.code == sf::Keyboard::X) {

					m_player.move(Player::playermove::Stop);
				}
			} // конец игры
	}
}

В методе обработки событий void input (), нажимая пробел, меняем состояние игры  с загрузки на показ заставки, далее на загрузку уровня.  Нажимаем ещё раз пробел и начинаем игру. В дальнейшем в этом участке кода мы реализуем заставку загрузки игры и предысторию.

void GameEngine::update(sf::Time const& deltaTime) {

	// Обновление логики игрока
	m_player.update(deltaTime, sf::Mouse::getPosition());
	// Записываем положение игрока в переменную 
	sf::Vector2f playerPosition(m_player.getCenter());
	// Устанавливаем центр окна mainView, согласно положению игрока
	m_mainView.setCenter(m_player.getCenter());
    // Загружаем новый уровень
	if (m_state == State::level_up)
	{
		m_level++;
		if (m_level > 5) {// Выиграли игру
			m_state = State::game_victory;
		}
		else {// Следующий уровень
			newLevel();
			m_state = State::level;
		}
	}
}
// метод рисует объекты в графическом окне
void GameEngine::draw() {

	if (m_state == State::playing || m_state == State::transition)
	{
		// очищаем графическое окно
        m_window.clear();
		// устанавливаем вид просмотра игры
		m_window.setView(m_mainView);
		// рисуем фон игры
		m_window.draw(m_background, &AssetManager::GetTexture("graphics/plan.png"));
		// рисуем игрока
		m_player.draw(m_window);
	}
	m_window.display();
}
// игровой цикл
void GameEngine::run()
{
	// Объявление переменной часы
	sf::Clock clock;
	// Цикл работает пока окно открыто
	while (m_window.isOpen())
	{
		// Текущее время присваиваем переменной времени dt
		sf::Time dt = clock.restart();

		input();
		update(dt);
		draw();
	}
}
// перезапуск игры
void GameEngine::restart() {
	m_player.resetPlayerStats();
}
// генерация нового уровня
void GameEngine::newLevel()
{
	m_planet.width = 10000 * m_level;
	m_planet.height = 10000 * m_level;
	m_background.clear();
	int tileSize = createBackground(m_background, m_planet, m_level);
	m_player.spawn(m_planet, m_resolution, tileSize);
}

Продолжение следует…

Более подробную инструкцию вы можете получить, посмотрев видео «SFML C++ games Шутер Проект Кощей #1»

Клонировать репозиторий

Телеграмм канал «Программирование игр С++/С#

Предыдущая тема

© Habrahabr.ru