Urho3D: Serious Games

  • Tutorial
In past articles ( Fundamentals , Editor: Part 1 and Editor: Part 2 ), we created small applications in the AngelScript language. This time I want to show that, thanks to the well-thought-out structure of the engine, writing games in such a terrible language as C ++ is as easy as writing in a scripting language. And so that you would not be too bored to read, I prepared a small game (Flappy Bird clone), which can be downloaded here: github.com/1vanK/FlappyUrho . By the way, the source code of the game can be read as an independent article, because it is commented in great detail.

image

The considered version of the engine


After thinking a bit, I decided to clarify in my articles which version of the engine is being considered, since the engine is actively developing and sometimes changes occur that break backward compatibility.

Version April 9, 2016.
:: Указываем путь к git.exe
set "PATH=c:\Program Files (x86)\Git\bin\"
:: Скачиваем репозиторий
git clone https://github.com/Urho3D/Urho3D.git
:: Переходим в папку со скачанными исходниками
cd Urho3D
:: Возвращаем состояние репозитория к определённой версии (9 апреля 2016)
git reset --hard 4c8bd3efddf442cd31b49ce2c9a2e249a1f1d082
:: Ждём нажатия ENTER для закрытия консоли
pause

Minimal application


#include 
// Чтобы везде не писать Urho3D::ИмяТипа.
using namespace Urho3D;
// Главный класс игры.
class Game : public Application
{
    // Макрос добавляет в класс информацию о текущем и базовом типе.
    URHO3D_OBJECT(Game, Application);
public:
    // Конструктор класса.
    Game(Context* context) : Application(context)
    {
    }
};
// Указываем движку главный класс игры.
URHO3D_DEFINE_APPLICATION_MAIN(Game)

I draw your attention to the first feature: each class that is derived from the Urho3D :: Object class (and most of them are in Urho3D) must contain the URHO3D_OBJECT macro (Class name, Base Class name).

For the curious.
This macro is defined in the Urho3D / Core / Object.h file and, among other things, saves the class name as a string, which (name) in the engine is actively used, for example, in the object factory, about which a little later. See also urho3d.github.io/documentation/1.5/_object_types.html .

Make life easier


Compare the example written in C ++ with the rotating cube from the first article with the original . As you can see, the difference is minimal. However, an abundance of header files catches your eye, which you needed to connect even in such a small example. Sometimes there is a desire to simply take and connect all the header files of the engine at once. This is not very professional, but convenient, so you can use this miracle file: github.com/1vanK/Urho3DAll.h .

Compiling your project


We assume that you already know how to compile the engine ( Urho3D Fundamentals ). I just want to note that it can be convenient to have several different engine configurations: the full version (which will be used as an editor and test site) and individual versions for specific games with the minimum necessary functionality for the compact size of the executable file. For example, for the “Flappy Urho” game mentioned above, I turned off scripts, network, navigation and left only physics. One small piece of advice: when using cmake-gui, turn on the “Grouped” checkbox so that you don’t drown in the settings, since recently SDL parameters have also appeared in the list.

To generate a project for your game, you need to create the CMakeLists.txt file in the source folder, the template of which is here:urho3d.github.io/documentation/1.5/_using_library.html . A slightly modified version that I usually use:

# Название проекта
project (Game)
# Имя результирующего исполняемого файла
set (TARGET_NAME Game)
# Можно не использовать переменные окружения, а указать путь к скомпилированному движку в самом скрипте
set (ENV{URHO3D_HOME} D:/MyGames/Engine/Build)
# Бывает удобно не копировать папку CMake в директорию с исходниками игры, а просто указать путь к ней
set (CMAKE_MODULE_PATH D:/MyGames/Engine/Urho3D/CMake/Modules)
# Остальное менять не нужно
cmake_minimum_required (VERSION 2.8.6)
if (COMMAND cmake_policy)
    cmake_policy (SET CMP0003 NEW)
    if (CMAKE_VERSION VERSION_GREATER 2.8.12 OR CMAKE_VERSION VERSION_EQUAL 2.8.12)
        cmake_policy (SET CMP0022 NEW)
    endif ()
    if (CMAKE_VERSION VERSION_GREATER 3.0.0 OR CMAKE_VERSION VERSION_EQUAL 3.0.0)
        cmake_policy (SET CMP0026 OLD)
        cmake_policy (SET CMP0042 NEW)
    endif ()
endif ()
include (Urho3D-CMake-common)
find_package (Urho3D REQUIRED)
include_directories (${URHO3D_INCLUDE_DIRS})
define_source_files ()
setup_main_executable ()

After that, use CMake in the usual way, just note that when generating the game project you need to set the same settings that you used to configure the engine itself.

IMHO.
Personally, it seems inconvenient to me to remember and repeat the settings manually instead of baking them into the header file when configuring the engine (although the most important ones are still stored in Urho3D.h). But such an opportunity was left, apparently, for some exotic cases. For example, you can compile an engine with support for logging, and disable logging for the game itself. And then only messages from the engine will be written to the log.

Register components


Remember that each component that you create must be registered with Context :: RegisterFactory () before use. For an example, see github.com/1vanK/FlappyUrho/blob/master/GameSrc/EnvironmentLogic.cpp .

For the curious.
Instances of components (this also applies to resources and interface elements, but it is unlikely that you will implement them yourself) are created through the object factory. If you do not get into deep jungle, you can describe the factory as a mechanism that allows you to create objects by type name. For example, when loading a scene from an XML file, we have nothing but a set of lines. And when the engine parses, for example, the text

    ...
    
        ....
    
he will be able to create and attach to the node the required StaticModel component.

Change of scenes and game state


The golden rule: never destroy scenes or change the state of the game in the middle of the game cycle.

Consider an example:

class Game : public Application
{
    void HandleUpdae(...)
    {
        Если была нажата клавиша ESC и состояние игры == игровой процесс,
            то состояние игры = главное меню.
    }
}
class UILogic : public LogicComponent
{
    void Update(...)
    {
        Если была нажата клавиша ESC и состояние игры == главное меню,
            то состояние игры = игровой процесс.
    }
}

It turns out that if a player presses the ESC key, then this press will be processed twice in different places of the program and as a result the player will not see the main menu. The situation when half of the game cycle is performed in one state of the game, and the other in another, can lead to serious logical errors and it will be extremely difficult to resolve them. And for a more or less large project with many components, keeping track of all logical connections and dealing with errors when pieces of code work in different game states is generally unrealistic.

The solution to this is not to change the state of the game instantly, but to store the required state in an additional variable and actually change the state at the beginning of the next iteration of the game cycle until any events are processed. See file for an example.github.com/1vanK/FlappyUrho/blob/master/GameSrc/Global.h , which declares two variables gameState_ (current state of the game) and neededGameState_ (required state of the game) and the source github.com/1vanK/FlappyUrho/blob/master/ GameSrc / Game.cpp , which implements a state change in the HandleBeginFrame handler of the main class of the game.

Another situation: the player pressed the button and he needs to go to the next level. If you, in the handler of one of the events, try to delete the current scene from memory and load another, then the game may even crash when the engine, going further than the loop, tries to access objects that no longer exist. The problem is solved in a similar way.

Smart pointers


One of the advantages of scripting languages ​​is the automatic release of memory for unused objects. Smart pointers add this convenience to the C ++ language. I will not particularly delve into this topic, since this information is full on the Internet, I will just make a few comments:

  • As long as there is at least one strict pointer, Urho3D :: SharedPtr, pointing to some object, this object will exist.
  • The weak pointer Urho3D :: WeakPtr does not prevent the object from being destroyed, which means it helps to solve the problem of link cycling. It looks like a regular pointer, but it allows you to know for sure whether the object was destroyed.
  • Urho3D uses intrusive reference counting, that is, unlike std :: shared_ptr, the counter is located in the object itself. This allows you to pass regular pointers to functions.
  • Creating smart pointers in a global scope is a bad idea. When you exit the game, you can get a crash when the pointer accesses the memory already freed when the context is destroyed.

By the way.
In the game “Flappy Urho” I did not use smart pointers at all, since all the necessary objects are created at the beginning of the game and exist throughout the program. Therefore, refer to the examples supplied with the engine. See also urho3d.github.io/documentation/HEAD/_conventions.html .

Custom Subsystems


To access global variables and functions, it can be very convenient to create your own subsystem. A subsystem is a regular Urho3D object that exists in a single instance. After registering a subsystem using the Context :: RegisterSubsystem () function, you can access it from any object like any other subsystem using the GetSubsystem <...> () method. This approach is used in the game “Flappy Urho” (Global subsystem). See also urho3d.github.io/documentation/1.5/_subsystems.html .

Attributes


There is nothing much to explain here, but I should have mentioned them. Attributes allow you to automate serialization / deserialization of objects, which is performed when they are downloaded and saved to disk, as well as during network replication. See urho3d.github.io/documentation/1.5/_serialization.html for details .

Thanks for attention!

Also popular now: