通过激光笔更改材质
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.我们有一个激光笔,可以被抓取、投掷和使用(打开激光束)。现在,我们将为其添加一个备用使用操作——更改激光笔指向对象的材质。当一个控制器的 Grip 侧键被按住,同时用另一个控制器握持激光笔并使用时,将执行该操作。
The ObjLaserPointer component implements the base functionality of the laser pointer.ObjLaserPointer 组件实现了激光笔的基本功能。
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.所有可交互对象的基础组件是 VRInteractable(source/Framework/Components/VRInteractable.h)。它通过相应的虚方法定义了可交互对象的所有状态(抓取、保持、使用、投掷)。继承自 VRInteractable 的组件必须实现这些方法的重写。例如,抓取对象的逻辑必须在子类组件中的 grabIt 方法中实现。
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):因此,我们需要添加一个新动作,并将其映射到新状态(即在一只控制器按住 Grip 侧边按钮的同时使用另一只控制器抓住的激光笔进行使用):
-
Add a new useItAlt action (alternative use) to the VRInteractable base component:将一个新的 useItAlt 动作(替代使用)添加到 VRInteractable 基础组件中:
源代码 (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); // 交互方法 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#}$ };
-
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.为 ObjLaserPointer 组件添加 useItAlt() 方法的重写(因为我们将为激光指针添加一个新动作)。在 update() 方法中,实现当对应标志被设置时执行的 useItAlt 动作的逻辑。
注意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.别忘了将 PROP_AUTOSAVE 宏的值更改为 1,以启用对由组件系统创建的属性的更新(因为我们将对组件进行更改)。源代码 (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); // 属性名称 PROP_NAME("laser_pointer"); ${#HL}$PROP_AUTOSAVE(1); ${HL#}$ // 参数 PROP_PARAM(Node, laser); PROP_PARAM(Node, laser_ray); PROP_PARAM(Node, laser_hit); ${#HL}$// 用于切换的材质数组 PROP_ARRAY(Material, materials); ${HL#}$ // 交互方法 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}$// 实现替代操作的重写方法 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}$// 标志:当前是否正在执行替代操作 bool alt_use = false; // 当前要应用的材质索引 int current_material = 0; ${HL#}$ Unigine::WorldIntersectionPtr intersection = Unigine::WorldIntersection::create(); };
源代码 (C++)#include "ObjLaserPointer.h" #include "../../Framework/Utils.h" #include "../Global/ObjectLabeling.h" // 为了避免射线与自身相交,我们使用了一个偏移量 #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()) { // 计算激光击中位置 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}$ // 实现“替代使用”操作的逻辑(根据列表循环切换材质) if (alt_use) { // 如果材质列表未定义或为空,则不执行任何操作 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); // 将射线平面朝向玩家头部旋转(固定Y轴,Z轴自由) setWorldDirectionY(laser_ray, laser_ray->getWorldDirection(Math::AXIS_Y), vec3(VRPlayer::get()->getHead()->getWorldPosition() - laser_ray->getWorldPosition())); // 显示文本 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->setEnabled(!laser->isEnabled()); } void ObjLaserPointer::grabIt(VRPlayer* player, int hand_num) { grabbed = 1; } void ObjLaserPointer::throwIt(VRPlayer* player, int hand_num) { grabbed = 0; } ${#HL}$// 实现操作:设置替代使用的标志位 void ObjLaserPointer::useItAlt(VRPlayer* player, int hand_num) { alt_use = true; } ${HL#}$
-
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\).在基础类 VRPlayer(Framework\Components\Players\VRPlayer.h)中添加一个新的信号 AltUse,并绑定相应的回调函数和按钮。
源代码 (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 /////////////////////////////////////// // 是否在命令行中添加了 -extern_plugin "OpenVR/Oculus" 并成功加载? static int isVRLoaded(); // 返回最后创建的 PlayerVR 指针 static VRPlayer* get(); /////////////////////////////////////// // 方法 /////////////////////////////////////// // 设置传送的碰撞掩码(玩家可以所在的位置) virtual void setRayIntersectionMask(int mask) {} virtual void setTeleportationMask(int mask) {} // 玩家 virtual Unigine::PlayerPtr getPlayer() { return Unigine::Game::getPlayer(); } virtual void setLock(int lock) {} virtual void setPlayerPosition(const Unigine::Math::Vec3 &pos); // 移动玩家到指定位置(然后使其落地)并设置方向 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)))); } // 头部 UNIGINE_INLINE virtual Unigine::NodePtr getHead() { return head; } void setController(bool value) { is_controller = value; } bool isController() { return is_controller; } // 手部 virtual void setGrabMode(GRAB_MODE mode) { grab_mode = mode; } // 抓取方式:包围盒-包围盒 或 线-表面 相交? virtual int getNumHands() { return 0; } // 获取手的数量 virtual Unigine::NodePtr getHandNode(int num) = 0; // 获取指定手的节点 virtual int getHandDegreesOfFreedom(int num) = 0; // 获取指定手的自由度(PC 为 0-5,OpenVR 为 6) virtual Unigine::Math::vec3 getHandLinearVelocity(int num) = 0; // 获取手的线速度 virtual Unigine::Math::vec3 getHandAngularVelocity(int num) = 0; // 获取手的角速度 virtual int getHandState(int num) = 0; // 获取手的状态(抓取、握持、投掷) virtual const Unigine::NodePtr &getGrabNode(int num) const = 0; // 获取当前手抓取的对象 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) {}; // 手柄控制器 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; // 眼动追踪 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; } // 回调信号 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(); // 在节点上查找特定组件 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: // 单例实例 static VRPlayer* instance; // 信号 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; };
源代码 (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() { // 即使窗口未聚焦也运行应用程序 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; } // 使用抓取的对象 if (getControllerButtonDown(i, USE)) { for (int j = 0; j < components.size(); j++) components[j]->useIt(this, i); use_signal.invoke(getGrabNode(i)); } ${#HL}$ // 使用抓取对象的替代操作 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; }
-
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:所有 VR 和 PC 模式的输入绑定分别在对应的类 VRPlayerVR 和 VRPlayerPC 中进行设置(Framework\Components\Players\)。因此,我们需要对 VRPlayerVR.cpp 文件中的 VRPlayerVR::getControllerButtonDown() 方法进行一些修改:
源代码 (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}$ // 获取相对的(另一只手的)控制器 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; } // ...
- 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.保存我们修改过的所有文件,然后按下 Ctrl + F5 来构建并运行应用程序,以使组件系统更新用于将组件分配到节点的属性。运行后关闭应用程序,并切换回 UnigineEditor。
-
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.现在只剩下最后一步:指定将应用于对象的材质列表。选择 laser_pointer 节点,在 参数 窗口中点击 编辑。然后找到 laser_pointer 属性,为 materials 数组指定元素数量(例如 3),并从 材质 窗口中拖动所需材质以填充数组。
- Save changes (Ctrl + S) and run the application via SDK Browser.保存更改(Ctrl + S),并通过 SDK Browser 运行应用程序。
本页面上的信息适用于 UNIGINE 2.20 SDK.