Writing game logic, triggers, input, raycasting and more.
Especially for those who are looking for an alternative to Unreal Engine or Unity, we continue a series of articles about a painless transition to UNIGINE from foreign engines. In the third issue, we will look at migration from Unreal Engine 4 from a programmer’s point of view.
general information
Game logic in a project on Unreal Engine 4 is implemented using classes C++ or Blueprint Visual Scripting — built-in visual node programming system. The Unreal Engine 4 editor allows you to create classes using the built-in Class Wizard by selecting the desired base type.
With UNIGINE you can create projects using the C++ and C# APIs. When creating a project, simply select the desired API and build system:
In this article, we will mainly touch on C++ programming, because full-fledged programming in Unreal Engine 4 is possible in this language.
For C++, you can choose from ready-made project templates for the following build systems:
Next, just select Open Code IDEto move on to developing logic in your chosen IDE for C++ projects:
In Unreal Engine 4, it is enough to inherit a class from the Game Framework base types, such as AActor, APawn, ACharacter, etc., to override their behavior in standard methods BeginPlay(), Tick() and EndPlay() and get the custom actor.
The component approach implies that the logic is implemented in custom components assigned to actors – classes inherited from UActorComponent and other components that extend the standard behavior defined in the methodsInitializeComponent() and TickComponent().
In UNIGINE, the standard approach implies that application logic consists of three main components with different life cycles:
System logic (source file AppSystemLogic.cpp) exists for the lifetime of the application.
The logic of the world (source file AppWorldLogic.cpp) is only executed when the world is loaded.
Editor logic (source file AppEditorLogic.cpp) is executed only while the custom editor is running.
Every logic has standard methods, called in the main engine loop. For example, you can use the following world logic methods:
init() – to initialize resources when loading the world;
update() – to update every frame;
shutdown() – to destroy used resources when closing the world;
Keep in mind that the logic of the world is not tied to a specific world and will be called for any loaded world. However, you can split world-specific code between separate classes inherited from WorldLogic.
The component approach is also available in UNIGINE using the built-in component system. The component logic is defined in a class derived from ComponentBasebased on which the engine will generate a set of component parameters – property, which can be assigned to any node in the editor. Each component also has a set of methods that are called by the corresponding functions. main loop engine.
For an example of creating a simple game using a component system, see the article series “Quick Guide to Programming”.
Let’s compare how simple components are created in both engines. component header file Unreal Engine 4 will look something like this:
UCLASS()
class UMyComponent : public UActorComponent
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere)
int32 TotalDamage;
// Called after the owning Actor was created
void InitializeComponent();
// Called when the component or the owning Actor is being destroyed
void UninitializeComponent();
// Component version of Tick
void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction);
};
And in UNIGINE. The component system must first be initialized in the system logic (AppSystemLogic.cpp):
#pragma once
#include <Unigine.h>
#include <UnigineComponentSystem.h>
using namespace Unigine;
class MyComponent : public ComponentBase
{
public:
// объявление компонента MyComponent
COMPONENT(MyComponent, ComponentBase);
// объявление методов, вызываемых на определенных этапах цикла жизни компонента
COMPONENT_INIT(init);
COMPONENT_UPDATE(update);
COMPONENT_SHUTDOWN(shutdown);
// объявление параметра компонента, который будет доступен в редакторе
PROP_PARAM(Float, speed, 30.0f);
// определение имени Property, которое будет сгенерировано и ассоциировано с компонентом
PROP_NAME("my_component");
protected:
void init();
void update();
void shutdown();
};
MyComponent.cpp:
#include "MyComponent.h"
// регистрация компонента MyComponent
REGISTER_COMPONENT(MyComponent);
// вызов будет произведен при инициализации компонента
void MyComponent::init(){}
// будет вызван каждый кадр
void MyComponent::update(){}
// будет вызван при уничтожении компонента или ноды, которой он назначен
void MyComponent::shutdown(){}
Now we need to generate a property for our component. For this:
UNIGINE supports both single precision (Float) and double precision coordinates (Double), available depending on the SDK edition. Read about usage generic data typessuitable for any project.
In Unreal Engine 4 component USceneComponent (or derivative) is responsible for actions with the actor’s transformation. To get the direction vector along one of the axes, taking into account the orientation in world coordinates, you can use the appropriate methods USceneComponent (GetForwardVector()) or AActor (GetActorForwardVector()).
In UNIGINE, the transformation of a node in space is represented by its transformation matrix (mat4), and all basic operations with transformation or node hierarchy are available using class methods node. The same direction vector in UNIGINE is obtained using the method Node::getWorldDirection():
AT unreal engine 4, to ensure that certain actions are performed in the same time regardless of the frame rate (for example, changing position once per second, etc.), a multiplier is used deltaTime (time in seconds it took for the last frame to complete) passed to the method Tick(float deltaTime). The same thing in UNIGINE is called Game::getIFps():
Note. Visualizer can also be enabled using the console command show_visualizer 1.
See also:
Actor / Node search
Unreal Engine 4:
// поиск Actor или UObject по имени
AActor* MyActor = FindObject<AActor>(nullptr, TEXT("MyNamedActor"));
// Поиск Actor по типу
for (TActorIterator<AMyActor> It(GetWorld()); It; ++It)
{
AMyActor* MyActor = *It;
// ...
}
UNIGINE:
// поиск Node по имени
NodePtr my_node = World::getNodeByName("my_node");
// поиск всех нод с данным именем
Vector<NodePtr> nodes;
World::getNodesByName("test", nodes);
// получение прямого потомка ноды
int index = node->findChild("child_node");
NodePtr direct_child = node->getChild(index);
// Рекурсивный поиск ноды по имени среди всех потомков в иерархии
NodePtr child = node->findNode("child_node", 1);
Casting from type to type
Classes of all types of nodes are derived from node in UNIGINE, so in order to access the functionality of a node of a certain type (for example, ObjectMeshStatic), it is necessary to carry out a downcast of the type – downcasting (casting from a base type to a derived type), which is performed using special constructions. To fulfill Upcasting (casting from a derived type to a base type), you can simply use the instance itself as usual:
In UNIGINE use Node::clone() to clone a node that exists in the world, and World::loadNode to load node hierarchy from an asset .node. In this case, the entire node hierarchy that was saved as Node Reference. You can access the asset either through component parameteror manually by specifying virtual path to him:
// MyComponent.h
PROP_PARAM(File, node_to_spawn);
// MyComponent.cpp
/* .. */
void MyComponent::init()
{
// создание новой ноды Dummy
NodeDummyPtr dummy = NodeDummy::create();
// клонирование существующей ноды
NodePtr cloned = dummy->clone();
// загрузка иерархии нод из ассета
NodePtr spawned = World::loadNode(node_to_spawn.get());
spawned->setWorldPosition(node->getWorldPosition());
// загрузка с указанием пути в файловой системе
NodePtr spawned_manually = World::loadNode("nodes/node_reference.node");
}
You must also specify an asset for the component parameter .node in editor:
Another way to load the contents of an asset *.node – create a NodeReference and work with the node hierarchy as with one object. The Node Reference type has a number of internal optimizations and subtleties (node caching, hierarchy unpacking, etc.), so it is important to consider specifics of work with these objects.
Unreal Engine 4 allows you to extend the functionality of the editor using Blueprint/Python scripts.
UNIGINE does not support executing C++ application logic inside the editor. The main way to extend the functionality of the editor is − plugins written in C++.
For quick testing or development automation, you can write logic in UnigineScript. UnigineScript API has only basic functionality and limited scope, but is available for any UNIGINE project, including C++ projects.
At UNIGINE trigger is a special type of node that triggers events in certain situations:
Important! PhysicalTrigger does not handle collision events, for this bodies and joints provide their own events.
World Trigger is the most common type of trigger that can be used in game logic:
WorldTriggerPtr trigger;
int enter_callback_id;
// коллбэк при попадании внутрь объема триггера
void AppWorldLogic::enter_callback(NodePtr node){
Log::message("\nA node named %s has entered the trigger\n", node->getName());
}
// implement the leave callback
void AppWorldLogic::leave_callback(NodePtr node){
Log::message("\nA node named %s has left the trigger\n", node->getName());
}
int AppWorldLogic::init() {
// создание WorldTrigger ноды
trigger = WorldTrigger::create(Math::vec3(3.0f));
// подписка на событие попадания ноды внутрь объема триггера
// и сохранение id коллбэка для будущего удаления
enter_callback_id = trigger->addEnterCallback(MakeCallback(this, &AppWorldLogic::enter_callback));
// подписка на событие покидания нодой объема триггера
trigger->addLeaveCallback(MakeCallback(this, &AppWorldLogic::leave_callback));
return 1;
}
/* .. */
#include <UnigineApp.h>
#include <UnigineConsole.h>
#include <UnigineInput.h>
/* .. */
void MyInputController::update()
{
// при нажатии правой кнопки мыши
if (Input::isMouseButtonDown(Input::MOUSE_BUTTON_RIGHT))
{
Math::ivec2 mouse = Input::getMouseCoord();
// сообщить координаты курсора мыши в консоль
Log::message("Right mouse button was clicked at (%d, %d)\n", mouse.x, mouse.y);
}
// закрыть приложение при нажатии клавиши 'Q' с учетом того, открыта ли консоль
if (Input::isKeyDown(Input::KEY_Q) && !Console::isActive())
{
App::exit();
}
}
/* .. */
You can also use a singleton ControlsApp to handle bindings of controls to a set of preset input states. To configure bindings, open settings Controls in editor:
#include "MyComponent.h"
#include <UnigineWorld.h>
#include <UnigineVisualizer.h>
#include <UnigineGame.h>
#include <UnigineInput.h>
using namespace Unigine;
using namespace Math;
REGISTER_COMPONENT(MyComponent);
void MyComponent::init()
{
Visualizer::setEnabled(true);
}
void MyComponent::update()
{
// получим координаты начальной и конечной точек луча
ivec2 mouse = Input::getMouseCoord();
float length = 100.0f;
vec3 start = Game::getPlayer()->getWorldPosition();
vec3 end = start + vec3(Game::getPlayer()->getDirectionFromScreen(mouse.x, mouse.y)) * length;
// игнорируем поверхности мешей с включенными битами маски Intersection
int mask = ~(1 << 2 | 1 << 4);
WorldIntersectionNormalPtr intersection = WorldIntersectionNormal::create();
ObjectPtr obj = World::getIntersection(start, end, mask, intersection);
if (obj)
{
vec3 point = intersection->getPoint();
vec3 normal = intersection->getNormal();
Visualizer::renderVector(point, point + normal, vec4_one);
Log::message("Hit %s at (%f,%f,%f)\n", obj->getName(), point.x, point.y, point.z);
}
}
* * *
We remind you that you can access the free version of UNIGINE 2 Community by filling out form on our website.
All configurations UNIGINE:
Community – basic version for hobbyists and independent developers. Sufficient for the development of video games of most popular genres (including VR).
Engineering – extended, specialized version. Includes many blanks for engineering tasks.
Sim – the maximum version of the platform for large-scale projects (the size of the planet and even more) with ready-made simulation mechanisms.