Переход на UNIGINE с Unreal Engine: программирование
Game logic in an Unreal Engine project is implemented via Blueprint Script / C++ components. You got used to determine actor's behavior by writing event functions like InitializeComponent() and TickComponent().Логика игры в проекте Unreal Engine реализована через Blueprint Script / C++ components. Вы привыкли определять поведение Actor, записывая функции событий, такие как InitializeComponent() и TickComponent().
In UNIGINE, you can create projects based on C++, C#, and UnigineScript API. A visual scripting feature is being under research and development at the moment.В UNIGINE вы можете создавать проекты на основе C++, C# и UnigineScript API. Функционал визуального скриптинга в настоящее время находится в стадии разработки.
The traditional workflow implies the Application Logic has three basic components that have different lifetimes:Традиционный подход подразумевает, что логика приложения состоит из трех основных компонентов с разной продолжительностью жизни:
- SystemLogic (the AppSystemLogic.cpp source file) exists during the application life cycle. SystemLogic (исходный файл AppSystemLogic.cpp) существует в течение жизненного цикла приложения.
- WorldLogic (the AppWorldLogic.cpp source file) takes effect only when a world is loaded. WorldLogic (исходный файл AppWorldLogic.cpp) действует только при загрузке мира.
- EditorLogic (the AppEditorLogic.cpp source file) takes effect only when a custom editor is loaded (there is a class derived from the EditorLogic class). EditorLogic (исходный файл AppEditorLogic.cpp) вступает в силу только при загрузке специального редактора (существует класс, производный от класса EditorLogic).
Check out the Programming Quick Start series to get started in traditional C++ programming in UNIGINE.Посмотрите серию уроков Быстрый старт программирования , чтобы начать программировать на C++ в UNIGINE.
Regarding components, UNIGINE has quite a similar concept, which can be easily adopted — C++ Component System, which is safe and secure and ensures high performance. Logic is written in C++ classes derived from the ComponentBase class, based on which the engine generates a set of component's parameters — Property that can be assigned to any node in the scene. Each component has a set of functions (init(), update(), etc.), that are called by the corresponding functions of the engine's main loop.Что касается компонентов, UNIGINE имеет довольно похожую концепцию, которая может быть легко адаптирована - Система компонентов C++ , этот подход безопасен и надежен и обеспечивает высокую производительность. Логика написана в классах C++, производных от класса ComponentBase, на основе которых движок генерирует набор параметров компонента - Property (свойство) которое может быть назначено любой ноде сцены. Каждый компонент имеет набор функций (init(), update() и т.д.), Которые вызываются соответствующими функциями основного цикла движка.
From here on, this article covers primarily programming in C++ using the C++ Component System as a more natural and familiar workflow for Unreal Engine users. Check out the Using C++ Component System article for an example for beginners.С этого момента эта статья в основном охватывает программирование на C++ с использованием системы компонентов C++ в качестве более естественного и знакомого подхода для пользователей Unreal Engine. Проверьте Использование системы компонентов C++ статья с примером для новичков.
Programming in UNIGINE using C++ is not much different from programming in Unreal Engine except that you need to make some preparations and create headers files for components as in the natural coding in C++. For example, let's compare how simple components are created in both engines. In UE4:Программирование в UNIGINE с использованием C++ мало чем отличается от программирования в Unreal Engine, за исключением того, что вам нужно сделать некоторые приготовления и создать файлы заголовков для компонентов, как обычно при программировании на C++. Например, давайте сравним, как создаются простые компоненты в обоих движках. В UE4:
UCLASS()
class UMyComponent : public UActorComponent
{
GENERATED_BODY()
// 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. To make the things work, you need to initialize the Component System in the AppSystemLogic.cpp file:И в UNIGINE. Чтобы все заработало, вам нужно инициализировать систему компонентов в файле AppSystemLogic.cpp:
/* .. */
#include <UnigineComponentSystem.h>
/* .. */
int AppSystemLogic::init()
{
// Write here code to be called on engine initialization.
Unigine::ComponentSystem::get()->initialize();
return 1;
}
And then you can write a new component.И тогда вы можете написать новый компонент.
MyComponent.h:
#pragma once
#include <Unigine.h>
#include <UnigineComponentSystem.h>
using namespace Unigine;
class MyComponent : public ComponentBase
{
public:
// declare the component
COMPONENT(MyComponent, ComponentBase);
// declare methods to be called at the corresponding stages of the execution sequence
COMPONENT_INIT(init);
COMPONENT_UPDATE(update);
COMPONENT_SHUTDOWN(shutdown);
// declare the name of the property to be used in the Editor
PROP_NAME("my_component");
protected:
void init();
void update();
void shutdown();
};
MyComponent.cpp:
#include "MyComponent.h"
// register the component in the Component System
REGISTER_COMPONENT(MyComponent);
// called on component initialization
void MyComponent::init(){}
// called every frame
void MyComponent::update(){}
// called on component or the owning node is being destroyed
void MyComponent::shutdown(){}
After that, you need to perform the following steps:После этого нужно выполнить следующие действия:
- Build the application using the IDE.Соберите приложение, используя IDE.
- Run the application once to get the component property generated by the engine.Запустите приложение один раз, чтобы получить свойство компонента (property), созданное движком.
- Assign the property to a node.Назначьте свойство ноде.
- Finally, you can check out its work by launching the application.Наконец, вы можете проверить его работу, запустив приложение.
To learn more about the execution sequence and how to build components, follow the links below:
Чтобы узнать больше о последовательности выполнения и о том, как создавать компоненты, перейдите по ссылкам ниже:
- Execution SequenceПоследовательность выполнения
- Using C++ Component SystemИспользование системы компонентов C++
For those who prefer C#, UNIGINE allows creating C# applications using C# API and, if required, C# Component System.Для тех, кто предпочитает C#, UNIGINE позволяет создавать C# приложения с помощью C# API и, при необходимости, Система компонентов C# .
Writing Gameplay CodeНаписание игрового кода#
Printing to ConsoleПечать в консоль#
Unreal Engine | UNIGINE |
---|---|
|
|
See AlsoСмотрите также#
- More types of messages in the Log class APIБольше типов сообщений в API класса Log
- Video tutorial demonstrating how to print user messages to console using C# Component SystemВидеоурок, демонстрирующий, как выводить сообщения пользователя на консоль с помощью системы компонентов C#
Loading a SceneЗагрузка сцены#
Unreal Engine | UNIGINE |
---|---|
|
|
Accessing Actor/Node from ComponentДоступ к Actor / Node из компонента#
Unreal Engine | UNIGINE |
---|---|
|
|
See AlsoСмотрите также#
- Video tutorial demonstrating how to access nodes from components using C# Component System.Видеоурок, демонстрирующий, как получить доступ к нодам из компонентов с помощью C# Component System.
Accessing a Component from the Actor/NodeДоступ к компоненту из Actor / Node#
Unreal Engine:
UMyComponent* MyComp = MyActor->FindComponentByClass<UMyComponent>();
UNIGINE:
MyComponent* my_component = getComponent<MyComponent>(node);
Finding Actors/NodesПоиск Actor / Node#
Unreal Engine:
// Find Actor by name (also works on UObjects)
AActor* MyActor = FindObject<AActor>(nullptr, TEXT("MyNamedActor"));
// Find Actors by type (needs a UWorld object)
for (TActorIterator<AMyActor> It(GetWorld()); It; ++It)
{
AMyActor* MyActor = *It;
// ...
}
UNIGINE:
// Find a Node by name
NodePtr my_node = World::getNodeByName("my_node");
// Find all nodes having this name
Vector<NodePtr> nodes;
World::getNodesByName("test", nodes);
// Find the index of a direct child node
int index = node->findChild("child_node");
NodePtr direct_child = node->getChild(index);
// Perform a recursive descend down the hierarchy to find a child Node by name
NodePtr child = node->findNode("child_node", 1);
Casting From Type to TypeПриведение типов#
Downcasting (from a pointer-to-base to a pointer-to-derived) is performed using different constructions. To perform Upcasting (from a pointer-to-derived to a pointer-to-base) you can simply use the instance itself.Сужение типа или downcasting (от указателя на базовый класс к указателю на производный класс ) выполняется с использованием различных конструкций. Чтобы выполнить расширение типа или upcasting (от указателя на производный класс к указателю на базовый класс ), вы можете просто использовать сам экземпляр.
Unreal Engine:
UPrimitiveComponent* Primitive = MyActor->GetComponentByClass(UPrimitiveComponent::StaticClass());
USphereComponent* SphereCollider = Cast<USphereComponent>(Primitive);
if (SphereCollider != nullptr)
{
// ...
}
UNIGINE:
// find a pointer to node by a given name
NodePtr baseptr = World::getNodeByName("my_meshdynamic");
// cast a pointer-to-derived from pointer-to-base with automatic type checking
ObjectMeshDynamicPtr derivedptr = checked_ptr_cast<ObjectMeshDynamic>(baseptr);
// static cast (pointer-to-derived from pointer-to-base)
ObjectMeshDynamicPtr derivedptr = static_ptr_cast<ObjectMeshDynamic>(World::getNodeByName("my_meshdynamic"));
// upcast to the pointer to the Object class which is a base class for ObjectMeshDynamic
ObjectPtr object = derivedptr;
// upcast to the pointer to the Node class which is a base class for all scene objects
NodePtr node = derivedptr;
Destroy Actor/NodeУдаление Actor / Node#
Unreal Engine | UNIGINE |
---|---|
|
|
To perform deferred removal of a node in UNIGINE, you can create a component that will be responsible for the timer and deletion.Чтобы выполнить отложенное удаление ноды в UNIGINE, вы можете создать компонент, который будет отвечать за таймер и удаление.
Instantiating Actor / Node ReferenceСоздание экземпляра Actor / Node#
In UE4, you create a clone of an actor the following way:В UE4 вы создаете клон Actor следующим образом:
AMyActor* CreateCloneOfMyActor(AMyActor* ExistingActor, FVector SpawnLocation, FRotator SpawnRotation)
{
UWorld* World = ExistingActor->GetWorld();
FActorSpawnParameters SpawnParams;
SpawnParams.Template = ExistingActor;
World->SpawnActor<AMyActor>(ExistingActor->GetClass(), SpawnLocation, SpawnRotation, SpawnParams);
}
In UNIGINE, you should use World::loadNode to load a hierarchy of nodes from a .node asset. In this case the hierarchy of nodes that was saved as a NodeReference will be added to the scene. You can refer to the asset either via a component parameter or manually by providing the virtual path to it:В UNIGINE вы должны использовать World::loadNode для загрузки иерархии нод из ресурса .node. В этом случае иерархия нод, которая была сохранена как NodeReference, будет добавлена в сцену. Вы можете ссылаться на ассет через компонентный параметр или вручную, предоставив виртуальный путь к нему:
// MyComponent.h
PROP_PARAM(File, node_to_spawn);
// MyComponent.cpp
/* .. */
void MyComponent::init()
{
// load a hierarchy of nodes from the asset
NodePtr spawned = World::loadNode(node_to_spawn.get());
spawned->setWorldPosition(node->getWorldPosition());
NodePtr spawned_manually = World::loadNode("nodes/node_reference.node");
}
In case of using the approach of component parameters, you should also specify the .node asset:В случае использования подхода компонентных параметров следует также указать ассет .node:
You can also spawn the NodeReference as a single node (without extracting the content) in the world:Вы также можете создать NodeReference как единую ноду (без извлечения содержимого) в мире:
void MyComponent::update()
{
NodeReferencePtr nodeRef = NodeReference::create("nodes/node_reference_0.node");
}
TriggersТриггеры#
Unreal Engine:
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
// My trigger component
UPROPERTY()
UPrimitiveComponent* Trigger;
AMyActor()
{
Trigger = CreateDefaultSubobject<USphereComponent>(TEXT("TriggerCollider"));
// Both colliders need to have this set to true for events to fire
Trigger.bGenerateOverlapEvents = true;
// Set the collision mode for the collider
// This mode will only enable the collider for raycasts, sweeps, and overlaps
Trigger.SetCollisionEnabled(ECollisionEnabled::QueryOnly);
}
virtual void NotifyActorBeginOverlap(AActor* Other) override;
virtual void NotifyActorEndOverlap(AActor* Other) override;
};
In UNIGINE, Trigger is a special built-in node type that raises events in certain situations:В UNIGINE Trigger - это специальный встроенный тип ноды, который вызывает события в определенных ситуациях:
- NodeTrigger is used to track events for the Trigger node - event handlers are executed when the Trigger node is enabled or its position has changed. NodeTrigger используется для отслеживания событий для ноды Trigger — обработчики событий выполняются, когда нода Trigger включается или меняется ее положение.
- WorldTrigger is used to track events for any node (collider or not) that gets inside or outside of it. WorldTrigger используется для отслеживания событий для любой ноды (коллайдер или нет), которая входит в объем триггера или же выходит из него.
-
PhysicalTrigger triggers events when physical objects get inside or outside of it.PhysicalTrigger запускает события, когда физические объекты входят в объем триггера или же выходят из него.
PhysicalTrigger does not handle collision events, for that purpose Bodies and Joints provide their own events.PhysicalTrigger не обрабатывает события столкновения, для этой цели у тел (bodies) и сочленений (joints) есть свои собственные события.
WorldTrigger is the most common type that can be used in gameplay. Here is an example on how to use it:WorldTrigger - наиболее распространенный тип, который можно использовать в игровом процессе. Вот пример того, как его использовать:
WorldTriggerPtr trigger;
EventConnectionId enter_event_connection;
// implement the enter event handler
void AppWorldLogic::enter_event_handler(const Unigine::NodePtr &node) {
Log::message("\nA node named %s has entered the trigger\n", node->getName());
}
// implement the leave event handler
void AppWorldLogic::leave_event_handler(const Unigine::NodePtr &node) {
Log::message("\nA node named %s has left the trigger\n", node->getName());
}
int AppWorldLogic::init()
{
node = NodeDummy::create();
node2 = NodeDummy::create();
node2->setParent(node);
node2->setName("child_node");
// create a world trigger node
trigger = WorldTrigger::create(Math::vec3(3.0f));
// subscribe for the enter ID to remove subscription when necessary
enter_event_connection = trigger->getEventEnter().connect(this, &AppWorldLogic::enter_event_handler);
// subscribe for the leave event (when a node leaves the world trigger)
trigger->getEventLeave().connect(this, &AppWorldLogic::leave_event_handler);
return 1;
}
InputВвод#
UE4 Input:UE4 Ввод:
UCLASS()
class AMyPlayerController : public APlayerController
{
GENERATED_BODY()
void SetupInputComponent()
{
Super::SetupInputComponent();
InputComponent->BindAction("Fire", IE_Pressed, this, &AMyPlayerController::HandleFireInputEvent);
InputComponent->BindAxis("Horizontal", this, &AMyPlayerController::HandleHorizontalAxisInputEvent);
InputComponent->BindAxis("Vertical", this, &AMyPlayerController::HandleVerticalAxisInputEvent);
}
void HandleFireInputEvent();
void HandleHorizontalAxisInputEvent(float Value);
void HandleVerticalAxisInputEvent(float Value);
};
UNIGINE:
void MyInputController::update()
{
// if right mouse button is clicked
if (Input::isMouseButtonDown(Input::MOUSE_BUTTON_RIGHT))
{
Math::ivec2 mouse = Input::getMousePosition();
// report mouse cursor coordinates to the console
Log::message("Right mouse button was clicked at (%d, %d)\n", mouse.x, mouse.y);
}
// closing the application if a 'Q' key is pressed, ignoring the key if the console is opened
if (Input::isKeyDown(Input::KEY_Q) && !Console::isActive())
{
Engine::get()->quit();
}
}
You can also use the ControlsApp class to handle control bindings. To configure the bindings, open the Controls settings:Вы также можете использовать класс ControlsApp для обработки привязок элементов управления. Чтобы настроить привязки, откройте настройки Controls:
void MyInputController::init()
{
// remapping states to other keys and buttons
ControlsApp::setStateKey(Controls::STATE_FORWARD, Input::KEY_PGUP);
ControlsApp::setStateKey(Controls::STATE_BACKWARD, Input::KEY_PGDOWN);
ControlsApp::setStateKey(Controls::STATE_MOVE_LEFT, Input::KEY_L);
ControlsApp::setStateKey(Controls::STATE_MOVE_RIGHT, Input::KEY_R);
ControlsApp::setStateMouseButton(Controls::STATE_JUMP, Input::MOUSE_BUTTON_LEFT);
}
void MyInputController::update()
{
if (ControlsApp::clearState(Controls::STATE_FORWARD))
{
Log::message("FORWARD key pressed\n");
}
else if (ControlsApp::clearState(Controls::STATE_BACKWARD))
{
Log::message("BACKWARD key pressed\n");
}
else if (ControlsApp::clearState(Controls::STATE_MOVE_LEFT))
{
Log::message("MOVE_LEFT key pressed\n");
}
else if (ControlsApp::clearState(Controls::STATE_MOVE_RIGHT))
{
Log::message("MOVE_RIGHT key pressed\n");
}
else if (ControlsApp::clearState(Controls::STATE_JUMP))
{
Log::message("JUMP button pressed\n");
}
}
Ray TracingБросание лучей (raycasting)#
Unreal Engine:
APawn* AMyPlayerController::FindPawnCameraIsLookingAt()
{
// You can use this to customize various properties about the trace
FCollisionQueryParams Params;
// Ignore the player's pawn
Params.AddIgnoredActor(GetPawn());
// The hit result gets populated by the line trace
FHitResult Hit;
// Raycast out from the camera, only collide with pawns (they are on the ECC_Pawn collision channel)
FVector Start = PlayerCameraManager->GetCameraLocation();
FVector End = Start + (PlayerCameraManager->GetCameraRotation().Vector() * 1000.0f);
bool bHit = GetWorld()->LineTraceSingle(Hit, Start, End, ECC_Pawn, Params);
if (bHit)
{
// Hit.Actor contains a weak pointer to the Actor that the trace hit
return Cast<APawn>(Hit.Actor.Get());
}
return nullptr;
}
In UNIGINE the same is handled by Intersections:В UNIGINE то же самое реализовано с использованием пересечений (intersections) :
#include "MyComponent.h"
using namespace Unigine;
using namespace Math;
REGISTER_COMPONENT(MyComponent);
void MyComponent::init()
{
Visualizer::setEnabled(true);
}
void MyComponent::update()
{
ivec2 mouse = Input::getMousePosition();
float length = 100.0f;
Vec3 start = Game::getPlayer()->getWorldPosition();
Vec3 end = start + Vec3(Game::getPlayer()->getDirectionFromMainWindow(mouse.x, mouse.y)) * length;
// ignore surfaces that have certain bits of the Intersection mask enabled
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 + Vec3(normal), vec4_one);
Log::message("Hit %s at (%f,%f,%f)\n", obj->getName(), point.x, point.y, point.z);
}
}