Простая веревка / крюк-кошка [2D]

Будем делать нечто наподобие крюк-кошки для двухмерного платформера. Смысл в том, чтобы персонаж мог цепляться за вышестоящие платформы и таким образом, забираться выше и выше, быстро перецепляя крюк с одной платформы на другую. Управление очень простое, нужно лишь прицелиться и удерживать клавишу, герой будет подтягиваться, если отпустить клавишу, крюк отцепиться, и персонаж перейдет в обычный режим управления. Но кроме этого, мы сделаем возможность зацепиться и повиснуть на веревке, с точки зрения геймплея это может быть интересно, при прохождении препятствий. Плюс, когда персонаж весит на веревке, можно раскачиваться, как на качелях.


На родительский объект персонажа, там же где и Rigidbody2D, вешаем скрипт:

using System.Collections;
using UnityEngine;

[RequireComponent(typeof(DistanceJoint2D))]

public class GrapplingHook : MonoBehaviour {

	[SerializeField] private float swingForce = 50; // сила импульса для раскачивания
	[SerializeField] private LineRenderer ropeLine; // линия веревки
	[SerializeField] private LineRenderer targetLine; // линия указатель
	[SerializeField] private float minDistance = 2; // минимальная длинна веревки
	[SerializeField] private float step = .2f; // шаг сокращения длинны
	[SerializeField] [Range(0.1f, 1f)] private float offset = .5f; // насколько объект подпрыгивает, когда цепляет веревку
	[SerializeField] private LayerMask layerMask; // фильтр объектов, за которые можно цепляться
	private DistanceJoint2D joint;
	private Vector2 mousePosition;
	private RaycastHit2D hit;
	private float distance, last;
	public static bool isActive { get; private set; }
	private Rigidbody2D body;
	private bool swing;

	void Awake() 
	{
		body = GetComponent<Rigidbody2D>();
		joint = GetComponent<DistanceJoint2D>();
		joint.enabled = false;
		joint.enableCollision = true;
		joint.maxDistanceOnly = true;
		joint.autoConfigureDistance = false;
		joint.autoConfigureConnectedAnchor = false;
		joint.distance = minDistance;
		isActive = false;
	}

	void TargetLine()
	{
		RaycastHit2D target = Physics2D.Linecast(transform.position, mousePosition, layerMask);

		if(target.transform != null)
		{
			targetLine.positionCount = 2;
			targetLine.SetPosition(0, transform.position);
			targetLine.SetPosition(1, target.point);
		}
		else
		{
			targetLine.positionCount = 0;
		}
	}

	void RopeLine()
	{
		if(joint.enabled)
		{
			ropeLine.positionCount = 2;
			ropeLine.SetPosition(0, transform.position);
			ropeLine.SetPosition(1, joint.connectedAnchor);
		}
		else
		{
			ropeLine.positionCount = 0;
		}
	}

	bool CanUse(Vector3 position)
	{
		hit = Physics2D.Linecast(transform.position, position, layerMask);

		if(hit.transform != null)
		{
			distance = Vector2.Distance(transform.position, hit.point);
			return true;
		}

		return false;
	}

	void LateUpdate()
	{
		mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
		TargetLine();
		RopeLine();
		MainControl();
		SwingControl();
	}

	Vector3 GetDirection() // перпендикуляр к веревке
	{
		return Vector3.Cross(hit.point - body.position, Vector3.forward).normalized;
	}

	void SetActive()
	{
		joint.connectedAnchor = hit.point;
		joint.distance = distance - offset;
		joint.enabled = true;
		isActive = true;
	}

	void MainControl()
	{
		if(Input.GetMouseButtonDown(0) && CanUse(mousePosition))
		{
			SetActive();
			swing = false;
		}
		else if(Input.GetMouseButtonDown(1) && CanUse(mousePosition))
		{
			SetActive();
			swing = true;
			last = 0;
		}

		if(Input.GetMouseButton(0) && joint.enabled)
		{
			if(joint.distance > minDistance)
			{
				joint.distance -= step;
			}
		}
		else if(Input.GetMouseButtonUp(0) && joint.enabled)
		{
			isActive = false;
			joint.enabled = false;
		}
	}

	void SwingControl()
	{
		if(!swing) return;

		float h = Input.GetAxis("Horizontal");
		if(h > 0) h = 1; else if(h < 0) h = -1;

		if(last != h && h != 0)
		{
			body.AddForce(GetDirection() * h * swingForce, ForceMode2D.Impulse);
		}

		if(h != 0) last = h;

		if(Input.GetKeyDown(KeyCode.Space)) // отцепиться
		{
			swing = false;
			isActive = false;
			joint.enabled = false;
		}
	}
}

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

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

if(!GrapplingHook.isActive) // если крюк не активен
{
	// код управления
}

Или в самом начале функций LateUpdate, Update и FixedUpdate вписать:

if(GrapplingHook.isActive) return;

Скачать демо проект:

У вас нет доступа!
Тестировалось на: Unity 5.6.1

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

Офлайн
даёт ошибку в скрипте
unity.engine.lineRenderer не содержит определения для positionCount
У меня Unity 5.5.1 как мне исправить ошибку или на что заменить этот компонент?
Мне очень нужен этот скрипт!!!
Офлайн
Light 21 июня 2017
Давуд Ахмедов, обнови юнити.
Офлайн
Light,
я не могу
Пожалуйста дай вариант скрипта на unity 5.5.1
Офлайн
Light 21 июня 2017
Давуд Ахмедов, вместо positionCount, можно юзать SetVertexCount(число);
Читай документацию к своей версии. А еще лучше обновить юнити, нет смысла сидеть на устаревшей версии.
Офлайн
Light,
тоже не подходит лучше постараюсь юнити обновить
Офлайн
Liphanes 21 июня 2017
А под 3д переделать выйдет или же уже лучше с 0 писать?

Цитата: Liphanes
А под 3д переделать выйдет или же уже лучше с 0 писать?

И если с 0 нуля проще написать то пожалуй есть вопрос по поводу отрисовки веревки в 3д, а именно - как её реализовать?
Офлайн
Light 22 июня 2017
Liphanes, в 3д другие компоненты физики, поэтому писать надо под них с нуля, кроме того зависит от задачи, например, от первого лица, или тот же платформер.
Офлайн
Liphanes,
В Интернете есть уроки посмотри
Офлайн
FlayTheSky 23 июня 2017
В игре phantom 2040 на сеге, все на веревке завязано (там типа лазер) =) Хороший годный урок!
Офлайн
fupsitekku 7 июля 2017
Цитата: Давуд Ахмедов
даёт ошибку в скрипте
unity.engine.lineRenderer не содержит определения для positionCount
У меня Unity 5.5.1 как мне исправить ошибку или на что заменить этот компонент?
Мне очень нужен этот скрипт!!!

Давуд Ахмедов, Если ты такой нубло, то тебе нечего делать в геймдеве)
Офлайн
Stason4ikRU 7 июля 2017
У вас есть в планах урок : переключение между оружием, предметами ?
Офлайн
Light 7 июля 2017
Stason4ikRU, все предложения и пожелания, нужно делать более развернуто и конкретно, и желательно в личку.
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Дешевый хостинг
  • Яндекс.Метрика