Shooter “Project Koschei”, game development in SFML C++

Previous topic

Shooters are one of the most popular genres of video games that appeared in the early 90s. The first shooters were created for personal computers and had the simplest graphics and controls. However, they were incredibly popular and soon became the top selling games.

The first game that can be classified as a shooter was Wolfenstein 3D, created by id Software in 1992. It is the first game in which the player controls a first-person character against on-screen enemies. Wolfenstein 3D was also the first game to use real-time 3D graphics. It was incredibly popular and became the basis for many subsequent shooters.

In 1993, id Software released Doom, which became even more popular than Wolfenstein 3D. Doom was the first game to use online multiplayer, allowing players from different parts of the world to fight each other over the internet.

In 1996, Valve released the game Half-Life, which became a new evolution of the genre. It introduced new elements such as continuous gameplay, story and characters, as well as new graphics and physics engine.

Shooters continue to develop and improve to this day, and their popularity does not fade away. They have become an integral part of the gaming industry and allow players to plunge into the exciting world of battles and action.

Shooter “Project Koschey”

Player class

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);		
}

In class constructor player we set the initial values ​​for the properties and create an animation of the movement and death of the character. We take pictures for animation from a file player.png

How to work with animation methods is described in detail in the article “Frame-by-frame animation”.

Using the setOrigin(m_Sprite.getGlobalBounds().width / 2, m_Sprite.getGlobalBounds().height / 2) method, we set the coordinates of the sprite’s position to the center of the sprite.

// появление игрока в мире
void Player::spawn(sf::IntRect planet, sf::Vector2f resolution, int tileSize) {
	
	// поместите игрока в центр игрового поля
	m_Position.x = static_cast<float>(planet.width / 2);
	m_Position.y = static_cast<float>(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;
}

The method void spawn(sf::IntRect planet, sf::Vector2f resolution, int tileSize), when the player appears in the game world, takes in parameters: planet game world size, screen resolution resolution, size of tileSize texture tiles from which the game world is built.

// сброс свойств игрока
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;
	}
}

The bool hit(sf::Time timeHit) method inflicts damage on the player, provided that his life m_Health is not equal to zero and the time of the previous damage received timeHit.asMilliseconds() – m_LastHit.asMilliseconds() is not less than 200 milliseconds. The method returns the state after the damage has been dealt – whether the damage has been received or not. The method parameters pass the damage time, 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;
}

The FloatRect getPosition() method returns the bounds of the area occupied by the player. This method is designed to calculate the collision of the player with other objects. Borders have been adjusted for this method to account for transparent animation fields.

// координаты расположения игрока
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);
}

The void draw(sf::RenderWindow& win) const method, which draws the player in the graphics window, takes a reference to the graphics window win as parameters.

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

	m_move = mov;
}

The void move(playermove mov) method changes the player’s direction vector. In the parameters, it takes the selected direction of movement mov.

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

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

In the parameters of the method that increases the maximum life size void upgradeHealth(float heal), we pass a real heal value, by which we increase the maximum life size.

// восстановление жизни игрока
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;
		}
	}
}

In the parameters of the health restoring method void increaseHealthLevel(float amount), we pass the number of amount units to restore the player’s life 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<float>((m_planet.width - m_TileSize)-(m_Sprite.getGlobalBounds().width/2))) {

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

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

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

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

In the parameters of the void update(sf::Time deltaTime, sf::Vector2i mousePosition) method, we pass the time unit deltaTime and the position of the mouse cursor mousePosition. Using the m_time_moving property, we calculate the time interval.

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

If the player is in motion m_animMove and his life m_Health is greater than zero, play the movement animation.

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

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

If the player’s life m_Health is zero or less than zero, change the animation to the death animation and play it.

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

If the animation is finished playing getEndAnim(), set the m_live player status to dead.

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;
                // дальше продолжается код 

In this section of the code, with an interval of 5000 microseconds, we move the character according to the set m_move movement vector.

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

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

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

We make a check for the character to go beyond the boundaries of the playing field and return it back if this happened.

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

Using the formula, we calculate the angle angle at which the player is looking.

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

We set new coordinates of the sprite, and rotate the sprite by the previously calculated angle angle.

MonsterPlanet.h

#pragma once
#include <SFML/Graphics.hpp>
#include <random>

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

Create header file planet of monsters. We declare the function for constructing the game background int createBackground(sf::VertexArray& rVA, sf::IntRect planet, int index).

In the parameters, we pass a reference to the array of vertices rVA, the size of the playing field planet, the number of the game level 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<float>(w * TILE_SIZE), static_cast<float>(h * TILE_SIZE));
            rVA[currentVertex + 1].position = sf::Vector2f(static_cast<float>((w * TILE_SIZE) + TILE_SIZE), static_cast<float>(h * TILE_SIZE));
            rVA[currentVertex + 2].position = sf::Vector2f(static_cast<float>((w * TILE_SIZE) + TILE_SIZE), static_cast<float>((h * TILE_SIZE) + TILE_SIZE));
            rVA[currentVertex + 3].position = sf::Vector2f(static_cast<float>(w * TILE_SIZE), static_cast<float>((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<float>(verticalOffset), TILE_SIZE * (index - 1));
                rVA[currentVertex + 1].texCoords = sf::Vector2f(static_cast<float>(verticalOffset + TILE_SIZE), TILE_SIZE * (index - 1));
                rVA[currentVertex + 2].texCoords = sf::Vector2f(static_cast<float>(verticalOffset + TILE_SIZE), TILE_SIZE * index);
                rVA[currentVertex + 3].texCoords = sf::Vector2f(static_cast<float>(verticalOffset), TILE_SIZE * index);
            }
            // позиция готова для следующих четырех вершин
            currentVertex = currentVertex + VERTS_IN_QUAD;
        }
    }
    return TILE_SIZE;
}

In the function definition, we start the random number generator. We initialize the variables.

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

Set the coordinates for each vertex of the quadrilateral.

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<float>(verticalOffset), TILE_SIZE * (index - 1));
                rVA[currentVertex + 1].texCoords = sf::Vector2f(static_cast<float>(verticalOffset + TILE_SIZE), TILE_SIZE * (index - 1));
                rVA[currentVertex + 2].texCoords = sf::Vector2f(static_cast<float>(verticalOffset + TILE_SIZE), TILE_SIZE * index);
                rVA[currentVertex + 3].texCoords = sf::Vector2f(static_cast<float>(verticalOffset), TILE_SIZE * index);
            }

For the border of the playing field, we use the texture of the wall, the rest of the playing field is made with the texture of the landscape in random order.

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

We change the position of the iteration of the currentVertex array of vertices. Return the texture size TILE_SIZE.

GameEngine class

GameEngine.h

#pragma once
#include<iostream>
#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<float>(sf::VideoMode::getDesktopMode().width), 
		static_cast<float>(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());
}

In the constructor, we set the parameters of the graphical window m_window, the state of the game m_state, the size of the view m_mainView and the coordinates of the playing field 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);
				}
			} // конец игры
	}
}

In the event handling method void input(), by pressing the space bar, we change the state of the game from loading to showing the splash screen, then to loading the level. Press the spacebar again and start the game. Later in this section of code, we will implement the game loading splash screen and background.

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);
}

To be continued…

You can get more detailed instructions by watching the video “SFML C++ games Shooter Project Koschei #1”

Clone repository

Telegram channel “C++/C# game programming

Previous topic

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *