Пример скрипта бота для платформера [2D]

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

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

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

#if UNITY_EDITOR
using UnityEngine;
using System.Collections;
using UnityEditor;

[CustomEditor(typeof(EnemyAI))]

public class EnemyAIEditor : Editor {

	public GameObject waypoint; // префаб вейпоинта

	public override void OnInspectorGUI()
	{
		DrawDefaultInspector();
		EnemyAI t = (EnemyAI)target;
		if(GUILayout.Button("Generate Waypoints"))
		{
			t.GenerateWaypoints(waypoint);
		}
	}
}
#endif

Пример скрипта бота для платформера [2D]

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

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

using UnityEngine;
using System.Collections;

public class GameManager : MonoBehaviour {

	private static Transform _player;

	void Awake()
	{
		_player = GameObject.FindGameObjectWithTag("Player").transform;
	}

	public static Transform player
	{
		get{ return _player; }
	}
}

Главное не забыть персонажу поставить тег Player. Данный скрипт необходимо добавить на сцену.

Далее, "мозги" бота. За основу был взят наш скрипт управления 2D персонажем.

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Rigidbody2D))]

public class EnemyAI : MonoBehaviour {

	// точки, между которыми бот будет двигаться, в ожидании игрока
	public Transform waypointA;
	public Transform waypointB;

	public float speed = 1.5f; // скорость движения
	public float acceleration = 10; // ускорение
	public float searchDistance = 3; // с какого расстояния бот сможет "увидеть" игрока
	public float checkDistance = 1; // расстояние с которого бот проверку перед собой, на наличие обрыва
	public float resetDistance = 50; // макс дистанция, когда бот отслеживает позицию игрока
	public float height = 2; // маск высота, для падения (например, если надо спуститься по ступеням за игроком)

	// действие, когда позиция игрока не отслеживается (например, он ушел далеко вперед)
	// возврат в стартовую позицию и отключение, отключение в текущей позиции, уничтожение объекта
	public enum Mode {WaypointsAndDisabled = 0, Disabled = 1, Destroy = 2};
	public Mode action = Mode.WaypointsAndDisabled;

	public bool facingRight = true; // бот смотрит вправо?

	private int layerMask;
	private bool isTarget, isWait;
	private Rigidbody2D body;
	private Vector3 direction;
	private Vector3 startPosition;
	private float curDist;

	void Awake()
	{
		layerMask = 1 << gameObject.layer | 1 << 2;
		layerMask = ~layerMask;
		body = GetComponent<Rigidbody2D>();
		body.freezeRotation = true;
		startPosition = transform.position;
	}

	void OnCollisionEnter2D(Collision2D coll) 
	{
		if(coll.transform.tag == "Player") 
		{
			// физический контакт с целью
		}

	}

	Vector3 SetDirection(float xPos)
	{
		return new Vector3(xPos, transform.position.y, transform.position.z) - transform.position;
	}

	void Walk() // зацикленное движение от А к В и обратно
	{
		float a = Vector3.Distance(transform.position, waypointA.position);
		float b = Vector3.Distance(transform.position, waypointB.position);

		if(a < 1)
		{
			direction = SetDirection(waypointB.position.x);
		}
		else if(b < 1)
		{
			direction = SetDirection(waypointA.position.x);
		}
		else if(body.velocity.x == 0)
		{
			direction = SetDirection(waypointA.position.x);
		}
		else if(curDist > resetDistance)
		{
			Choose();
		}

		if(SearchPlayer()) isTarget = true;
	}

	void Follow() // преследование игрока
	{
		if(!CheckPath() && curDist > checkDistance || body.velocity.magnitude == 0 && curDist > searchDistance)
		{
			direction = Vector3.zero;
			body.velocity = Vector3.zero;
			isWait = true;
		} 
		else if(curDist > resetDistance)
		{
			Choose();
		}
		else
		{
			direction = SetDirection(GameManager.player.position.x);
		}
	}

	void Wait() // режим ожидания
	{
		if(curDist < searchDistance)
		{
			isWait = false;
		}
		else if(curDist > resetDistance)
		{
			Choose();
		}
	}

	void Choose() // финальное действие
	{
		switch(action)
		{
		case Mode.Disabled:
			isWait = false;
			isTarget = false;
			gameObject.SetActive(false);
			break;

		case Mode.WaypointsAndDisabled:
			transform.position = startPosition;
			isWait = false;
			isTarget = false;
			gameObject.SetActive(false);
			break;

		case Mode.Destroy:
			Destroy(gameObject);
			break;
		}
	}

	void LateUpdate()
	{
		if(!waypointA || !waypointB) return;

		curDist = Vector3.Distance(GameManager.player.position, transform.position);

		if(!isTarget)
		{
			Walk();
		}
		else if(!isWait && isTarget)
		{
			Follow();
		}
		else if(isWait && isTarget)
		{
			Wait();
		}
	
		if(body.velocity.x > 0 && !facingRight) Flip();
		else if(body.velocity.x < 0 && facingRight) Flip();
	}

	bool CheckPath() // проверка поверхности на пути следования
	{
		Vector3 pos = new Vector3(transform.position.x + checkDistance * Mathf.Sign(body.velocity.x), transform.position.y, transform.position.z);

		Debug.DrawRay(pos, Vector3.down * height, Color.red);

		RaycastHit2D hit = Physics2D.Raycast(pos, Vector3.down, Mathf.Infinity, layerMask);

		if(hit.collider && hit.distance < height)
		{
			return true;
		}

		return false;
	}

	bool SearchPlayer() // поиск игрока на пути следования
	{
		Vector3 dir = Vector3.right * searchDistance * Mathf.Sign(body.velocity.x);

		Debug.DrawRay(transform.position, dir, Color.blue);

		RaycastHit2D hit = Physics2D.Raycast(transform.position, dir, searchDistance, layerMask);

		if(hit.collider && hit.transform.tag == "Player")
		{
			return true;
		}

		return false;
	}

	void FixedUpdate()
	{
		body.AddForce(direction.normalized * body.mass * speed * acceleration);

		if(Mathf.Abs(body.velocity.x) > speed)
		{
			body.velocity = new Vector2(Mathf.Sign(body.velocity.x) * speed, body.velocity.y);
		}
	}

	void Flip() // отражение по горизонтали
	{
		facingRight = !facingRight;
		Vector3 theScale = transform.localScale;
		theScale.x *= -1;
		transform.localScale = theScale;
	}

	public void GenerateWaypoints(GameObject point) // вспомогательная функция для создания вейпоинтов
	{
		if(!waypointA && !waypointB)
		{
			GameObject obj = new GameObject(gameObject.name + "_Waypoints");
			obj.transform.position = transform.position;

			GameObject clone = Instantiate(point) as GameObject;
			clone.transform.parent = obj.transform;
			clone.transform.localPosition = new Vector2(3, 0);
			clone.name = "Point_A";
			waypointA = clone.transform;

			clone = Instantiate(point) as GameObject;
			clone.transform.parent = obj.transform;
			clone.transform.localPosition = new Vector2(-3, 0);
			clone.name = "Point_B";
			waypointB = clone.transform;
		}
	}
}

Поиск игрока или сканирование поверхности, делается с помощью рейкаста, одновременно возможно использование только одного луча. При настройке параметров нужно учитывать, что бот будет останавливаться перед обрывам, если он превышает допустимую высоту, однако при этом не должно возникать ситуации, когда игрок может перепрыгнуть яму, а бот тем временем просто упадет на нижнюю платформу. То есть высоту надо ставить такую, которая подходит под не высокие спуски/ступеньки и подобное. Еще важный момент, это чтобы для ботов и персонажа, был отдельный слой, иначе рейкаст будет работать не корректно. Когда в нужную позицию добавлен бот, просто жмем кнопочку Generate Waypoints и рядом с ним появятся вейпоинты, остается только растащить точки на нужное расстояние и всё.

Перед тестированием рекомендуется в плеере редактора включить Gizmos.

Скачать демо сцену:

Внимание! Посетители, находящиеся в группе Гости, не могут скачивать файлы.

Комментариев 6

Офлайн
ferter101 18 марта 2019
Вау спасибо, особенно за пояснения в скрипте. Даже нубику все понятно
Офлайн
Не понимаю вот эти строки
layerMask = 1 << gameObject.layer | 1 << 2;
layerMask = ~layerMask;
объясните пожалуйста ?)
Офлайн
Light 1 октября 2016
bogdanhik1, может потому что не видит его, где-то что-то пропущено.
Офлайн
bogdanhik1 29 сентября 2016
Здравствуйте пару вопросов. Не пойму по какой причине моб не агрится. (теги прописаны ошибок нет. Скрипты добавленны)
Офлайн
Light 17 июня 2016
chudaky, это зависит от того, как он должен его убивать.
Офлайн
chudaky 16 июня 2016
что нужно дописать что бы бот убивал героя ?
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика