Написание игровой логики, триггеры, ввод, рейкастинг и другое.
Специально для тех, кто ищет альтернативу Unreal Engine или Unity, мы продолжаем цикл статей про безболезненный переход на UNIGINE с зарубежных движков. В третьем выпуске рассмотрим миграцию с Unreal Engine 4 с точки зрения программиста.
Общая информация
Игровая логика в проекте на Unreal Engine 4 реализуется с помощью классов C++ или Blueprint Visual Scripting — встроенной системы визуального нодового программирования. Редактор Unreal Engine 4 позволяет создавать классы при помощи встроенного мастера классов (Class Wizard), выбрав нужный базовый тип.
В UNIGINE вы можете создавать проекты, используя C++ и C# API. При создании проекта просто выберите желаемое API и систему сборки:
В данной статье в основном затронем программирование на C++, т.к. полноценное программирование в Unreal Engine 4 возможно именно на этом языке.
Для C++ на выбор представлены готовые шаблоны проектов для следующих систем сборки:
-
Windows:
Visual Studio 2015+;
CMake;
Qt-based: Qt Creator, QMake или CMake (доступно для Engineering и Sim редакций SDK);
-
Linux:
GNU Make.
Далее просто выберите Open Code IDE, чтобы перейти к разработке логики в выбранной IDE для C++ проектов:
В Unreal Engine 4 достаточно унаследовать класс от базовых типов Game Framework, таких как AActor, APawn, ACharacter и т.п., чтобы переопределить их поведение в стандартных методах BeginPlay(), Tick() и EndPlay() и получить пользовательский actor.
Компонентный подход подразумевает, что логика реализуется в пользовательских компонентах, назначаемых на actor’ы — классах, унаследованных от UActorComponent и других компонентов, расширяющих стандартное поведение, определенное в методах InitializeComponent() и TickComponent().
В UNIGINE стандартный подход подразумевает, что логика приложения состоит из трех основных компонентов с разным циклом жизни:
Системная логика (исходный файл AppSystemLogic.cpp) существует в течение жизненного цикла приложения.
Логика мира (исходный файл AppWorldLogic.cpp) выполняется только когда мир загружен.
Логика редактора (исходный файл AppEditorLogic.cpp) выполняется только во время работы пользовательского редактора.
У каждой логики есть стандартные методы, вызываемые в основном цикле движка. К примеру, можно использовать следующие методы логики мира:
init() - для инициализации ресурсов при загрузке мира;
update() - для обновления каждый кадр;
shutdown() - для уничтожения использованных ресурсов при закрытии мира;
Следует учитывать, что логика мира не привязана к конкретному миру и будет вызвана для любого загруженного мира. Однако вы можете разделить специфичный для мира код между отдельными классами, унаследованными от WorldLogic.
Компонентный подход также доступен в UNIGINE при помощи встроенной компонентной системы. Логика компонента определяется в классе, производном от ComponentBase, на основе которого движок сгенерирует набор параметров компонента — Property, которые можно назначить любой ноде в редакторе. Каждый компонент также имеет набор методов, которые вызываются соответствующими функциями основного цикла движка.
Для примера создания простой игры с использованием компонентной системы, обратитесь к серии статей «Краткое руководство по программированию».
Сравним, как создаются простые компоненты в обоих движках. Заголовочный файл компонента в Unreal Engine 4 будет выглядеть примерно так:
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);
};
И в UNIGINE. Компонентную систему сперва необходимо инициализировать в системной логике (AppSystemLogic.cpp):
/* .. */
#include <UnigineComponentSystem.h>
/* .. */
int AppSystemLogic::init()
{
Unigine::ComponentSystem::get()->initialize();
return 1;
}
И тогда можно написать новый компонент:
MyComponent.h:
#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(){}
Теперь необходимо сгенерировать property для нашего компонента. Для этого:
Соберите приложение с помощью IDE.
Запустите приложение один раз, чтобы получить property компонента, сгенерированное движком.
Перейдите в редактор и назначьте сгенерированное property ноде.
Наконец, работу логики компонента можно проверить, запустив приложение.
Чтобы узнать больше о последовательности выполнения и о том, как создавать компоненты, перейдите по ссылкам ниже:
Использование C++ Component System.
Немного про API
Все объекты в Unreal Engine 4 наследуются от UObject, доступ к ним возможен при помощи стандартных C++ указателей или умных указателей Unreal Smart Pointer Library.
В UNIGINE API есть система умных указателей, управляющих существованием нод и других объектов в памяти:
// создать ноду типа NodeType
<NodeType>Ptr nodename = <NodeType>::create(<construction_parameters>);
// удалить ноду из мира
nodename.deleteLater();
К примеру, вот как выглядит создание меша из ассета, редактирование, присвоение новой ноде типа ObjectMeshStatic и удаление:
MeshPtr mesh = Mesh::create();
mesh->load("fbx/model.fbx/model.mesh");
mesh->addBoxSurface("box_surface", Math::vec3(0.5f, 0.5f, 0.5f));
ObjectMeshStaticPtr my_object = ObjectMeshStatic::create(mesh);
my_object.deleteLater();
mesh.clear();
Экземпляры пользовательских компонентов, как и любых других классов, хранятся при помощи стандартных указателей:
MyComponent *my_component = getComponent<MyComponent>(node);
Типы данных
Тип данных |
Unreal Engine 4 |
UNIGINE |
Числовые типы |
int8/uint8 int16/uint16 int32/uint32 int64/uint64, float, double |
Стандартные типы C++: signed и unsigned char, short, int, long, long long, float, double |
Строки |
FString: FString MyStr = TEXT("Hello, Unreal 4!"). |
String str("Hello, UNIGINE 2!"); |
Контейнеры |
TArray, TMap, TSet |
Vector<NodePtr> nodes; World::getNodes(nodes); for(NodePtr n : nodes) { } |
Векторы и матрицы |
FVector3f - FVector3d, FMatrix44f - FMatrtix44d и другие |
vec3 - dvec3, mat4 - dmat4 и другие типы в математической библиотеке. |
UNIGINE поддерживает как одинарную точность (Float), так и двойную точность координат (Double), доступную в зависимости от редакции SDK. Почитайте про использование универсальных типов данных, подходящих под любой проект.
Основные примеры кода
Вывод в консоль
Unreal Engine 4 |
UNIGINE |
|
|
См. также:
Дополнительные типы сообщений в API класса Log.
Загрузка сцены
Unreal Engine 4 |
UNIGINE |
|
|
Доступ к Actor / Node из компонента
Unreal Engine 4 |
UNIGINE |
|
N |
См. также:
Видеоруководство, демонстрирующее, как получить доступ к нодам из компонентов с помощью C++ Component System.
Доступ к компоненту из Actor / Node
Unreal Engine 4:
UMyComponent* MyComp = MyActor->FindComponentByClass<UMyComponent>();
UNIGINE:
MyComponent *my_component = getComponent<MyComponent>(node);
Работа с направлениями
В Unreal Engine 4 компонент USceneComponent (или производный) отвечает за действия с трансформацией actor’а. Чтобы получить вектор направления по одной из осей с учетом ориентации в мировых координатах, можно использовать соответствующие методы USceneComponent (GetForwardVector()) или AActor (GetActorForwardVector()).
В UNIGINE трансформация ноды в пространстве представлена ее матрицей трансформации (mat4), а все основные операции с трансформацией или иерархией нод доступны при помощи методов класса Node. Такой же вектор направления в UNIGINE получается с помощью метода Node::getWorldDirection():
Unreal Engine 4 |
UNIGINE |
|
|
См. также:
Система координат в UNIGINE.
Более плавный игровой процесс с DeltaTime / IFps
В Unreal Engine 4, чтобы гарантировать, что определенные действия выполняются за одно и то же время независимо от частоты кадров (например, изменение положения один раз в секунду и т. д.), используется множитель deltaTime (время в секундах, которое потребовалось для завершения последнего кадра), передаваемый методу Tick(float deltaTime). То же самое в UNIGINE называется Game::getIFps():
Unreal Engine 4 |
UNIGINE |
|
|
Рисование отладочных данных
Unreal Engine 4:
DrawDebugLine(GetWorld(), traceStart, traceEnd, FColor::Green, true, 1.0f);
В UNIGINE за вспомогательную отрисовку отвечает синглтон Visualizer:
// включаем вспомогательную визуализацию
Visualizer::setEnabled(true);
/*..*/
Visualizer::renderLine3D(vec3_zero, vec3(5, 0, 0), vec4_one);
Visualizer::renderVector(node->getPosition(), node->getDirection(Math::AXIS_Y) * 10, vec4(1, 0, 0, 1));
Примечание. Visualizer также можно включить с помощью консольной команды show_visualizer 1.
См. также:
Все типы визуализаций в API класса Visualizer.
Поиск Actor / Node
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);
Приведение от типа к типу
Классы всех типов нод являются производными от Node в UNIGINE, поэтому чтобы получить доступ к функциональности ноды определенного типа (например, ObjectMeshStatic), необходимо провести понижающее приведение типа — Downcasting (приведение от базового типа к производному), которое выполняется с использованием специальных конструкций. Чтобы выполнить Upcasting (приведение от производного типа к базовому), можно как обычно просто использовать сам экземпляр:
Unreal Engine 4 |
UNIGINE |
|
|
Уничтожение Actor / Node
Unreal Engine 4 |
UNIGINE |
|
|
Для выполнения отложенного удаления ноды в UNIGINE можно создать компонент, который будет отвечать за таймер и удаление.
Создание экземпляра Actor / Node Reference
За создание нового экземпляра actor (Spawning) отвечает метод UWorld::SpawnActor():
AKAsset* SpawnedActor1 = (AKAsset*)
GetWorld()->SpawnActor(AKAsset::StaticClass(), NAME_None, &Location);
В Unreal Engine 4 клонировать существующий 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);
}
В UNIGINE используйте Node::clone() для клонирования ноды, существующей в мире, и World::loadNode для загрузки иерархии нод из ассета .node. В этом случае на сцену будет добавлена вся иерархия нод, которая была сохранена как Node Reference. Вы можете обратиться к ассету либо через параметр компонента, либо вручную, указав виртуальный путь к нему:
// 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");
}
Для параметра компонента также необходимо указать ассет .node в редакторе:
Еще один способ загрузить содержимое ассета *.node — создать NodeReference и работать с иерархией нод как с одним объектом. Тип Node Reference имеет ряд внутренних оптимизаций и тонких моментов (кэширование нод, распаковка иерархии и т.д.), поэтому важно учитывать специфику работы с этими объектами.
void MyComponent::update()
{
NodeReferencePtr nodeRef = NodeReference::create("nodes/node_reference_0.node");
}
Запуск скриптов в редакторе
Unreal Engine 4 позволяет расширять функциональность редактора с помощью Blueprint/Python скриптов.
UNIGINE не поддерживает выполнение логики приложения на C++ внутри редактора. Основной способ расширить функциональность редактора — плагины, написанные на C++.
Для быстрого тестирования или автоматизации разработки можно написать логику на UnigineScript. UnigineScript API обладает только базовой функциональностью и ограниченной сферой применения, но доступен для любого проекта на UNIGINE, включая проекты на C++.
Есть два способа добавить скриптовую логику в проект:
Создав скрипт мира:
Создайте ассет скрипта .usc.
Определите в нем логику. При необходимости добавьте проверку, загружен ли редактор:
//Исходный код (UnigineScript)
#include <core/unigine.h>
vec3 lookAtPoint = vec3_zero;
Node node;
int init() {
node = engine.world.getNodeByName("material_ball");
return 1;
}
int update() {
if(engine.editor.isLoaded())
node.worldLookAt(lookAtPoint);
return 1;
}
Выделите текущий мир и укажите для него сценарий мира. Нажмите Apply и перезагрузите мир.
Проверьте окно консоли на наличие ошибок.
После этого логика скрипта будет выполняться как в редакторе, так и в приложении.
Используя WorldExpression. С той же целью можно использовать ноду WorldExpression, выполняющую логику при добавлении в мир:
Нажмите Create -> Logic -> Expression и поместите новую ноду WorldExpression в мир.
Напишите логику на UnigineScript в поле Source:
//Исходный код (UnigineScript)
{
vec3 lookAtPoint = vec3_zero;
Node node = engine.world.getNodeByName("my_node");
node.worldLookAt(lookAtPoint);
}
Проверьте окно Console на наличие ошибок.
Логика будет выполнена немедленно.
Триггеры
Unreal Engine 4:
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
// компонент триггера
UPROPERTY()
UPrimitiveComponent* Trigger;
AMyActor()
{
Trigger = CreateDefaultSubobject<USphereComponent>(TEXT("TriggerCollider"));
Trigger.bGenerateOverlapEvents = true;
Trigger.SetCollisionEnabled(ECollisionEnabled::QueryOnly);
}
virtual void NotifyActorBeginOverlap(AActor* Other) override;
virtual void NotifyActorEndOverlap(AActor* Other) override;
};
В UNIGINE Trigger — это специальный тип нод, вызывающих события в определенных ситуациях:
NodeTrigger вызывает коллбэк при изменении состояния включен/выключен и позиции самой ноды.
WorldTrigger вызывает коллбэк, когда какая-либо нода (независимо от типа) попадает внутрь или за его пределы.
PhysicalTrigger вызывает коллбэк, когда физические объекты попадают внутрь или за его пределы.
Важно! PhysicalTrigger не обрабатывает события столкновения, для этого тела и сочленения предоставляют свои собственные события.
WorldTriger — наиболее распространенный тип триггера, который можно использовать в игровой логике:
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;
}
Обработка ввода
Unreal Engine 4:
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:
/* .. */
#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();
}
}
/* .. */
Также можно использовать синглтон ControlsApp для обработки привязок элементов управления к набору предустановленных состояний ввода. Чтобы настроить привязки, откройте настройки Controls в редакторе:
#include <Unigine.h>
/* .. */
void MyInputController::init()
{
// переназначение состояний клавишам и кнопкам вручную
ControlsApp::setStateKey(Controls::STATE_FORWARD, App::KEY_PGUP);
ControlsApp::setStateKey(Controls::STATE_BACKWARD, App::KEY_PGDOWN);
ControlsApp::setStateKey(Controls::STATE_MOVE_LEFT, 'l');
ControlsApp::setStateKey(Controls::STATE_MOVE_RIGHT, 'r');
ControlsApp::setStateButton(Controls::STATE_JUMP, App::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");
}
}
/* .. */
Проверка пересечения луча с геометрией (Raycast)
Unreal Engine 4:
APawn* AMyPlayerController::FindPawnCameraIsLookingAt()
{
FCollisionQueryParams Params;
Params.AddIgnoredActor(GetPawn());
FHitResult Hit;
FVector Start = PlayerCameraManager->GetCameraLocation();
FVector End = Start + (PlayerCameraManager->GetCameraRotation().Vector() * 1000.0f);
bool bHit = GetWorld()->LineTraceSingle(Hit, Start, End, ECC_Pawn, Params);
if (bHit)
{
return Cast<APawn>(Hit.Actor.Get());
}
return nullptr;
}
В UNIGINE то же самое достигается с помощью Intersections:
#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);
}
}
Напоминаем, что получить доступ к бесплатной версии UNIGINE 2 Community можно, заполнив форму на нашем сайте.
Все комплектации UNIGINE:
Community — базовая версия для любителей и независимых разработчиков. Достаточна для разработки видеоигр большинства популярных жанров (включая VR).
Engineering — расширенная, специализированная версия. Включает множество заготовок для инженерных задач.
Sim — максимальная версия платформы под масштабные проекты (размеров планеты и даже больше) с готовыми механизмами симуляции.
Комментарии (2)
loginmen
09.06.2022 21:58+2void InitializeComponent();
И далее если метод переопределяется то должен быть либо virtual либо что предпочтительнее override
В данной статье в основном затронем программирование на C++, т.к. полноценное программирование в Unreal Engine 4 возможно именно на этом языке.
Можно создавать игры без использования С++, BP тоже полноценное программирование.
чтобы переопределить их поведение в стандартных методах BeginPlay(), Tick() и EndPlay() и получить пользовательский actor
определенное в методах InitializeComponent() и TickComponent()
Эти методы переопределяются, но не только они, я бы сказал это одни из самых редкоиспользуемых методов. Большая часть настройки происходит в конструкторах.
using namespace Unigine;
Плохая практика выдающая нуба в плюсах.
Все объекты в Unreal Engine 4 наследуются от UObject, доступ к ним возможен при помощи стандартных C++ указателей или умных указателей Unreal Smart Pointer Library.
В движке используется сборщик мусора и поэтому не нужны никакие умные указатели если речь идет об объектах наследуемых от UObject
Типы данных
Помимо своих типов данных в UE можно использовать и стандартные плюсовые типы, странно что в табличке не перечислены типы с фиксированным размером, например uint32_t
Что делает в контейнерах ranged for?
В UE есть еще другие контейнеры, например стэк, очередь и тд.
Вывод в консоль
Не будет работать в UE без предвариательного создания логгера с указанным именем
Доступ к компоненту
Зачем искать компоненты если в полях класса есть ссылки на все компоненты?
В Unreal Engine 4 компонент USceneComponent (или производный) отвечает за действия с трансформацией actor’а.
Не отвечает и только при условии что он есть root актера, но рута может и не быть или им может быть любой другой объект
Поиск Actor
Зачем использовать итераторы в обычном цикле если можно использовать ranged for?
if (SphereCollider != nullptr)
лишняя писанина, можно просто короче
if (SphereCollider)
AKAsset* SpawnedActor1 = (AKAsset*) GetWorld()->SpawnActor(AKAsset::StaticClass(), NAME_None, &Location);
Где проверка на наличие мира (то есть не равенство nullptr)? И зачем использовать небезопасный Си-стайл кастинг?
Unreal Engine 4 позволяет расширять функциональность редактора с помощью Blueprint/Python скриптов.
Ложь. Блюипринты предназначены только для игровой логики и они прилично ограничены. А питон только для автоматизации. Расширять функционал редактора и создавать новые ноды для BP можно только на С++.
Создав скрипт мира
В UE данный скрипт (называется Level BluePrint) популярностью не пользуется, о нем мало кто знает и не рекомендуется его использовать.
UPrimitiveComponent* Trigger;
Обычно принято создавать тип тот же что и у создаваемого объекта, в данном случае
USphereComponent
bool bHit = GetWorld()->LineTraceSingle(Hit, Start, End, ECC_Pawn, Params);
Где проверка мира на корректность? И В Чаще используется для рэйкаста LineTraceSingleByChannel
Гуи UE написан на Slate который можно использовать вне игровых проектов. А ваш движок, точнее редактор на сколько знаю написан на Qt, хоть на нем написан весь коммерческий 3д софт, но он тяжелый и неповоротливый.
В UE очень многие пользуются только BP, тех кто использует плюсы в разы меньше, особенно в ру сегменте. Еще в UE очень распространена практика: Класс Движка -> С++ Класс Пользователя -> Blueprint Класс. В С++ классе вся логика, а в BP все что относится к внешнему виду. Причем писать логику можно почти для всего, гуи, ниагара и тд, простыми актерами не ограничивается
Ни слова что в UE в С++ используется сборщик мусора и рефлексия, которая повсеместно используется. Можно было пару слов сказать про UPROPERTY и привести аналог из вашего движка. Я еще думал что в UE много макросов, но судя по именам (заглавными буквами) тут их еще больше, макросы зло.
В целом ощущение что статью писал человек далекий ил очень поверхностно знакомый с программированием. Качество кода и его наполнение оставляют желать лучшего, особенно UE код.
tas
Мне кажется, подобные гайды было бы очень полезно сопровождать примером проекта, в котором было бы показано практическое применение того, что написано выше.