Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр Мультикоптеры

Импорт модели и подготовка скрипта

Итак, как я писал выше, скопируйте модель и текстуру в папку Assets вашего проекта, либо можете просто перетащить их прямо из своего explorer’a, во вкладку Project, главное чтобы модель и текстура находились в одной папке для того чтобы текстура наложилась автоматически, если же по каким – либо причинам текстуры на гусеницах вы не видите (перед тем как увидеть надо естественно вытянуть модель на сцену), или вы хотите вопреки всему закинуть её в другую папку, не беда, просто выберите одну из гусениц (они называются Track_line_left и Track_line_right), затем в инспекторе найдите настройку текстуры, нажмите кнопку Select и выберите текстуру гусеницы.

image

Также установите Tiling по y равным 2, как на изображении, для того чтобы увеличить количество траков.

Теперь создайте новый C# скрипт (Assets -> Create -> C Sharp Script), назовем его TankTrackController, откроем его и объявим необходимые нам переменные с которыми в дальнейшем будем работать:

using UnityEngine;
using System.Collections;
using System.Collections.Generic; //1

public class TankTrackController : MonoBehaviour {
	
	public GameObject wheelCollider; //2
	
	public float wheelRadius = 0.15f; //3
	public float suspensionOffset = 0.05f; //4
		
	public float trackTextureSpeed = 2.5f; //5
		
	public GameObject leftTrack;  //6
	public Transform[] leftTrackUpperWheels; //7
	public Transform[] leftTrackWheels; //8
	public Transform[] leftTrackBones; //9
	
	public GameObject rightTrack; //6
	public Transform[] rightTrackUpperWheels; //7
	public Transform[] rightTrackWheels; //8
	public Transform[] rightTrackBones; //9
	
	public class WheelData { //10
		public Transform wheelTransform; //11
		public Transform boneTransform; //12
		public WheelCollider col; //13
		public Vector3 wheelStartPos; //14
		public Vector3 boneStartPos; //15
		public float rotation = 0.0f; //16
		public Quaternion startWheelAngle;	//17
	}
	
	protected WheelData[] leftTrackWheelData; //18
	protected WheelData[] rightTrackWheelData; //18
	
	protected float leftTrackTextureOffset = 0.0f; //19
	protected float rightTrackTextureOffset = 0.0f; //19

}
  1. Разрешаем пространство имен необходимое для использования динамических списков, они пригодятся нам позже.
  2. Объявляем переменную в которой будет храниться префаб нашего Wheel Collider’а (вспоминаем п.2 предыдущего урока).
  3. Радиус наших колес.
  4. Смещение колеса относительно начальной позиции, когда оно не касается поверхности.
  5. Скорость движения гусеницы (по сути скорость смещения текстурных координат).
  6. Левая и правая гусеницы.
  7. Левые и правые верхние колеса к которым не будут добавляться Wheel Collider’ы.
  8. Колеса к которым будут добавляться Wheel Collider’ы (как видите здесь я в отличие от предыдущего урока, не объявлял массивов для Wheel Collider’ов, они будут добавляться к нашим колесам прямо из скрипта используя позицию колес, так что не перемещайте колеса, перед нажатием на кнопку Play они должны быть выровнены относительно друг друга).
  9. Кости привязанные к левой и правой гусенице.
  10. Объявим класс в котором будем хранить необходимую нам информацию о каждом колесе (кроме UpperWheels), а именно:
  11. Transform колеса;
  12. Transform кости привязанной к гусенице;
  13. WheelCollider колеса;
  14. Начальную позицию колеса;
  15. Начальную позицию кости;
  16. Угол вращения колеса;
  17. Начальные углы поворота колеса.
  18. Объявляем массивы хранящие данные о левых и правых колесах, как видите тут используется модификатор доступа protected, для того чтобы мы не могли изменить эти данные вне класса.
  19. И наконец объявляем переменные которые будут хранить текущее смещение текстурных координат на гусеницах.


Итак основные переменные объявлены, осталось связать их с нашей моделью, сохраняем скрипт, переходим в редактор, перетаскиваем наш скрипт на объект tank (если вы его не переименовывали конечно).

Перейдите на сам объект tank, как видите помимо прикрепленного только что скрипта там есть объект Animation, смело можете его удалить он там ни к чему.
image
image

Начинаем перетаскивать объекты в наш скрипт, первым делом можете перетащить только что созданный префаб на поле Wheel Collider внутри нашего скрипта. Далее перетащите гусеницы (они называются Track_line_left и Track_line_right) на поля Left Track и Right Track.

Затем верхние колеса (Upper_wheel[номер]_left, Upper_wheel[номер]_right), в массивы Left Track Upper Wheels и Right Track Upper Wheels. Ну с остальными колесами и костями я думаю сами разберетесь (колеса называются rowheel_[номер]_right и rowheel_[номер]_left, а кости Suspension_bone[номер]_left и Suspension_bone[номер]_right), главное чтобы все колеса и кости были переданы в одинаковом порядке, для того чтобы это было легче сделать я их специально пронумеровал, и еще не трогайте кости которые называются Chain_bone[номер]_left и Chain_bone[номер]_right, к ним привязана статичная часть гусеницы.

Вот как все это в конце концов должно выглядеть:
image
image

Затем найдите дочерний объект hull (корпус танка), добавьте к нему Mesh Collider (Component -> Physics -> Mesh Collider), и поставьте галочку Convex (эта галочка значит что данный Mesh Collider будет вычислять столкновения не всех треугольников содержащихся в корпусе танка, а создаст свой Mesh который будет содержать максимум 255 треугольников).

«Оживляем» колеса и гусеницу

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

void FixedUpdate(){ 
		 
	UpdateWheels(); //1  
	 
} 
 
 
public void UpdateWheels(){ //1  
	float delta = Time.fixedDeltaTime;  //2 
	 
	float trackRpm = CalculateSmoothRpm(leftTrackWheelData); //3		 
		 
	foreach (WheelData w in leftTrackWheelData){  //4 
		w.wheelTransform.localPosition =  CalculateWheelPosition(w.wheelTransform,w.col,w.wheelStartPos); //5 
		w.boneTransform.localPosition = CalculateWheelPosition(w.boneTransform,w.col,w.boneStartPos); //6     
		 
		w.rotation = Mathf.Repeat(w.rotation   delta * trackRpm * 360.0f / 60.0f, 360.0f);  //7 
		w.wheelTransform.localRotation = Quaternion.Euler(w.rotation, w.startWheelAngle.y, w.startWheelAngle.z); //8 			 
		
	} 
	 
	 
	leftTrackTextureOffset = Mathf.Repeat(leftTrackTextureOffset   delta*trackRpm*trackTextureSpeed/60.0f,1.0f); //9 
	leftTrack.renderer.material.SetTextureOffset("_MainTex",new Vector2(0,-leftTrackTextureOffset)); //10 
	 
	trackRpm = CalculateSmoothRpm(rightTrackWheelData);  //3 
		 
	foreach (WheelData w in rightTrackWheelData){  //4   
		w.wheelTransform.localPosition = CalculateWheelPosition(w.wheelTransform,w.col,w.wheelStartPos); //5 
		w.boneTransform.localPosition = CalculateWheelPosition(w.boneTransform,w.col,w.boneStartPos); //6 
					 
		w.rotation = Mathf.Repeat(w.rotation   delta * trackRpm * 360.0f / 60.0f, 360.0f);  //7 
		w.wheelTransform.localRotation = Quaternion.Euler(w.rotation, w.startWheelAngle.y, w.startWheelAngle.z);  //8 
		 
		
	} 
			 
	rightTrackTextureOffset = Mathf.Repeat(rightTrackTextureOffset   delta*trackRpm*trackTextureSpeed/60.0f,1.0f);  ///9 
	rightTrack.renderer.material.SetTextureOffset("_MainTex",new Vector2(0,-rightTrackTextureOffset));  //10 
	 
	for(int i=0;i<leftTrackUpperWheels.Length;i  ){  //11 
	 	leftTrackUpperWheels[i].localRotation = Quaternion.Euler(leftTrackWheelData[0].rotation, leftTrackWheelData[0].startWheelAngle.y, leftTrackWheelData[0].startWheelAngle.z);  //11 
	} 
	 
	for(int i=0;i<rightTrackUpperWheels.Length;i  ){  //11 
	 	rightTrackUpperWheels[i].localRotation = Quaternion.Euler(rightTrackWheelData[0].rotation, rightTrackWheelData[0].startWheelAngle.y, rightTrackWheelData[0].startWheelAngle.z);  //11 
	} 
} 
 
 
private float CalculateSmoothRpm(WheelData[] w){ //12 
	float rpm = 0.0f;  
	 
	List<int> grWheelsInd = new List<int>(); //13 
			 
	for(int i = 0;i<w.Length;i  ){ //14 
		if(w[i].col.isGrounded){  //14 
			grWheelsInd.Add(i); //14 
		} 
	} 
	 
	if(grWheelsInd.Count == 0){  //15   
		foreach(WheelData wd in w){  //15 
			rpm  =wd.col.rpm;  //15				 
		} 
		 
		rpm /= w.Length; //15 
					 
	}else{  //16 
								 
		for(int i = 0;i<grWheelsInd.Count;i  ){  //16 
			rpm  =w[grWheelsInd[i]].col.rpm; //16	 
		} 
		 
		rpm /= grWheelsInd.Count; //16 
	} 
	 
	return rpm; //17 
} 
 
 
private Vector3 CalculateWheelPosition(Transform w,WheelCollider col,Vector3 startPos){  //18 
	WheelHit hit; 
			 
	Vector3 lp = w.localPosition; 
	if (col.GetGroundHit(out hit)) { 
		lp.y -= Vector3.Dot(w.position - hit.point, transform.up) - wheelRadius; 
		 
	}else { 
		lp.y = startPos.y - suspensionOffset; 
					 
	} 
	 
	return lp;		 
}
  1. Будем вызывать внутри функции FixedUpdate() нашу функцию UpdateWheels() которая будет вычислять позицию и угол вращения колес.
  2. См. урок 1.
  3. Функция CalculateSmoothRpm() будет вычислять среднюю скорость вращения колес (подробнее расскажу дальше) для того чтобы они вращались с одинаковой скоростью в неё мы передаем весь массив leftTrackWheelData[], а потом массив rightTrackWheelData[].
  4. Для всех элементов массивов содержащих данные о левых и правых колесах выполняем следующие операции:
  5. Вычисляем локальную позицию колеса по оси Y, в чем нам поможет функция CalculateWheelPosition() (подробности ниже), в которую мы передаем Transform колеса, его WheelCollider, и его начальную локальную позицию;
  6. Те же самые операции, только на этот раз чтобы вычислить локальную позицию кости к которой привязана данная часть гусеницы;
  7. Вычисляем угол вращения колеса (см. урок 1), только на этот раз используем не rpm коллайдера, а среднюю скорость вращения которую вычислили ранее;
  8. Применяем вычисленный угол вращения к локальному углу поворота колеса (см. урок 1).
  9. Вычисляем смещение текстуры гусеницы. К сожалению я не нашел универсальной формулы которая позволила бы с одинаковой скоростью вращать колесо и перемещать гусеницу, поэтому пришлось ввести дополнительную переменную trackTextureSpeed (см. выше), которую позже придется подгонять вручную чтобы колесо и гусеница двигались с равномерной скоростью.
  10. Применяем смещение гусеницы к координате Y (new Vector2(0,-leftTrackTextureOffset)), главной текстуры (“_MainTex”) материала который используют GO leftTrack и rightTrack.
  11. Вычисляем вращение верхних колес к которым у нас не привязаны WheelCollider’ы, можем позаимствовать угол вращения у какого нибудь другого колеса, все равно все они теперь двигаются с одинаковой скоростью.
  12. Ну наконец мы добрались до функции CalculateSmoothRpm() которая принимает первым аргументом массив типа WheelData, который в свою очередь носит оригинальное имя w.
  13. Создаем новый динамический список который будет содержать индексы тех элементов массива w, внутри которых WheelCollider col, в данный момент касается поверхности (террейна).
  14. Пробегаемся по массиву и находим индексы элементов в которых коллайдеры касаются поверхности.
  15. Если количество элементов списка равно нулю, то есть если ни один из коллайдеров не касается земли, то складываем все rpm коллайдеров внутри массива w, затем делим получившееся значение на количество элементов в массиве w, таким образом находим среднее значение.
  16. Если у нас один или более элементов списка, тоесть если один или более коллайдеров касается земли, то складываем скорости только этих коллайдеров и делим на количество элементов списка.
  17. Возвращаем получившееся значение. (Должно быть у вас возник вопрос: для чего все эти операции по вычислению коллайдеров которые касаются земли, и почему нельзя просто постоянно находить среднюю скорость вращения всех колес? Ответ – домашнее задание: подумайте (а лучше поэкспериментируйте) как тогда будут вращаться колеса в данной ситуации:
    image
  18. Функция CalculateWheelPosition() которая первым аргументом принимает Transform колеса (либо кости), вторым WheelCollider, и третьим стартовую локальную позицию колеса (либо кости). Как я уже говорил выше, данная функция вычисляет локальную позицию колеса (либо кости), и не несет в себе абсолютно ничего нового, так как данный алгоритм мы разбирали в первом уроке.
Смотрите про коптеры:  Как делать трюки на дроне

Итак теперь вы можете смело нажимать на Play, и убедиться что колеса пришли в движение, но так как наш танк еще не умеет ездить, просто переместите его на неровную поверхность. Также вы скорее всего заметили что колеса ушли под землю, не беда, вспомните, у нас же есть переменная wheelRadius, подгоните её значение не выходя из Play mode так чтобы колеса с гусеницей лежали на земле, а также значение переменной trackTextureSpeed, чтобы синхронизировать движение гусеницы с движением колес. У меня данные значения получились следующими:

image

Учимся ездить

Какая наверное самая главная особенность движения гусеничного транспорта которая отличает его от обычного автомобиля? Я считаю что это поворот на месте. Не верите?

. Будь у обычного автомобиля возможность разворачиваться на месте глядишь блондинки выезжающие со стоянки не так часто врезались бы в автомобиль стоящий перед ними.

Итак каким же образом танк может поворачивать на месте, или поворачивать вообще не имея поворачивающих направляющих колес? Все до безобразия просто, как видите из видео выше одна гусеница двигается вперед, другая двигается назад, так танк поворачивается вокруг своей оси.

Я предлагаю начать с самого простого и для начала научить наш танк поворачивать на месте. Тут казалось бы все понятно. И мы можем сделать так:

public float rotateOnStandTorque = 1500.0f; //1
public float rotateOnStandBrakeTorque = 500.0f; //2
public float maxBrakeTorque = 1000.0f; //3

void FixedUpdate(){ 
	float accelerate = 0;  
	float steer = 0;  
			 
	accelerate = Input.GetAxis("Vertical");  //4
	steer = Input.GetAxis("Horizontal"); //4
	 
	UpdateWheels(accelerate,steer); //5 
} 
 
 
public void UpdateWheels(float accel,float steer){ //5
	float delta = Time.fixedDeltaTime; 
	 
	float trackRpm = CalculateSmoothRpm(leftTrackWheelData);		 
		 
	foreach (WheelData w in leftTrackWheelData){ 
		w.wheelTransform.localPosition = CalculateWheelPosition(w.wheelTransform,w.col,w.wheelStartPos); 
		w.boneTransform.localPosition = CalculateWheelPosition(w.boneTransform,w.col,w.boneStartPos); 
		 
		w.rotation = Mathf.Repeat(w.rotation   delta * trackRpm * 360.0f / 60.0f, 360.0f); 
		w.wheelTransform.localRotation = Quaternion.Euler(w.rotation, w.startWheelAngle.y, w.startWheelAngle.z); 
		 
		CalculateMotorForce(w.col,accel,steer);  //6 
	} 
	 
	 
	leftTrackTextureOffset = Mathf.Repeat(leftTrackTextureOffset   delta*trackRpm*trackTextureSpeed/60.0f,1.0f); 
	leftTrack.renderer.material.SetTextureOffset("_MainTex",new Vector2(0,-leftTrackTextureOffset)); 
	 
	trackRpm = CalculateSmoothRpm(rightTrackWheelData); 
		 
	foreach (WheelData w in rightTrackWheelData){ 
		w.wheelTransform.localPosition = CalculateWheelPosition(w.wheelTransform,w.col,w.wheelStartPos); 
		w.boneTransform.localPosition = CalculateWheelPosition(w.boneTransform,w.col,w.boneStartPos); 
					 
		w.rotation = Mathf.Repeat(w.rotation   delta * trackRpm * 360.0f / 60.0f, 360.0f); 
		w.wheelTransform.localRotation = Quaternion.Euler(w.rotation, w.startWheelAngle.y, w.startWheelAngle.z); 
		 
		CalculateMotorForce(w.col,accel,-steer); //6 
	} 
			 
	rightTrackTextureOffset = Mathf.Repeat(rightTrackTextureOffset   delta*trackRpm*trackTextureSpeed/60.0f,1.0f); 
	rightTrack.renderer.material.SetTextureOffset("_MainTex",new Vector2(0,-rightTrackTextureOffset)); 
	 
	for(int i=0;i<leftTrackUpperWheels.Length;i  ){ 
	 	leftTrackUpperWheels[i].localRotation = Quaternion.Euler(leftTrackWheelData[0].rotation, leftTrackWheelData[0].startWheelAngle.y, leftTrackWheelData[0].startWheelAngle.z); 
	} 
	 
	for(int i=0;i<rightTrackUpperWheels.Length;i  ){ 
	 	rightTrackUpperWheels[i].localRotation = Quaternion.Euler(rightTrackWheelData[0].rotation, rightTrackWheelData[0].startWheelAngle.y, rightTrackWheelData[0].startWheelAngle.z); 
	} 
}


public void CalculateMotorForce(WheelCollider col, float accel, float steer){  //6
	 if(accel == 0 && steer == 0){ //7
		col.brakeTorque = maxBrakeTorque; //7
	}else if(accel == 0.0f){  //8
		col.brakeTorque = rotateOnStandBrakeTorque; //9
		col.motorTorque = steer*rotateOnStandTorque; //10
	} 
			 
}

  1. Крутящий момент который будем передавать на коллайдеры, когда танк стоит на месте.
  2. Тормозной момент который будем передавать на коллайдеры, когда танк стоит на месте.
  3. Максимальный тормозной момент.
  4. См. урок 1.
  5. Модифицируем функцию UpdateWheels() сделаем так чтобы она могла принимать значения с виртуальных осей.
  6. Будем использовать функцию CalculateMotorForce() для контроля крутящего и тормозного момента на коллайдерах, будем передавать в эту функцию коллайдер и виртуальные оси (обратите внимание для leftTrackWheelData мы передаем положительное значение с горизонтальной оси, а для rightTrackWheelData отрицательное, что позволит нам двигать гусеницы в разных направлениях).
  7. Если не нажата ни одна клавиша движения, то передаем коллайдеру максимальный тормозной момент (для того чтобы он не скатывался с неровных мест).
  8. Если не нажата клавиша движения вперед, но нажата клавиша движения в сторону то:
  9. Передаем коллайдеру тормозной момент.
  10. Передаем крутящий момент умноженный на значение полученное с горизонтальной оси (данный крутящий момент должен быть больше тормозного момента rotateOnStandBrakeTorque, для того чтобы танк смог двигаться).

Итак, нажимаем Play, а затем на клавишу движения в сторону (A или D). И что же мы видим, только жалкие попытки нашего танка развернуться на месте, такое ощущение что ему не хватает мощности. Можно конечно увеличить значение переменной rotateOnStandTorque но результат будет довольно забавным.

На самом деле здесь дело не в силе крутящего момента, её более чем достаточно для поворота танка. Давайте вернемся к истокам, тоесть к нашему префабу tank_collider настройки которого у нас наследуются для всех WheelCollider’ов танка. Обратите внимание на поле Sideways Friction, в предыдущем уроке я упомянул об этой настройке, я говорил что это «боковая» сила трения колеса, и она полезна если мы хотим реализовать дрифт. Сейчас эта самая сила трения которая действует на наши коллайдеры с боковых сторон неимоверно большая, крутящий момент колес не в силах её преодолеть, поэтому наш танк не поворачивается. Теперь обратите внимание на переменную Stiffness Factor внутри Sideways Friction, она та нам и нужна, по сути это число на которое умножается боковая сила трения, ставьте его в ноль, нажимайте Play.
image

О чудо наш танк сошел с ума и теперь может крутиться со скоростью волчка, да еще и ездить боком, все правильно, теперь бокового трения нету совсем. Теперь выйдите из Play mode, поставьте значение в 0.06 и снова нажмите Play. Ну наконец то, теперь наш танк поворачивается вокруг своей оси и вполне адекватно.

Выходите из Play mode и установите значение Sideways Friction обратно в 1. Конечно все это хорошо менять значение прямо из префаба, но лучше это делать из скрипта, потомучто когда боковое трение не управляемо есть шанс что наш танк будет входить в самый настоящий дрифт.

public float forwardTorque = 500.0f; //1
public float rotateOnMoveBrakeTorque = 400.0f; //2 
public float minBrakeTorque = 0.0f; //3 
public float minOnStayStiffness = 0.06f; //4 
public float minOnMoveStiffness = 0.05f;  //5 
public float rotateOnMoveMultiply = 2.0f; //6

public void CalculateMotorForce(WheelCollider col, float accel, float steer){ 
	WheelFrictionCurve fc = col.sidewaysFriction;  //7 
			 
	if(accel == 0 && steer == 0){ 
		col.brakeTorque = maxBrakeTorque; 
	}else if(accel == 0.0f){ 
		col.brakeTorque = rotateOnStandBrakeTorque; 
		col.motorTorque = steer*rotateOnStandTorque;	 
		fc.stiffness = 1.0f   minOnStayStiffness - Mathf.Abs(steer); 
		 
	}else{ //8 
		 
		col.brakeTorque = minBrakeTorque;  //9 
		col.motorTorque = accel*forwardTorque;  //10 
					 
		if(steer < 0){ //11 
			col.brakeTorque = rotateOnMoveBrakeTorque; //12 
			col.motorTorque = steer*forwardTorque*rotateOnMoveMultiply;//13 
			fc.stiffness = 1.0f   minOnMoveStiffness - Mathf.Abs(steer);  //14 
		} 
		 
		if(steer > 0){ //15 
			 
			col.motorTorque = steer*forwardTorque*rotateOnMoveMultiply;//16 
			fc.stiffness = 1.0f   minOnMoveStiffness - Mathf.Abs(steer); //17
		} 
		 
					 
	} 
	 
	if(fc.stiffness > 1.0f)fc.stiffness = 1.0f; //18		 
	col.sidewaysFriction = fc; //19
	 
	if(col.rpm > 0 && accel < 0){ //20 
		col.brakeTorque = maxBrakeTorque;  //21
	}else if(col.rpm < 0 && accel > 0){ //22 
		col.brakeTorque = maxBrakeTorque; //23
	} 
}
  1. Крутящий момент при движении (вперед, назад).
  2. Тормозной момент при повороте во время движения.
  3. Минимальный тормозной момент.
  4. Минимальное боковое трение при повороте на месте.
  5. Минимальное боковое трение при повороте во время движения.
  6. Множитель крутящего момента при повороте во время движения.
  7. Объявляем переменную fc типа WheelFrictionCurve, сохраняем в ней Sideways Friction нашего Wheel Collider’а.
  8. Если нажата клавиша движения, либо одновременно клавиша движения и клавиша поворота то:
  9. Передаем коллайдеру минимальный тормозной момент;
  10. Умножаем значение полученное с вертикально оси на максимальный крутящий момент;
  11. Если значение полученное с горизонтальной оси меньше нуля (тоесть при повороте налево операции заключенные в данное условие будут выполняться только для коллайдеров расположенных на левых опорных катках, при повороте направо, соответственно на правых) то:
  12. Передаем коллайдеру тормозной момент (тоесть при повороте налево, левые коллайдеры будут притормаживать);
  13. Передаем крутящий момент умноженный на значение полученное с вертикальной оси и умноженный на множитель крутящего момента (мало того что данные коллайдеры будут притормаживать они еще и будут стремиться вращаться в противоположную сторону, благодаря этому поворот станет более резким);
  14. Понижаем множитель боковой силы трения который сохранен в переменной fc.
  15. Если значение полученное с горизонтальной оси больше нуля (тоесть при повороте налево операции заключенные в данное условие будут выполняться только для коллайдеров расположенных на правых опорных катках, при повороте направо, соответственно на левых) то:
  16. Коллайдеры расположенные на противоположной гусенице не должны притормаживать, они будут наоборот разгоняться.
  17. Понижаем множитель боковой силы трения который сохранен в переменной fc.
  18. Проверяем не получилось ли значение множителя боковой силы трения больше единицы, если получилось, присваиваем ему единицу.
  19. Присваиваем переменной sidewaysFriction нашего коллайдера переменную fs (к сожалению нельзя сразу написать col.sidewaysFriction.stiffnes = (float)значение, компилятор будет ругаться).
  20. Если колеса вращаются вперед, а мы жмем клавишу назад то:
  21. Передаем на коллайдеры тормозной момент.
  22. Если колеса вращаются назад а мы жмем клавишу вперед то:
  23. Без комментариев.
Смотрите про коптеры:  Радио модуль NRF24L01: описание, подключение, схема, характеристики | ВИКИ

Ну вот и все, ваш танк в боевой готовности, хоть расстрелять врагов он пока не может, зато может их задавить.

В заключение скажу что это далеко не универсальный алгоритм движения танка, над ним еще работать и работать, например необходимо прописать ограничение максимальной скорости, чтобы наш танк не мог разгоняться до 300 км. в час. Я думаю если вы поймете все то что написано выше, вы без труда сможете это модифицировать. Спасибо за внимание, следующие уроки coming soon.

Виниловые гусеницы для танка —

  1. Масштабные модели59520
  2. Отечественные автомобили24532
  3. Зарубежные автомобили29433
  4. Авиация1263
  5. Бронетехника2026
  6. Мотоциклы626
  7. Прочее806
  8. Тракторы, строительно-дорожная техника1049
  9. Раритетные и редкие модели2974
  10. Сборные модели / Киты6347
  11. Бронетехника, танки (БТТ)1222
  12. Самолеты, вертолеты, ракеты (авиация)1635
  13. Корабли, подводные лодки (флот)212
  14. Пушки, оружие (артиллерия)132
  15. Автомобили2557
  16. Фигуры, миниатюры539
  17. Мотоциклы21
  18. Другое190
  19. Журнальные серии / Партворки7453
  20. Тракторы. История, люди, машины (Hachette)674
  21. Автолегенды СССР (DeAgostini)2691
  22. Полицейские машины мира (DeAgostini)406
  23. ГАЗ-М20 Победа в масштабе 1:8 (DeAgostini)5
  24. Насекомые и их знакомые (DeAgostini)11
  25. Другие серии1817
  26. Ferrari Collection (GeFabbri)234
  27. Kultowe Auta PRL-u (Польша)128
  28. Автомобиль на службе (DeAgostini)453
  29. Суперкары (DeAgostini)414
  30. Русские танки (GeFabbri) 1:72346
  31. Танки Мира 1:72112
  32. Боевые машины мира 1:72 (Eaglemoss collections)100
  33. The James Bond Car Collection (Автомобили Джеймса Бонда)81
  34. Фототравление, декали, краски, материалы4983
  35. Фигурки1451
  36. Элементы для диорам (объекты, материалы)704
  37. Железнодорожные модели438
  38. Детали, запчасти, комплектующие7009
  39. Боксы, коробки, стеллажи, подставки1217
  40. Инструменты, расходные материалы432
  41. Радиоуправляемые модели25
  42. Литература, каталоги, журналы3450
  43. Другое1419
  44. Торги с одного рубля793

Поиск

Моделирование и анимация гусеницы танка, велосипедной цепи

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

Все началось с того, что два пользователя 3DSMax, живущие в разных городах, в одно и то же время столкнулись с одной и той же проблемой. Делать им было нечего, один моделировал танковую гусеницу (просто так), другой – велосипедную цепь (не просто так, а для привода винтов подводной лодки), и оба никак не могли уговорить модель корректно скользить вдоль траектории, изламываясь именно в тех местах, в которых им хотелось.

Можно было, конечно, использовать Path Deform. Но при этом звенья объекта тоже повторяют изгибы траектории, и их геометрия <плывет>. Издалека – еще ничего, но вблизи – мрак:

Пришлось напрячься.

И вот – результат, которым мы решили поделиться со всеми единомышленниками.

Предлагаемый метод позволяет имитировать движение подобных объектов вдоль траектории без нарушения геометрии составляющих его звеньев, изгибая его только в местах соединения этих звеньев.

Мы расскажем о том, как это удалось сделать, на примере танковой гусеницы.

  1. Моделируем трак гусеницы.

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр
Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр
Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр
Можно, конечно, условно представить трак в виде параллелепипеда с двумя цилиндрами-осями, но это было бы грустно. Поэтому предлагаем вам сделать что-то вроде объектов, изображенных на рисунке: сам трак, передняя ось (потоньше и подлиннее) и задняя ось (потолще и покороче). Очень важно, чтобы Pivot Points всех объектов находились строго по их центру в плане, у осей, естественно, по их геометрическому центру, и у всех трех должна совпадать координата Z. То есть в окне проекций Front все Pivot Points должны лежать на одной прямой.

Далее возможны два варианта моделирования – сгруппировать все три объекта, либо преобразовать их в один объект Editable Mesh, кому как больше нравится.

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

Группируем эти объекты в один, именуем его #001, и (это важно!) переносим Pivot Point группы в центр задней оси (толстого цилиндра).

  1. Строим гусеницу из траков

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / ХабрВыделим трак-группу #001, и, используя кнопку Shift и инструмент Select & Move, создадим 20 (количество – просто для урока, взято <с потолка>) копий трака таким образом, чтобы тонкая ось каждого последующего трака попала в центр толстой оси предыдущего.

Далее – соединим траки друг с другом для дальнейшего <натягивания> их на траекторию. Для этого выделим объект #001 и начнем назначать связи объектам при помощи инструмента Select and link. #001 будет дочерним по отношению к #002, #002 – по отношению к #003, и так далее до конца цепочки. При этом не забывайте, что, щелкнув по объекту #002, надо снять все выделения, затем выделить объект #002 и щелкнуть по объекту #003, вновь снять выделение и так далее до конца.

Гусеница готова.

  1. Располагаем гусеницу по траектории.

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / ХабрРисуем замкнутый сплайн-траекторию и располагаем его таким образом, как показано на рисунке. Траектория должна располагаться строго по оси гусеницы в плане, другие координаты значения не имеют. Только при таком расположении гусеница ляжет на траекторию корректно, ее не <поведет набок>.

Далее – самое интересное. <Натягиваем> цепь на траекторию. Для этого выделяем последнее звено #021. В главном меню открываем команды:

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

От объекта #021 к курсору протянется белая пунктирная линия. Щелкаем мышью сначала на объекте #001, затем на сплайне-траектории. Цепь <запрыгивает> на траекторию.

Появляются новые (очень полезные) объекты – Point 01 – 04. Количество точек соответствует количеству точек сплайна-траектории. О пользе этих точек – чуть позже.

Далее надо лишь отредактировать сплайн таким образом, чтобы начало трака #001 совпало с концом трака #021, то есть замкнуть гусеницу путем изменения протяженности сплайна. Сплайн-траектория редактируется путем перемещения упомянутых выше точек.

Получилось замечательно, не правда ли?

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / ХабрUnity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / ХабрUnity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

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

Вновь выделяем последний трак #021 и анимируем по времени параметр % Along Path, находящийся в свитке параметров Motion – Parameters. Попробуйте пока просто изменить его – гусеница <поползет> вдоль траектории.

  1. Крепим гусеницы к танку.

Естественно, гусеницу надо анимировать лишь после того, как вы закончите анимацию самого танка, чтобы привести ее движение как минимум в соответствие с его скоростью и разворотами. Как же <зацепить> всю эту сложную систему с обратной кинематикой за корпус танка? Простой привязкой траектории к корпусу вы ничего не добьетесь. Попробуйте выделить и вращать по всем осям либо траки гусеницы, либо сплайн траектории. Гусеницу сразу начинает уводить куда-то вбок. Решение – в операциях с точками сплайна-траектории.

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / ХабрНо сначала создадим вторую гусеницу.

Для этого выделим все объекты и сделаем их копии. При этом траектория-копия примет первоначальную конфигурацию, и ее также придется подредактировать. Перенос второй гусеницы на соответствующее ей место возможен лишь одним способом – выделением и переносом точек ее траектории.

Теперь можно дорисовать корпус танка.

Далее выделяем все точки обеих траекторий. При помощи инструмента Select & Link привязываем их к корпусу танка. Проверка: выделяем корпус танка и вращаем его. Гусеницы следуют за движениями корпуса. Все, система <танк гусеницы) представляет собой единое целое.

Простите нас за модель – издевательство над советским танкостроением. Поверьте, что если бы целью урока было достоверное моделирование военной техники, мы постарались бы сделать что-нибудь поприличнее.

Смотрите про коптеры:  Китайские квадрокоптеры с камерой iflight. Квадрокоптеры с камерой iflight китай купить в интернет-магазине АлиЭкспресс

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / ХабрАнимированные гусеницы танка можно посмотреть на прилагаемом файле tank.gif.

Кстати: почему-то у нас при создании второй гусеницы сбились порядковые номера ее траков. Макс создал два трака с номером #022, и мы не сразу нашли последний трак для его анимации. Будьте внимательны.

  1. Интересные детали для любителей <покопаться поглубже>

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

Если вы помните, мы обещали также рассмотреть второй вариант моделирования – представить звенья объекта не в виде групп, а в виде Editable Mesh. На этом примере мы и поговорим о встречающихся нюансах.

Итак,

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / ХабрСоздадим велосипедную цепь, состоящую из следующих деталей (обратите внимание на места расположение их центров координат, это обеспечит излом объекта в нужных местах).

Закончить цепочку можно простым цилиндром – осью.

Вот и первый интересный нюанс: зачем нужен этот последний объект-цилиндр.

Дело в том, что при размещении звеньев цепи на сплайне-траектории они ложатся на нее своими Pivot Points. Начало первого звена не привязано к другим звеньям, поэтому на изгибах траектории это звено будет располагаться по касательной к траектории. Нам необходимо что-то, что <притянет> начало звена к траектории. Именно эту роль и играет первый цилиндр. Он фактически и будет первым звеном цепи. При замыкании цепи на траектории он войдет вовнутрь оси последнего звена, и не будет виден.

Резонный вопрос: Почему этого не происходит, когда звенья представляют собой сгруппированные объекты?

Ответ: Не знаем.

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / ХабрЕсть только одно предположение. Когда мы сдвигаем Pivot Point группы в центр ее оси, Pivot Point каждого объекта группы остается на своем месте. Вы можете проверить это, открыв группу. Возможно, именно Pivot Point переднего объекта и притягивает передний край звена к траектории.

Цепь <одеваем> на траекторию таким же способом, как и гусеницу. И тут может возникнуть другой интересный нюанс. Очень часто цепь ложится на сплайн некорректно, съезжает набок или вообще перекручивается винтом на поворотах сплайна. Но есть выход и из этого положения.

Дело в том, что когда мы представляем звенья цепи (или траки гусеницы) в виде Editable Mesh, 3DSMax предоставляет нам дополнительные параметры для настройки (в случае <групп> эти параметры отсутствуют).

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / ХабрЕсли звенья объекта <ведет>, зайдите в свиток параметров Hierarchy – IK – Rotational Joint – Preferred Angle. Эти углы мы редактировали <методом тыка>, так как не смогли объяснить, в каком случае какой угол надо менять. Иногда решения бывают самыми неожиданными.

Так, например, когда велосипедная цепь легла с перекосом, пришлось каждому звену углы по осям X и Y сделать равными 0, а по оси Z – 90 градусов.

В другом случае (изображен на рисунке) достаточно было последнему траку гусеницы присвоить по оси Y любой угол, но только не 0 градусов.

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

Но вернемся к нашей многострадальной велосипедной цепи.

При анимации лучше поставить галочку , так как в противном случае скорость движения будет зависеть от расстояния между контрольными точками траектории, и будет очень трудно подогнать под движение цепи вращение зубчатого колеса (если его добавлять в сцену).

Цепь готова. Пример использования – привод подводной лодки из мультфильма (на рисунке).

  1. Важная деталь для пользователей 3dsMax5.0

При построении гусеницы в этой версии Макса был замечен еще один неприятный момент, о котором мы хотим вас предупредить. Даже, пожалуй, два момента:

Для анимации гусеницы необходимо предварительно осуществить описанные ниже действия, в противном случае вы сможете лишь <натянуть> ее на траекторию.

Выделите последний трак в цепи.

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / ХабрВо вкладке Animation выберите Constraints.

Теперь для проверки попробуйте покрутить счетчик траектории во вкладке Motion.

Есть еще одно Но. Несмотря на то, что стоит галочка constant velocity (равномерное движение), движение вовсе не становится равномерным (почему – непонятно).

Исправить это можно в окне редактирования треков (Graph editors ->Track view – curve edition).

Вы увидите на экране примерно следующее:

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

Выделите эту кривую и нажмите на кнопку, обведенную синим.

Кривая изменит свою форму

Нажмите на эту кнопку еще раз.

Все. Кривая распрямилась. Движение стало равномерным.

Вот, собственно, и все, чем мы хотели с вами поделиться.

Будем рады, если эти <записки двух сумасшедших> смогли помочь вам в решении аналогичных проблем, и вы вспомните нас добрым словом.

Удачи вам, коллеги.

Файлы к уроку: 1-lesson-002.max1-lesson-005.max

Танк т-34, изготовление траков

Многие делают гусянку из петель. Достаточно быстро, но не копийно. Да и подумав, что вряд ли удастся на собирать нужное количество петель (для такого масштаба нужны мелкие 3 см в ширину) сразу отказался от такого способа.

Второй вариант – отлитие из алюминия. Долго, проблематично, но можно получить практически копию траков. Собственно об этом методе хочу и рассказать, о проблемах и о моих способах их решения. Может кому ни будь, это поможет решится на создание своего танка.

С ходу возникает первая проблема – обеспечение повторяемости траков. Варианты решения: первый – создание мастер модели трака, с последующим изготовлением по нему форм из гипса; второй – отлитие траков в кокиль. Остановился на втором, так как он показался мне более простым.

Гусеница Т-34 состоит из двух видов траков, один с гребнем, второй без. Наиболее подробные чертежи траков, после долгих поисков, удалось найти в журнале Танкомастер за 2002г. №3. По ним в Rhino была создана 3д модель траков. Что очень упростило последующую работу.

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

Этапы обработки на фото.

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

Углубления под ребра жесткости и грунтозацепы делались 1мм фрезой.

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр
Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

Благодаря 3д модели основу кокиля выгрыз за день. Если честно, то сначала пробовал делать без нее, но испортил несколько заготовок и потерял неделю времени. Как говориться «Лучше день потерять и создать 3д модель, но потом за пять минут долететь»

Далее фрезеровал две стальные пластины в толщину трака, сложил их вместе и зажав вертикально в тисках, выбрал 3мм фрезой углубление по форме внешнего контура половины трака.

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр
Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

Осталось сделать форму для гребня. Основой послужили два кусочка уголка. Здесь важно сделать две поверхности под 90 градусов. Иначе потом будет щель.

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

Сложил все вместе, просверлил центровочные отверстия, вставил штифты, прошелся фрезой по торцам.

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

К уголкам приварил заливную горловину

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр
Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

Вот собственно кокиль в сборе

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

Для быстрого зажима на скорою руку сделал такую приспособу

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

Принялся к плавке алюминия, первая отливка….

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр
Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

Как говорится первый блин комом…. Пришлось немного дорабатывать, расширил немного отверстие для заливки, подогнал детали, чтоб устранить щели. Второй блин тоже оказался комом…. Путем проб и ошибок добился приемлемого результата.

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

Проблема оказалась в недостаточно прогретом кокиле, нужно прогревать его градусов до 400 перед отливкой. Прогревал на газовой плите. Также алюминий необходимо греть градусов до 800. Определял по цвету, светло вишневый цвет. Тогда все получается четко.

Таким же образом был сделан кокиль и для второго трака без гребня.

Вот собственно первая партия траков

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

Следующим этапом было сверление 1мм отверстий под соединительный палец. Вот тут возникла серьёзная проблема. Длина трака 31 мм, длина сверла 1 мм 35 мм, а режущей части всего 12 мм. За один проход просверлить не получается. Пришлось делать приспособление и сверлить с двух сторон.

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

Попробовал сверлить…. Сломав с десяток сверл, так ничего и не просверлил. Сверла ломались, не проходя и первого выступа…. Обратился за помощью на один из форумов по металлообработке. Спасибо людям, посоветовали применить СОЖ. Можно заменить ВДешкой, керосином, изопропиловым спиртом. Я использовал керосин. С ним сверление идет как по маслу.

Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр
Unity 3d Tank Tutorial: Ходовая часть (Урок 2. Гусеничное шасси) / Хабр

Сделать гусеницы – вопрос времени. А с ним как раз напряжно. За один заход получается отлить 5 – 10 траков. Думаю, за месяц другой управлюсь.

На этом все. Был рад если моя статья была кому ни будь полезной или помогла решится начать делать свой танк. Всем удачи в моделизме и творчестве.

Оцените статью
Радиокоптер.ру
Добавить комментарий

Adblock
detector