Как стать автором
Обновить
0
UNIGINE
3D-движок. Игры, симуляторы, GPU бенчмарки, VR.

Переход на UNIGINE с Unreal Engine 4: гайд для программистов

Время на прочтение15 мин
Количество просмотров4.5K

Написание игровой логики, триггеры, ввод, рейкастинг и другое.

Специально для тех, кто ищет альтернативу 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 для нашего компонента. Для этого:

  1. Соберите приложение с помощью IDE.

  2. Запустите приложение один раз, чтобы получить property компонента, сгенерированное движком.

  3. Перейдите в редактор и назначьте сгенерированное property ноде.

  4. Наконец, работу логики компонента можно проверить, запустив приложение.

Чтобы узнать больше о последовательности выполнения и о том, как создавать компоненты, перейдите по ссылкам ниже:

Немного про 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:

String str("Hello, UNIGINE 2!");

Контейнеры

TArray, TMap, TSet

Vector, Map, Set и другие:

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

UE_LOG(LogTemp, Warning, TEXT("Your message"));

Log::message("Debug info: %s\n", text);

Log::message("Debug info: %d\n", number);

См. также:

  • Дополнительные типы сообщений в API класса Log.

Загрузка сцены

Unreal Engine 4

UNIGINE

UGameplayStatics::OpenLevel(GetWorld(), TEXT("MyLevelName"));

World::loadWorld("YourWorldName");   

Доступ к Actor / Node из компонента

Unreal Engine 4

UNIGINE

MyComponent->GetOwner();

NodePtr owning_node = node;

См. также:

  • Видеоруководство, демонстрирующее, как получить доступ к нодам из компонентов с помощью 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

FVector forward = MyActor->GetActorForwardVector();

FVector up = MyActor->GetActorUpVector();

FVector right = MyActor->GetActorRightVector();

FVector CurrentLocation = MyActor->GetActorLocation();

CurrentLocation += forward * speed * DeltaTime;

MyActor->SetActorLocation(CurrentLocation);

mat4 t_local = node->getTransform();

mat4 t_world = node->getWorldTransform();

vec3 pos_world = node->getWorldPosition();

vec3 forward = node->getWorldDirection(Math::AXIS_Y);

vec3 right = node->getWorldDirection(Math::AXIS_X);

vec3 up = node->getWorldDirection(Math::AXIS_Z);

node->translate(forward * speed * Game::getIFps());

См. также:

Более плавный игровой процесс с DeltaTime / IFps

В Unreal Engine 4, чтобы гарантировать, что определенные действия выполняются за одно и то же время независимо от частоты кадров (например, изменение положения один раз в секунду и т. д.), используется множитель deltaTime (время в секундах, которое потребовалось для завершения последнего кадра), передаваемый методу Tick(float deltaTime). То же самое в UNIGINE называется Game::getIFps():

Unreal Engine 4

UNIGINE

void AMyActor::Tick(float deltaTime)

{

    Super::Tick(deltaTime);

    /* .. */

}

node->rotate(0, 0, speed * Game::getIFps());

Рисование отладочных данных

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

UPrimitiveComponent* Primitive = MyActor->GetComponentByClass(UPrimitiveComponent::StaticClass());

USphereComponent* SphereCollider = Cast<USphereComponent>(Primitive);

if (SphereCollider != nullptr)

{

// ...

}

// поиск ноды в мире по имени

NodePtr baseptr = World::getNodeByName("my_meshdynamic");

// приведение к производному типу с автоматической проверкой типа

ObjectMeshDynamicPtr derivedptr = checked_ptr_cast<ObjectMeshDynamic>(baseptr);

// статическое приведение

ObjectMeshDynamicPtr derivedptr = static_ptr_cast<ObjectMeshDynamic>(World::getNodeByName("my_meshdynamic"));

// приведение к Object — базовому типу для ObjectMeshDynamic

ObjectPtr object = derivedptr;

// приведение к Node — базовому типу для всех объектов мира

NodePtr node = derivedptr;

Уничтожение Actor / Node

Unreal Engine 4

UNIGINE

MyActor->Destroy();

// уничтожение actor’а с 1-секундной задержкой

MyActor->SetLifeSpan(1);

node.deleteLater(); // рекомендуемый способ уничтожить ноду

//вызов будет произведен между кадрами

node.deleteForce(); // форсированное удаление, может быть небезопасным

Для выполнения отложенного удаления ноды в 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++.

Есть два способа добавить скриптовую логику в проект:

  • Создав скрипт мира:

  1. Создайте ассет скрипта .usc.

  1. Определите в нем логику. При необходимости добавьте проверку, загружен ли редактор:

//Исходный код (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;
}
  1. Выделите текущий мир и укажите для него сценарий мира. Нажмите Apply и перезагрузите мир.

  1. Проверьте окно консоли на наличие ошибок.

После этого логика скрипта будет выполняться как в редакторе, так и в приложении.

  • Используя WorldExpression. С той же целью можно использовать ноду WorldExpression, выполняющую логику при добавлении в мир:

  1. Нажмите Create -> Logic -> Expression и поместите новую ноду WorldExpression в мир.

  2. Напишите логику на UnigineScript в поле Source:

//Исходный код (UnigineScript)
{
vec3 lookAtPoint = vec3_zero;
Node node = engine.world.getNodeByName("my_node");
node.worldLookAt(lookAtPoint);
}
  1. Проверьте окно Console на наличие ошибок.

  2. Логика будет выполнена немедленно.

Триггеры

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 — это специальный тип нод, вызывающих события в определенных ситуациях:

Важно! 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 — максимальная версия платформы под масштабные проекты (размеров планеты и даже больше) с готовыми механизмами симуляции.

Подробнее о комплектациях и ценах

Теги:
Хабы:
+11
Комментарии3

Публикации

Информация

Сайт
www.unigine.com
Дата регистрации
Дата основания
2005
Численность
51–100 человек
Местоположение
Россия

Истории