Сборка персонажа с управлением
To make a main character, we will need a controller node implementing basic player functionality (handling keyboard and mouse input, movement settings, etc.). Attached to this node, we will have a first-person view camera, hands with a gun, and a body imitation to check for collisions with other characters, bullets, and environment. Later we will assign logic components to the nodes to implement shooting, visual effects, etc. Let's start with the character controller.Для создания главного персонажа, нам понадобится нода-контроллер, реализующая базовые функции игрока (обработка ввода с клавиатуры и мыши, настройки движения и т.д.). К этой ноде у нас будет привязана камера с видом от первого лица, руки с пистолетом и имитация тела для проверки столкновений с другими персонажами, пулями и окружающей средой. Позже мы назначим нодам логические компоненты для реализации стрельбы, визуальных эффектов и т.д., а пока займемся контроллером персонажа.
For the character controller, we will use the template first-person controller. It is included in the default scene of a new C# project to simplify the programmer's task — find the Node Reference named first_person_controller in the hierarchy, it contains a Dummy Node with the FirstPersonController component assigned.Для этой цели мы будем использовать шаблон контроллера от первого лица. При создании нового проекта C# он добавляется автоматически, чтобы упростить задачу программисту – найдите в иерархии Node Reference с именем first_person_controller, внутри которой хранится Dummy Node с назначенным ему компонентом FirstPersonController.
Let's rename it to player: enable editing of the Node Reference by clicking Edit and rename both nodes. Then select the Node Reference again and click Apply. This node will represent our character.Давайте переименуем его в player: включите редактирование Node Reference, нажав Edit и переименуйте обе ноды, затем выделите Node Reference и нажмите Apply. Это и будет наш персонаж.
Arranging a First-Person SetupНастройка вида от первого лица#
For a first-person setup you will need the hands and weapon models and animations previously created in a 3D modeling software. If you have your own assets, that's great, otherwise you can use our ready-to-use assets available in the data folder.Для настройки вида от первого лица нам понадобятся модели рук и оружия, а также анимации, ранее созданные в сторонней программе для 3D-моделирования. Если у вас заготовлены свои – хорошо, если нет, воспользуйтесь готовыми в папке data.
We'll start with adding hands, and then attaching a pistol to them. In Asset Browser, find the data/fps/hands/hands.fbx asset and add it to the scene.Итак, начнем с добавления рук, а затем прикрепим к ним пистолет. В Asset Browser найдите ассет data/fps/hands/hands.fbx и добавьте его в сцену.
To simulate a player's body that takes damage when hit by enemy bullets, let's create an additional object (it will approximate the player's body with a box):Чтобы имитировать тело игрока, в которое будут попадать вражеские пули, создадим дополнительный объект (в качестве упрощенной модели тела игрока будем использовать коробку):
-
In the Menu bar, choose Create → Primitive → Box to create a box primitive of the size (1,1,2), add it to the scene and rename to player_hit_box.В меню выберем Create → Primitive → Box, чтобы создать примитив box с размерами (1,1,2), добавим его в сцену и переименуем в player_hit_box.
-
Add it as a child to the hands Dummy Node and reset its position to the parent one. And for player_hit_box let's enable the Intersection option in the Surfaces section of the Parameters window.Затем добавим его в качестве дочернего элемента в Dummy Node hands и сбросим его положение на положение родительской ноды. А также включим для player_hit_box опцию Intersection в разделе Surfaces окна Parameters.
- Adjust the position of the player_hit_box so that it is placed immediately below the hands.Настроим положение player_hit_box так, чтобы он располагался непосредственно под руками.
-
Make it invisible by clearing its Viewport mask in the Node tab of the Parameters window using the Clear All button. Also clear the Shadow mask to disable shadows rendering. You'll get something like this:Затем сделаем его невидимым, очистив его маску Viewport на вкладке Node окна Parameters через Clear All, а также выключим маску Shadow, чтобы не рисовать для него тень. Должно получиться что-то в этом роде:
We'll assign the Health component to it later.Компонент Health назначим на него позже.
Adding a CameraДобавление камеры#
By default, FirstPersonController creates the camera during application execution, and to be able to see through the eyes of the character in UnigineEditor, you can create a new camera (PlayerDummy) and instruct the controller to use it. This will simplify testing of the first-person setup.По умолчанию FirstPersonController сам создает камеру в процессе выполнения приложения, а чтобы иметь возможность видеть глазами персонажа в UnigineEditor, можно создать новую камеру (PlayerDummy) и указать контроллеру использовать ее. Это облегчит тестирование настройки вида от первого лица.
-
Enable editing of the player Node Reference, then right-click the player Dummy Object and select Create → Camera → Dummy. It will make it easier to test the first-person setup.Включите редактирование Node Reference player, затем щелкните правой кнопкой мыши на Dummy Object player и выберите Create → Camera → Dummy. Поместите созданную камеру где-нибудь в сцене.
-
In the Node tab of the Parameters window, reset the position of the camera to the player position. Then adjust the rotation so that the camera is directed forward: set the rotation around the X axis to 90.Во вкладке Node в окне Parameters сбросьте положение камеры в позицию player. Затем отрегулируйте поворот так, чтобы камера была направлена вперед: установите вращение вокруг оси X равным 90.
-
Add the hands Dummy Node as a child to the PlayerDummy node.Добавьте Dummy Node hands в качестве дочерней ноды к ноде PlayerDummy.
- Adjust the position of the hands so that you can see them through the PlayerDummy camera. Transformation of the player's body should also be adjusted.Настройте положение рук так, чтобы вы могли видеть их через камеру PlayerDummy. Положение тела игрока также нужно будет откорректировать.
- In the Parameters window, change Near Clipping and FOV Degrees values of the PlayerDummy node: it will help you to get the required camera view.В окне Parameters измените значения Near Clipping и FOV Degrees ноды PlayerDummy: это поможет вам получить требуемый обзор камеры.
-
Check the Main Player box in the parameters of the PlayerDummy to make it the main camera.Включите опцию Main Player в параметрах PlayerDummy, чтобы сделать его основной камерой.
-
To avoid hands falling under gravity, adjust the position of the player Dummy Object's ShapeCapsule in the Physics tab of the Parameters window. It should almost coincide with the position of the player_hit_box node.Чтобы руки не падали под действием силы тяжести, настройте положение ShapeCapsule у Dummy Object player на вкладке Physics окна Parameters. Оно должно совпадать с положением ноды player_hit_box.
- Select the player Dummy Object and go to the Node tab of the Parameters window.Выберите Dummy Object player и перейдите на вкладку Node окна Parameters.
- In the Node Components and Properties section, choose Camera → Camera mode → USE EXTERNAL.В разделе Node Components and Properties выберите Camera → Camera mode → USE EXTERNAL.
-
Drag and drop the PlayerDummy node to the Camera field.Перетащите ноду PlayerDummy в поле Camera.
Now you can switch to the PlayerDummy camera in the Editor Viewport.Теперь можно переключиться на камеру PlayerDummy во вьюпорте редактора.
Attaching a Weapon to the HandsПрикрепление оружия к рукам#
In UNIGINE, you should use the Skinned Mesh with bones for animated models. Our FBX model of hands contains several bones. We can attach the object to a particular bone to make it follow this bone. For this purpose, we use a WorldTransformBone node that has a controlled object (a pistol in our case) as a child, and the Skinned Mesh with bones as its parent.В UNIGINE для анимированных моделей используется нода Skinned Mesh со скелетом. Наша FBX модель рук содержит набор костей. Можно прикрепить объект к определенной кости, чтобы заставить его следовать за этой костью. Для этого используется нода WorldTransformBone, дочерним объектом у которой должен быть управляемый объект (в нашем случае – пистолет), а родительским – Skinned Mesh со скелетом.
- In the Asset Browser, find the data/fps/pistol/pistol.fbx asset and add it to the scene.В Asset Browser найдите ассет data/fps/pistol/pistol.fbx и добавьте его в сцену.
-
In the Menu bar, choose Create -> Mesh -> SkinnedBone: a WorldTransformBone node will be created. Add it as a child to the hands Skinned Mesh (the one that is inherited from the hands Dummy Node).В меню выберите Create -> Mesh -> SkinnedBone: будет создана нода WorldTransformBone. Добавьте ее в качестве дочернего элемента в Skinned Mesh hands (являющийся дочерним элементом Dummy Node hands).
-
In the Bone drop-down list, select joint_hold. This will be the bone to which the pistol will be attached.В выпадающем списке Bone выберем joint_hold. Это будет кость, к которой мы прикрепим пистолет.
-
Make the pistol a child of the WorldTransformBone node. Reset its relative position and rotation to zero if needed.Сделаем пистолет дочерним элементом ноды WorldTransformBone. Сбросьте его относительное положение и поворот на ноль.
Testing AnimationsПроверка анимации#
There is also a number of animations to be used for the hands (idle, walking, shooting). You can check how a certain animation looks like, for example:У нас также есть набор готовых анимаций для рук (покой, ходьба, стрельба). Можно проверить, как выглядит каждая из них, например:
- In the Asset Browser, find the data/fps/hands/hands_animations/hands_pistol_idle.anim file and drag it to the Preview Animation section of the hands Skinned Mesh parameters.В Asset Browser найдите файл data/fps/hands/hands_animations/hands_pistol_idle.anim и перетащите его в раздел Preview Animation параметров Skinned Mesh hands.
-
Check the Loop option and click Play.Установите флажок Loop и нажмите Play.
Blending Animations and Playing Them via CodeСмешивание и воспроизведение анимаций с помощью кода#
When the character changes its states (shooting, walking forward/backward/left/right), the corresponding animations should change smoothly. Let's implement a component for mixing our animations.При изменении состояний персонажа (стрельба, ходьба вперед / назад / влево / вправо), соответствующие анимации должны плавно меняться. Напишем свой компонент для смешивания наших анимаций.
To ensure a seamless transition, we need to play two animations simultaneously and blend them. To do so, we will use multiple layers; we can assign different weights to each layer and achieve smooth blending.Чтобы получить плавный переход, нужно воспроизвести две анимации одновременно и смешать их. Для этого мы будем использовать несколько слоев; каждому из слоев можно назначить разные веса и добиться плавного смешивания.
The following scheme shows the blend tree we are going to use:На схеме ниже показан граф смешивания, который мы будем использовать:
-
Create a new C# component named HandAnimationController.cs: in the Asset Browser, right-click and choose Create Code -> C# Component in the drop-down list. Copy and paste the following code to the created component:Создайте новый C# компонент HandAnimationController.cs: в Asset Browser щелкните правой кнопкой мыши и выберите Create Code -> C# Component в выпадающем списке. Скопируйте и вставьте следующий код в созданный компонент:
HandAnimationController.cs
using System; using System.Collections; using System.Collections.Generic; using Unigine; [Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- идентификатор генерируется автоматически для нового компонента public class HandAnimationController : Component { // контроллер игрока с видом от первого лица (FirstPersonController) public FirstPersonController fpsController = null; public float moveAnimationSpeed = 30.0f; public float shootAnimationSpeed = 30.0f; public float idleWalkMixDamping = 5.0f; public float walkDamping = 5.0f; public float shootDamping = 1.0f; // параметры анимации [ParameterFile(Filter = ".anim")] public string idleAnimation = null; [ParameterFile(Filter = ".anim")] public string moveForwardAnimation = null; [ParameterFile(Filter = ".anim")] public string moveBackwardAnimation = null; [ParameterFile(Filter = ".anim")] public string moveRightAnimation = null; [ParameterFile(Filter = ".anim")] public string moveLeftAnimation = null; [ParameterFile(Filter = ".anim")] public string shootAnimation = null; public vec2 LocalMovementVector { get { return new vec2( MathLib.Dot(fpsController.SlopeAxisY, fpsController.HorizontalVelocity), MathLib.Dot(fpsController.SlopeAxisX, fpsController.HorizontalVelocity) ); } set {} } private ObjectMeshSkinned meshSkinned = null; private float currentIdleWalkMix = 0.0f; // 0 анимация покоя, 1 анимация ходьбы private float currentShootMix = 0.0f; // 0 комбинация бездействие/ходьба, 1 анимация стрельбы private float currentWalkForward = 0.0f; private float currentWalkBackward = 0.0f; private float currentWalkRight = 0.0f; private float currentWalkLeft = 0.0f; private float currentWalkIdleMixFrame = 0.0f; private float currentShootFrame = 0.0f; private int numShootAnimationFrames = 0; // задаем число анимационных слоев private const int numLayers = 6; private void Init() { // берем ноду, которой назначена компонента // и преобразовываем ее к типу ObjectMeshSkinned meshSkinned = node as ObjectMeshSkinned; // устанавливаем количество анимационных слоев для каждого объекта meshSkinned.NumLayers = numLayers; // устанавливаем анимацию для каждого слоя meshSkinned.SetLayerAnimationFilePath(0, idleAnimation); meshSkinned.SetLayerAnimationFilePath(1, moveForwardAnimation); meshSkinned.SetLayerAnimationFilePath(2, moveBackwardAnimation); meshSkinned.SetLayerAnimationFilePath(3, moveRightAnimation); meshSkinned.SetLayerAnimationFilePath(4, moveLeftAnimation); meshSkinned.SetLayerAnimationFilePath(5, shootAnimation); numShootAnimationFrames = meshSkinned.GetLayerNumFrames(5); // включаем все анимационные слои for (int i = 0; i < numLayers; ++i) meshSkinned.SetLayerEnabled(i, true); } public void Shoot() { // включаем анимацию стрельбы currentShootMix = 1.0f; // устанавливаем кадр анимационного слоя в 0 currentShootFrame = 0.0f; } private void Update() { vec2 movementVector = LocalMovementVector; // проверяем, движется ли персонаж bool isMoving = movementVector.Length2 > MathLib.EPSILON; // обработка ввода: проверка нажатия клавиши 'огонь' bool isShooting = Input.IsMouseButtonDown(Input.MOUSE_BUTTON.LEFT); if (isShooting) Shoot(); // рассчитываем целевые значения для весовых коэффициентов слоев float targetIdleWalkMix = (isMoving) ? 1.0f : 0.0f; float targetWalkForward = (float) MathLib.Max(0.0f, movementVector.x); float targetWalkBackward = (float) MathLib.Max(0.0f, -movementVector.x); float targetWalkRight = (float) MathLib.Max(0.0f, movementVector.y); float targetWalkLeft = (float) MathLib.Max(0.0f, -movementVector.y); // применяем текущие весовые коэффициенты float idleWeight = 1.0f - currentIdleWalkMix; float walkMixWeight = currentIdleWalkMix; float shootWalkIdleMix = 1.0f - currentShootMix; meshSkinned.SetLayerWeight(0, shootWalkIdleMix * idleWeight); meshSkinned.SetLayerWeight(1, shootWalkIdleMix * walkMixWeight * currentWalkForward); meshSkinned.SetLayerWeight(2, shootWalkIdleMix * walkMixWeight * currentWalkBackward); meshSkinned.SetLayerWeight(3, shootWalkIdleMix * walkMixWeight * currentWalkRight); meshSkinned.SetLayerWeight(4, shootWalkIdleMix * walkMixWeight * currentWalkLeft); meshSkinned.SetLayerWeight(5, currentShootMix); // обновляем анимационные кадры: устанавливаем один и тот же кадр для всех слоев, чтобы обеспечить их синхронизацию meshSkinned.SetLayerFrame(0, currentWalkIdleMixFrame); meshSkinned.SetLayerFrame(1, currentWalkIdleMixFrame); meshSkinned.SetLayerFrame(2, currentWalkIdleMixFrame); meshSkinned.SetLayerFrame(3, currentWalkIdleMixFrame); meshSkinned.SetLayerFrame(4, currentWalkIdleMixFrame); // устанавливаем текущий кадр для каждого анимационного слоя в 0, чтобы начать воспроизведение сначала meshSkinned.SetLayerFrame(5, currentShootFrame); currentWalkIdleMixFrame += moveAnimationSpeed * Game.IFps; currentShootFrame = MathLib.Min(currentShootFrame + shootAnimationSpeed * Game.IFps, numShootAnimationFrames); // плавно обновляем текущие значения весовых коэффициентов currentIdleWalkMix = MathLib.Lerp(currentIdleWalkMix, targetIdleWalkMix, idleWalkMixDamping * Game.IFps); currentWalkForward = MathLib.Lerp(currentWalkForward, targetWalkForward, walkDamping * Game.IFps); currentWalkBackward = MathLib.Lerp(currentWalkBackward, targetWalkBackward, walkDamping * Game.IFps); currentWalkRight = MathLib.Lerp(currentWalkRight, targetWalkRight, walkDamping * Game.IFps); currentWalkLeft = MathLib.Lerp(currentWalkLeft, targetWalkLeft, walkDamping * Game.IFps); currentShootMix = MathLib.Lerp(currentShootMix, 0.0f, shootDamping * Game.IFps); } }
- In UnigineEditor, assign the component to the hands Skinned Mesh.В UnigineEditor назначьте компонент HandAnimationController на объект hands (Skinned Mesh).
- Remove data/fps/hands/hands_animations/hands_pistol_idle.anim from the Preview Animation field of the Mesh Skinned section.Удалите data/fps/hands/hands_animations/hands_pistol_idle.anim из поля Preview Animation в разделе Mesh Skinned.
-
Add animations stored in the data/fps/hands/hands_animations folder to the corresponding parameters.Добавьте анимации, хранящиеся в папке data/fps/hands/hands_animations, к соответствующим параметрам.
-
Assign (drag and drop) the player Dummy Node to the Fps Controller field of the HandAnimationController component so that it could get required data from the player's first person controller to perform blending.Назначьте (перетащите) Dummy Node player в поле Fps Controller компонента HandAnimationController, чтобы он мог получать необходимые данные из first person controller для смешивания анимаций.
-
Save all changes and run the application logic via the UnigineEditor to check the result.Сохраните все изменения и запустите логику приложения, нажав кнопку Play в UnigineEditor, чтобы проверить результат.