Как создать 2D-шутер в Unity

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

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

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

Евгений Кучерявый

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


Что делает шутер шутером

Шутер (от англ. Shoot — стрелять) — это игра, в которой игрок должен стрелять из какого-либо оружия (пистолет, лук, лазерная винтовка), чтобы побеждать врагов. Добавления этой механики в игру достаточно, чтобы ее можно было назвать шутером.

Кроме стрельбы, также можно реализовать и другие возможности:

  • управление транспортом;
  • прокачку персонажа;
  • торговлю;
  • исследование мира;
  • крафтинг (создание игровых предметов).

При этом не важно, в каком сеттинге находится игра (фэнтези, фантастика, Средневековье) и сколько в ней измерений (два или три), — она все равно будет считаться шутером, если есть возможность стрелять. Поэтому в статье основное внимание уделено именно этой механике.

Стрельба

Есть два основных способа реализовать стрельбу:

  1. Префабы. Добавляет на карту снаряд, которому можно прописать поведение — направление полета, действия при попадании и так далее.
  2. Лучи (Raycast). Движок рисует невидимую линию от какой-нибудь точки в заданном направлении и возвращает данные о том, есть ли что-нибудь на пути.

Каждый стоит разобрать более подробно.

Начало выстрела

В первую очередь нужно подготовить всё, чтобы персонаж мог стрелять. Начать стоит с создания точки, откуда будет лететь снаряд или направляться луч. Для этого добавьте пустой объект с именем FirePoint и поместите его внутрь персонажа, расположив возле дула его оружия:

Затем нужно написать код, который позволит персонажу вращаться вместе с этой точкой. Создайте скрипт Controller.cs и прикрепите его к персонажу:

public class Controller : MonoBehaviour
{

	private Rigidbody2D rb;
	private float speed = 5f;

	private bool isRightSide = true;

	void Start()
	{
		rb = GetComponent<Rigidbody2D>(); //Получение компонентов
	}

	void Update()
	{
		//Движение 
		float moveX = Input.GetAxis("Horizontal");
		rb.MovePosition(rb.position + Vector2.right * moveX * speed * Time.deltaTime);

		if ((moveX > 0f && !isRightSide) || (moveX < 0f && isRightSide)) //Если персонаж начал двигаться в противоположную сторону
		{
			Spin();
		}

	}


	void Spin()
	{
		isRightSide = !isRightSide;

		transform.Rotate(0f, 180f, 0f); //Вращение персонажа по оси X на 180 градусов
	}
} 

Теперь персонаж сможет двигаться и вращаться вместе с объектом FirePoint:

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

public class Shooting : MonoBehaviour
{

	public GameObject bullet; //Снаряд
	public Transform firePoint; //Точка, с которой будут отправляться снаряды и лучи

	public LineRenderer lineRenderer; //Луч

	void Update()
	{
		if (Input.GetKeyDown(KeyCode.RightControl)) //Если игрок нажал на правый Ctrl
		{
			//Вызов метода стрельбы снарядами
			ShootBullet();

			//Вызов метода стрельбы лучами
			StartCoroutine(Shoot());
			//Выберите один из них
		}
	}
}

Теперь можно разобрать оба способа стрельбы.

Стрельба снарядами

Для начала нужно создать снаряд. Для этого перетащите на карту спрайт и назовите его Bullet:

Добавьте коллайдер с триггером и создайте скрипт Bullet.cs, в котором будут обрабатываться попадания (он будет рассмотрен чуть позже). Сохраните объект в качестве префаба, а потом перетащите его в компонент Shooting.cs. Туда же перетащите FirePoint:

Теперь нужно написать метод, который будет создавать (инстанцировать) новые снаряды на карте:

void ShootBullet()
{
	Instantiate(bullet, firePoint.position, firePoint.rotation);
}

Вот как это выглядит:

Пока снаряд остается на месте. Чтобы это исправить, нужно прописать в Bullet.CS этот код:

public class Bullet : MonoBehaviour
{

	private Rigidbody2D rb;

	private float speed = 15f;
	private int damage = 20;

	private int life = 0;

	private int lifeMax = 500;

	void Start()
	{
		rb = GetComponent<Rigidbody2D>();
		rb.velocity = transform.right * speed; //Изменение скорости
	}

	void Update()
	{
		life++;

		if (life >= lifeMax)
		{
			Explode(); //Если снаряд пролетел определенное расстояние и ни с чем не столкнулся, его нужно удалить, чтобы он не расходовал ресурсы
		}
	}

	void OnTriggerEnter2D(Collider2D hitInfo) //Метод, который срабатывает при попадании
	{
		Explode();
	}

	void Explode()
	{
		Destroy(gameObject); //Уничтожение объекта
	}
}

Теперь снаряд будет лететь и уничтожаться при попадании во что-то:

Стрельба лучами

В первую очередь нужно добавить персонажу объект Effect -> Line.

Укажите в X — 1, а в Z — 0, а затем поставьте галочку Use World Space. После этого можно изменить толщину линии, поменять цвет, закруглить края и так далее.

Прикрепите получившуюся линию к скрипту Shooting.cs и добавьте следующий метод:

IEnumerator Shoot() //Выполнение методов этого типа можно остановить на определенный срок
{
	RaycastHit2D hitInfo = Physics2D.Raycast(firePoint.position, firePoint.right); //Создание луча

	if (hitInfo) //Если луч во что-то попал
	{

		lineRenderer.SetPosition(0, firePoint.position); //Задание позиции начала линии
		lineRenderer.SetPosition(1, hitInfo.point); //Конец линии

	}
	else //Если попадания не было
	{
		lineRenderer.SetPosition(0, firePoint.position); 
		lineRenderer.SetPosition(1, firePoint.position + firePoint.right * 50);
	}

	lineRenderer.enabled = true; //Включение отображения линии

	yield return 0; //Ждать один кадр

	lineRenderer.enabled = false; //Отключить отображение линии
}

Вот как это выглядит:

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

Получение повреждений

За очки жизни будет отвечать скрипт Health.cs — его нужно добавить всем объектам, которые должны получать повреждения при попадании.

public class Health : MonoBehaviour
{

	public int hp = 100;
	public int hpMax = 100;

	void Update()
	{

		if (hp > hpMax)
		{
			hp = hpMax;
		}

		if (hp <= 0)
		{
			Die();
		}
	}

	public void Hit(int damage)
	{
		hp -= damage;
	}

	void Die()
	{
		Destroy(gameObject);
	}
}

Теперь нужно изменить код попадания внутри класса Bullet.cs:

void OnTriggerEnter2D(Collider2D hitInfo)
{
	Health health = hitInfo.GetComponent<Health>();
	if (health)
	{
		health.Hit(damage);
	}

	Explode();

}

Обновленный метод проверяет, есть ли у объекта, в который попал снаряд, компонент Health. Если он существует, то вызывается метод Hit(), который отнимает здоровье.

Примерно такой же код можно добавить в метод запуска луча.

NPC для 2D-шутера

Чтобы игра не превратилась в стрельбу по неподвижным мишеням, нужно написать скрипт поведения объектов — NPC.cs:

public class NPC : MonoBehaviour
{

	public GameObject bullet;
	public Transform firePoint;

	private Rigidbody2D rb;
	private Animator animator;
	private float speed = 5f;

	private bool isRightSide = true;


	private int timer = 0;
	private int timerMax = 50;
	private int action = 0;

	private int reload = 0;
	private int reloadMax = 5;

	void Start()
	{
		rb = GetComponent<Rigidbody2D>();
		animator = GetComponent<Animator>();
	}

	void Update()
	{
		timer++;

		if (timer >= timerMax)
		{
			timer = 0;
			action++;
		}

		float moveX = 0f;

		switch (action)
		{
			case 0:
				moveX = 1f;
				break;
			case 1:
				moveX = 0f;
				break;
			case 2:
				moveX = -1f;
				break;
			case 3:
				moveX = 0f;
				break;
			default:
				action = 0;
				break;
		}
		
		rb.MovePosition(rb.position + Vector2.right * moveX * speed * Time.deltaTime);

		if ((moveX > 0f && !isRightSide) || (moveX < 0f && isRightSide))
		{
			Spin(moveX);
		}



		//Shooting

		if (reload >= 1)
		{
			reload--;
		}

		RaycastHit2D hitInfo = Physics2D.Raycast(firePoint.position, firePoint.right);

		if (hitInfo)
		{
			if (hitInfo.transform.tag == "Player") //Если объекту присвоен тег Player
			{
				Shoot();
			}
		}

	}

	void Spin(float moveX)
	{
		isRightSide = !isRightSide;

		transform.Rotate(0f, 180f, 0f);
	}

	void Shoot()
	{
		if (reload <= 0)
		{
			Instantiate(bullet, firePoint.position, firePoint.rotation);
			reload = reloadMax;
		}
	}
}

Так создается примитивная логика поведения персонажа. Он может двигаться или стоять на месте, а если заметит героя (это проверяется с помощью Raycast), то начнет стрелять.

Заключение

Это довольно простой шутер, но полученных знаний должно хватить, чтобы разработать что-то более сложное и интересное. Если вы хотите глубже погрузиться в тему разработки игр на Unity, читайте статьи в нашем блоге и записывайтесь на курс  «Профессия разработчик игр».

Курс

Профессия разработчик игр


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

Хочешь получать крутые статьи по программированию?
Подпишись на рассылку Skillbox
Посмотреть