avatar

Искусственный интеллект автомобиля [Unity3d]

опубликовал в Unity3D / Программирование
Здравствуйте посетители GameSetup. В этом уроке я объясню как создать искусственный интеллект(ИИ) авто в Unity3d. Этот ИИ работает как зомби, т.е. тупо преследует цель.
Немного предыстории, кому не интересно пропустите =).

Когда я только начинал изучать Unity я хотел сделать что-то вроде дерби на машинах дабы поиграть с друзьями. Я начал делать мультиплеер, в итоге за неделю не без помощи был сделан кривой мультиплеер на сетевом движке Unity(всё знают что он ужасен), но не суть. Позже я захотел сделать ИИ но знаний тогда не хватало и я забил и начал делать «MX Ttral». Cпустя много времени я нашёл тот проект с машинками и подумал почему бы щас не сделать ботов. Я начал сутками зависать в google смотрел кучу туториалов по созданию ИИ, но для авто ничего не подходило. За тем я думал сделать через RayCast, но потом подумал что мне это не нужно совершенно для простейшего бота. Я начал разбираться с Quaternion, но у меня не получалось его вычислить относительно машины и я на него забил, после я подумал, почему бы мне не использовать угол цели относительно бота. И это оказалось то что нужно. Ну вот и конец этой скучной предыстории.

Вообщем, работает он достаточно просто.

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

В этом уроке я буду делать ИИ переделывая скрипт для авто на WheelCollider который ниже.
using UnityEngine;
using System.Collections;

public class ControlCarScript : MonoBehaviour {
	
	public WheelCollider WheelBackLeftCol;// колайдеры колёс
	public WheelCollider WheelBackRightCol;
	public WheelCollider WheelForwardLeftCol;
	public WheelCollider WheelForwardRightCol;
	
	public Transform WheelBackLeftTransform;// Объекты колёс
	public Transform WheelBackRightTransform;
	public Transform WheelForwardLeftTransform;
	public Transform WheelForwardRightTransform;
	
	public Transform CentrOfMass;// Центр тяжести авто
	
	public float maxAccel = 25;// Мощьность
	public float maxBrake = 50;// Сила торможения
	public float maxRotate = 30;// угол поворота колёс
	public float maxSpeed = 60;// Скорость
	
	private float StartRadiusBackLeft;// Запись радиуса всех колёс по отдельности
	private float StartRadiusBackRight;
	private float StartRadiusForwardLeft;
	private float StartRadiusForwardRight;
	
	private float StartSusDistBackLeft;// Запись высоты подвески всех колёс по отдельности
	private float StartSusDistBackRight;
	private float StartSusDistForwardLeft;
	private float StartSusDistForwardRight;
	
	private Vector3 wheelStartPosBackLeft;// Начальное положение колёс
	private Vector3 wheelStartPosBackRight;
	private Vector3 wheelStartPosForwardLeft;
	private Vector3 wheelStartPosForwardRight;
	
	private float WheelRotationBackLeft;
	private float WheelRotationBackRight;
	private float WheelRotationForwardLeft;
	private float WheelRotationForwardRight;
	
	void Start () {// Тут идёт запись данных колёс
		if(WheelBackLeftCol == null || WheelBackRightCol == null || WheelForwardLeftCol == null || WheelForwardRightCol == null){
			print("No Wheel Collider");// если нет какого-то из колайдеров
			return;
		}	
		if(WheelBackLeftTransform == null || WheelBackRightTransform == null || WheelForwardLeftTransform == null || WheelForwardRightTransform == null){
			print("No Transform");// Если нет мешей 
			return;
		}
		if(CentrOfMass == null){
			print("No Centr Of Mass");// Если нет мешей 
			return;
		}
		
		StartRadiusBackLeft = WheelBackLeftCol.radius;// Запись радиуса всех колёс по отдельности
		StartRadiusBackRight = WheelBackRightCol.radius;
		StartRadiusForwardLeft = WheelForwardLeftCol.radius;
		StartRadiusForwardRight = WheelForwardRightCol.radius;
		
		StartSusDistBackLeft = WheelBackLeftCol.suspensionDistance;// Запись высоты подвески всех колёс по отдельности
		StartSusDistBackRight = WheelBackRightCol.suspensionDistance;
		StartSusDistForwardLeft = WheelForwardLeftCol.suspensionDistance;
		StartSusDistForwardRight = WheelForwardRightCol.suspensionDistance;
		
		wheelStartPosBackLeft = WheelBackLeftTransform.localPosition;// Запись начального положение колёс
		wheelStartPosBackRight = WheelBackRightTransform.localPosition;
		wheelStartPosForwardLeft = WheelForwardLeftTransform.localPosition;
		wheelStartPosForwardRight = WheelForwardRightTransform.localPosition;
		
		rigidbody.centerOfMass = CentrOfMass.localPosition;// Задаём центр тяжести
	}
	
	void Update () {
		CarMove(Input.GetAxis("Vertical"),Input.GetAxis("Horizontal"));// Просчёт движения
		UpdateWheels();// Просчёт поведения колеса
	}
	
	private void CarMove(float accel,float rotare){// Вычисления управления	
		if(accel > 0){
			if(WheelBackLeftCol.rpm < 0) WheelBackLeftCol.brakeTorque = maxBrake;
			if(WheelBackRightCol.rpm < 0) WheelBackRightCol.brakeTorque = maxBrake;
			else{
				WheelBackLeftCol.brakeTorque = 0;
				WheelBackRightCol.brakeTorque = 0;
				if(WheelBackLeftCol.rpm > maxSpeed*10)WheelBackLeftCol.motorTorque = 0;
				else WheelBackLeftCol.motorTorque = accel*maxAccel;
			
				if(WheelBackRightCol.rpm > maxSpeed*10)WheelBackRightCol.motorTorque = 0;
				else WheelBackRightCol.motorTorque = accel*maxAccel;
			}
		}
		else if(accel < 0){
			if(WheelBackLeftCol.rpm > 0) WheelBackLeftCol.brakeTorque = maxBrake;
			if(WheelBackRightCol.rpm > 0) WheelBackRightCol.brakeTorque = maxBrake;
			else{
				WheelBackLeftCol.brakeTorque = 0;
				WheelBackRightCol.brakeTorque = 0;
				if(WheelBackLeftCol.rpm < -maxSpeed*10)WheelBackLeftCol.motorTorque = 0;
				else WheelBackLeftCol.motorTorque = accel*maxAccel;
			
				if(WheelBackRightCol.rpm < -maxSpeed*10)WheelBackRightCol.motorTorque = 0;
				else WheelBackRightCol.motorTorque = accel*maxAccel;
			}
		}
		
		WheelForwardLeftCol.steerAngle = rotare*maxRotate;
		WheelForwardRightCol.steerAngle = rotare*maxRotate;
	}
	
	private void UpdateWheels(){// Просчёт Поведения колеса
		WheelHit hit;
		Vector3 lpBl = WheelBackLeftTransform.localPosition;
		Vector3 lpBr = WheelBackRightTransform.localPosition;
		Vector3 lpFl = WheelForwardLeftTransform.localPosition;
		Vector3 lpFr = WheelForwardRightTransform.localPosition;
		
		if(WheelBackLeftCol.GetGroundHit(out hit))lpBl.y -= Vector3.Dot(WheelBackLeftTransform.position - hit.point, transform.up) - StartRadiusBackLeft;
		else lpBl.y = wheelStartPosBackLeft.y - StartSusDistBackLeft;
		if(WheelBackRightCol.GetGroundHit(out hit))lpBr.y -= Vector3.Dot(WheelBackRightTransform.position - hit.point, transform.up) - StartRadiusBackRight;
		else lpBr.y = wheelStartPosBackRight.y - StartSusDistBackRight;
		if(WheelForwardLeftCol.GetGroundHit(out hit))lpFl.y -= Vector3.Dot(WheelForwardLeftTransform.position - hit.point, transform.up) - StartRadiusForwardLeft;
		else lpFl.y = wheelStartPosForwardLeft.y - StartSusDistForwardLeft;
		if(WheelForwardRightCol.GetGroundHit(out hit))lpFr.y -= Vector3.Dot(WheelForwardRightTransform.position - hit.point, transform.up) - StartRadiusForwardRight;
		else lpFr.y = wheelStartPosForwardRight.y - StartSusDistForwardRight;
		
		WheelBackLeftTransform.localPosition = lpBl;
		WheelBackRightTransform.localPosition = lpBr;
		WheelForwardLeftTransform.localPosition = lpFl;
		WheelForwardRightTransform.localPosition = lpFr;
		
		WheelRotationBackLeft = Mathf.Repeat(WheelRotationBackLeft + Time.fixedDeltaTime * WheelBackLeftCol.rpm * 360.0f / 60.0f, 360.0f);
		WheelRotationBackRight = Mathf.Repeat(WheelRotationBackRight + Time.fixedDeltaTime * WheelBackRightCol.rpm * 360.0f / 60.0f, 360.0f);
		WheelRotationForwardLeft = Mathf.Repeat(WheelRotationForwardLeft + Time.fixedDeltaTime * WheelForwardLeftCol.rpm * 360.0f / 60.0f, 360.0f);
		WheelRotationForwardRight = Mathf.Repeat(WheelRotationForwardRight + Time.fixedDeltaTime * WheelForwardRightCol.rpm * 360.0f / 60.0f, 360.0f);
		
		WheelBackLeftTransform.localRotation = Quaternion.Euler(WheelRotationBackLeft, WheelBackLeftCol.steerAngle, 90.0f);
		WheelBackRightTransform.localRotation = Quaternion.Euler(WheelRotationBackRight, WheelBackRightCol.steerAngle, 90.0f);	
		WheelForwardLeftTransform.localRotation = Quaternion.Euler(WheelRotationForwardLeft, WheelForwardLeftCol.steerAngle, 90.0f);
		WheelForwardRightTransform.localRotation = Quaternion.Euler(WheelRotationForwardRight, WheelForwardRightCol.steerAngle, 90.0f);	
	}
}


Начнём с того что добавим несколько новых переменных.
public Transform Target;// Цель бота
	public float MaxDistance = 5;// Дистанция до цели при достижении которой он будет отдаляться от цели
	public float MaxAlienation = 4;// Дистанция отдаления
        public GameObject[] Targets;// Список целей бота 
	public float TimeTargetUpdate = 2;// Через какое время бот обновит цель
        private float TimerUpdateTarget;// Таймер проверки цели  
	private bool ModeAlienation;// если бот приблизился ближе чем MaxDistance, то true и он начинает отъезжать 

Вот и всё все нам нужные переменные у нас есть.
Теперь в void Update() убираем всё кроме UpdateWheels();
И перед теперь принимаемся писать поиск целей для бота

if(Target == null){// если переменная Target пуста
			Targets = GameObject.FindGameObjectsWithTag("Player");// 
			if(Targets == null && Targets.Length == 1)return;// если цель одна или их вовсе нет то останавливаем исполнение
			for(int T = 0; T < Targets.Length; T++){
				if(transform != Targets[T].transform){// Если цель не сам бот
					Target = Targets[T].transform;// Тогда задаём цель
				}
			}
		}else{// если переменная не пуста 
			TimerUpdateTarget += Time.deltaTime;// Заводим таймер
			if(TimerUpdateTarget >= TimeTargetUpdate){// если время обновлять цель, то
				Targets = GameObject.FindGameObjectsWithTag("Player");// обновляем список целей 
				for(int T = 0; T < Targets.Length; T++){
					if(Vector3.Distance(transform.position, Target.position) > Vector3.Distance(transform.position, Targets[T].transform.position) && transform != Targets[T].transform){// если до новой цели ближе чем до старой
						Target = Targets[T].transform;// задаём новую цель
					}
				}
				TimerUpdateTarget = 0;// обнуляем таймер
			}
		}


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

теперь в void Update () добавляем 4 переменные

                float accel = 0f;// эта переменная будет от 1 до -1 она для самого движения взад и вперёд
		float rotare = 0f;// эта переменная будет содержать угол поворота колёс с рамками maxRotate
		float angleAccel = 0f;// эта переменная будет содержать угол относительно синей плоскости(рис.)
		float angleRotate = 0f;// эта переменная будет содержать угол относительно красной плоскости(рис.)


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

if(Target != null){// Если есть цель	
			if(ModeAlienation == false){// Если не включена инверсия
				angleAccel = -(Vector3.Angle(Target.position - transform.position, transform.forward)-90);// Это угол между целью и ботом для определения угла относительно синей плоскости(рис.)
				accel = Mathf.Clamp(angleAccel,-1,1);// Задаём accel в промежутке от -1 до 1
				if(accel > 0){// если цель спереди
					angleRotate = -(Vector3.Angle(Target.position - transform.position, transform.right)-90);// Это угол между целью и ботом для определения угла относительно красной плоскости(рис.)
				}else{//ecли цель сзади
					angleRotate = Vector3.Angle(Target.position - transform.position, transform.right)-90;// Это угол между целью и ботом для определения угла относительно красной плоскости(рис.)
				}
                                rotare = Mathf.Clamp(angleRotate,-maxRotate,maxRotate);// Задаём rotare в промежутке от -maxRotate до maxRotate
				if(MaxDistance > Vector3.Distance(transform.position, Target.position)){// если MaxDistance больше дистанции до цели
					ModeAlienation = true;// включаем инверсию чтобы бот отъезжал
				}
			}else{// Если включена
				angleAccel = (Vector3.Angle(Target.position - transform.position, transform.forward)-90);// Это угол между целью и ботом для определения угла относительно синей плоскости(рис.) с инверсией
				accel = Mathf.Clamp(angleAccel,-1,1);// Задаём accel в промежутке от -1 до 1
				angleRotate = Vector3.Angle(Target.position - transform.position, transform.right)-90;// Это угол между целью и ботом для определения угла относительно красной плоскости(рис.) с инверсией
				rotare = Mathf.Clamp(angleRotate,-maxRotate,maxRotate);// Задаём rotare в промежутке от -maxRotate до maxRotate
				if(MaxDistance + MaxAlienation < Vector3.Distance(transform.position, Target.position)){// если бот достаточно отдалился
					ModeAlienation = false;// выключаем режим инверсии
				}
			}
		}
CarMove(accel,rotare);// Просчёт движения

Ну всё осталась самая малость. В private void CarMove, это
WheelForwardLeftCol.steerAngle = rotare*maxRotate;
		WheelForwardRightCol.steerAngle = rotare*maxRotate;

поменяйте на
WheelForwardLeftCol.steerAngle = rotare;
		WheelForwardRightCol.steerAngle = rotare;

Вот и всё, я надеюсь всё объяснил как надо и доходчиво, если имеются ошибки говорите. В боте имеются глюки и баги(возможно) но мне нет времени их искать и убирать, думаю сами поймёте как это сделать. Для ленивых выкладываю весь код. Для любопытных предлагаю скачать и наглядно увидеть как эти «умники» работают. И не забудьте поставить тег Player y на игрока и других ботов.

26 комментариев

avatar
Отличный скрипт для игр типа дерби (гонки на выживание)!
avatar
Так он для этого и задумывался  
Можно его и под гонки запилить, просто в Targets пихаешь пойнты куда ехать и чтобы он переключал их при достижении, ну и чтобы препятствия объезжал
avatar
А ну да, написано же в топике. Читаю между строк…
avatar
Это выражение имеет совсем другое значение
avatar
Спасибо за урок.
avatar
Можете пожалуйста подсказать. Скачал исходник, создал в unity c# файл (с именем ControlCarScriptNPC). Машины у меня нет, поэтому код наложил на куб, цель сделал на игрока. но куб не двигается. Прочитал весь топик, кроме кода, так как практически не разбираюсь в нем.
avatar
Скрипт двигает машину с помощью Wheel Collider, если нужно могу помочь
avatar
Да, если бы помогли было бы здорово.
avatar
Пиши или в лс здесь или в ВК
avatar
Уже разобрался, спасибо, все дело в Wheel Collider.
avatar
классный скрипт, спасибо)) вот только вопрос, может кто подскажет как сделать так чтоб бот(ы) не агрелся на тебя, а чтоб именно кто первей приедет?? или это другой скрипт нужен??? помогите плиз(
avatar
Это совершенно другой скрипт нужен
avatar
понятно, спасибо(
avatar
ublic float maxAccel = 25;// Мощьность

Доставило.)
avatar
1. «для ленивых» скрипт удален)
2. Есть пара вопросов по работе скрипта. Можно узнать поподробнее, желательно личной перепиской?)
avatar
А ты не будь ленивым :)
avatar
Я то всё написал по инструкции, я просто сказал что ссылка более недействительна)
Можно ли увидеть, как надо располагать колеса? Они у меня улетают в неизвестном направлении)
avatar
Я бы вообще не советовал использовать этот скрипт. Физика и поведение автомобиля гораздо интереснее :) Да и ИИ хороший делается не так
avatar
Вполне возможно, но опыта в юнити у меня едва ли неделя, и пока что я не представляю, как связать скрипты, скажем, Vcar и «целеустремленность» бота. А хочется сделать, чтобы боты… Ну хотя бы круг по чекпоинтам проехали)
avatar
Там всё не так тривиально. Бросай Юнити, если хочешь стать действительно стоящим програмером.
avatar
Надо же на чем то учиться) Да и под мобильные устройства больше пока ничего не видел.
avatar
Нельзя просто так взять и начать делать игры
avatar
Я немного знаю другие языки, немного есть общий навык программирования. Так что можно)
avatar
Посмотрел демо. Неплохо всё работает. Очень похоже на игру знакомых разработчиков: play.google.com/store/apps/details?id=com.SmartMove.MaximumDestruction
avatar
Скорее на CrashDay на ПК
avatar
Так то да. Похоже.
Чтобы оставить комментарий необходимо .