Реализация стрельбы
Now, we can set up the shooting ability and prepare effects for it. When a player presses the Left Mouse Button, the robot fires a bullet from one of its guns. Since there are two guns, we can alternate the fire. Теперь мы можем реализовать возможность стрельбы и подготовить для нее эффекты. Когда игрок нажимает левую кнопку мыши, робот выстреливает пулю из одного из своих орудий. Поскольку здесь два орудия, мы можем чередовать выстрелы.
Step 1. Create, Move, and Delete a BulletШаг 1. Создание, перемещение и удаление пули#
We need to set up a node to represent a bullet in the game. The bullet will fly in the specified direction and explode on impact with an object. If it doesn't hit anything and the time runs out, it will be destroyed. Upon an impact with any dynamic object within the Play Area, an impulse will be applied to its physical body.Нам нужно настроить ноду, которая будет представлять пули в игре. Пуля полетит в указанном направлении и взорвется при столкновении с предметом. Если она ни во что не попадет и время ее существования закончится, она будет уничтожена. При столкновении с любым динамическим объектом в пределах Игровой зоны к его физическому телу будет применен импульс.
We will use the bit masking mechanism to identify the objects that can be hit by a bullet. A bullet checks for an intersection between its trajectory and surfaces of other objects with the BulletIntersection bit (described below) enabled in an Intersection mask. Our bullets will hit the walls and explode with a hit effect. The effect consists of a light flash, a cracks decal, a blaster sound, and sparks particles. The effect loads and plays at the hit position.Мы будем использовать механизм битовых масок для идентификации объектов, в которые может попасть пуля. Пуля проверяет пересечение своей траектории с поверхностями других объектов с включенным битом BulletIntersection (описанным ниже) в маске Intersection. Наши пули будут попадать в стены и взрываться с эффектом попадания. Эффект состоит из световой вспышки, декали трещин, звука взрыва и искр частиц. Эффект загружается и воспроизводится в позиции попадания.
Every node has a transformation matrix, which encodes position, rotation, and scale of the node in the world. There are different ways to perform basic node transformations. We will calculate a new position for the bullet's trajectory each frame based on the time it took to render the last game frame. This way we will make sure its speed is the same (frame-rate independent) no matter how often the Update method is called by the player's hardware.Каждая нода имеет матрицу трансформации, которая кодирует положение, поворот и масштаб ноды в мире. Существуют различные способы выполнения базовых преобразований ноды. Мы будем вычислять новую позицию для траектории пули в каждом кадре на основе времени, затраченного на рендеринг последнего игрового кадра. Таким образом, мы обеспечим одинаковую скорость (не зависящую от частоты кадров), на которую не повлияет изменение частоты вызова метода Update аппаратным обеспечением игрока.
-
For every wall's box surface enable the 7th bit of the Intersection Mask, and call it BulletIntersection.Для поверхности box каждой стены включите 7-й бит маски Intersection и назовите его BulletIntersection.
-
Create a new C# component and call it Projectile. Open your IDE and copy the following code. Save your code in the IDE to ensure it's automatic compilation on switching back to UnigineEditor.Создайте новый компонент C# и назовите его Projectile. Откройте свою среду разработки и скопируйте следующий код. Сохраните свой код в IDE, чтобы обеспечить его автоматическую компиляцию при возврате к UnigineEditor.
using System; using System.Collections; using System.Collections.Generic; using Unigine; #if UNIGINE_DOUBLE using Vec3 = Unigine.dvec3; using Mat4 = Unigine.dmat4; using Scalar = System.Double; #else using Vec3 = Unigine.vec3; using Mat4 = Unigine.mat4; using Scalar = System.Single; #endif [Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component public class Projectile : Component { // the speed of bullet movement public float speed = 30.0f; // asset file that contains the hit effect public AssetLinkNode bulletHitEffect; static WorldIntersectionNormal intersection; void Init() { if (intersection == null) intersection = new WorldIntersectionNormal(); } void Update() { Vec3 oldPos = node.WorldPosition; vec3 dir = node.GetWorldDirection(MathLib.AXIS.Y); // calculate the next position of the bullet Vec3 newPos = oldPos + dir * speed * Game.IFps; // check intersection with other objects Unigine.Object obj = World.GetIntersection(oldPos, newPos, ~0, intersection); //all mask bits are set to 1 if (obj) { // spawn a bullet at the hit point Node hitEffect = bulletHitEffect.Load(intersection.Point); // orient the effect towards the hit direction hitEffect.SetWorldDirection(intersection.Normal, vec3.UP, MathLib.AXIS.Y); // add impulse to an object if it is a body rigid BodyRigid rb = obj.BodyRigid; if (rb != null) { rb.Frozen = false; rb.AddWorldImpulse(obj.WorldPosition, node.GetWorldDirection(MathLib.AXIS.Y) * speed); } // remove the bullet node.DeleteLater(); return; } else { // move the bullet to a new position node.WorldPosition = newPos; } } }
As the Life Time of the bullet runs out we should delete it by simply calling the DeleteLater() method. Create a new C# component and call it Destroy. Open your IDE, add the following code, save and switch to UnigineEditor to compile it.Когда время существования пули истечет, мы должны удалить ее, просто вызвав метод DeleteLater(). Создайте новый компонент C# и назовите его Destroy. Откройте свою среду разработки, добавьте следующий код, сохраните и переключитесь на UnigineEditor, чтобы скомпилировать его.
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 Destroy : Component { [ShowInEditor][Parameter(Tooltip = "Object's lifetime")] private float lifeTime = 1.0f; private float startTime = 0.0f; void Init() { // remember initialization time of an object startTime = Game.Time; } void Update() { // wait until the lifetime ends and delete the object if (Game.Time - startTime > lifeTime) node.DeleteLater(); } }
-
Drag programming_quick_start\character\bullet\bullet.node from the Asset Browser into the Viewport and click Edit in the Reference section of the Parameters window to make modifications. Add a Destroy component to the child ObjectMeshStatic bullet node and set the Life Time to 5.0.Перетащите ноду programming_quick_start\character\bullet\bullet.node из Asset Browser во Viewport и нажмите Edit в разделе Reference окна Parameters, чтобы внести изменения. Добавьте компонент Destroy к дочерней ноде ObjectMeshStatic bullet и установите для Life Time значение 5.0.
-
Next, drag the programming_quick_start\character\bullet_hit\bullet_hit.node from the Asset Browser into the Viewport, click Edit on the Parameters window, add the Destroy component to the bullet_hit's NodeDummy and to the LightOmni. Set Life Time values to 10.0 and 0.05 seconds respectively.Затем перетащите ноду programming_quick_start\character\bullet_hit\bullet_hit.node из Asset Browser во Viewport, нажмите Edit в окне Parameters, добавьте компонент Destroy в NodeDummy bullet_hit и в LightOmni. Установите значения Life Time равными 10,0 и 0,05 секунд соответственно.
- Then select the bullet_hit Node Reference and click Apply in the Reference section to save all changes made to it.Затем выберите Node Reference bullet_hit и нажмите Apply в разделе Reference, чтобы сохранить все внесенные в нее изменения.
-
Then, add a Projectile component to the child ObjectMeshStatic bullet of the bullet Node Reference. Assign the bullet_hit node from the Asset Browser window to the Bullet Hit Effect field.Затем добавьте компонент Projectile к дочернему компоненту ObjectMeshStatic bullet из Node Reference bullet. Назначьте ноду bullet_hit из окна Asset Browser полю Bullet Hit Effect.
- Now disable Intersection detection for the bullet to avoid the bullet detecting intersections with itself. Select the bullet_mat surface and uncheck the Intersection option for it.Теперь отключите обнаружение пересечений для пули, чтобы пуля не обнаруживала пересечения с самой собой. Выберите поверхность bullet_mat и снимите для нее флажок Intersection.
- Save changes to the bullet Node Reference, by selecting it and clicking Apply in the Reference section or simply press Ctrl+S hotkey to save all changed assets.Сохраните изменения в Node Reference bullet, выбрав ее и нажав Apply в разделе Reference, или просто нажмите горячую клавишу Ctrl+S, чтобы сохранить все измененные ассеты.
- Now you can delete bullet and bullet_hit nodes from the world as we will spawn them via code.Теперь вы можете удалить ноды bullet и bullet_hit из мира, поскольку мы будем создавать их с помощью кода.
Step 2. Spawn a BulletШаг 2. Генерация пули#
Let's create special spawn nodes using Dummy Nodes with no visual representation. Their positions will be used as initial bullet positions.Давайте создадим специальные spawn-ноды, используя Dummy Node без визуального представления. Их позиции будут использоваться в качестве начальных позиций для пуль.
- Select the robot Node Reference in the World Nodes window and click Edit in the Reference section of the Parameters window.Выберите Node Reference robot в окне World Nodes и нажмите Edit в разделе Reference окна Parameters.
- Right-click on the child robot ObjectMeshSkinned in the World Nodes window to add a child node. Choose Create->Node->Dummy and position it in the Viewport near the end of the left gun. The Y axis (green arrow) must point in the fire direction, since Unigine uses the right-handed Cartesian coordinate system.Щелкните правой кнопкой мыши на дочерней ноде ObjectMeshSkinned robot в окне World Nodes, чтобы добавить дочернюю ноду. Выберите Create->Node->Dummy и поместите его во Viewport рядом с дулом левого пистолета. Ось Y (зеленая стрелка) должна указывать в направлении огня, поскольку UNIGINE использует правую декартову систему координат.
-
Rename the Dummy Node as "left_bullet_spawn".Переименуйте Dummy Node в "left_bullet_spawn".
- Create a spawn point for the right gun the same way and call it "right_bullet_spawn".Создайте spawn-точку для правого дула таким же образом и назовите ее "right_bullet_spawn".
To spawn bullets at run-time via API on Right Mouse Button click, add the following code to the PlayerController component. We use AssetLinkNode to refer to the bullet node file. Save your code in an IDE to ensure it's automatic recompilation on switching back to UnigineEditor. Чтобы создавать пули во время выполнения через API при щелчке правой кнопкой мыши, добавьте следующий код в компонент PlayerController. Мы используем AssetLinkNode для ссылки на файл ноды пули. Сохраните свой код в IDE, чтобы обеспечить его автоматическую перекомпиляцию при возврате к UnigineEditor.
using System; using System.Collections; using System.Collections.Generic; using Unigine; #if UNIGINE_DOUBLE using Vec3 = Unigine.dvec3; using Mat4 = Unigine.dmat4; using Scalar = System.Double; #else using Vec3 = Unigine.vec3; using Mat4 = Unigine.mat4; using Scalar = System.Single; #endif [Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component public class PlayerController : Component { //========================== NEW - BEGIN =============================== bool isNextLeft = false; // mouse fire button public Input.MOUSE_BUTTON mouseFireKey = Input.MOUSE_BUTTON.RIGHT; // asset file that contains the bullet public AssetLinkNode bullet; public Node leftSpawn, rightSpawn; //========================== NEW - END =============================== Player player; BodyRigid rigid; Vec3 pos; //a WorldIntersection object to store the information about the intersection WorldIntersection intersection = new WorldIntersection(); void Init() { player = Game.Player; rigid = node.ObjectBodyRigid; rigid.AngularScale = new vec3(0.0f, 0.0f, 0.0f); // restricting the rotation rigid.LinearScale = new vec3(1.0f, 1.0f, 0.0f); // restricting Z movement rigid.MaxLinearVelocity = 8.0f; // clamping the max linear velocity } //========================== NEW - BEGIN =============================== void Update() { if (Input.IsMouseButtonDown(mouseFireKey) && bullet.IsFileExist) { // load the bullet and set its position if (isNextLeft) bullet.Load(rightSpawn.WorldTransform); else bullet.Load(leftSpawn.WorldTransform); // alternate between the left and the right gun isNextLeft = !isNextLeft; } // press ESC button to close the game if (Input.IsKeyDown(Input.KEY.ESC)) { Engine.Quit(); } } //========================== NEW - END =============================== void UpdatePhysics() { // forward if (Input.IsKeyPressed(Input.KEY.W)) Move(player.GetWorldDirection(MathLib.AXIS.Y)); // backward if (Input.IsKeyPressed(Input.KEY.S)) Move(player.GetWorldDirection(MathLib.AXIS.NY)); // left if (Input.IsKeyPressed(Input.KEY.A)) Move(player.GetWorldDirection(MathLib.AXIS.NX)); // right if (Input.IsKeyPressed(Input.KEY.D)) Move(player.GetWorldDirection(MathLib.AXIS.X)); // finding the positions of the cursor and the point moved 100 units away in the camera forward direction ivec2 mouse = Input.MousePosition; Vec3 p0 = player.WorldPosition; Vec3 p1 = p0 + new Vec3(player.GetDirectionFromMainWindow(mouse.x, mouse.y)) * 100; // casting a ray from p0 to p1 to find the first intersected object Unigine.Object 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 transofrm matrix for the body preserving angular and linear velocities if (obj) { pos = intersection.Point; pos.z = rigid.Transform.Translate.z; // project the position vector to the Body Rigid pivot plane Mat4 transform = MathLib.SetTo(rigid.Transform.Translate, pos, vec3.UP, MathLib.AXIS.Y); rigid.SetPreserveTransform(transform); } } // moving the rigid body with linear impulse in the specified direction void Move(vec3 direction) { //directon is a normalized camera axis vector rigid.AddLinearImpulse(direction); } }
-
Drag left_bullet_spawn and right_bullet_spawn nodes to the corresponding fields of the PlayerController component of the robot node (ObjectMeshSkinned). And assign the bullet.node to the Bullet Path field.Перетащите ноды left_bullet_spawn и right_bullet_spawn в соответствующие поля компонента PlayerController ноды robot (ObjectMeshSkinned). И присвойте bullet.node полю Bullet Path.
- Save changes to the world, go to File->Save World or press Ctrl+S hotkey.Сохраните изменения в мире, выбрав File->Save World или нажав горячую клавишу Ctrl+S.