This page has been translated automatically.
Unigine Basics
1. Introduction
2. Managing Virtual Worlds
3. Preparing 3D Models
4. Materials
5. Cameras and Lighting
6. Implementing Application Logic
7. Making Cutscenes and Recording Videos
8. Preparing Your Project for Release
9. Physics
10. Optimization Basics
11. PROJECT2: First-Person Shooter
12. PROJECT3: Third-Person Cross-Country Arcade Racing Game

Changing Material by Laser Pointer

Another way to extend functionality is to add a new action for the object.

We have a laser pointer that we can grab, throw, and use (turn on a laser beam). Now, let's add an alternative use action - change the pointed object's material. This action will be executed when we press and hold the Grip side button on one controller while using the laser pointer gripped by the opposite controller.

The VRLaserPointer.cs component implements the base functionality of the laser pointer.

The base component for all interactive objects is VRBaseInteractable (vr_template/components/base/VRInteractable.cs). It defines all available states of the interactive objects (NOT_INTERACT, HOVERED, GRABBED, USED) and methods executed on certain actions performed on the objects (OnHoverBegin, OnHoverEnd, OnGrabBegin, OnGrabEnd, OnUseBegin, OnUseEnd). Components inherited from VRBaseInteractable must implement overrides of these methods. For example, the logic for grabbing the object must be implemented in the OnGrabBegin method of the inherited component.

So, we need to add a new action and map it to a new state (the Grip side button is pressed on one controller while the laser pointer gripped (Grip) by the opposite controller is used):

  1. Add a new OnAltUse action (alternative use) and a new ALT_USED state to the VRBaseInteractable base component:

    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 VRBaseInteractable : Component
    {
    	static public event Action<VRBaseInteractable> onInit;
    
    	public enum INTERACTABLE_STATE
    	{
    		NOT_INTERACT,
    		HOVERED,
    		GRABBED,
    		USED${#HL}$,
    		ALT_USED   // add a new state  ${HL#}$
    	}
    
    	public INTERACTABLE_STATE CurrentState { get; set; }
    
    	protected void Init()
    	{
    		onInit?.Invoke(this);
    	}
    
    	public virtual void OnHoverBegin(VRBaseInteraction interaction, VRBaseController controller) { }
    	public virtual void OnHoverEnd(VRBaseInteraction interaction, VRBaseController controller) { }
    	public virtual void OnGrabBegin(VRBaseInteraction interaction, VRBaseController controller) { }
    	public virtual void OnGrabEnd(VRBaseInteraction interaction, VRBaseController controller) { }
    	public virtual void OnUseBegin(VRBaseInteraction interaction, VRBaseController controller) { }
    	public virtual void OnUseEnd(VRBaseInteraction interaction, VRBaseController controller) { }
    ${#HL}$
    	// add the new action and stop it
    	public virtual void OnAltUse(VRBaseInteraction interaction, VRBaseController controller) { }
    	public virtual void OnAltUseEnd(VRBaseInteraction interaction, VRBaseController controller) { }  ${HL#}$
    }
  2. Add overrides of the OnAltUse() and OnAltUseEnd() methods to the VRLaserPointer component (as we are going to add a new action for the laser pointer). In the Update() method, implement the logic of the AltUse action that is executed when the corresponding flags are set.

    Source code (C#)
    #region Math Variables
    #if UNIGINE_DOUBLE
    	using Scalar = System.Double;
    	using Vec2 = Unigine.dvec2;
    	using Vec3 = Unigine.dvec3;
    	using Vec4 = Unigine.dvec4;
    	using Mat4 = Unigine.dmat4;
    #else
    using Scalar = System.Single;
    using Vec2 = Unigine.vec2;
    using Vec3 = Unigine.vec3;
    using Vec4 = Unigine.vec4;
    using Mat4 = Unigine.mat4;
    using WorldBoundBox = Unigine.BoundBox;
    using WorldBoundSphere = Unigine.BoundSphere;
    using WorldBoundFrustum = Unigine.BoundFrustum;
    #endif
    #endregion
    
    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 VRLaserPointer : VRBaseInteractable
    {
    	[ShowInEditor]
    	[Parameter(Title = "Laser", Group = "VR Laser Pointer")]
    	private Node laser = null;
    	
    	[ShowInEditor]
    	[Parameter(Title = "Laser Ray", Group = "VR Laser Pointer")]
    	private Node laserRay = null;
    
    	[ShowInEditor]
    	[Parameter(Title = "Laser Hit", Group = "VR Laser Pointer")]
    	private Node laserHit = null;
    
    	[ShowInEditor]
    	[Parameter(Title = "Object Text", Group = "VR Laser Pointer")]
    	private ObjectText objText = null;
    
    	private Mat4 laserRayMat;
    	private WorldIntersection intersection = new WorldIntersection();
    	private float rayOffset = 0.05f;
    
    	private bool grabbed = false;
    ${#HL}$
    	private bool altuse = false;
    	public  List<Material> materials = null;
    	private int current_material = 0;  ${HL#}$
    
    	protected override void OnReady()
    	{
    		laserRayMat = new Mat4(laserRay.Transform);
    		laser.Enabled = false;
    	}
    
    	private void Update()
    	{
    		if(laser.Enabled && grabbed)
    		{
    			laserRay.Transform = laserRayMat;
    
    			vec3 dir = laserRay.GetWorldDirection(MathLib.AXIS.Y);
    			Vec3 p0 = laserRay.WorldPosition + dir * rayOffset;
    			Vec3 p1 = p0 + dir * 1000;
    			Unigine.Object hitObj = World.GetIntersection(p0, p1, 1, intersection);
    
    			if(hitObj != null) 
    			{
    				laserRay.Scale = new vec3(laserRay.Scale.x, MathLib.Length(intersection.Point - p0) + rayOffset, laserRay.Scale.z);
    				laserHit.WorldPosition = intersection.Point;
    				laserHit.Enabled = true;
    			}
    			else
    			{
    				laserHit.Enabled = false;
    			}
    
    			if (hitObj != null)
    			{
    				objText.Enabled = true;
    				objText.Text = hitObj.Name;
    				float radius = objText.BoundSphere.Radius;
    				vec3 shift = vec3.UP * radius;
    				objText.WorldTransform = MathLib.SetTo(laserHit.WorldPosition + shift, VRPlayer.LastPlayer.HeadController.WorldPosition, vec3.UP, MathLib.AXIS.Z);
    ${#HL}$
    				// implement logic of the 'alternative use' action (cyclic change of materials on the object according to the list)
    				if(altuse)
    				{
    					// if the list of materials is empty/not defined, do nothing
    					if(materials == null || materials.Count < 0)
    						return;
    
    					current_material++;
    					if (current_material >= materials.Capacity)
    						current_material = 0;
    
       			 		hitObj.SetMaterial(materials[current_material], 0);
    					Log.Message("ALTUSE HIT\n");
    					altuse = false;
    				}  ${HL#}$
    			}
    			else
    				objText.Enabled = false;
    		}
    	}
    
    	public override void OnGrabBegin(VRBaseInteraction interaction, VRBaseController controller) 
    	{
    		grabbed = true;
    	}
    
    	public override void OnGrabEnd(VRBaseInteraction interaction, VRBaseController controller) 
    	{
    		grabbed = false;
    		laser.Enabled = false;
    		objText.Enabled = false;
    	}
    
    	public override void OnUseBegin(VRBaseInteraction interaction, VRBaseController controller) 
    	{
    		if(grabbed)
    			laser.Enabled = true;
    	}
    
    	public override void OnUseEnd(VRBaseInteraction interaction, VRBaseController controller) 
    	{
    		laser.Enabled = false;
    		objText.Enabled = false;
    	}
    ${#HL}$
    	// override the method that is called when the 'alternative use' action is performed on the object
    	public override void OnAltUse(VRBaseInteraction interaction, VRBaseController controller) 
    	{
    		altuse = true;
    		CurrentState = VRBaseInteractable.INTERACTABLE_STATE.ALT_USED;
    	}
    
    	// override the method that is called when the 'alternative use' action is performed on the object
    	public override void OnAltUseEnd(VRBaseInteraction interaction, VRBaseController controller) 
    	{
    		altuse = false;
    		CurrentState = VRBaseInteractable.INTERACTABLE_STATE.NOT_INTERACT;
    	}  ${HL#}$
    }
  3. Add an execution condition for the OnAltUse action. All available types of interactions are implemented in the VRHandShapeInteraction component (vr_template/components/interactions/interactions/VRHandShapeInteraction.cs). So, add the following code to the Interact() method of this component:

    Source code (C#)
    public override void Interact(VRInteractionManager.InteractablesState interactablesState, float ifps)
    {
    	// ...
    	// update current input
    	bool grabDown = false;
    	bool grabUp = false;
    	bool useDown = false;
    	bool useUp = false;
    ${#HL}$
    	// reset flags before checking the current input
    	bool altUse = false;
    	bool altUseUp = false;  ${HL#}$
    
    	switch (controller.Device)
    	{
    		case InputSystem.VRDevice.LEFT_CONTROLLER:
    			grabDown = InputSystem.IsLeftButtonDown(InputSystem.ControllerButtons.GRAB_BUTTON);
    			grabUp = InputSystem.IsLeftButtonUp(InputSystem.ControllerButtons.GRAB_BUTTON);
    			useDown = InputSystem.IsLeftButtonDown(InputSystem.ControllerButtons.USE_BUTTON);
    			useUp = InputSystem.IsLeftButtonUp(InputSystem.ControllerButtons.USE_BUTTON);
    ${#HL}$
    			// for the left controller, the Use button must be hold on the right controller  
    			altUse = InputSystem.IsRightButtonPress(InputSystem.ControllerButtons.USE_BUTTON);
    			altUseUp = InputSystem.IsRightButtonUp(InputSystem.ControllerButtons.USE_BUTTON);  ${HL#}$
    			break;
    
    		case InputSystem.VRDevice.RIGHT_CONTROLLER:
    			grabDown = InputSystem.IsRightButtonDown(InputSystem.ControllerButtons.GRAB_BUTTON);
    			grabUp = InputSystem.IsRightButtonUp(InputSystem.ControllerButtons.GRAB_BUTTON);
    			useDown = InputSystem.IsRightButtonDown(InputSystem.ControllerButtons.USE_BUTTON);
    			useUp = InputSystem.IsRightButtonUp(InputSystem.ControllerButtons.USE_BUTTON);
    ${#HL}$
    			// for the right controller, the Use button must be hold on the left controller 
    			altUse = InputSystem.IsLeftButtonPress(InputSystem.ControllerButtons.USE_BUTTON);
    			altUseUp = InputSystem.IsLeftButtonUp(InputSystem.ControllerButtons.USE_BUTTON);  ${HL#}$
    			break;
    
    		case InputSystem.VRDevice.PC_HAND:
    			grabDown = InputSystem.IsGeneralButtonDown(InputSystem.GeneralButtons.FIRE_1);
    			grabUp = InputSystem.IsGeneralButtonUp(InputSystem.GeneralButtons.FIRE_1);
    			useDown = InputSystem.IsGeneralButtonDown(InputSystem.GeneralButtons.FIRE_2);
    			useUp = InputSystem.IsGeneralButtonUp(InputSystem.GeneralButtons.FIRE_2);
    ${#HL}$
    			// for the keyboard, the JUMP button must be held 
    			altUse = InputSystem.IsGeneralButtonPress(InputSystem.GeneralButtons.JUMP);
    			altUseUp = InputSystem.IsGeneralButtonUp(InputSystem.GeneralButtons.JUMP);  ${HL#}$
    			break;
    
    		default: break;
    	}
    ${#HL}$
    	// stop the 'alternative use' action - call OnAltUseEnd
    	// for all components of the hovered object that have an implementation of this method
    	if (altUseUp)
    	{
    		foreach (var hoveredObjectComponent in hoveredObjectComponents)
    			hoveredObjectComponent.OnAltUseEnd(this, controller);
    	}  ${HL#}$
    
    	// can grab and use hovered object
    	if (hoveredObject != null)
    	{
    		if (grabDown && grabbedObject == null)
    		{
    
    			grabbedObject = hoveredObject;
    			grabbedObjectComponents.Clear();
    			grabbedObjectComponents.AddRange(hoveredObjectComponents);
    			foreach (var grabbedObjectComponent in grabbedObjectComponents)
    			{
    				if (VRInteractionManager.IsGrabbed(grabbedObjectComponent))
    				{
    					VRBaseInteraction inter = VRInteractionManager.GetGrabInteraction(grabbedObjectComponent);
    					VRInteractionManager.StopGrab(inter);
    				}
    
    				grabbedObjectComponent.OnGrabBegin(this, controller);
    				interactablesState.SetGrabbed(grabbedObjectComponent, true, this);
    			}
    		}
    
    		if (useDown)
    		{
    			usedObject = hoveredObject;
    			usedObjectComponents.Clear();
    			usedObjectComponents.AddRange(hoveredObjectComponents);
    			foreach (var usedObjectComponent in usedObjectComponents)
    			{
    				usedObjectComponent.OnUseBegin(this, controller);
    				interactablesState.SetUsed(usedObjectComponent, true, this);
    			}
    		}
    ${#HL}$
    		// call OnAltUse for all components of the hovered object that have an implementation of this method
    		// for all components of the hovered object that have an implementation of this method
    		if (altUse)
    		{
    			foreach (var hoveredObjectComponent in hoveredObjectComponents)
    				if(hoveredObjectComponent.CurrentState != VRBaseInteractable.INTERACTABLE_STATE.ALT_USED)
    					hoveredObjectComponent.OnAltUse(this, controller);
    			altUse = false;
    		}  ${HL#}$
    	}
    
    	// ...
    }
  4. Specify the list of materials that will be applied to the object. Select the laser_pointer node and click Edit in the Parameters window. Then, find the VRLaserPointer component, specify the number of elements for the materials array (for example, 3), and drag the required materials from the Materials window to fill the array.

  5. Save changes (Ctrl+S) and press the Play button to run the application.
Last update: 2024-03-25
Build: ()