Ближний / контактный бой персонажа [2D]

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

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

Подготовка. Делаем подготовленный спрайт с анимацией, дочерним к пустому объекту, назовем его Player. Затем уже на этот пустой объект вешаем коллайдер и Rigidbody2D. То есть анимируем мы только дочерний объект, а управляем родительским. Суть в том, что каждый удар рукой, ногой, мечом и т.п. это определенное положение спрайта / модели. Переключаться в режим удара, например:

Ближний / контактный бой персонажа [2D]

И ставим в точку удара пустышку, делаем ее дочерней к Player, не к спрайту! Эта точка должна сохранять свое положение, вне зависимости от анимации. Называем точку, допустим, punch-1.

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


Или


После того, как радиус атаки будет определен, необходимо удалить коллайдер!

А в основном скрипте, где мы управляем движением и анимацией, добавляем необходимые переменные:

public Transform punch1;
public float punch1Radius;

То бишь, тут и точка и радиус ее атаки.

Так, когда все точки и радиусы будут установлены, продолжим.

Следующий шаг, на все вражеские юниты повесить скрипт EnemyHP:

using UnityEngine;
using System.Collections;

public class EnemyHP : MonoBehaviour {

	public float HP = 100;

}

Одноклеточный скрипт, тем не менее, очень важный.

Далее, в папку вашего проекта, где находятся все скрипт, добавляем еще Fight2D:

using UnityEngine;
using System.Collections;

public class Fight2D : MonoBehaviour {

	// функция возвращает ближайший объект из массива, относительно указанной позиции
	static GameObject NearTarget(Vector3 position, Collider2D[] array) 
	{
		Collider2D current = null;
		float dist = Mathf.Infinity;

		foreach(Collider2D coll in array)
		{
			float curDist = Vector3.Distance(position, coll.transform.position);

			if(curDist < dist)
			{
				current = coll;
				dist = curDist;
			}
		}

		return (current != null) ? current.gameObject : null;
	}

	// point - точка контакта
	// radius - радиус поражения
	// layerMask - номер слоя, с которым будет взаимодействие
	// damage - наносимый урон
	// allTargets - должны-ли получить урон все цели, попавшие в зону поражения
	public static void Action(Vector2 point, float radius, int layerMask, float damage, bool allTargets)
	{
		Collider2D[] colliders = Physics2D.OverlapCircleAll(point, radius, 1 << layerMask);

		if(!allTargets)
		{
			GameObject obj = NearTarget(point, colliders);
			if(obj != null && obj.GetComponent<EnemyHP>())
			{
				obj.GetComponent<EnemyHP>().HP -= damage;
			}
			return;
		}

		foreach(Collider2D hit in colliders) 
		{
			if(hit.GetComponent<EnemyHP>())
			{
				hit.GetComponent<EnemyHP>().HP -= damage;
			}
		}
	}
}

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

Пример использования:

На клавише "пробел" у нас удар мечом, делаем следующее:

if(Input.GetKeyDown(KeyCode.Space))
{
	Fight2D.Action(punch1.position, punch1Radius, 8, 12, false);
}

Здесь мы сообщаем функции, -точку контакта, -радиус, -номер слоя юнита, -урон по цели, и в конце обозначаем что урон получит только одна цель (ближайшая от точки). Соответственно, если поставить true, урон получат все юниты попавшие в радиус атаки, это удобно для супер приема например.

Все вражеские юниты должны иметь свой отдельный слой, например, под именем Enemy:


Отсюда же и берем номер слоя.

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

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

Офлайн
Enmaboya 21 марта 2017
За статью спасибо, но пока как-то сложновато воспринимать только текст.
Не подскажите где можно найти хороший видео урок создания атаки у 2д персонажей? На актуальную версию юнити. Или уже готовый проект, чтобы посмотреть чего и как
Офлайн
telonnikas 6 июня 2017
Есть одна проблема: если на объекте например коллайдер физический и коллайдер тригер, как избежать нанесения двойного урона?
Офлайн
Light 6 июня 2017
telonnikas, надо вешать EnemyHP на объект, где только один коллайдер, который и будет получать повреждения, например, это может быть дочерний триггер.
Офлайн
AlexMcKing 1 января 2018
А ты не знаешь как реализовать, чтобы перс (Спрайтовый) ходил не только в лево и право, но и в верх и низ? Наподобие как в черепашках ниндзя на сеги было!
Офлайн
Light 1 января 2018
AlexMcKing, это надо управление как для Top-Down Shooter https://null-code.ru/project/93-upravlenie-pod-platformer-i-top-down-shooter-2d.html
Офлайн
Rogwar 1 февраля 2018
что надо установить в public Transform punch1 ?
Офлайн
Light 1 февраля 2018
Rogwar, пустой трансформ, например, точка где кулак находится во время анимации удара, указываешь точку и радиус, который охватывает этот удар.
Офлайн
Rogwar 1 февраля 2018
Light,
крч его просто можно не трогать? Unity ругается просто, что punch1 над какое-то значение задать, да и объекты со скриптом EnemyHP и слоем Enemy он не дамажит
Офлайн
Light 1 февраля 2018
Rogwar, если не сделать punch1 то и работать ничего не будет.
Офлайн
Rogwar 1 февраля 2018
Light,
так я его сделал, Unity ток хочет чтоб я значения туда вписал, а я хз чё вписывать
Офлайн
Light 1 февраля 2018
Rogwar, это переменная типа Transform, значит туда надо назначить Transform. как я сказал пустой объект, точка удара, и обозначить радиус punch1Radius удара.
Офлайн
tripjunkie 7 мая 2018
код рабочий, но выбивает в консоли:

NullReferenceException: Object reference not set to an instance of an object
Fight2D.NearTarget (Vector3 position, UnityEngine.Collider2D[] array) (at Assets/Scripts/Fight2D.cs:24)
Fight2D.Action (Vector2 point, Single radius, Int32 layerMask, Single damage, Boolean allTargets) (at Assets/Scripts/Fight2D.cs:38)
Player.Update () (at Assets/Scripts/Player.cs:45)

конкретно ругается на эти строчки:

return current.gameObject;

GameObject obj = NearTarget(point, colliders);
Офлайн
Light 7 мая 2018
tripjunkie, стоит добавить проверку на наличие объекта в строке:
if(obj.GetComponent<EnemyHP>())
изменить на:
if(obj && obj.GetComponent<EnemyHP>())
Офлайн
tripjunkie 8 мая 2018
Light,
всё равно ругается на то же самое. ошибка происходит в момент удара "по воздуху", но не в момент удара по цели. мб можно проинициализировать current как-то по другому?...
Офлайн
Light 8 мая 2018
tripjunkie, забыл там про возврат: return current.gameObject;
Нужна проверка: return (current != null) ? current.gameObject : null;
И соответственно: if(obj != null && obj.GetComponent<EnemyHP>())
То есть, перед тем как что-то делать с объектом, надо проверить что он существует.
Офлайн
tripjunkie 9 мая 2018
Light,
спасибо! помогло!
Офлайн
mo1111man 9 июня 2018
Помогите, только начал изучать С#. Хочу реализовать, чтобы после удара объект (то есть вражина) удалялся. Пытался реализовать через функцию Destroy, но ничего не получилось. Адмен помоги!
Офлайн
Light 9 июня 2018
mo1111man, чтобы что-то делать с объектом, нужна проверка текущего НР. Для этого скрипт EnemyHP нужно изменить так:

using UnityEngine;
using System.Collections;

public class EnemyHP : MonoBehaviour
{
    public float HP = 100;

    public void AddDamage(float damage)
    {
        HP += damage;

        if(HP <= 0)
        {
            HP = 0;

            Destroy(gameObject); // удаляем объект
        }
    }
}
Затем, в скрипте Fight2D найти строки:

obj.GetComponent<EnemyHP>().HP -= damage;
И заменить их на:

obj.GetComponent<EnemyHP>().AddDamage(-damage);
Офлайн
mo1111man 9 июня 2018
Light,
Все объект уничтожается, но теперь удаление с первого "удара". И еще можно ли как-то урон ограничить?
Офлайн
Light 9 июня 2018
mo1111man, сделай НР больше или damage меньше.
Офлайн
mo1111man 10 июня 2018
Light,
Сделал, но проблема осталась. damagе так ограничить и не смог
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика