Пишем собственный игровой движок с помощью C++

С нуля создадим собственный игровой движок с помощью библиотеки SFML и C++, чтобы разобраться, как происходит создание ядра.

4

В этом проекте мы создадим собственный игровой движок на C++. Движок будет очень простым, но готовым к расширению возможностей. Конечная игра с использованием этого кода тоже крайне проста: наш персонаж сможет перемещаться влево и право, а из графики – только бэкграунд и фигурка персонажа.

background with pers

Подготовка Visual Studio

Создадим новый проект в Visual Studio. Обратите внимание, что проект требует библиотеку SFML, поэтому если вы не настраивали окружение для нее, прочтите сначала небольшое руководство по настройке.

Настройка VS:

  1. Откройте Visual Studio и выберите File | New Project. В левом меню отметьте язык C++ и шаблон HelloSFML. Назовите проект Simple Game Engine.
  2. Выберите правой кнопкой мыши файл HelloSFML.cpp в окне Solution Explorer (под Source Files), далее – Rename и назовите файл Main. Это подходящее имя, так как файл будет содержать основную функцию.
  3. Откройте Main.cpp, удалите все содержимое файла.
  4. Переместите файл библиотеки SFML.dll из каталога Диск:\SFML\bin в Диск:\Visual Studio Stuff\Projects\Simple Game Engine\Simple Game Engine. Если названия в путях отличаются, вероятно, вы сделали отличную от руководства настройку.

Теперь можно приступить к коду. Исходный код и дополнительные ресурсы будут доступны на этой странице.

Проектируем собственный игровой движок

Самое важное – запуск движка, который будет происходить в файле Main.cpp, но им мы займемся немного позже.

Класс персонажа

Bob – простой класс для представления фигурки персонажа, управляемой игроком. Код класса будет легко расширяться, а что самое главное – его несложно переписать под любой другой игровой объект, который вы захотите добавить. Для этого потребуется заменить текстуру и описать поведение нового объекта в методе update().

Bob.h

Займемся заголовками класса. Выберите правой кнопкой Header Files в Solution Explorer и нажмите Add | New Item. В окне Add New Item выберите Header File (.h), затем в поле Name введите Bob. Нажмите Add и добавьте код заголовка класса:

#pragma once
#include <SFML/Graphics.hpp>
 
using namespace sf;
 
class Bob
{
    // Все private переменные могут быть доступны только внутри объекта
private:
 
    // Позиция Боба
    Vector2f m_Position;
 
    // Объявляем объект Sprite
    Sprite m_Sprite;
 
    // Добавдяем текстуру
    Texture m_Texture;
 
    // Логические переменные для отслеживания направления движения
    bool m_LeftPressed;
    bool m_RightPressed;
 
    // Скорость Боба в пикселях в секунду
    float m_Speed;
 
    // Открытые методы
public:
 
    // Настраиваем Боба в конструкторе
    Bob();
 
    // Для отправки спрайта в главную функцию
    Sprite getSprite();
 
    // Для движения Боба
    void moveLeft();
 
    void moveRight();
 
    // Прекращение движения
    void stopLeft();
 
    void stopRight();
 
    // Эта функция будет вызываться на каждый кадр
    void update(float elapsedTime);
 
};

Здесь мы объявили объекты типа Texture и Sprite. Дальше мы свяжем эти объекты и любое действие на экране с объектом Sprite будет сопровождаться изображением Боба:

bob

Кликните правой кнопкой, чтобы сохранить

Bob.cpp

Теперь приступим к описанию методов.

Выберите правой кнопкой мыши Source Files в Solution Explorer и откройте Add | New Item. В окне Add New Item кликните по C++ File (.cpp), а в поле Name укажите Bob.cpp. Теперь добавьте в файл код:

#include "stdafx.h"
#include "bob.h"
 
Bob::Bob()
{
    // Вписываем в переменную скорость Боба
    m_Speed = 400;
 
    // Связываем текстуру и спрайт
    m_Texture.loadFromFile("bob.png");
    m_Sprite.setTexture(m_Texture);     
 
    // Устанавливаем начальную позицию Боба в пикселях
    m_Position.x = 500;
    m_Position.y = 800;
 
}
 
// Делаем приватный спрайт доступным для функции draw()
Sprite Bob::getSprite()
{
    return m_Sprite;
}
 
void Bob::moveLeft()
{
    m_LeftPressed = true;
}
 
void Bob::moveRight()
{
    m_RightPressed = true;
}
 
void Bob::stopLeft()
{
    m_LeftPressed = false;
}
 
void Bob::stopRight()
{
    m_RightPressed = false;
}
 
// Двигаем Боба на основании пользовательского ввода в этом кадре,
// прошедшего времени и скорости
void Bob::update(float elapsedTime)
{
    if (m_RightPressed)
    {
        m_Position.x += m_Speed * elapsedTime;
    }
 
    if (m_LeftPressed)
    {
        m_Position.x -= m_Speed * elapsedTime;
    }
 
    // Теперь сдвигаем спрайт на новую позицию
    m_Sprite.setPosition(m_Position);   
 
}

В конструкторе мы установили значение переменной m_Speed на 400. Это значит, что Боб пересечет экран шириной в 1920 пикселей за 5 секунд. Также мы загрузили файл Bob.png в Texture и связали его с объектом Sprite. В переменных m_Position.x и m_Position.y установлено начальное положение Боба.

Функция update обрабатывает два If. Первое If проверяет, нажата ли правая кнопка (m_RightPressed), а второе следит за левой (m_LeftPressed). В каждом If скорость (m_Speed) умножается на elapsedTime. Переменная elapsedTime рассчитывается в функции Start движка (класс Engine). Им мы и займемся далее.

Пишем класс Engine

Класс Engine будет контролировать все остальное.

Engine.h

Добавим заголовок. Откройте окно Add New Item (так же, как для класса Bob), выберите Header File (.h) и в поле Name введите Engine.h. Добавьте в файл следующий код:

#pragma once
#include <SFML/Graphics.hpp>
#include "Bob.h";
 
using namespace sf;
 
class Engine
{
private:
 
    RenderWindow m_Window;  
 
    // Объявляем спрайт и текстуру для фона
    Sprite m_BackgroundSprite;
    Texture m_BackgroundTexture;
 
    // Экземпляр Боба
    Bob m_Bob;
 
    void input();
    void update(float dtAsSeconds);
    void draw();
 
public:
    // Конструктор движка
    Engine();
 
    // Функция старт вызовет все приватные функции
    void start();
 
};

Класс библиотеки SFML, RenderWIndow, используется для рендера всего, что есть на экране. Переменные Sprite и Texture нужны для создания фона. Также в заголовке мы создали экземпляр класса Bob.

Engine.cpp

В Engine.cpp мы опишем конструктор и функцию start. Создайте файл класса так же, как для Bob.cpp, и добавьте в него код:

#include "stdafx.h"
#include "Engine.h"
 
Engine::Engine()
{
    // Получаем разрешение экрана, создаем окно SFML и View
    Vector2f resolution;
    resolution.x = VideoMode::getDesktopMode().width;
    resolution.y = VideoMode::getDesktopMode().height;
 
    m_Window.create(VideoMode(resolution.x, resolution.y),
        "Simple Game Engine",
        Style::Fullscreen);
 
    // Загружаем фон в текстуру
    // Подготовьте изображение под ваш размер экрана в редакторе
    m_BackgroundTexture.loadFromFile("background.jpg");
 
    // Связываем спрайт и текстуру
    m_BackgroundSprite.setTexture(m_BackgroundTexture);
 
}
 
void Engine::start()
{
    // Расчет времени
    Clock clock;
 
    while (m_Window.isOpen())
    {
        // Перезапускаем таймер и записываем отмеренное время в dt
        Time dt = clock.restart();
 
        float dtAsSeconds = dt.asSeconds();
 
        input();
        update(dtAsSeconds);
        draw();
    }
}

Функция конструктора получает разрешение экрана и разворачивает игру на весь экран с помощью m_Window.create. В конструкторе же загружается Texture и связывается с объектом Sprite.

background

Пример фонового изображения

Скачайте пример изображения или используйте любое другое на свое усмотрение. Переименуйте файл в background.jpg и поместите в каталог Simple Game Engine/Simple Game Engine.

Игровой цикл

Следующие три функции будут описаны каждая в своем файле, но при этом они должны быть частью Engine.h. Поэтому в начале каждого файла укажем директиву #include «Engine.h», так что Visual Studio будет знать, что мы делаем.

Обрабатываем пользовательский ввод

Создайте файл Input.cpp и добавьте в него код:

#include "stdafx.h"
#include "Engine.h"
 
void Engine::input()
{
    // Обрабатываем нажатие Escape
    if (Keyboard::isKeyPressed(Keyboard::Escape))
    {
        m_Window.close();
    }
 
    // Обрабатываем нажатие клавиш движения
    if (Keyboard::isKeyPressed(Keyboard::A))
    {
        m_Bob.moveLeft();
    }
    else
    {
        m_Bob.stopLeft();
    }
 
    if (Keyboard::isKeyPressed(Keyboard::D))
    {
        m_Bob.moveRight();
    }
    else
    {
        m_Bob.stopRight();
    }                       
 
}

Функция input обрабатывает нажатия клавиш через константу Keyboard::isKeyPressed, предоставляемую SFML. При нажатии Escape m_Window будет закрыто. Для клавиш A и D вызывается соответствующая функция движения.

Обновляем игровые объекты

Теперь опишем простую функцию update. Каждый игровой объект будет иметь собственную функцию update.

Создайте файл Update.cpp и добавьте в него код:

#include "stdafx.h"
#include "Engine.h"
 
using namespace sf;
 
void Engine::update(float dtAsSeconds)
{
    m_Bob.update(dtAsSeconds);
}

Поскольку у нас пока только один объект "Боб", мы вызываем только функцию m_Bob.update.

Отрисовка сцены

Это последняя функция класса Engine. Создайте файл Draw.cpp и добавьте в него код:

#include "stdafx.h"
#include "Engine.h"
 
void Engine::draw()
{
    // Стираем предыдущий кадр
    m_Window.clear(Color::White);
 
    // Отрисовываем фон
    m_Window.draw(m_BackgroundSprite);
    m_Window.draw(m_Bob.getSprite());
 
    // Отображаем все, что нарисовали
    m_Window.display();
}

Экран очищается методом clear, затем отрисовывается фон. Первым делом должен быть отрисован фон, чтобы потом поверх него можно было отрисовать Боба.

Запускаем движок в main()

Теперь вернемся к нашему Main.cpp. Время добавить в него немного кода:

#include "stdafx.h"
#include "Engine.h"
 
int main()
{
    // Объявляем экземпляр класса Engine
    Engine engine;
 
    // Вызываем функцию start
    engine.start();
 
    // Останавливаем программу программу, когда движок остановлен
    return 0;
}

Несколько слов в конце

Наш собственный игровой движок получился очень простым: он умеет только двигать главный объект и закрывать программу. Он не умеет обрабатывать столкновения, работать с интерфейсом и еще много чего. Однако он отлично описывает то, как строится ядро игрового проекта с нуля. К тому же, как мы уже выяснили, класс Bob расширяется и адаптируется под другие объекты, так что дайте волю фантазии и попробуйте поэкспериментировать с окружением.

Больше о разработке игр:

МЕРОПРИЯТИЯ

Комментарии

 
 
15 апреля 2020

У меня фон не на весь экран и Боба вообще нет на Экране, хотя ошибок никаких нет

01 ноября 2019

Не компилируется уже на первой строчке

01 ноября 2019

Не работает

ВАКАНСИИ

Добавить вакансию
Senior Marketing Analyst
по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

LIVE >

Подпишись

на push-уведомления