Jump to content

Делаем летательный/космический аппарат, возможно самолет.


photo

Recommended Posts

Posted (edited)

Работа с Unigine как обычно похожа на остросюжетный триллер, с убийствами, расследованиями и неожиданной концовкой.

Итак, пока я жду ответ на другую тему, в этот раз речь пойдет о том, как создать управление с клавиатуры самолетом/космическим кораблем/вертолетом.

Как создавать физическое тело, которое живет каким-то паразитом у node я рассказывать не буду и буду считать что вы уже достигли того уровня просветления чтобы понимать о чем речь.

Тут немного моих шаманских танцев с бубном

 

Теперь начнем пожалуй.

 

С управлением векторами через клавиши всё просто -

body_Rigid.AddForce(node.GetWorldDirection(MathLib.AXIS.Z) * -мощность_движения);

 

Что тут происходит? Мы живущему самостоятельной жизнью у node физическому телу (body_Rigid) придаем силу (мощность_движения) по вектору (node.GetWorldDirection) с указанием оси куда направлен вектор (MathLib.AXIS.Z).

Все это должно вызываться в:

Quote

 

Notice

You can call this function only from updatePhysics() function in the world script. Do not apply forces in the update() function, because you will get unstable result that varies each rendering frame.

 

В котором стабильно не работают

if (Input.IsKeyPressed(Input.KEY.W)).

Обожаю эти моменты с оборачиванием физики bool в update,чтобы потом все это запускать в updatePhysics. Поэтому корректно делать так (все вариации на тему будут делать так, но могут быть в другом виде):

bool движение_по_оси_х = false;

int мощность_движения = 3000;

public BodyRigid body_Rigid;

private void Update()
{
	if (Input.IsKeyPressed(Input.KEY.W))
	{
		движение_по_оси_х = true;
	}
  	else
	{
		движение_по_оси_х = false;
	}
}

void UpdatePhysics()
{
	if(движение_по_оси_х)
		body_Rigid.AddForce(node.GetWorldDirection(MathLib.AXIS.Z) * мощность_движения);
}

Всего 6 направлений по трем осям: вверх, вниз, влево, вправо, вперед, назад. Меняем направление по оси, добавлением -мощность_движения, меняем знак -на+ если нужно.

 

Теперь крутимся по осям.

Тут тоже самое, только с помощью AddTorque.

//крутимся бочкой
if (Input.IsKeyPressed(Input.KEY.Q))
{
  поворот += node.GetWorldDirection(MathLib.AXIS.Y) * -мощность_вращения;
}
if (Input.IsKeyPressed(Input.KEY.E)) 
{
  поворот += node.GetWorldDirection(MathLib.AXIS.Y) * мощность_вращения;
}	

Ах, да, забыл что просто так задать направление поворота нажав все клавиши не выйдет, так как они не смешиваются. Точнее можно задать только одно направление - нажали крутиться влево, крутимся, нажали крутиться по тангажу, прекратили крутиться влево, крутимся по тангажу. Это нормально, паниковать не нужно. Мы собираем ось вращения отдельно, а уже потом крутимся. 

Это хороший подход и по идее примерно так все должно происходить - вы сначала считаете все вектора, а затем один раз его прикладываете в качестве сил. Этакая машина состояний.

vec3 поворот = new vec3(0,0,0);

//крутимся бочкой
if (Input.IsKeyPressed(Input.KEY.Q))
{
  поворот += node.GetWorldDirection(MathLib.AXIS.Y) * -мощность_вращения;
  двигатель_текущее_давление = (int)(двигатель_рабочее_давление * мощность_вращения/100);
}
if (Input.IsKeyPressed(Input.KEY.E)) 
{
  поворот += node.GetWorldDirection(MathLib.AXIS.Y) * мощность_вращения;
  двигатель_текущее_давление = (int)(двигатель_рабочее_давление * мощность_вращения/100);
}	

body_Rigid.AddTorque(поворот);

А вот теперь начинается самое интересное — управление с помощью мыши.

 

Есть два варианта:

  1. Как управление камерой от первого лица.

  2. Отклонение курсора от центра экрана.

 

Вариант 1.

//поворот вверх/вниз
body_Rigid.AddTorque(new vec3(0, 0, -Input.MouseDelta.x * мощность_вращения));

//поворот влево/право
body_Rigid.AddTorque(new vec3(-Input.MouseDelta.y * мощность_вращения, 0, 0));

Очень просто, пока не начнешь искать MouseDelta — почему то не бьется в гугле, но зато бьется в документации за версию 2.13. В версии 2.16.1 это уже MouseDeltaPosition.

Боже, как я это обожаю (нет).

 

А вот с отклонением курсора от центра экрана...

Итак, у нас есть окно игры, размеры которого мы можем узнать с помощью ширины, это ось Х, gui.Width и высоты, это ось У, gui.Height. Помним что все координаты экрана начинаются с верхнего левого угла, вправо Х и вниз У.

Забавно что все высоту экрана называют Height, хотя на самом деле, хех, это низина экрана. Еще забавней то, что глубину экрана называют то что внутрь экрана. Но это что-то я отвлекся.

Середину экрана мы найдем так же просто:

float половина_экрана_ось_х = gui.Width / 2;

float половина_экрана_ось_у = gui.Height / 2;

Возможно, для этого есть отдельная особая функция, но главное правило которому должен следовать любой разработчик — сделал один раз, работает всегда (и нет, это не слоган Java).

В общем, надо по максимуму использовать универсальную методику, на случай если вам придется переносить код на что-то другое, а вам иногда придется это делать - сделал один раз, работает всегда.

А вот теперь магия соотношений. Нам нужна мертвая зона при попадании в которую курсор не будет инициализировать повороты. Вторая зона — активная зона курсора. И опционально, третья зона, мертвая зона к краю экрана, в которую курсор попадать не будет.

И того, в первом простом случае, мы половинку, делим еще раз пополам — одна из них будет активной зоной, вторая мертвой. Слева, направо.

float начало_активной_зоны_экрана_ось_х = центр_экрана_ось_х / 2;
float начало_активной_зоны_экрана_ось_y =  центр_экрана_ось_y / 2;

Узнаем положение курсора, в версии документации 2.15 на которой я все это делаю ибо в 2.16 что-то они перемудрили с настройками графония, это MouseCoord, в версии документации 2.16 это MousePosition.

float положение_курсора_x = Input.MouseCoord.x - центр_экрана_ось_х;
float положение_курсора_y = Input.MouseCoord.y - центр_экрана_ось_y;

Ставим курсор посередине экрана и от Input.MousePosition.x вычитаем половина_экрана_ось_х, таким образом у нас он будет в нулевых координатах. Далее вычитаем половину, половины и это будет конец мертвой зоны и начало вектора интенсивность вращения.

float скорость_поворота_x = (положение_курсора_x - начало_активной_зоны_экрана_ось_х) * -мощность_вращения_мышью;
float скорость_поворота_y = (начало_активной_зоны_экрана_ось_y + положение_курсора_y) *-мощность_вращения_мышью;

Как видите, ничего сложного.

Ну и у вас должно получаться что-то типа того что в скрипте ниже. Не забываем засовывать физику в UpdatePhysics() ибо артефакты.

Так же, оси направлений могут отличаться от моих.

 

Spoiler
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;

[Component(PropertyGuid = "ваш id скрипта")]
public class ShuntingEngines : Component
{
		BodyRigid body_Rigid;

		//power engine
		public float movement_power = 50;
		public float power_rotation = 10;
		public float power_rotation_mouse = 0.005f;
	
		vec3 movement = new vec3(0,0,0);
		vec3 rotation = new vec3(0,0,0);

		private void Init()
		{
			body_Rigid = this.node.ObjectBodyRigid;
		}
		
		private void Update()
		{
			//Stop running the script if the physical body is not found
			if(!body_Rigid)
			{
				Log.Error("\n Stop running the script if the physical body is not found.");
				return;
			}

			/*
			The button press call event occurs in Update, but the physics is always processed in UpdatePhysics. 
			Therefore, first we collect the vector in Update and then use it in	UpdatePhysics.
			This is very important to remember
			*/

			//Reset the vector to correctly assemble it again
			movement = new vec3(0,0,0);

			//movement forward/backward
			if (Input.IsKeyPressed(Input.KEY.W)) 
			{
				movement += node.GetWorldDirection(MathLib.AXIS.X) * -movement_power;
			}
			if (Input.IsKeyPressed(Input.KEY.S))
			{
				movement += node.GetWorldDirection(MathLib.AXIS.X) * movement_power;
			}

			//movement left/right
			if (Input.IsKeyPressed(Input.KEY.A)) 
			{
			//We assemble the vector in Update(), and apply it in UpdatePhysics() since the button click event listener is in Update().
				movement += node.GetWorldDirection(MathLib.AXIS.Y) * -movement_power;
			}
			if (Input.IsKeyPressed(Input.KEY.D)) 
			{
				movement += node.GetWorldDirection(MathLib.AXIS.Y) * movement_power;
			}

			//movement up/down
			if (Input.IsKeyPressed(Input.KEY.Z)) 
			{
				movement += node.GetWorldDirection(MathLib.AXIS.Z) * -movement_power;
			}
			if (Input.IsKeyPressed(Input.KEY.X)) 
			{
				movement += node.GetWorldDirection(MathLib.AXIS.Z) * movement_power;
			}

			//Reset the vector to correctly assemble it again
				rotation = new vec3(0,0,0);

			//spinning along the longitudinal axis
			if (Input.IsKeyPressed(Input.KEY.Q))
			{      
				rotation += node.GetWorldDirection(MathLib.AXIS.X) * power_rotation;
			}
			if (Input.IsKeyPressed(Input.KEY.E)) 
			{
				rotation += node.GetWorldDirection(MathLib.AXIS.X) * -power_rotation;
			}	

			//rotate along the vertical axis
			if (Input.IsKeyPressed(Input.KEY.LEFT))
			{
				rotation += node.GetWorldDirection(MathLib.AXIS.Z) * power_rotation;
			}
			if (Input.IsKeyPressed(Input.KEY.RIGHT)) 
			{
				rotation += node.GetWorldDirection(MathLib.AXIS.Z) * -power_rotation;
			}		

			//spinning along the transverse axis
			if (Input.IsKeyPressed(Input.KEY.UP))
			{      
				rotation += node.GetWorldDirection(MathLib.AXIS.Y) * power_rotation;
			}

			if (Input.IsKeyPressed(Input.KEY.DOWN)) 
			{
				rotation += node.GetWorldDirection(MathLib.AXIS.Y) * -power_rotation;
			}

			Gui gui = Gui.GetCurrent();

			float screen_center_x_axis = gui.Width / 2;
			float screen_center_y_axis =  gui.Height / 2;

			//an option in which the active area of the cursor extends to the end of the screen, with a dead area in the middle
			//This dead zone could be designated by the UI, if only it were so easy to do here, so do it yourself.
			float beginning_active_screen_area_x_axis = screen_center_x_axis / 10;
			float beginning_active_screen_area_y_axis =  screen_center_y_axis / 10;

			//Input.MousePosition works correctly with full window
			float cursor_position_x = Input.MousePosition.x - screen_center_x_axis;
			float cursor_position_y = Input.MousePosition.y - screen_center_y_axis;

			float turning_speed_x = (cursor_position_x - beginning_active_screen_area_x_axis) * -power_rotation_mouse;
			float turning_speed_y = (beginning_active_screen_area_y_axis + cursor_position_y) * -power_rotation_mouse;

			if (cursor_position_x > beginning_active_screen_area_x_axis)
			{
				rotation += node.GetWorldDirection(MathLib.AXIS.Z) * turning_speed_x;
			}
			else
			if(cursor_position_x < -beginning_active_screen_area_x_axis)
			{
				rotation += node.GetWorldDirection(MathLib.AXIS.Z) * turning_speed_x;
			}

			if (cursor_position_y > beginning_active_screen_area_y_axis)
			{
				rotation += node.GetWorldDirection(MathLib.AXIS.Y) * turning_speed_y;
			}
			else
			if(cursor_position_y < -beginning_active_screen_area_y_axis)
			{
				rotation += node.GetWorldDirection(MathLib.AXIS.Y) * turning_speed_y;
			}
		}
	
	//We always use physics in UpdatePhysics()
		void UpdatePhysics()
		{
			body_Rigid.AddForce(movement);
			body_Rigid.AddTorque(rotation);
		}
}

 

 

Edited by nikolay.sykharev
  • Like 1
×
×
  • Create New...