从Unity迁移到UNIGINE:编程
Game logic in a Unity project is implemented via Script components. You got used to determine GameObject's behavior by writing event functions like Start(), Update(), etc.Unity项目中的游戏逻辑是通过脚本组件实现的。您习惯于通过编写事件函数(例如Start(), Update()等)来确定GameObject的行为。
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# components 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 main loop.UNIGINE的概念非常相似,可以很容易地采用- C#组件系统,这是安全可靠的,并确保了高性能。逻辑用C#组件编写,可以分配给场景中的任何节点。每个组件都有一组功能(Init(), Update()等),这些功能由引擎的相应功能调用主循环。
Programming in UNIGINE using C# is not much different from programming in Unity software. For example, let's compare how rotation is performed in Unity software:使用C#在UNIGINE中进行编程与在Unity软件中进行编程没有太大区别。例如,让我们比较一下在Unity软件中如何执行旋转:
using UnityEngine;
public class MyComponent : MonoBehaviour
{
public float speed = 90.0f;
void Update()
{
transform.Rotate(0, speed * Time.deltaTime, 0, Space.Self);
}
}
and in UNIGINE:在UNIGINE中:
using Unigine;
/* .. */
public class MyComponent : Component
{
public float speed = 90.0f;
private void Update()
{
node.Rotate(0, 0, speed * Game.IFps);
}
The Run button is available in the Editor to run an instance of the application in a separate window. Along with the button, there are settings available to fine-tune run options.在编辑器中可以使用Run按钮来运行应用程序的实例在一个单独的窗口中。除按钮外,还有一些设置可用于微调运行选项。
That's how we'll make the wheel rotate using C# Component System and run an instance to immediately check it:这就是我们使用C#组件系统使轮子旋转并运行实例以立即对其进行检查的方式:
Moreover in UNIGINE, you can also implement Application Logic for the whole application by writing code in the AppWorldLogic.cs, AppSystemLogic.cs and AppEditorLogic.cs files stored in the source/ project folder.此外,在UNIGINE中,您还可以实现应用逻辑通过在存储在source/项目文件夹中的AppWorldLogic.cs,AppSystemLogic.cs和AppEditorLogic.cs文件中编写代码来实现整个应用程序。
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打印到控制台#
Unity software | UNIGINE |
---|---|
|
|
See Also也可以看看#
- More types of messages in the Log class APILog类API中的更多消息类型
- The video tutorial demonstrating how to print user messages to console using C# Component System这视频教程,演示如何使用C#组件系统将用户消息打印到控制台
Accessing the GameObject / Node from Component从组件访问GameObject / Node#
Unity software | UNIGINE |
---|---|
|
|
See Also也可以看看#
- The video tutorial demonstrating how to access nodes from components using C# Component System这视频教程,演示如何使用C#组件系统从组件访问节点
Working with Directions指导工作#
In Unity software to get a vector on a certain axis while also considering the rotation of a game object in world coordinates, you use the corresponding properties of the Transform component. The same vector in UNIGINE is got by using Node.GetWorldDirection() function:在Unity软件中,要获取某个轴上的向量,同时还要考虑游戏对象在世界坐标中的旋转,您可以使用Transform组件的相应属性。通过使用Node.GetWorldDirection()函数,可以在UNIGINE中获得相同的向量:
Unity software | UNIGINE |
---|---|
|
|
See Also也可以看看#
- Coordinate System in UNIGINE坐标系在UNIGINE中
Smoother Gameplay with DeltaTime / IFpsDeltaTime / IFps使游戏玩法更流畅#
In Unity software to ensure that certain actions are performed at the same time periods regardless of the framerate (e.g. change something once per second etc) you use a scaling multiplier Time.deltaTime (the time in seconds it took to complete the last frame). The same thing in UNIGINE is called Game.IFps:在Unity软件中,为确保某些操作在相同的时间段内执行而与帧速率无关(例如,每秒更改一次等),请使用缩放乘数Time.deltaTime(完成最后一帧所花费的时间(以秒为单位))。 UNIGINE中的相同内容称为Game.IFps:
Unity software | UNIGINE |
---|---|
|
|
Drawing Debug Data绘图调试数据#
Unity software:Unity软件:
Debug.DrawLine(Vector3.zero, new Vector3(5, 0, 0), Color.white, 2.5f);
Vector3 forward = transform.TransformDirection(Vector3.forward) * 10;
Debug.DrawRay(transform.position, forward, Color.green);
UNIGINE:
Visualizer.Enabled = true;
/*..*/
Visualizer.RenderLine3D(vec3.ZERO, new vec3(5, 0, 0), vec4.ONE);
Visualizer.RenderVector(node.Position, node.GetDirection(MathLib.AXIS.Y) * 10, new vec4(1, 0, 0, 1));
See Also也可以看看#
- More types of visualizations in the Visualizer class API.Visualizer类API中的更多可视化类型。
Loading a Scene加载场景#
Unity software | UNIGINE |
---|---|
|
|
Accessing a Component from the GameObject / Node从GameObject/Node访问组件#
Unity software:Unity软件:
MyComponent my_component = gameObject.GetComponent<MyComponent>();
UNIGINE:
MyComponent my_component = node.GetComponent<MyComponent>();
MyComponent my_component = GetComponent<MyComponent>(node);
Accessing Standard Components访问标准组件#
Unity software provides component-based workflow so such standard entities as MeshRenderer, Rigidbody, Collider, Transform and other are treated as usual components.Unity软件提供了基于组件的工作流程,因此将诸如MeshRenderer, Rigidbody, Collider, Transform等标准实体视为其他常规组件。
In UNIGINE, analogs for these entities are accessed differently. For example, to access an entity of a type derived from the Node class (e.g. ObjectMeshStatic), you should downcast the instance to the corresponding class. Let's consider these most popular use cases:在UNIGINE中,对这些实体的类似物的访问方式有所不同。例如,要访问从Node类派生的类型的实体(例如ObjectMeshStatic),您应该降低实例到相应的班级。让我们考虑以下最流行的用例:
Unity software:Unity软件:
// accessing the transform of the game object
Transform transform_1 = gameObject.GetComponent<Transform>();
Transform transform_2 = gameObject.transform;
// accessing the Mesh Renderer component
MeshRenderer mesh_renderer = gameObject.GetComponent<MeshRenderer>();
// accessing the Rigidbody component
Rigidbody rigidbody = gameObject.GetComponent<Rigidbody>();
// accessing a collider
Collider collider = gameObject.GetComponent<Collider>();
BoxCollider boxCollider = collider as BoxCollider;
UNIGINE:
// getting the transformation matrix of the node
dmat4 transform = node.WorldTransform;
// downcasting the node to the ObjectMeshStatic class
ObjectMeshStatic mesh_static = node as ObjectMeshStatic;
// accessing the rigid body assigned to the node
Body body = (node as Unigine.Object).Body;
BodyRigid rigid = body as BodyRigid;
// fetch all collision shapes of the ShapeBox type
for (int i = 0; i < body.NumShapes; i++)
{
Shape shape = body.GetShape(i);
if (shape is ShapeBox)
{
ShapeBox shape_box = shape as ShapeBox;
/* some code */
}
}
Finding GameObjects / Nodes查找GameObject/Node#
Unity software:Unity软件:
// Find a GameObject by name
GameObject myGameObj = GameObject.Find("My Game Object");
// Find the child named "ammo" of the gameobject "magazine" (magazine is a child of "gun").
Transform ammo_transform = gameObject.transform.Find("magazine/ammo");
GameObject ammo = ammo_transform.gameObject;
// Find GameObjects by the type of component assigned
MyComponent[] components = Object.FindObjectsOfType<MyComponent>();
foreach (MyComponent component in components)
{
// ...
}
// Find GameObjects by tag
GameObject[] taggedGameObjects = GameObject.FindGameObjectsWithTag("MyTag");
foreach (GameObject gameObj in taggedGameObjects)
{
// ...
}
UNIGINE:
// Find a Node by name
Node my_node = World.GetNodeByName("my_node");
// Find all nodes having this name
List<Node> nodes = new List<Node>();
World.GetNodesByName("my_node", nodes);
// Find the index of a direct child node
int index = node.FindChild("child_node");
Node direct_child = node.GetChild(index);
// Perform a recursive descend down the hierarchy to find a child Node by name
Node child = node.FindNode("child_node", 1);
// Find Nodes by the type of component assigned
MyComponent[] my_comps = FindComponentsInWorld<MyComponent>();
foreach(MyComponent comp in my_comps)
{
Log.Message("{0}\n",comp.node.Name);
}
Casting From Type to Type从类型到类型的转换#
Downcasting (from a pointer-to-base to a pointer-to-derived) is performed similarly in both engines, by using the C# as native construction:向下转换(从一个指针到基地一个指针到衍生的)在两个发动机同样地进行,通过使用C#as天然结构:
Unity software | UNIGINE |
---|---|
|
|
To perform Upcasting (from a pointer-to-derived to a pointer-to-base) you can simply use the instance itself:要执行Upcasting (从派生指针到基本指针),您可以简单地使用实例本身:
Unity software | UNIGINE |
---|---|
|
|
Destroy GameObject/Node销毁GameObject / Node#
Unity software | 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中执行节点的延迟删除,您可以创建一个负责计时器和删除的组件。
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;
[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
public class LifeTimeController : Component
{
public float lifetime = 5.0f;
void Update()
{
lifetime = lifetime - Game.IFps;
if (lifetime < 0)
{
// destroy current node with its properties and components
node.DeleteLater();
}
}
}
public class MyComponent : Component
{
private void Update()
{
if (/a reason to die/)
{
LifeTimeController lc = node.AddComponent<LifeTimeController>();
lc.lifetime = 2.0f;
}
}
}
Instantiating Prefab / Node Reference实例化prefab/NodeReference#
In Unity software, you instantiate a prefab using the Object.Instantiate function:在Unity软件中,使用Object.Instantiate函数实例化prefab:
using UnityEngine;
public class MyComponent : MonoBehaviour
{
public GameObject myPrefab;
void Start()
{
Instantiate(myPrefab, new Vector3(0, 0, 0), Quaternion.identity);
}
}
Then, you should specify the prefab to be instantiated in the script component parameters:然后,您应该在脚本组件参数中指定要实例化的prefab:
You can also use World.LoadNode to load a hierarchy of nodes from a .node asset manually by providing the virtual path to it:在UNIGINE中,应使用World.LoadNode从.node资源加载节点的层次结构。在这种情况下,将另存为NodeReference的节点层次结构添加到场景中。您可以通过以下方式引用资源:组件参数或通过提供虚拟路径对此:
public class MyComponent : Component
{
public Node node_to_clone;
public AssetLinkNode node_to_spawn;
private void Init()
{
Node cloned = node_to_clone.Clone();
Node spawned = node_to_spawn.Load(node.WorldPosition, quat.IDENTITY);
Node spawned_manually = World.LoadNode("nodes/node_reference.node");
}
}
如果使用组件参数方法,则还应指定.node资源:
You can also spawn the node reference as a single node (without extracting the content) in the world:您还可以在世界中将节点引用作为单个节点生成(不提取内容):
public class MyComponent : Component
{
private void Init()
{
NodeReference nodeRef = new NodeReference("nodes/node_reference_0.node");
}
}
Running Scripts in the Editor在编辑器中运行脚本#
You are likely to be accustomed that Unity software enables you to extend the Editor using C# scripts. For this purpose you can use special attributes in your scripts:您可能已经习惯了Unity软件使您能够使用C#脚本扩展编辑器。为此,您可以在脚本中使用特殊属性:
- [ExecuteInEditMode] — to execute the script logic during Edit mode, while your application is not running.[ExecuteInEditMode] —在应用程序未运行时在Edit模式下执行脚本逻辑。
- [ExecuteAlways] — to execute the script logic both as part of Play mode and when editing.[ExecuteAlways] —在Play模式下和在编辑时都执行脚本逻辑。
For example, this is how you write a component that makes GameObject orient towards the certain point in the scene:例如,这是您编写使GameObject朝向场景中特定点定向的组件的方式:
//C# Example (LookAtPoint.cs)
using UnityEngine;
[ExecuteInEditMode]
public class LookAtPoint : MonoBehaviour
{
public Vector3 lookAtPoint = Vector3.zero;
void Update()
{
transform.LookAt(lookAtPoint);
}
}
UNIGINE doesn't support executing C# logic inside the Editor. The most common way to extend functionality of UnigineEditor is C++ plugin.UNIGINE不支持在编辑器中执行C#逻辑。相反,您可以在UnigineScript中编写逻辑以优化项目创建过程。 UnigineScript可用于您为项目选择的任何编程工作流程,包括C# .NET。
There are two methods to add a script to the project:有两种方法向项目添加脚本:
-
By creating a World script. Perform the following steps:通过创建一个World脚本。执行以下步骤:
- Create a .usc script asset.创建一个.usc脚本资源。
-
Write logic inside this script file. If necessary, add a condition checking if the Editor is loaded:写逻辑。如有必要,添加条件检查是否已加载编辑器:
#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; }
-
Select the current world and specify the world script for it. Click Apply and reload the world.选择当前世界并为其指定世界脚本。单击Apply并重新加载世界。
- Check the Console window for errors.检查Console窗口是否有错误。
After that the script logic will be executed in both Editor and application.之后,脚本逻辑将在编辑器和应用程序中执行。
-
By using WorldExpression. For the same purpose you can use a built-in WorldExpression node executing scripts when added to the world:通过使用WorldExpression 。出于相同的目的,您可以使用内置的WorldExpression节点添加到世界上时执行脚本:
- Click Create -> Logic -> Expression and place the new WorldExpression node in the world.单击Create -> Logic -> Expression,然后将新的WorldExpression节点放置在世界中。
-
Write logic in UnigineScript in the Source field:在Source字段的UnigineScript中写入逻辑:
{ vec3 lookAtPoint = vec3_zero; Node node = engine.world.getNodeByName("my_node"); node.worldLookAt(lookAtPoint); }
- Check the Console window for errors.检查Console窗口是否有错误。
- The logic will be executed immediately.该逻辑将立即执行。
Triggers扳机 (Triggers)#
In addition to collision detection, Unity Collider component is accountable for being a Trigger that is executed when one collider enters the space of another.除碰撞检测外,Unity Collider组件还应负责当一个撞机进入另一个撞机的空间时执行的Trigger。
public class MyComponent : MonoBehaviour
{
void Start()
{
collider.isTrigger = true;
}
void OnTriggerEnter(Collider other)
{
// ...
}
void OnTriggerExit(Collider other)
{
// ...
}
}
In UNIGINE, Trigger is a special built-in node that raises events in certain situations:在UNIGINE中,Trigger是一个特殊的内置节点,在某些情况下会引发事件:
- NodeTrigger is used to track events for a particluar node (to which the Trigger is attached as a child) - it executes event handlers when the Trigger node is enabled or the Trigger node 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 is used to track events for 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是可用于游戏中的最常见类型。这是有关如何使用它的示例:
public class MyComponent : Component
{
WorldTrigger trigger;
void enter_event_handler(Node incomer)
{
Log.Message("\n{0} has entered the trigger space\n", incomer.Name);
}
private void Init()
{
trigger = node as WorldTrigger;
if(trigger != null)
{
trigger.EventEnter.Connect(enter_event_handler);
trigger.EventLeave.Connect( leaver => Log.Message("{0} has left the trigger space", leaver.Name));
}
}
}
Input输入#
Unity Conventional Game Input:Unity常规游戏输入:
public class MyPlayerController : MonoBehaviour
{
void Update()
{
if (Input.GetButtonDown("Fire"))
{
// ...
}
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
// ...
}
}
UNIGINE:
public class MyPlayerController : Component
{
void Update()
{
if(Input.IsMouseButtonDown(Input.MOUSE_BUTTON.LEFT))
{
Log.Message("Left mouse button was clicked at {0}\n", Input.MousePosition);
}
if (Input.IsKeyDown(Input.KEY.Q) && !Unigine.Console.Active)
{
Log.Message("Q was pressed and the Console is not active.\n");
Engine.Quit();
}
}
}
You can also use the ControlsApp class to handle control bindings. To configure the bindings, open the Controls settings:您也可以使用ControlsApp类来处理控件绑定。要配置绑定,请打开Controls设置:
public class MyPlayerController : Component
{
private void Init()
{
// remapping states to keys and buttons
ControlsApp.SetStateKey(Controls.STATE_FORWARD, Input.KEY.W);
ControlsApp.SetStateKey(Controls.STATE_BACKWARD, Input.KEY.S);
ControlsApp.SetStateKey(Controls.STATE_MOVE_LEFT, Input.KEY.A);
ControlsApp.SetStateKey(Controls.STATE_MOVE_RIGHT, Input.KEY.D);
ControlsApp.SetStateMouseButton(Controls.STATE_JUMP, Input.MOUSE_BUTTON.LEFT);
}
void Update()
{
if (ControlsApp.ClearState(Controls.STATE_FORWARD) != 0)
{
Log.Message("FORWARD key pressed\n");
}
else if (ControlsApp.ClearState(Controls.STATE_BACKWARD) != 0)
{
Log.Message("BACKWARD key pressed\n");
}
else if (ControlsApp.ClearState(Controls.STATE_MOVE_LEFT) != 0)
{
Log.Message("MOVE_LEFT key pressed\n");
}
else if (ControlsApp.ClearState(Controls.STATE_MOVE_RIGHT) != 0)
{
Log.Message("MOVE_RIGHT key pressed\n");
}
else if (ControlsApp.ClearState(Controls.STATE_JUMP) != 0)
{
Log.Message("JUMP button pressed\n");
}
}
}
Raycasting射线广播#
In Unity software, Physics.Raycast is used. GameObject should have a Collider component attached to take part in raycasting:在Unity软件中,使用Physics.Raycast。 GameObject应该附加一个Collider组件才能参与光线投射:
using UnityEngine;
public class ExampleClass : MonoBehaviour
{
public Camera camera;
void Update()
{
// ignore the 2nd layer
int layerMask = 1 << 2;
layerMask = ~layerMask;
RaycastHit hit;
Ray ray = camera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit, Mathf.Infinity, layerMask))
{
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * hit.distance, Color.yellow);
Debug.Log("Did Hit");
}
else
{
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * 1000, Color.white);
Debug.Log("Did not Hit");
}
}
}
In UNIGINE the same is handled by Intersections:在UNIGINE中,相同的处理方式为交叉口:
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;
#region Math Variables
#if UNIGINE_DOUBLE
using Scalar = System.Double;
using Vec3 = Unigine.dvec3;
#else
using Scalar = System.Single;
using Vec3 = Unigine.vec3;
#endif
#endregion
[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
public class IntersectionExample : Component
{
void Init()
{
Visualizer.Enabled = true;
}
void Update()
{
ivec2 mouse = Input.MousePosition;
float length = 100.0f;
Vec3 start = Game.Player.WorldPosition;
Vec3 end = start + new Vec3(Game.Player.GetDirectionFromMainWindow(mouse.x, mouse.y)) * length;
// ignore surfaces that have certain bits of the Intersection mask enabled
int mask = ~(1 << 2 | 1 << 4);
WorldIntersectionNormal intersection = new WorldIntersectionNormal();
Unigine.Object obj = World.GetIntersection(start, end, mask, intersection);
if (obj)
{
Vec3 point = intersection.Point;
vec3 normal = intersection.Normal;
Visualizer.RenderVector(point, point + normal, vec4.ONE);
Log.Message("Hit {0} at {1}\n", obj.Name, point);
}
}
}