Sawing the Arcanum engine. Lesson 01. The beginning

  1. Motivation

    First of all, it is of course a personal passion for this wonderful game. Many hundreds of hours were spent on passing different characters, opening new locations. Different types of passing, ha mage, techie, attempts to balance between magic and technology.

  2. The choice of programming language is, of course, C++.

    In 2024, I have 100500 different languages ​​at my disposal. New, old, with a garbage collector, without a garbage collector. But preference is given to C++. First of all, its knowledge, understanding and several professional projects developed on it.

  3. Portability.

    In the 21st century, writing non-portable code is not even bad manners, it's just cringe-worthy, having a huge number of cross-platform libraries, compilers for each platform, and screwing up only for Windows. That's why I chose the SDL library for this project. I also like the idea of ​​ensuring compatibility with older versions of Windows 95, 98 and Linux (Debian 3), for this I will use the SDL 1.2 library for older versions of operating systems, and SDL2 for modern Windows and Linux. That's why I will use the C++ 98 language standard (Oh my God!).

    This is the only simple way that can ensure compatibility. In principle, it is not so bad. C++ 98 has a quite acceptable set of containers and in any case it is an order of magnitude better than C in capabilities and expressiveness. I have no particular worries about this. I will be glad if in the comments someone offers another option, for example, the ability to write in C++ 11-17 and compile under Windows 95.

    The main thing is no compiler-specific code. The project for all systems for all architectures should be built from one code base and a minimum number of ifdefs. Support 32- and 64-bit builds.

  4. Development tools.

    I plan to develop on Windows 10, Visual Studio 2022, cmake. For manual assembly, there are also bat files. For compatibility with Windows 95 and 98, I use the Visual C++ 6.0 compiler. For assembly under Debian 3, the gcc 3 compiler.

  5. Performance

    I plan to monitor performance and optimize the code at all stages of game engine development. Arcanum is a game from the early 2000s, with ridiculous system requirements for the current time, so I want to keep similar requirements or at least not increase them by an order of magnitude.

    For performance testing I will use emulators like x86box, as well as my retro PC with Pentium 4 (with a reduced frequency to 1000 mhz) and Geforce 4.

  6. Architecture

    In my opinion, it is necessary to support simplicity as much as possible, both architecturally and in the frequency of the codebase. Not to get carried away writing everything according to SOLID, but also not to slide into multi-thousands of function sheets. Everything in moderation. Classes are simple and small, implementing one functionality. Dependency between classes is passed through the constructor. Interfaces are almost never used. Each class depends on a specific class. The engine is divided by code into 2 libraries: Arcanum as a game, and the 2D isometric engine itself (Pollux). This will allow it to be used in the future for other old games, the engine itself will expand along the way, subsystems will be generalized, for example, working with a map and tiles.

    The Pollux engine contains a single API over SDL1 and SDL2, so that the engine and games can be written once, without changing the code or adding ifdefs.

  7. General development process.

    I develop in a single repository. Each branch is a lesson in which I reveal the topic. I don't see the point in posting cpp files in the article. I'll limit myself to hpp files with a short description of simple things and a more voluminous one for complex ones.

    To simplify the project assembly out of the box, SDL dependencies for Windows are located directly in the repo in assembled form, dll and lib. Yes, I understand that this is not done, but this allows you not to install msys, cmake, spend a long time setting up paths to the compiler, libraries, etc. Do git clone in Visual Studio and press build.

  8. Code style.

    I apologize, but I write in C# for work, so the code is CSharp style. Please understand and forgive me 🙂 In the future, I will drag some clang format into the project.

  9. About the game file format.

    The new engine will work only with the game's graphic and sound formats. Other formats such as dialogs, scripts, map formats, and object prototypes will have the xml text format. Scripts will be written in C++, which allows you not to be distracted by embedding the scripting language and its binding. The engine will also support modern graphic formats, jpeg, png. Arcanum's palette graphics will be converted to rgb when displayed on the screen.

  10. Links to lessons. Lessons are in ArcanumTutorial_Habrswitch to branch ArcanumTutorial_01_Start.

I think the input is enough to draw general conclusions. Now let's go write the code.

The first lesson is a minimum of code. I will describe more about the project infrastructure.

Turnip – Lesson 1.

Now the engine can only open and close a window. This functionality is enough for us to check the operability and compilation of the project on all systems. Below are screenshots.

Each functionality is located in its own subfolder.

Pollux

Events – OS event system, pressing, mouse click, etc.

EventHandler allows you to convert SDL_Event events into engine events.

bool EventHandler::GetEvent(Event& dstEvent)
{
	SDL_Event event = { 0 };

	if (_Running)
	{
		SDL_PollEvent(&event);

		if (event.type == SDL_QUIT)
		{
			dstEvent.Type = IsEventQuit;
		}
		else if (event.type == SDL_MOUSEMOTION)
		{
			dstEvent.Type = IsEventMove;
			dstEvent.Move.PosX = event.motion.x;
			dstEvent.Move.PosY = event.motion.y;
		}
	}

	return _Running;
}

Grphics – working with graphics

For now, the Canvas class is available, which can initialize a window; in the next lessons, I will add texture drawing.

In the constructor, we initialize the window and the SDL renderer. The Canvas::Present method is responsible for updating the window.

#include <Pollux/Graphics/Canvas.hpp>
#include <stdexcept>

using namespace Pollux;

Canvas::Canvas(const Point& size) :
	_Window(NULL),
	_Render(NULL),
	_Size(size)
{
	if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
		throw std::runtime_error(SDL_GetError());

	_Window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, _Size.x, _Size.y, SDL_WINDOW_SHOWN);

	if (!_Window)
		throw std::runtime_error(SDL_GetError());

	_Render = SDL_CreateRenderer(_Window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);

	if (!_Render)
		throw std::runtime_error(SDL_GetError());
}

Canvas::~Canvas()
{
	SDL_DestroyRenderer(_Render);
	SDL_DestroyWindow(_Window);
	SDL_Quit();
}

const Point& Canvas::Size() const
{
	return _Size;
}

void Canvas::Present()
{
	SDL_RenderPresent(_Render);
}

SDL_Renderer* Canvas::GetRenderImpl()
{
	return _Render;
}

Arcanum

—Game – Code related to the game

Minimum game engine.

#include <Arcanum/Game/Engine.hpp>

using namespace Arcanum;
using namespace Pollux;

Engine::Engine() :
	_Canvas(Point(800, 600))
{
}

Engine::~Engine()
{
}

void Engine::Run()
{
	Event report;

	while (_EventHandler.GetEvent(report))
	{
		if (report.Type == IsEventQuit)
		{
			_EventHandler.StopEvent();
		}

		_Canvas.Present();
	}
}

We initialize the window, start the message handler and wait for the user to click exit.

#include <Arcanum/Game/Engine.hpp>

using namespace Arcanum;

int main(int argc, char* argv[])
{
	Engine engine;
	engine.Run();

	return 0;
}

This is what cmake looks like.


if (MSVC)
    add_definitions(-D_CRT_SECURE_NO_WARNINGS)
    add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)  
endif()

if (WIN32)
    cmake_minimum_required(VERSION 2.9)

    set(SDL2_INCLUDE_DIRS "dependencies/SDL2-2.30.3/include")
    set(SDL2_LIBRARIES SDL2main SDL2)

    if(CMAKE_SIZEOF_VOID_P EQUAL 8)
        link_directories("dependencies/SDL2-2.30.3/lib/x64")
    elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
        link_directories("dependencies/SDL2-2.30.3/lib/x86")
    endif()
else()
    find_package(SDL2 REQUIRED)
endif()

include_directories(${SDL2_INCLUDE_DIRS})

include_directories("Pollux/SDL2")
file(GLOB_RECURSE POLLUX_SOURCES "Pollux/SDL2/*.cpp")

include_directories("Pollux/Shared")
file(GLOB_RECURSE SHARED_SOURCES "Pollux/Shared/*.cpp")

include_directories("Arcanum/Shared")
file(GLOB_RECURSE ARCANUM_SOURCES "Arcanum/Shared/*.cpp")

add_executable(Arcanum "main.cpp" ${POLLUX_SOURCES} ${SHARED_SOURCES} ${ARCANUM_SOURCES})
target_link_libraries(Arcanum ${SDL2_LIBRARIES})

Also in the make directory, there are batch files for building with mingw and visual C++ 6.0

Now let's make sure it works.

Windows 98 – SDL1

Lubuntu 22.04

In the next lesson, we will get acquainted with other formats and output the first sprite.

The article turned out to be more of a review and there is more text than code, but we will pick up the pace in the next lessons.

Thank you for your attention. I will be glad to receive criticism, advice and suggestions both on the code and on the design of the articles.

Similar Posts

Leave a Reply

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