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 ObjLaserPointer component implements the base functionality of the laser pointer.

The base component for all interactive objects is VRInteractable (source/Framework/Components/VRInteractable.h). It defines all available states of the interactive objects (grab, hold, use, throw) via the corresponding virtual methods. Components inherited from VRInteractable must implement overrides of these methods. For example, the logic for grabbing the object must be implemented in the grabIt 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 is gripped (Grip) by the opposite controller is used):

  1. Add a new useItAlt action (alternative use) to the VRInteractable base component:

    Source code (C++)
    #pragma once
    #include <UnigineNode.h>
    #include <UniginePhysics.h>
    #include <UnigineComponentSystem.h>
    #include "Players/VRPlayer.h"
    
    class VRPlayer;
    
    class VRInteractable : public Unigine::ComponentBase
    {
    public:
    	COMPONENT(VRInteractable, Unigine::ComponentBase);
    
    	// interact methods
    	virtual void grabIt(VRPlayer* player, int hand_num) {}
    	virtual void holdIt(VRPlayer* player, int hand_num) {}
    	virtual void useIt(VRPlayer* player, int hand_num) {}
    	virtual void throwIt(VRPlayer* player, int hand_num) {}
    	${#HL}$virtual void useItAlt(VRPlayer* player, int hand_num) {} ${HL#}$
    };
  2. Add an override of the useItAlt() method to the ObjLaserPointer component (as we are going to add a new action for the laser pointer). In the update() method, implement the logic of the useItAlt action that is executed when the corresponding flags are set.

    Notice
    Don't forget to change the value in the PROP_AUTOSAVE macro to 1 to enable updating the property created by the Component System as we're going to make changes to the component.
    Source code (C++)
    #pragma once
    #include <UnigineWorld.h>
    #include "../../Framework/Components/VRInteractable.h"
    
    class ObjLaserPointer : public VRInteractable
    {
    public:
    	COMPONENT(ObjLaserPointer, VRInteractable);
    	COMPONENT_INIT(init);
    	COMPONENT_UPDATE(update, 1);
    
    	// property
    	PROP_NAME("laser_pointer");
    	${#HL}$PROP_AUTOSAVE(1); ${HL#}$
    
    	// parameters
    	PROP_PARAM(Node, laser);
    	PROP_PARAM(Node, laser_ray);
    	PROP_PARAM(Node, laser_hit);
    
    	${#HL}$// array of materials to be used for switching
    	PROP_ARRAY(Material, materials); ${HL#}$
    
    	// interact methods
    	void useIt(VRPlayer* player, int hand_num) override;
    	void grabIt(VRPlayer* player, int hand_num) override;
    	void throwIt(VRPlayer* player, int hand_num) override;
    	${#HL}$// override implementing the alternative action
    	void useItAlt(VRPlayer* player, int hand_num) override; ${HL#}$
    
    protected:
    	void init();
    	void update();
    
    private:
    	Unigine::ObjectTextPtr obj_text;
    	Unigine::Math::Mat4 laser_ray_mat;
    	int grabbed;
    	${#HL}$// flag indicating that the alternative action is currently performed
    	bool alt_use = false;
    	// index of the current material to be applied
    	int current_material = 0; ${HL#}$
    	Unigine::WorldIntersectionPtr intersection = Unigine::WorldIntersection::create();
    };
    Source code (C++)
    #include "ObjLaserPointer.h"
    #include "../../Framework/Utils.h"
    #include "../Global/ObjectLabeling.h"
    
    // we don't want to get intersection af the ray with itself, so we use offset
    #define RAY_OFFSET 0.05f
    
    REGISTER_COMPONENT(ObjLaserPointer);
    
    using namespace Unigine;
    using namespace Math;
    
    void ObjLaserPointer::init()
    {
    	laser_ray_mat = laser_ray->getTransform();
    	grabbed = 0;
    	laser->setEnabled(false);
    }
    
    void ObjLaserPointer::update()
    {
    	if (laser->isEnabled())
    	{
    		// calculate laser hit position
    		laser_ray->setTransform(laser_ray_mat);
    
    		Vec3 dir = Vec3(laser_ray->getWorldDirection(Math::AXIS_Y));
    		Vec3 p0 = laser_ray->getWorldPosition() + dir * RAY_OFFSET;
    		Vec3 p1 = p0 + dir * 1000;
    		ObjectPtr hit_obj = World::getIntersection(p0, p1, 1, intersection);
    		if (hit_obj)
    		{
    			laser_ray->setScale(vec3(laser_ray->getScale().x, (float)length(intersection->getPoint() - p0) + RAY_OFFSET, laser_ray->getScale().z));
    			laser_hit->setWorldPosition(intersection->getPoint());
    			laser_hit->setEnabled(1);
    ${#HL}$
    			// implement logic of the 'alternative use' action (cyclic change of materials on the object according to the list)
    			if (alt_use)
    			{
    				// if the list of materials is empty/not defined, do nothing
    				if (materials.nullCheck() || materials.size() < 0)
    					return;
    
    				current_material++;
    				if (current_material >= materials.size())
    					current_material = 0;
    
    				hit_obj->setMaterial(materials[current_material], 0);
    
    				alt_use = false;
    			} ${HL#}$
    		}
    		else
    			laser_hit->setEnabled(0);
    
    		// rotate ray plane to player's head (Y fixed, Z floats)
    		setWorldDirectionY(laser_ray, laser_ray->getWorldDirection(Math::AXIS_Y), vec3(VRPlayer::get()->getHead()->getWorldPosition() - laser_ray->getWorldPosition()));
    
    		// show text
    		ObjectLabeling *labeling = ObjectLabeling::get();
    		if (labeling)
    		{
    			if (hit_obj && hit_obj->getProperty() && grabbed)
    				labeling->setTarget(hit_obj, laser_hit->getWorldPosition(), true);
    		}
    	}
    }
    
    void ObjLaserPointer::useIt(VRPlayer* player, int hand_num)
    {
    	// laser pointer switch on/off (any laser pointer object)
    	laser->setEnabled(!laser->isEnabled());
    }
    
    void ObjLaserPointer::grabIt(VRPlayer* player, int hand_num)
    {
    	grabbed = 1;
    }
    
    void ObjLaserPointer::throwIt(VRPlayer* player, int hand_num)
    {
    	grabbed = 0;
    }
    
    ${#HL}$// implementation of the action setting the corresponding flag
    void ObjLaserPointer::useItAlt(VRPlayer* player, int hand_num)
    {
    	alt_use = true;
    } ${HL#}$
  3. Add a new AltUse signal with a callback method and a button to the base VRPlayer class (Framework\Components\Players\VRPlayer.h). All input bindings for VR and PC modes are made in the corresponding classes VRPlayerVR and VRPlayerPC respectively (Framework\Components\Players\).

    Source code (C++)
    #pragma once
    #include <UnigineNode.h>
    #include <UnigineNodes.h>
    #include <UnigineWorld.h>
    #include <UniginePlayers.h>
    #include <UniginePhysics.h>
    #include <UnigineVector.h>
    #include <UnigineGui.h>
    #include <UnigineMap.h>
    #include <UnigineGame.h>
    #include <UnigineSignal.h>
    
    #include <UnigineComponentSystem.h>
    #include "../VRInteractable.h"
    
    class VRInteractable;
    
    class VRPlayer : public Unigine::ComponentBase
    {
    public:
    	COMPONENT(VRPlayer, Unigine::ComponentBase);
    	COMPONENT_INIT(init);
    	COMPONENT_POST_UPDATE(postUpdate);
    	COMPONENT_SHUTDOWN(shutdown);
    
    	///////////////////////////////////////
    	// enums
    	///////////////////////////////////////
    
    	enum {
    		HAND_FREE,
    		HAND_GRAB,
    		HAND_HOLD,
    		HAND_THROW
    	};
    
    	enum GRAB_MODE
    	{
    		BOUNDBOX,
    		INTERSECTIONS
    	};
    
    	enum BUTTONS
    	{
    		TELEPORT = 0,
    		USE,
    		GRAB,
    		MENU,
    ${#HL}$		ALT_USE ${HL#}$
    	};
    
    	///////////////////////////////////////
    	// static
    	///////////////////////////////////////
    
    	// is -extern_plugin "OpenVR/Oculus" added to command line and successfully loaded?
    	static int isVRLoaded();
    
    	// return pointer to last created PlayerVR
    	static VRPlayer* get();
    
    	///////////////////////////////////////
    	// methods
    	///////////////////////////////////////
    	
    	// sets intersection mask for teleportation (where player can be)
    	virtual void setRayIntersectionMask(int mask) {}
    	virtual void setTeleportationMask(int mask) {}
    
    	// player
    	virtual Unigine::PlayerPtr getPlayer() { return Unigine::Game::getPlayer(); }
    	virtual void setLock(int lock) {}
    	virtual void setPlayerPosition(const Unigine::Math::Vec3 &pos);
    
    	// move player to position (and then land him to ground) with some direction
    	virtual void landPlayerTo(const Unigine::Math::Vec3 &position, const Unigine::Math::vec3 &direction);
    	UNIGINE_INLINE virtual void landPlayerTo(const Unigine::Math::Mat4 &transform)
    	{
    		landPlayerTo(transform.getColumn3(3), normalize(Unigine::Math::vec3(-transform.getColumn3(2))));
    	}
    
    	// head
    	UNIGINE_INLINE virtual Unigine::NodePtr getHead() { return head; }
    	
    	void setController(bool value) { is_controller = value; }
    	bool isController() { return is_controller; }
    
    	// hands
    	virtual void setGrabMode(GRAB_MODE mode) { grab_mode = mode; } // grab via BoundBox-BoundBox or Line-Surface intersection?
    	virtual int getNumHands() { return 0; }						// get count of hands
    	virtual Unigine::NodePtr getHandNode(int num) = 0;			// get hand's node
    	virtual int getHandDegreesOfFreedom(int num) = 0;	// get hand's degrees of freedom (0-5 for PC, 6 for OpenVR)
    	virtual Unigine::Math::vec3 getHandLinearVelocity(int num) = 0;	// get speed of hand
    	virtual Unigine::Math::vec3 getHandAngularVelocity(int num) = 0;	// get angular speed of hand
    	virtual int getHandState(int num) = 0;				// get hand state (grab, hold, throw)
    	virtual const Unigine::NodePtr &getGrabNode(int num) const = 0;			// get object, grabbed by hand
    	virtual const Unigine::Vector<VRInteractable*> &getGrabComponents(int num) const = 0;
    	UNIGINE_INLINE virtual Unigine::Math::vec3 getHandyPos() { return Unigine::Math::vec3(0, -0.024f, 0.067f); }
    	UNIGINE_INLINE virtual Unigine::Math::quat getHandyRot() { return Unigine::Math::quat(10.0f, 0, 0); }
    
    	virtual void visualizeController(int num, bool value) {};
    
    	// hand's controllers
    	virtual int getControllerButtonPressed(int controller_num, VRPlayer::BUTTONS button) = 0;
    	virtual int getControllerButtonDown(int controller_num, VRPlayer::BUTTONS button) = 0;
    	virtual int getControllerButtonUp(int controller_num, VRPlayer::BUTTONS button) = 0;
    	virtual float getControllerAxis(int controller_num, Unigine::InputVRController::AXIS_TYPE axis) = 0;
    	virtual void vibrateController(int controller_num) = 0;
    
    	// eye tracking
    	UNIGINE_INLINE virtual bool isEyetrackingValid() const { return false; }
    	UNIGINE_INLINE virtual bool isEyetrackingAvailable() const { return false; }
    	UNIGINE_INLINE virtual Unigine::Math::Vec3 getFocusWorldPosition() const { return Unigine::Math::Vec3_zero; }
    
    	// callbacks
    	UNIGINE_CALLBACK_METODS(Use, use_signal);
    	UNIGINE_CALLBACK_METODS(Grab, grab_signal);
    	UNIGINE_CALLBACK_METODS(Hold, hold_signal);
    	UNIGINE_CALLBACK_METODS(Throw, throw_signal);
    ${#HL}$	UNIGINE_CALLBACK_METODS(UseAlt, use_alt_signal); ${HL#}$
    
    protected:
    	void init();
    	void postUpdate();
    	void shutdown();
    
    	// find specific components on the node
    	int can_i_grab_it(const Unigine::NodePtr &node);
    	int is_it_switcher(const Unigine::NodePtr &node);
    	int is_it_handle(const Unigine::NodePtr &node);
    
    	Unigine::NodeDummyPtr head;
    	GRAB_MODE grab_mode = GRAB_MODE::BOUNDBOX;
    
    	bool is_zero_pose_reseted = false;
    
    	bool have_grip_button = false;
    	int grip_axis = -1;
    
    private:
    
    	// singleton
    	static VRPlayer* instance;
    
    	// signals
    	Unigine::Signal use_signal;
    	Unigine::Signal grab_signal;
    	Unigine::Signal hold_signal;
    	Unigine::Signal throw_signal;
    ${#HL}$	Unigine::Signal use_alt_signal; ${HL#}$
    
    	bool is_controller = false;
    };
    Source code (C++)
    #include "VRPlayer.h"
    
    #include <UnigineGame.h>
    #include <UnigineEditor.h>
    #include <UnigineEngine.h>
    #include <UnigineWindowManager.h>
    #include <UnigineVR.h>
    
    #include "../Objects/ObjSwitch.h"
    #include "../Objects/ObjHandle.h"
    
    using namespace Unigine;
    using namespace Math;
    
    VRPlayer* VRPlayer::instance;
    
    int VRPlayer::isVRLoaded()
    {
    	return VR::isInitialized() && (VR::getApiType() != VR::API_NULL);
    }
    
    VRPlayer* VRPlayer::get()
    {
    	return instance;
    }
    
    void VRPlayer::init()
    {
    	// run application even when unfocused
    	Engine::get()->setBackgroundUpdate(Engine::BACKGROUND_UPDATE_RENDER_ALWAYS);
    
    	if(!is_controller)
    		instance = this;
    
    	is_zero_pose_reseted = VR::resetZeroPose();
    }
    
    void VRPlayer::postUpdate()
    {
    	if(!is_zero_pose_reseted)
    		is_zero_pose_reseted = VR::resetZeroPose();
    
    	for (int i = 0; i < getNumHands(); i++)
    	{
    		int hand_state = getHandState(i);
    		if (hand_state != HAND_FREE)
    		{
    			auto &components = getGrabComponents(i);
    			switch (hand_state)
    			{
    			case HAND_GRAB:
    				for (int j = 0; j < components.size(); j++)
    					components[j]->grabIt(this, i);
    				grab_signal.invoke(getGrabNode(i));
    				break;
    			case HAND_HOLD:
    				for (int j = 0; j < components.size(); j++)
    					components[j]->holdIt(this, i);
    				hold_signal.invoke(getGrabNode(i));
    				break;
    			case HAND_THROW:
    				for (int j = 0; j < components.size(); j++)
    					components[j]->throwIt(this, i);
    				throw_signal.invoke(getGrabNode(i));
    				break;
    			default:
    				break;
    			}
    
    			// use grabbed object
    			if (getControllerButtonDown(i, USE))
    			{
    				for (int j = 0; j < components.size(); j++)
    					components[j]->useIt(this, i);
    				use_signal.invoke(getGrabNode(i));
    			}
    
    ${#HL}$			// alternative use for grabbed object
    			if (getControllerButtonDown(i, ALT_USE))
    			{
    				for (int j = 0; j < components.size(); j++)
    					components[j]->useItAlt(this, i);
    				use_alt_signal.invoke(getGrabNode(i));
    			} ${HL#}$
    
    		}
    	}
    }
    
    void VRPlayer::shutdown()
    {
    	use_signal.clear();
    	grab_signal.clear();
    	hold_signal.clear();
    	throw_signal.clear();
    ${#HL}$	use_alt_signal.clear(); ${HL#}$
    
    	instance = nullptr;
    }
    
    void VRPlayer::setPlayerPosition(const Vec3 &pos)
    {
    	getPlayer()->setPosition(pos);
    }
    
    void VRPlayer::landPlayerTo(const Vec3 &position, const vec3 &direction)
    {
    	Vec3 pos1 = position;
    	Vec3 pos2 = position + Vec3(0, 0, -1) * getPlayer()->getZFar();
    	WorldIntersectionPtr intersection = WorldIntersection::create();
    	ObjectPtr hitObj = World::getIntersection(pos1, pos2, 1, intersection);
    	if (hitObj)
    		getPlayer()->setWorldTransform(setTo(intersection->getPoint(), intersection->getPoint() + Vec3(direction), vec3_up));
    }
    
    int VRPlayer::can_i_grab_it(const NodePtr &node)
    {
    	return ComponentSystem::get()->getComponent<VRInteractable>(node) != nullptr;
    }
    
    int VRPlayer::is_it_switcher(const NodePtr &node)
    {
    	return ComponentSystem::get()->getComponent<ObjSwitch>(node) != nullptr;
    }
    
    int VRPlayer::is_it_handle(const NodePtr &node)
    {
    	return ComponentSystem::get()->getComponent<ObjHandle>(node) != nullptr;
    }
  4. All input bindings for VR and PC modes are made in the corresponding classes VRPlayerVR and VRPlayerPC respectively (Framework\Components\Players\). So we should make some changes to the VRPlayerVR::getControllerButtonDown()method in the VRPlayerVR.cpp file:

    Source code (C++)
    // ...
    int VRPlayerVR::getControllerButtonDown(int controller_num, VRPlayer::BUTTONS button)
    {
    	if (!controller_valid[controller_num])
    		return 0;
    
    	InputVRControllerPtr controller = (controller_num == 0 ? left_controller_device : right_controller_device);
    ${#HL}$
    	// getting the opposite controller
    	InputVRControllerPtr other_controller = (controller_num == 0 ? right_controller_device : left_controller_device); ${HL#}$
    	if (!controller)
    		return 0;
    
    	switch(button)
    	{
    	case VRPlayer::TELEPORT:
    		{
    			int axis_index = controller->findAxisByType(InputVRController::AXIS_TYPE_TRACKPAD_X);
    
    			if(axis_index == -1)
    				axis_index = controller->findAxisByType(InputVRController::AXIS_TYPE_JOYSTICK_X);
    
    			if(axis_index != -1)
    				return controller->isButtonDown(Input::VR_BUTTON(Input::VR_BUTTON_AXIS_0 + axis_index));
    		}
    	case VRPlayer::USE:
    		{
    			int axis_index = controller->findAxisByType(InputVRController::AXIS_TYPE_TRIGGER_VALUE);
    
    			if(axis_index == -1)
    				axis_index = controller->findAxisByType(InputVRController::AXIS_TYPE_TRIGGER_FORCE);
    
    			if(axis_index != -1)
    				return controller->getAxis(axis_index) > 0.5f;
    		}
    	case VRPlayer::GRAB:
    		return controller->isButtonDown(Input::VR_BUTTON_GRIP);
    	case VRPlayer::MENU:
    		return controller->isButtonDown(Input::VR_BUTTON_APPLICATION) || controller->isButtonDown(Input::VR_BUTTON_Y);
    ${#HL}$
    	case VRPlayer::ALT_USE:
    	{
    		if (!other_controller)
    			return 0;
    		int axis_index = controller->findAxisByType(InputVRController::AXIS_TYPE_TRIGGER_VALUE);
    
    		if (axis_index == -1)
    			axis_index = controller->findAxisByType(InputVRController::AXIS_TYPE_TRIGGER_FORCE);
    
    		if (axis_index != -1)
    			return (controller->isButtonPressed(Input::VR_BUTTON_GRIP) && other_controller->isButtonDown(Input::VR_BUTTON_GRIP));
    	} ${HL#}$
    	}
    
    	return 0;
    }
    // ...
  5. Save all the files that we modified and then build and run the application by hitting Ctrl + F5 to make the Component System update properties used to assign the components to nodes. Close the application after running it and switch to UnigineEditor.
  6. The only thing left to do is to 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 laser_pointer property, 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.

  7. Save changes (Ctrl + S) and run the application via SDK Browser.
Last update: 2024-11-06
Build: ()