📜 ⬆️ ⬇️

Urho3D: Games in earnest

In previous articles ( Basics , Editor: Part 1 and Editor: Part 2 ) we created small AngelScript applications. This time I want to show that, thanks to the engineered structure of the engine, writing games in such a terrible language as C ++ is as easy as in a scripting language. And so that you would not be too boring to read, I prepared a small game (clone Flappy Bird), 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

Considered version of the engine


A little thought, I decided to clarify in my articles exactly which version of the engine is being considered, since the engine is actively developing and sometimes changes occur that break backward compatibility.

Version of 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 

Minimum application


 #include <Urho3D/Engine/Application.h> //     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 class Urho3D :: Object (and in Urho3D most of them) must contain the macro URHO3D_OBJECT (Class name, name Base Class).
')
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) is actively used in the engine, for example, in the object factory, about which later. See also urho3d.imtqy.com/documentation/1.5/_object_types.html .

Make life easier


Compare the rewritten in C ++ example with the rotating cube from the first article with the original . As you can see, the difference is minimal. However, the abundance of header files that were required to be connected even in such a small example is striking. Sometimes there is a desire to just take and connect all the header files of the engine at once. This is not quite professional, but convenient, so you can use this wonderful file: github.com/1vanK/Urho3DAll.h .

Compiling your project


We assume that you already know how to compile the engine ( Fundamentals of Urho3D ). I want to note only that it is 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 a compact executable file. For example, for the “Flappy Urho” game mentioned above, I turned off scripts, network, navigation, and left only physics. One small tip: when using cmake-gui, turn on the “Grouped” checkbox in order not to drown in the settings, since more recently the SDL parameters also appeared in the list.

To generate a project for your game, you need to create a CMakeLists.txt file in the source folder, the template of which is here: urho3d.imtqy.com/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 a game project, you need to set the same settings that you used when configuring the engine itself.

IMHO.
Personally, it seems to me inconvenient that you need to memorize 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 this possibility is left, apparently, for some exotic cases. For example, you can compile a logging engine, and disable logging for the game itself. And then only engine messages will be written to the log.

Register components


Remember that each component you create must be registered with Context :: RegisterFactory () before it can be used. As 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 the deep jungle, you can describe the factory as a mechanism 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 parsit, for example, the text
 <node id="2"> ... <component type="StaticModel" id="3"> .... </component> </node> 
it will be able to create and attach the required StaticModel component to the node.

Scene change 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. A situation where half of the game cycle is executed in one state of the game, and the other in another, can lead to serious logical errors and it will be extremely problematic to resolve them. And for a more or less large project with many components to keep track of all the logical connections and deal with errors when pieces of code work in different game states in general is unrealistic.

The solution to this is not to change the state of the game instantly, but to store the desired state in an additional variable and to actually change the state at the beginning of the next iteration of the game cycle before processing any events. For an example, see the file github.com/1vanK/FlappyUrho/blob/master/GameSrc/Global.h , which declares two gameState_ variables (the current game state) and neededGameState_ (the required game state) and the source github.com/1vanK/FlappyUrho /blob/master/GameSrc/Game.cpp , which implements a state transition 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 try to remove the current scene from memory in the handler of one of the events and load the other one, the game may crash altogether when the engine goes further to the loop and 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 go too deeply into this topic, since this information is full on the Internet, I’ll just make a few comments:


By the way.
In the game “Flappy Urho” I didn’t 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.imtqy.com/documentation/HEAD/_conventions.html .

Own subsystems


For access to global variables and functions it is 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” (subsystem Global). See also urho3d.imtqy.com/documentation/1.5/_subsystems.html .

Attributes


There is nothing special to explain here, but I had to mention them. Attributes allow you to automate the serialization / deserialization of objects, which is performed when they are loaded and saved to disk, as well as during network replication. See urho3d.imtqy.com/documentation/1.5/_serialization.html for more details.

Thanks for attention!

Source: https://habr.com/ru/post/280752/


All Articles