从虚幻引擎迁移到UNIGINE:编程
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实现的。您习惯于通过编写事件函数(例如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.看看编程快速入门系列开始使用UNIGINE中的传统C ++编程。
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 ++组件系统,这是安全可靠的,并确保了高性能。逻辑是使用从ComponentBase类派生的C ++类编写的,基于该类,引擎将生成一组组件的参数-财产可以分配给场景中的任何节点。每个组件都有一组功能(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:使用C ++在UNIGINE中进行编程与在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.运行应用程序一次获取引擎生成的组件属性。
- 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:
要了解有关执行顺序以及如何构建组件的更多信息,请单击以下链接:
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 APILog类API中的更多消息类型
- 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#组件系统从组件访问节点。
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.向下转换(从指向基础的指针到指向派生的指针)是使用不同的构造执行的。要执行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扳机 (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 fires callbacks when the Trigger node is enabled or the Trigger node position has changed. 当Trigger节点为已启用或Trigger节点位置已更改时,NodeTrigger将运行回调函数。
- WorldTrigger fires callbacks when any node (collider or not) gets inside or outside of it. WorldTrigger在任何节点(是否发生对撞器)获取时触发回调里面或者外部它的。
-
PhysicalTrigger fires callbacks 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不处理碰撞事件身体和关节提供自己的事件。
WorldTrigger is the most common type that can be used in gameplay. Here is an example on how to use it:WorldTrigger是可用于游戏中的最常见类型。这是有关如何使用它的示例:
WorldTriggerPtr trigger;
void* enter_callback_id;
// implement the enter callback
void AppWorldLogic::enter_callback(Unigine::NodePtr node) {
Log::message("\nA node named %s has entered the trigger\n", node->getName());
}
// implement the leave callback
void AppWorldLogic::leave_callback(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));
// add the enter callback to be fired when a node enters the world trigger
//and keep its id to be used to remove the callback when necessary
enter_callback_id = trigger->addEnterCallback(MakeCallback(this, &AppWorldLogic::enter_callback));
// add the leave callback to be fired when a node leaves the world trigger
trigger->addLeaveCallback(MakeCallback(this, &AppWorldLogic::leave_callback));
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光线追踪#
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中,相同的处理方式为交叉口:
#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::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);
}
}