Как разработать ещё один платформер с помощью Unity. Ещё один туториал, ч.2

В продолжении первой статьи (https://habr.com/ru/post/458846/), продолжаю разрабатывать платформер на основе статьи Паттерны дизайна уровней для 2D-игр (https://habr.com/ru/post/456152/ )

После выпуска первой статьи, однозначно было решено, что кнопочное управление, которое в ней описывалось — совершенно не удобно. Поэтому, Управление в игре было переделано на джойстик. Далее, к сожалению игра не прошла модерацию в плеймаркете. В прошедшую пятницу мне пришло уведомление о том, что проект отклонён по причине сбора метаданных. К слову сказать, мой первый платформер «Рыцарь Кадавар» так же в первый раз был отклонён, по причине якобы запроса на разрешение управления звонками и просмотра смс (что было полнейшей глупостью со стороны их бота. Игра не требовала никаких разрешений). Google тогда потребовал от меня письменное уведомление о том, зачем мне это нужно. Но, всё закончилось тем, что я исправил пару ошибок, которые заметил и отправил игру на повторную модерацию. Она была успешно добавлена в плеймаркет. Сейчас, я планирую сделать точно так же и с этой игрой.

Итак, как только была создана вторая по счёту сцена, автоматически был закрыт паттерн

Сцена

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

Бонус


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

Реализуем 2 вида бонусов для Лукаса:

  • Аптечка, которая будет выпадать при уничтожении монстров
  • Сундуки с мечами, которые Лукас сможет метать во врагов

Чтобы реализовать бонус с аптечкой, необходимо модифицировать префабы с монстрами. Конкретнее, указать место, откуда будут выпадать бонусы.

Затем, модифицируем скрипт Enemy.cs:

Заголовок спойлера

/* Определяем, выпадет ли бонус для игрока при смерти монстра*/ 	[SerializeField] 	private GameObject bonusPref; // Префаб бонуса 	[SerializeField] 	private Transform instBonus; // Откуда будет вылетать бонус 	[SerializeField] 	private int isBonus; // Реализуем с помощью рандома, будет ли выдан бонус 	/* Конец определения бонусов*/  public void ifDie() 	{ 		if (Damage(0) <= 0) 		{ 			isBonus = Random.Range(0,3); 			if (isBonus == 0) 			{ 				Instantiate(bonusPref, instBonus.position, instBonus.rotation); 			} 			Destroy(this.gameObject); 		} 	}

Вызываем функцию с возвращаемым типом Damage(0) и проверяем, возвращает ли Health 0. Если да, что вызываем генератор случайных чисел. Если генератор останавливается на выборе цифры 0, то выбрасываем игроку бонус и уничтожаем монстра.

Далее описываем, что можно сделать с этим бонусом. Для этого создадим его префаб с компонентами SpriteRenderer, BoxCollider2D и Rigidbody2D. Так же, создадим скрипт, который будет отвечать за то, что необходимо сделать, если яблоко столкнулось с игроком:

Заголовок спойлера

public void OnTriggerEnter2D(Collider2D collision)     { 		switch (collision.gameObject.tag)         {             case "Player":             { 				HeroScript.Health = 100; 				Destroy(this.gameObject); 			} 			break; 		} 	}

Посмотреть превью ролик

Далее, реализовываем бонус выпадения мечей. Его можно реализовать по тому же принципу, как в первой части реализовывалось выпадение брёвен. Интересная часть будет заключаться в том, что когда Лукас подберёт мечи, их необходимо будет бросать только в зоне видимости противников, а не то тогда, когда Лукас собирает брёвна. Ведь в том виде, в котором сейчас реализована кнопка Атаки\сбора предметов сделала бы именно так. Мечи выбрасывались в любой ситуации. Для этого модифицируем код скрипта атаки\сбора:

Заголовок спойлера

 /* Определяем поля для метания меча */     [SerializeField] 	private GameObject swordPref; // Префаб хранения меча 	[SerializeField] 	private Transform instSword; // Откуда выпускать префаб меча 	[SerializeField] 	private float swordSpeed; // Скорость полёта мечей 	private float attackInBoxX, attackInBoxY; // Для отрисовки куба /* Конец определения полей для метания мечей */  // ED значит EnemyDamage Collider2D[] ED = Physics2D.OverlapBoxAll(Hero.position, new Vector2(attackInBoxX, attackInBoxY), 12, lEnemy); 			{ 				if (ED.Length > 0) 				{ 					if (InventoryOnHero.swordCount  == 0) 					{ 						Debug.Log("Нечем стрелять"); 					} 				 					if (InventoryOnHero.swordCount > 0) 					{ 						instantiateSword(); 						InventoryOnHero.swordCount = InventoryOnHero.swordCount - 1; 					 					} 					else 					{ 						for (int i = 0; i < ED.Length; i++) 						{ 							ED[i].GetComponent<Enemy>().Damage(1); 						} 					} 				} 			}

Что означают данные строчки кода? Сперва, мы определяем массив коллайдеров и проверяем всё, что попало в наш куб

Collider2D[] ED = Physics2D.OverlapBoxAll(Hero.position, new Vector2(attackInBoxX, attackInBoxY), lEnemy);

Обратите внимание в передаваемые параметры, они сильно отличаются от тех, что используются для OvelapCircleAll. Ключевые параметры —

Vector2(attackInBoxX, attackInBoxY)

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

ED[i].GetComponent<Enemy>().Damage(1);

как если бы это был обычный удар. Если больше 0, то выпускаем меч и уменьшаем количество мечей на 1.

Далее, реализация метода instantiateSword();

Заголовок спойлера

 private void instantiateSword()     { 		GameObject newArrow = Instantiate(swordPref) as GameObject; 		newArrow.transform.position = instSword.transform.position; 		Rigidbody2D rb = newArrow.GetComponent<Rigidbody2D>(); 		if (GameObject.Find("Hero").GetComponent<HeroScript>().localScale.x > 0) 		{ 			rb.velocity = new Vector3(swordSpeed, 0, 0); 		} 		else 		{ 			rb.velocity = new Vector3(-swordSpeed, 0, 0); 			newArrow.transform.Rotate(0,0,-180); 		} 	}

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

(GameObject.Find("Hero").GetComponent<HeroScript>().localScale.x > 0)

Если Лукас смотрит налево, то

rb.velocity = new Vector3(-swordSpeed, 0, 0); newArrow.transform.Rotate(0,0,-180);

меч летит налево и так же вращаем его на 180 градусов.
Посмотреть превью ролик

Выполнив данное действие, мы автоматически закрыли ещё 1 паттерн:

Объект


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

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

В итоге, на данный момент осталось не реализовано 3 паттерна:

  • Недостижимая область
  • Механика
  • Босс

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

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

Босс! Так же останется вишенкой на торте. Я хочу сделать его максимально самостоятельным и с возможностью менять своё обличье.

Ссылка на мою витрину приложений в Google Play

Ссылка на Лукаса Джонса на sourceforge.net

Чувствуйте себя свободным и пишите комментарии на хабре или мне на почту worldofonehero@gmail.com.
Благодарю за прочтение, удачи.


ссылка на оригинал статьи https://habr.com/ru/post/460561/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *