实施射击
现在,我们可以设置射击能力并为其准备效果。 当玩家按下鼠标左键时,机器人会从它的一把枪中发射子弹。 由于有两支枪,我们可以交替开火。
步骤1。创建、移动和删除一颗子弹#
我们需要建立一个节点代表一颗子弹在游戏中。子弹飞在指定的方向和爆炸影响的对象。如果没有击中任何和耗尽的时候,它将被摧毁。在影响与任何游戏区域中的动态对象,一个脉冲将被应用到它的身体。
我们将使用 位掩码 机制来识别可以被子弹击中的对象。 子弹通过 Intersection 掩码中启用的 BulletIntersection 位(如下所述)检查其轨迹和其他对象的曲面之间的相交。 我们的子弹会击中墙壁并以命中效果爆炸。 效果包括 light 闪光、裂缝 贴花、爆破器 声音,并激发粒子。 效果在命中位置加载和播放。
每个节点都有一个变换矩阵,它编码了节点在世界中的位置、旋转和比例。 有不同的方式来执行基本节点 transformations。 我们将根据渲染最后一帧游戏所花费的时间为每一帧计算子弹轨迹的新位置。 这样,无论播放器硬件调用 Update 方法的频率如何,我们都将确保其速度相同(与帧速率无关)。
-
对于每一个墙的box曲面启用Intersection位掩码的第七位,并称之为BulletIntersection。
-
在 IDE 中创建一个新的 C++ 组件,并将其命名为 Projectile。 将以下代码复制到相应的文件并保存解决方案。
#pragma once #include <UnigineComponentSystem.h> #include <UnigineGame.h> class Projectile : public Unigine::ComponentBase { public: // declare constructor and destructor for our class and define a property name. COMPONENT_DEFINE(Projectile, ComponentBase) // declare methods to be called at the corresponding stages of the execution sequence COMPONENT_INIT(init); COMPONENT_UPDATE(update); PROP_PARAM(File, bulletHitEffect); protected: void init(); void update(); private: // speed of the bullet float speed = 30.0f; Unigine::WorldIntersectionNormalPtr intersection; };
#include "Projectile.h" #include "Destroy.h" REGISTER_COMPONENT(Projectile); using namespace Unigine; using namespace Math; void Projectile::init() { if (intersection == nullptr) intersection = WorldIntersectionNormal::create(); } void Projectile::update() { Vec3 oldPos = node->getWorldPosition(); vec3 dir = node->getWorldDirection(MathLib::AXIS::AXIS_Y); // calculate the next position of the bullet Vec3 newPos = oldPos + Vec3(dir) * speed * Game::getIFps(); // check the intersection with wall objects Unigine::ObjectPtr obj = World::getIntersection(oldPos, newPos, 0x00000080, intersection); // 7th BulletIntersection bit is set if (obj) { // spawn the hit effect at the hit point NodePtr hitEffect = World::loadNode(bulletHitEffect); if (hitEffect) { hitEffect->setPosition(intersection->getPoint()); // orient the effect towards the hit direction hitEffect->setWorldDirection(intersection->getNormal(), vec3_up, AXIS::AXIS_Y); } // add impulse to an object if it is a body rigid BodyRigidPtr rb = obj->getBodyRigid(); if (rb) { rb->setFrozen(false); rb->addWorldImpulse(obj->getWorldPosition(), node->getWorldDirection(MathLib::AXIS::AXIS_Y) * speed); } // remove the bullet node.deleteLater(); } else { // move the bullet to a new position node->setWorldPosition(newPos); } }
随着子弹的生命周期用完,我们应该通过调用 deleteLater() 方法来删除它。 创建一个新的 C++ 组件 并将其命名为 Destroy。 复制下面的代码并将其粘贴到项目中的相应文件中,并保存在 IDE 中。
#pragma once #include <UnigineComponentSystem.h> #include <UnigineGame.h> class Destroy : public Unigine::ComponentBase { public: // declare constructor and destructor for our class and define a property name. COMPONENT_DEFINE(Destroy, ComponentBase) // declare methods to be called at the corresponding stages of the execution sequence COMPONENT_INIT(init); COMPONENT_UPDATE(update); // object's lifetime PROP_PARAM(Float, lifeTime); protected: void init(); void update(); private: float startTime; };
#include "Destroy.h" using namespace Unigine; using namespace Math; REGISTER_COMPONENT(Destroy); void Destroy::init() { // remember initialization time of an object startTime = Game::getTime(); } void Destroy::update() { // wait until the life time ends and delete the object if (Game::getTime() - startTime > lifeTime) node.deleteLater(); }
- 构建和运行项目通过IDE(在Visual Studio 中按Ctrl + F5),组件系统将生成一个组件的属性文件。
-
将 programming_quick_start\character\bullet\bullet.node节点从 Asset Browser 拖到 Viewport 并单击 Parameters 窗口的 Reference 部分中的 Edit 进行修改。 将 Destroy 组件添加到 ObjectMeshStatic 的 bullet 子节点并设置 Life Time 到 5.0。
-
接下来,将 Asset Browser 中的 programming_quick_start\character\bullet_hit\bullet_hit.node 拖到 Viewport 中,点击 Edit 在 Parameters 窗口中,将 Destroy 组件添加到 bullet_hit 的 NodeDummy 和 LightOmni。 将 Life Time 值分别设置为 10.0 和 0.05 秒。
- 然后选择 bullet_hit Node Reference 并在 Reference 部分中点击 Apply 保存对其所做的所有更改。
-
将Projectile属性添加到bullet Node Reference 的 ObjectMeshStatic bullet 子节点。将bullet_hit.node节点从Asset Browser窗口拖到Bullet Hit Effect字段。
还有Speed字段允许指定一个产卵子弹的速度。
- 现在禁用子弹的碰撞测试避免子弹与自身检测十字路口。选择bullet_mat曲面和取消Intersection选项。
- 保存更改的bullet Node Reference,通过选择它并单击Apply Reference部分或简单地按Ctrl+S热键保存所有更改资源。
- 现在您可以从世界中删除bullet和bullet_hit,因为我们将通过代码生成它们。
步骤2。生成子弹#
让我们使用 Dummy Node创建特殊刷出节点,但不使用视觉表示。它们的位置将被用作初始子弹的位置。
- 在 World Nodes 窗口中选择 robot Node Reference,然后在 Parameters 窗口的 Reference 部分中单击 Edit。
- 在 World Nodes 窗口中右键单击子 robot ObjectMeshSkinned 以添加子节点。 选择 Create->Node->Dummy 并将其放置在 Viewport 靠近左枪末端的位置。 Y 轴(绿色箭头)必须指向射击方向,因为 UNIGINE 使用 右手 笛卡尔坐标系。
-
将 Dummy Node 重命名为 "left_bullet_spawn"。
- 以同样的方式为右枪创建一个刷出点,并将其命名为 "right_bullet_spawn"。
要在运行时通过 API 在单击鼠标右键时生成子弹,请将以下代码添加到 PlayerController 组件。 我们使用属性的file参数来引用子弹节点。 不要忘记将代码保存在 IDE 中。
#pragma once #include <UnigineComponentSystem.h> #include <UnigineGame.h> #include <UnigineControls.h> #include <UnigineStreams.h> #include <UniginePlayers.h> #include <UnigineWorld.h> #include <UnigineConsole.h> #include <UnigineMathLib.h> #include <UnigineRender.h> class PlayerController : public Unigine::ComponentBase { public: // declare constructor and destructor for our class and define a property name. COMPONENT_DEFINE(PlayerController, ComponentBase) // declare methods to be called at the corresponding stages of the execution sequence COMPONENT_INIT(init); COMPONENT_UPDATE_PHYSICS(updatePhysics); //========================== NEW - BEGIN =============================== COMPONENT_UPDATE(update); // asset file that contains the bullet PROP_PARAM(File, bullet_path); // nodes in the world for bullets' spawn PROP_PARAM(Node, leftSpawn); PROP_PARAM(Node, rightSpawn); //========================== NEW - END =============================== protected: void init(); void updatePhysics(); //========================== NEW - BEGIN =============================== void update(); //=========================== NEW - END ================================ private: void move(const Unigine::Math::vec3& direction); Unigine::BodyRigidPtr rigid; Unigine::PlayerPtr player; // a WorldIntersection object to store the information about the intersection Unigine::WorldIntersectionPtr intersection = Unigine::WorldIntersection::create(); Unigine::Math::Vec3 pos; //========================== NEW - BEGIN =============================== bool isNextLeft = false; // mouse fire button Unigine::Input::MOUSE_BUTTON mouseFireKey = Unigine::Input::MOUSE_BUTTON::MOUSE_BUTTON_RIGHT; Unigine::NodePtr bullet; //=========================== NEW - END ================================ };
#include "PlayerController.h" //========================== NEW - BEGIN =============================== #include "Projectile.h" #include "Destroy.h" //========================== NEW - END =============================== using namespace Unigine; using namespace Math; REGISTER_COMPONENT(PlayerController); void PlayerController::init() { player = Game::getPlayer(); if (node) { rigid = node->getObjectBodyRigid(); if (rigid) { rigid->setAngularScale(vec3(0.0f, 0.0f, 0.0f)); // restricting the rotation rigid->setLinearScale(vec3(1.0f, 1.0f, 0.0f)); // restricting Z movement rigid->setMaxLinearVelocity(8.0f); // clamping the max linear velocity } } } //========================== NEW - BEGIN =============================== void PlayerController::update() { if (Input::isMouseButtonDown(mouseFireKey) && !Console::isActive()) { // load the bullet and set its position bullet = World::loadNode(bullet_path); if (isNextLeft && bullet) { if (rightSpawn) { bullet->setPosition(rightSpawn->getWorldPosition()); bullet->setRotation(rightSpawn->getWorldRotation()); } } else { if (leftSpawn) { bullet->setPosition(leftSpawn->getWorldPosition()); bullet->setRotation(leftSpawn->getWorldRotation()); } } // alternate between the left and the right gun isNextLeft = !isNextLeft; } // press ESC button to close the game if (Input::isKeyDown(Input::KEY::KEY_ESC)) { Engine::get()->quit(); } } //=========================== NEW - END ================================ void PlayerController::updatePhysics() { if (!Console::isActive()) // do not process input if the console is shown { // check if W key is pressed if (Input::isKeyPressed(Input::KEY::KEY_W)) move(player->getWorldDirection(MathLib::AXIS::AXIS_Y)); // move forward // check if S key is pressed if (Input::isKeyPressed(Input::KEY::KEY_S)) move(player->getWorldDirection(MathLib::AXIS::AXIS_NY)); // move backward // check if A key is pressed if (Input::isKeyPressed(Input::KEY::KEY_A)) move(player->getWorldDirection(MathLib::AXIS::AXIS_NX)); // move left // check if D key is pressed if (Input::isKeyPressed(Input::KEY::KEY_D)) move(player->getWorldDirection(MathLib::AXIS::AXIS_X)); // move right // finding the positions of the cursor and the point moved 100 units away in the camera forward direction ivec2 mouse = Input::getMousePosition(); Vec3 p0 = player->getWorldPosition(); Vec3 p1 = p0 + Vec3(player->getDirectionFromMainWindow(mouse.x, mouse.y)) * 100; // casting a ray from p0 to p1 to find the first intersected object ObjectPtr obj = World::getIntersection(p0, p1, 1, intersection); // the first bit of the intersection mask is set to 1, the rest are 0s // finding the intersection position, creating a transformation matrix to face this position and setting the transform matrix for the body preserving current angular and linear velocities if (obj && rigid) { pos = intersection->getPoint(); pos.z = rigid->getTransform().getTranslate().z; // project the position vector to the Body Rigid pivot plane Mat4 transform = Math::setTo(rigid->getTransform().getTranslate(), pos, vec3_up, AXIS::AXIS_Y); rigid->setPreserveTransform(transform); // turn the character's body } } } // moving the rigid body with linear impulse in the specified direction void PlayerController::move(const Unigine::Math::vec3& direction) { // direction is a normalized camera axis vector if (rigid) // direction is a normalized camera axis vector rigid->addLinearImpulse(direction); }
- 在IDE中构建并运行解决方案以重新生成 PlayerController属性。
-
将 left_bullet_spawn 和 right_bullet_spawn 拖到 robot节点的 PlayerController 属性的对应字段中 (ObjectMeshSkinned)。 并将 bullet.node 分配给 Bullet Path 字段。
- 拯救世界的变化,去File->Save World或者按Ctrl+S热键。
- 构建和运行您的IDE中的游戏尝试新的拍摄能力。