Tanks — Unity Learn

Tanks  - Unity Learn World of Tanks

Вступление

Итак вы уже не новички в Unity, вы умеете создавать сцену, расставлять объекты на сцене, создавать terrain, делать префабы и тд… и главное — работать со скриптами, либо вы уже изучили статьи:

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

А именно: в первой части этого урока вы узнаете что такое Wheel Collider, как с помощью него управлять транспортным средством и создать реалистичное поведение подвески автомобиля, во второй части урока, на основе всего этого я покажу как сделать ходовую часть танка с соответствующей физикой и контроллерами.

Собственно то что вы видите на изображении сверху (это будет результат данного урока) вы можете опробовать «вживую» прямо в браузере. Хотите сделать это сами? Тогда добро пожаловать под кат

Перед тем как начать рассказывать об основах моделирования механики автомобиля на Unity я не могу не отметить что на официальном сайте уже есть уроки на эту тему например здесь. Правда на протяжении всего этого даются лишь указания на то, какой скрипт прикрепить к какому объекту и лишь изредка объясняют чтонибудь полезное, в третьей части урока наконец то объясняют как работает главный скрипт, хотя по моему это надо было делать в самой первой части, и начинать объяснять с того момента как работают wheel collider’ы и как они взаимодействуют с rigidbody, в итоге я сделал вывод что данный урок не рассчитан на новичков в Unity и хочу это исправить.

Но если вы уже знаете что такое Wheel Collider и с чем его едят, то вы можете пропустить этот урок.

Создаем «Автомобиль»

Для начала проделайте стандартные процедуры, создайте сцену, создайте terrain, либо используйте уже готовые, затем создайте пустой Game Object(GO), (GameObject->Create Empty), назовем его какнибудь оригинально, например Car.

Затем создайте куб (GameObject->Create Other -> Cube) примените к нему какой нибудь материал из стандартных, чтобы его было лучше видно. Представим себе что это кузов нашего автомобиля, и растянем его с помощью Scale по ширине и длине так чтобы передняя часть этого кузова смотрела вдоль оси Z (синяя ось, именно она официально считается передом в мире Unity) затем перетянем наш Cube на объект Car во вкладке Hierarchy, чтобы он стал дочерним объекту Car. Перейдем на сам объект Car и добавим ему Rigidbody (Component -> Physics -> Rigidbody). Назначим ему соответствующий вес, примерно 1500 кг., кузов автомобиля готов.
image
image
Переименуем наш только что созданный цилиндр в Wheel Forward Left, установим колесо на свое место, тоесть спереди и слева от нашего куба, (повторяю, передом в Unity считается положительная чать оси Z).
image
Перетянем Wheel Forward Left на объект Car. Назначим колесу текстуру по детальнее чтобы лучше видеть вращение колеса. Теперь скопируем наше колесо (Правой кнопкой по Wheel Forward Left и Duplicate), назовем Wheel Back Left и поставим на законное место, по аналогии поступите с остальными двумя колесами.
image

Итак, наш суперкар почти готов! Поднимите его немного над поверхностью terrain’а и нажмите Play, автомобиль упадет на землю и встанет колом на колеса, о мягкой подвеске ему еще только мечтать, выходите из Play mode. Как видите у наших колес имеется компонент под названием Capsule Collider, а у кузова Box Collider, я не буду рассказывать в этом уроке что такое коллизии и как на основе них рассчитать положение объекта в пространстве, объясню проще: если у объекта имеется какой – либо из простых collider’ов (Box, Capsule, Mesh,…) то этот объект может взаимодействовать с другими объектами содержащими Rigidbody и один или несколько collider’ов, в свою очередь объект содержащий Rigidbody будет вычислять свою позицию на основе столкновений всех подчиненных ему collider’ов и назначенных им физических материалов.

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

А пока что выберите каждое из наших колес и удалите у них Capsule Collider (Правой кнопкой по нему и Remove Component). Нажмите Play, теперь автомобиль больше не стоит на колесах, они прошли сквозь террейн и взаимодействует с поверхностью у нас теперь только кузов.

Добавляем Wheel Colliders


Итак что же такое этот загадочный

? Давайте разбираться. Создайте пустой GameObject назовите его WCollider Forward Left, теперь добавьте к нему компонент Wheel Collider (Component -> Physics -> Wheel Collider), он выглядит как окружность с отрезком внутри, он может быть слишком большим или слишком маленьким по сравнению с вашим колесом, все зависит от того насколько вы увлеклись использованием scale.

К счастью у него есть параметр Radius, изменяя данный параметр подгоните его под радиус своего колеса, затем создайте новый префаб (Assets -> Create -> Prefab) назовите его WCollider затем перетяните WCollider Forward Left на префаб. Мы создали префаб для удобства, так как коллайдеров у нас будет столько же сколько колес, и чтобы не изменять параметры каждого, будем изменять параметры префаба, остальные коллайдеры унаследуют его свойства.

image

Нажмите Play, автомобиль упадет и опять встанет на колеса (кстати если колеса все равно проходят сквозь террейн, это значит что автомобиль падает на землю с очень большой скоростью, и вам просто напросто нужно уменьшить расстояние до земли, либо увеличите параметр Drag у Rigidbody).

Про WoT:  Карелия и Карелия Штурм

Итак, выйдите из Game Mode, выберите наш префаб WCollider, поехали меняем свойства компонента Wheel Collider:

  1. Suspension Distanse – по сути это длина пружины нашей подвески, ставим 0.15
  2. Suspension Spring -> Spring – жесткость подвески, чем больше вес нашего Rigidbody, тем больше должна быть жесткость, ставим 9000
  3. Suspension Spring -> Damper – Смягчитель подвески, большее значение заставляет пружину двигаться медленнее, ставим 100
  4. Mass – масса колеса, оставляем 1
  5. Forward Friction – «передняя» сила трения колеса, оставляем как есть
  6. Sideways Friction – «боковая» сила трения колеса, полезно если хотим реализовать дрифт автомобиля, оставляем как есть

Итого у нас получилось:

(Не смотрите на радиус, у вас он может быть другой, главное чтобы окружность совпадала с вашим колесом)

image

Итак, нажмите Play сейчас, посмотрите, наш автомобиль ведет себя теперь совсем по другому, теперь он пружинит от поверхности террейна, а если вы поставите его на склон какой нибудь горки, он покатится с неё, вот так у нас работают Wheel Collider’ы.

Но это далеко не все на что они способны, давайте выясним как с помощью них управлять автомобилем и сделать так чтобы наши колеса крутились и реагировали на неровности ландшафта. Начинаем скриптовать!

Управляем Wheel Collider’ами

В этом уроке я буду писать скрипты на C#, простите меня JavaScript’еры, он мне привычнее, я думаю вы разберетесь.

Создаем новый С# скрипт (Assets -> Create -> C Sharp Script), назовем его CarController, открываем и напишем следующее:

using UnityEngine;
using System.Collections;

public class CarController : MonoBehaviour { //1
	
	public WheelCollider[] WColForward; //2
	public WheelCollider[] WColBack;  //3
	
	// Use this for initialization
	void Start () {  //4
	
	}
	
	
	void FixedUpdate () {  //5
		
	
	}
}
  1. Главный класс скрипта должен называться точно так же как и файл скрипта, иначе компилятор выдаст ошибку.
  2. Массив передних коллайдеров.
  3. Массив задних коллайдеров.
  4. Будем использовать функцию Start() для инициализации.
  5. Как то в уроке Unity3d script basics я говорил что для физического взаимодействия с объектами лучше использовать функцию FixedUpdate(), нежели функцию Update().

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

image

Далее работаем с нашими коллайдерами, дополняем скрипт:

using UnityEngine;
using System.Collections;

public class CarController : MonoBehaviour {
	
	public WheelCollider[] WColForward;
	public WheelCollider[] WColBack;
	
	public float maxSteer = 30; //1
	public float maxAccel = 25; //2
	public float maxBrake = 50; //3
	
	
	// Use this for initialization
	void Start () {
	
	}
	
	
	void FixedUpdate () {
		
		float accel = 0;
		float steer = 0;
				
		accel = Input.GetAxis("Vertical");  //4
		steer = Input.GetAxis("Horizontal");	 //4	
		
		CarMove(accel,steer); //5
		
	}
	
	private void CarMove(float accel,float steer){ //5
		
		foreach(WheelCollider col in WColForward){ //6
			col.steerAngle = steer*maxSteer; //6
		}
		
		if(accel == 0){ //7
			foreach(WheelCollider col in WColBack){  //7
				col.brakeTorque = maxBrake; //7
			}	
			
		}else{ //8
								
			foreach(WheelCollider col in WColBack){ //8
				col.brakeTorque = 0; //8
				col.motorTorque = accel*maxAccel; //8
			}	
			
		}
		
				
		
	}
}

Теперь можете нажать Play, и используя кнопки W,S или стрелки вперед — назад, привести в движение нашу «формулу один», а кнопками A,D, или стрелками влево – вправо, поворачивать.

Давайте же разберемся что делает этот скрипт:

  1. Максимальный угол поворота колес.
  2. Максимальный крутящий момент передающийся на колесо.
  3. Максимальный тормозной момент.
  4. Функция GetAxis() класса Input ждет события которое возникает когда мы нажимаем на клавиши движения на контроллере, либо двигаем стики джойстика, функция принимает направление виртуальной оси, (например «Vertical» – это клавиши W,S, либо движение джойстика вперед – назад) и возвращает число от -1 до 1, где -1 – движение назад либо влево, (клавиша S в «Vertical» оси, D в «Horizontal») 0 – оси не активны и 1 – движение вперед, либо вправо (клавиша W в «Vertical» оси, A в «Horizontal», либо движение стика джойстика влево — вправо).
  5. Передаем результаты полученные от виртуальных осей в функцию CarMove().
  6. Для каждого Wheel Collider’а находящегося в массиве WColForward мы изменяем переменную steerAngle, (угол поворота колеса) умножая её на значение полученное с горизонтальной оси.
  7. Если вертикальная ось у нас в положении покоя (тоесть если мы не давим на клавиши W или S) то изменяем переменную коллайдера brakeTorque, которая отвечает за силу торможения, это нужно для того чтобы автомобиль не двигался по инерции если мы не нажимаем на клавиши движения.
  8. В других случаях, (если мы нажимаем на клавиши W или S) обнуляем силу торможения, а затем изменяем переменную motorTorque, (крутящий момент колеса) умножая её на значение полученное с вертикальной оси, вследствие чего наш автомобиль начинает ускоряться.

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

Давайте обо всем по порядку.

Центр тяжести

Задается он удивительно просто, во первых нам надо создать пустой GO, назовем его Center of mass, затем перетянуть его на объект Car, и разместить его приблизительно там где вы считаете он должен быть, например чтобы моё авто не переворачивалось при повороте на высокой скорости мне пришлось сделать его вот так:

У вас он может располагаться по другому, все зависит от геометрических размеров Box Collider’а нашего кузова, так что по экспериментируйте с центром тяжести, но для начала дополните наш скрипт:

  1. Объявите переменную: public Transform COM.
  2. Перетащите GO Center of mass на поле COM в скрипте.
  3. Внутри функции Start() напишите следующую строчку: rigidbody.centerOfMass = COM.localPosition
Про WoT:  ПОСЛЕДНИЙ ДЕНЬ В ПРОДАЖЕ


Теперь можете нажать Play и проверить результат.

«Оживляем» колеса

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

Для того чтобы оживить колесо, нам необходимо вычислять позицию и угол его вращения в каждом фиксированном кадре, в этом нам опять поможет чудесный WheelCollider, его метод GetGroundHit(), который способен вернуть структуру WheelHit в которой в свою очередь содержится точка соприкосновения коллайдера и террейна (переменная point).

Благодаря методу GetGroundHit() мы можем вычислить позицию колеса на основе движения пружины подвески. Ну а что касается угла поворота – это совсем просто, в WheelCollider’е есть float переменная rpm, это аббревиатура от rotation per minute, на её основе мы и можем определить угол вращения колеса.

Переходим собственно к скрипту, он вырос, потолстел и теперь выглядит так:

using UnityEngine;
using System.Collections;

public class CarController : MonoBehaviour {
	
	public WheelCollider[] WColForward;
	public WheelCollider[] WColBack;
	
	public Transform[] wheelsF; //1
	public Transform[] wheelsB; //1
	
	public float wheelOffset = 0.1f; //2
	public float wheelRadius = 0.13f; //2
	
	public float maxSteer = 30;
	public float maxAccel = 25;
	public float maxBrake = 50;
	
	public Transform COM;
	
	public class WheelData{ //3
		public Transform wheelTransform; //4
		public WheelCollider col; //5
		public Vector3 wheelStartPos; //6 
		public float rotation = 0.0f;  //7
	}
	
	protected WheelData[] wheels; //8
	
	// Use this for initialization
	
		
	void Start () {
		rigidbody.centerOfMass = COM.localPosition;
		
		wheels = new WheelData[WColForward.Length WColBack.Length]; //8
		
		for (int i = 0; i<WColForward.Length; i  ){ //9
			wheels[i] = SetupWheels(wheelsF[i],WColForward[i]); //9
		}
		
		for (int i = 0; i<WColBack.Length; i  ){ //9
			wheels[i WColForward.Length] = SetupWheels(wheelsB[i],WColBack[i]); //9
		}
		
	}
	
	
	private WheelData SetupWheels(Transform wheel, WheelCollider col){ //10
		WheelData result = new WheelData(); 
		
		result.wheelTransform = wheel; //10
		result.col = col; //10
		result.wheelStartPos = wheel.transform.localPosition; //10
		
		return result; //10
		
	}
	
	void FixedUpdate () {
		
		float accel = 0;
		float steer = 0;
				
		accel = Input.GetAxis("Vertical");
		steer = Input.GetAxis("Horizontal");		
		
		CarMove(accel,steer);
		UpdateWheels(); //11
	}
	
	
	private void UpdateWheels(){ //11
		float delta = Time.fixedDeltaTime; //12
		
		
		foreach (WheelData w in wheels){ //13
			WheelHit hit; //14
								
			Vector3 lp = w.wheelTransform.localPosition; //15
			if(w.col.GetGroundHit(out hit)){ //16
				lp.y -= Vector3.Dot(w.wheelTransform.position - hit.point, transform.up) - wheelRadius; //17
			}else{ //18
				
				lp.y = w.wheelStartPos.y - wheelOffset; //18
			}
			w.wheelTransform.localPosition = lp; //19
			
			
			w.rotation = Mathf.Repeat(w.rotation   delta * w.col.rpm * 360.0f / 60.0f, 360.0f); //20
			w.wheelTransform.localRotation = Quaternion.Euler(w.rotation, w.col.steerAngle, 90.0f); //21
		}	
		
	}
	
	private void CarMove(float accel,float steer){
		
		foreach(WheelCollider col in WColForward){
			col.steerAngle = steer*maxSteer;
		}
		
		if(accel == 0){
			foreach(WheelCollider col in WColBack){
				col.brakeTorque = maxBrake;
			}	
			
		}else{
								
			foreach(WheelCollider col in WColBack){
				col.brakeTorque = 0;
				col.motorTorque	= accel*maxAccel;
			}	
			
		}
		
				
		
	}
	
}

Скопируйте его, вставьте в свой скрипт, затем во вкладке hierarchy выберите наш объект Car, как вы можете видеть в инспекторе у нас появились новые переменные и массивы в скрипте, в массивы wheelsF и wheelsB необходимо передать передние и задние колеса соответственно, (колеса а не Wheel Collier’ы!) как сделаете это нажмите Play.

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

Если же они крутятся у вас не по той оси, или в другую сторону, то вам следует перечитать п. 1 где мы создавали автомобиль, я там говорил о том что важно повернуть колеса именно по оси Z на 90 градусов далее я объясню вам почему. Давайте разберем скрипт:

  1. Создаем массивы которые будут хранить Transform наших колес.
  2. Данные переменные нам пригодятся, ниже объясню для чего.
  3. Создадим класс который будет содержать нужную нам информацию о каждом нашем колесе, а именно:
  4. Transform колеса;
  5. Wheel Collider Колеса;
  6. Стартовую позицию колеса;
  7. Угол вращения колеса.
  8. Объявляем массив wheels c типом WheelData.
  9. Передаем в массив wheels необходимые нам данные, для этого я написал функцию SetupWheels().
  10. Функция SetupWheels() принимает Transform колеса и его WheelCollider, передает в переменные содержащиеся в классе WheelData необходимые нам данные и возвращает его.
  11. Напишем функцию UpdateWheels() в которой будет вычисляться позиция и угол поворота наших колес.
  12. Запоминаем переменную fixedDeltaTime класса Time она нужна нам для того чтобы вращение колеса было равномерно растянуто по времени.
  13. Для каждого элемента массива wheels выполняем следующие операции:
  14. Создаем переменную класса WheelHit (о ней я рассказывал выше);
  15. Запоминаем локальную позицию колеса (локальная позиция – это позиция относительно родительских координат, глобальная – относительно мировых);
  16. Если WheelCollider колеса сталкивается с поверхностью террейна (либо чего нибудь другого);
  17. То из координаты Y локальной позиции колеса вычитаем Dot() между вектором с началом в точке в которой коллайдер соприкасается с поверхностью террейна (hit.point) и концом в текущей позиции колеса (w.wheelTransform.position) и между вектором направленным вверх относительно объекта Car (transform.up) и из всего этого вычитаем еще и переменную wheelRadius чтобы колесо заняло правильное место. (Если из вышесказанного вы не поняли ни слова, не расстраивайтесь большинство людей не понимает этого с первого раза, для этого вам нужно хорошо знать что такое Dot product и как он связан с перечисленными выше векторами, либо просто знайте что позиция колеса должна вычисляться именно так);
  18. Если WheelCollider не касается поверхности террейна. То из координаты Y начальной локальной позиции колеса отнимаем wheelOffset, благодаря этому наши колеса не улетают в неизвестном направлении когда автомобиль падает с высоты или лежит на «спине»;
  19. Применяем измененную позицию колеса к его текущей позиции;
  20. Вычисляем угол «вращения» колеса, используя функцию Repeat() класса Mathf, данная функция помогает нам держать угол в интервале от 0 до 360 градусов, сам же угол мы вычисляем благодаря не хитрой формуле (w.rotation delta * w.col.rpm * 360.0f / 60.0f) где мы собственно к текущему углу поворота прибавляем дельту времени умноженную на число поворотов в минуту хранящееся в коллайдере и умноженную на 360/60.
  21. Применяем к текущим локальным углам поворота получившийся результат, в этом нам помогает функция Euler() класса Quaternion которая возвращает кватернион в Эйлеровых углах (как бы не было страшно слово кватернион я советую вам абстрагироваться и воспринимать его как структуру хранящую локальные и глобальные углы поворота, хотя означает оно совсем другое, здесь я не буду рассказывать что именно, так просто будет понятнее). Первым аргументом (тоесть угол вокруг оси X) мы передаем наш вычисленный угол «вращения» колеса, вторым аргументом (угол вокруг оси Y) мы передаем угол поворота коллайдера, и третьим аргументом (угол по оси Z, теперь понимаете почему в пункте 1 было важно повернуть колеса именно вокруг оси Z) предаем константу 90.0f.
Про WoT:  Как отправить другу танк в world of tanks

Components

  • Component adding, setup, & collapsing
    • Rigidbody
      • req’d for Physics system
      • constraints on axes (position/rotation)
      • Kinematics: turn on/off (receive physical forces/no forces)
    • Colliders (primitive)
      • reports physics events (collisions) to Rigidbody
      • Trigger vs. Physical events
      • proper sizing for model
    • Audio Source
      • Populating references using «Circle Select» in Inspector
      • versus dragging from Project Assets folders
      • Empty references to be populated at runtime via scripting
    • Scripts
      • are also Components (provided they extend from Monobehaviour)

Coroutines

  • A function that can suspend its execution (yield) until the given YieldInstruction finishes.
  • Normal coroutine updates are run after the Update() function returns.
  • Can be used as a way to spread an effect over a period time
  • It is also a useful optimization since it also allows you to determine at what rate any function gets called.

Return types & different uses of Coroutines:

Coroutines also admit a nice, slick, readable game loop:

Balance the audio in the game with a dynamic mix where sound effects duck the volume of the music.

  • A MainMix audio mixer default is created that outputs directly to the AudioListener.
  • Audio Mixer window is used to create 3 additional Audio Mixer Groups: Music, SFX, Driving.
    • Used to modify/mix the audio output before reaching the AudioListener.
  • Audio Source components (in our Prefabs) are setup to output their clips through the Audio Mixer Groups.
    • Default setup was to output clips straight to the AudioListener, without mixing.
  • A «Duck Volume» effect is then used to lower the bg music when sfx are playing.
    • The SFX group is setup to «Send Effect» its signal to the Music group’s «Duck Volume» effect.

General

  • Layers
    • For collider interaction isolation
    • Only collider owner needs to be on layer (parent GO, not children)
  • Duplication of GameObjects (Ctrl D or right-click context menu)
  • Project Settings
    • Input Manager: configuring input control & names

Index

  1. Scene Setup
  2. Tank Creation & Control
  3. Camera Control
  4. Tank Control
  5. Shell Creation
  6. Firing Shells
  7. Game Managers
  8. Audio Mixing

Basic scene setup

Lighting

  • Dedicated window for lighting options
  • Precomputed Real Time GI selected for use
    • Baking the lighting (a lengthy bg process)
    • Low resolution OK for lowpoly models
  • Ambient light source & color vs. skybox

How to add the tank artwork and components to let the player control the tank.

Particle systems

  • World vs. Local simulation space
  • Simulate over time vs. distance
  • Various options for behavior

Prefabs

  • Dragging configured scene objects from Hierarchy into Project Prefabs folder
  • Prefab options: Select, Revert, Apply
    • between prefab & scene object linkage
    • Applying changes from changed Hierarchy/Scene game objects to prefabs.
    • Blue vs gray text in Hierarchy: Saved vs. unsaved changes to prefabs from scene gameobjects
      • Also bold fields in Inspector are unsaved changes to prefabs

Scripting

  • Script editing, compiling, serializing workflow in Unity
  • Console window messages for debugging.
  • Public vs. Private fields/member variables
    • relation to Inspector view (public fields show up for editing & populating references)
    • UnassignedReferenceExceptions when forgetting to assign reference in Inspector panel.
  • Inspector serialized values override values assigned in the script’s public/serializable variables.
  • Dynamically referencing & manipulating Components at runtime
  • Variable naming conventions
    • e.g. m_Name denotes class member variable (scoping mnemonic)
  • TankMovement (inherits from MonoBehavior)
    • Script Component for Tank GameObject to control its movement
    • coding MonoBehaviour Event methods (Awake, Start, Update, etc)
  • Vector manipulation math for Rigidbody movement by applying forces via method calls
  • Quaternion representation in Unity for rotations of Transformations
    • Quaternion.Euler() method for easy Euler angle conversions

How to create a Camera rig which pans and zooms to keep all tanks on-screen at once.

Setup tank damage, update UI heal slider based on health value, tank deactivation on death

Create a ballistic shell for the tank & a radius for explosion forces

Tanks — unity learn

Social

Заключение


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

Ну а в следующей части урока я как и обещал расскажу о том как построить механику гусениц танка, также я расскажу о методах его моделирования в 3D редакторе и немного про экспорт модели из него и импорт в Unity ну и естественно я не буду вас заставлять моделировать то что вы видели в данном демо, а предоставлю уже готовую модель. Спасибо за внимание и удачи в изучении Unity3d.

>> Урок 2

Оцените статью
TankMod's
Добавить комментарий