This page has been translated automatically.
Video Tutorials
Interface
Essentials
Advanced
How To
Rendering
Professional (SIM)
UnigineEditor
Interface Overview
Assets Workflow
Version Control
Settings and Preferences
Working With Projects
Adjusting Node Parameters
Setting Up Materials
Setting Up Properties
Lighting
Sandworm
Using Editor Tools for Specific Tasks
Extending Editor Functionality
Built-in Node Types
Nodes
Objects
Effects
Decals
Light Sources
Geodetics
World Nodes
Sound Objects
Pathfinding Objects
Players
Programming
Fundamentals
Setting Up Development Environment
Usage Examples
C++
C#
UnigineScript
UUSL (Unified UNIGINE Shader Language)
Plugins
File Formats
Materials and Shaders
Rebuilding the Engine Tools
GUI
Double Precision Coordinates
API
Animations-Related Classes
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
IG Plugin
CIGIConnector Plugin
Rendering-Related Classes
VR-Related Classes
Content Creation
Content Optimization
Materials
Material Nodes Library
Miscellaneous
Input
Math
Matrix
Textures
Art Samples
Tutorials

Migrating to UNIGINE from Unity: Programming

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.

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.

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:

Source code (C#)
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:

Source code (C#)
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.

That's how we'll make the wheel rotate using C# Component System and run an instance to immediately check it:

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.

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.

Writing Gameplay Code#

Printing to Console#

Unity software UNIGINE
Source code (C#)
Debug.Log("Text: " + text);
Debug.LogFormat("Formatted text: {0}", text);
Source code (C#)
Log.Message("Debug info:" + text + "\n");
Log.Message("Debug info: {0}\n", new vec3(1, 2, 3));

See Also#

  • More types of messages in the Log class API
  • The video tutorial demonstrating how to print user messages to console using C# Component System

Accessing the GameObject / Node from Component#

Unity software UNIGINE
Source code (C#)
GameObject this_go = gameObject;
string n = gameObject.name;
Source code (C#)
Node this_node = node;
string n = node.Name;

See Also#

  • The video tutorial demonstrating how to access nodes from components using C# Component System

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 software UNIGINE
Source code (C#)
Vector3 forward = transform.forward;
Vector3 right = transform.right;
Vector3 up = transform.up;
transform.Translate(forward * speed * Time.deltaTime);
Source code (C#)
vec3 forward = node.GetWorldDirection(MathLib.AXIS.Y);
vec3 right = node.GetWorldDirection(MathLib.AXIS.X);
vec3 up = node.GetWorldDirection(MathLib.AXIS.Z);
node.Translate(forward * speed * Game.IFps);
Notice
Intances of the Players-Related classes use different direction vectors and are to be treated correspondingly.

See Also#

Smoother Gameplay with DeltaTime / 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 software UNIGINE
Source code (C#)
transform.Rotate(0, speed * Time.deltaTime, 0, Space.Self);
Source code (C#)
node.Rotate(0, 0, speed * Game.IFps);

Drawing Debug Data#

Unity software:

Source code (C#)
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:

Source code (C#)
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));
Notice
The visualizer can be toggled on and off via show_visualizer 1 console command as well.

See Also#

  • More types of visualizations in the Visualizer class API.

Loading a Scene#

Unity software UNIGINE
Source code (C#)
SceneManager.LoadScene("YourSceneName",LoadSceneMode.Single);
Source code (C#)
World.LoadWorld("YourSceneName");

Accessing a Component from the GameObject / Node#

Unity software:

Source code (C#)
MyComponent my_component = gameObject.GetComponent<MyComponent>();

UNIGINE:

Source code (C#)
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.

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:

Unity software:

Source code (C#)
// 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:

Source code (C#)
// 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#

Unity software:

Source code (C#)
// 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:

Source code (C#)
// 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:

Unity software UNIGINE
Source code (C#)
Collider collider = gameObject.GetComponent<Collider>;
BoxCollider boxCollider = collider as BoxCollider;
Source code (C#)
Node node = World.GetNodeByName("my_mesh");
ObjectMeshStatic mesh = node as ObjectMeshStatic;

To perform Upcasting (from a pointer-to-derived to a pointer-to-base) you can simply use the instance itself:

Unity software UNIGINE
Source code (C#)
Collider collider = gameObject.GetComponent<Collider>;
BoxCollider boxCollider = collider as BoxCollider;
Collider coll = boxCollider;
Source code (C#)
Node node = World.GetNodeByName("my_mesh");
ObjectMeshStatic mesh = node as ObjectMeshStatic;
Unigine.Object obj = mesh;

Destroy GameObject/Node#

Unity software UNIGINE
Source code (C#)
Destroy(maGameObject);

// destroy the game object with 1 second delay
Destroy(maGameObject, 1);
Source code (C#)
node.DeleteLater(); // recommended option
//called between the current and the next frames

node.DeleteForce(); // called during the same frame but unsafe

To perform deferred removal of a node in UNIGINE, you can create a component that will be responsible for the timer and deletion.

Source code (C#)
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();
		}
	}
}

And then use it for any node in any of your other logic components.

Source code (C#)
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#

In Unity software, you instantiate a prefab using the Object.Instantiate function:

Source code (C#)
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:

In UNIGINE, you can access any node via a component parameter as well, and clone it via Node.Clone(). However, assets are not nodes, they belong to the file system. To access assets from components use the following parameters types:

The same way you will need to specify the asset in UnigineEditor:

You can also use World.LoadNode to load a hierarchy of nodes from a .node asset manually by providing the virtual path to it:

Source code (C#)
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");

	}
}

You can also spawn the node reference as a single node (without extracting the content) in the world:

Source code (C#)
public class MyComponent : Component
{

	private void Init()
	{

		NodeReference nodeRef = new NodeReference("nodes/node_reference_0.node");

	}
}

NodeReferences have a set of peculiarities which are to be taken into account.

Protect GameObject/Node from Destroying#

In Unity, the load of a new Scene destroys all current Scene objects. Using Object.DontDestroyOnLoad allows for preserving an Object during scene loading.

In UNIGINE, nodes have an intrinsic Lifetime property accountable for their life cycle. By default, each node is assigned the Node.LIFETIME.WORLD option, i.e. it shall be deleted when the current world is closed. But you can also switch the node's lifetime to ENGINE and MANUAL mode if necessary.

Unity software UNIGINE
Source code (C#)
public class data_transfer : MonoBehaviour
{
	void Awake()
	{
		DontDestroyOnLoad(transform.gameObject);
	}
}
Source code (C#)
node.Lifetime = Node.LIFETIME.ENGINE;
// This node will not be deleted when switching to another world.
// It will remain in memory until the application is finished.

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:

  • [ExecuteInEditMode] — to execute the script logic during Edit mode, while your application is not running.
  • [ExecuteAlways] — to execute the script logic both as part of Play mode and when editing.

For example, this is how you write a component that makes GameObject orient towards the certain point in the scene:

Source code (C#)
//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.

For quick tests and prototyping, you can write logic in UnigineScript to optimize project creation process. UnigineScript is available for any programming workflow you have chosen for your project including C# .NET.

There are two methods to add a script to the project:

  • By creating a World script. Perform the following steps:

    1. Create a .usc script asset.

    2. Write logic inside this script file. If necessary, add a condition checking if the Editor is loaded:

      Source code (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;
      }
    3. Select the current world and specify the world script for it. Click Apply and reload the world.

    4. Check the Console window for errors.

    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:

    1. Click Create -> Logic -> Expression and place the new WorldExpression node in the world.
    2. Write logic in UnigineScript in the Source field:

      Source code (UnigineScript)
      {
      vec3 lookAtPoint = vec3_zero;
      Node node = engine.world.getNodeByName("my_node");
      node.worldLookAt(lookAtPoint);
      }
    3. Check the Console window for errors.
    4. The logic will be executed immediately.

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.

Source code (C#)
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:

  • 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.
  • WorldTrigger is used to track events for any node (collider or not) that gets inside or outside of it.
  • PhysicalTrigger is used to track events for physical objects get inside or outside of it.

    Notice
    PhysicalTrigger does not handle collision events, for that purpose Bodies and Joints provide their own events.

WorldTrigger is the most common type that can be used in gameplay. Here is an example on how to use it:

Source code (C#)
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:

Source code (C#)
public class MyPlayerController : MonoBehaviour
{
    void Update()
    {
        if (Input.GetButtonDown("Fire"))
        {
            // ...
        }
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        // ...
    }
}

UNIGINE:

Source code (C#)
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:

Source code (C#)
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:

Source code (C#)
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:

There are several types of them in UNIGINE:

  • World Intersections - ray intersections with object surfaces (or intersections between object bound volumes). This type can be used to select objects with the mouse cursor
  • Physics Intersections - intersections with collider geometry or with physical objects having bodies and collider shapes assigned. Physics Intersections use a separate mask with the corresponding name (Physics Intersection Mask), other than the one used for World Intersections (Intersection Mask). This offers more flexibility enabling you to control them independently. Physics Intersections can be used, for example, to detect collisions of spawned particles with physical shapes and bodies, or static collider surfaces to ensure proper interaction, or as a quick way to detect collisions for raycast-wheels of a simulated ground vehicle, or to check if a destructible object or a player was hit by a projectile.

The example of checking if there is an object under the mouse cursor given above for Unity Software is a case of using World Intersections in UNIGINE, and it will look like this:

Source code (C#)
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);
		}
	}
}

There are also Game Intersections - intersections with pathfinding nodes like obstacles.

Last update: 2023-12-22
Build: ()