Вертикальные лестницы [2D платформер]

Улучшение предыдущего нашего проекта, на тему создание лестниц в двухмерном платформере. Основное отличие, это перенос функционала, в отдельные классы. Что в свою очередь, позволяет использовать различные скрипты управления для персонажа, без необходимости внесения дополнительного кода. Важное условие, чтобы управление персонажем осуществлялось через Rigidbody2D, именно этот компонент является базовым, при контроле персонажа, когда он перемещается по лестнице. Дополнительно, мы создадим небольшой инструмент, который на основе одного шаблона лестничного пролета, будет создавать лестницу со всеми необходимыми компонентами и настройками. Это значительно упростит сборку лестниц для игры.


Итак, для начала, нужно создать префаб лестничного пролета, просто перетаскиваем соответствующий спрайт на сцену, а потом в папку с префабами. Затем создаем пустой объект и вешаем на него скрипт:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(BoxCollider2D))]

public class Ladder : MonoBehaviour {

	[Header("Шаблон:")]
	[SerializeField] private SpriteRenderer section;
	private Vector3 position;
	private float offset;

	void OnTriggerEnter2D(Collider2D other)
	{
		if(string.Compare(other.tag, "Player") == 0)
		{
			LadderManager.SetLadderBounds(GetComponent<BoxCollider2D>().bounds);
		}
	}

	void OnTriggerExit2D(Collider2D other)
	{
		if(string.Compare(other.tag, "Player") == 0)
		{
			LadderManager.ResetStatus();
		}
	}

	SpriteRenderer GetLast()
	{
		Transform tr = (transform.childCount > 0) ? transform.GetChild(transform.childCount-1) : null;
		return (tr != null) ? tr.GetComponent<SpriteRenderer>() : null;
	}

	public void AddSection()
	{
		if(section == null) return;

		SpriteRenderer ren = GetLast();
		int id = transform.childCount + 1;

		if(ren != null)
		{
			position = ren.transform.position;
			offset = Vector3.Distance(ren.bounds.min, new Vector3(ren.bounds.min.x, ren.bounds.max.y, ren.bounds.min.z));
		}
		else
		{
			position = transform.position;
			offset = 0;
		}

		SpriteRenderer clone = Instantiate(section) as SpriteRenderer;
		clone.transform.SetParent(transform);
		clone.transform.name = "Section - " + id;
		clone.transform.position = position + Vector3.up * offset;

		CalculateTriggerSize(clone, id);
	}

	void CalculateTriggerSize(SpriteRenderer ren, int id)
	{
		if(ren == null)
		{
			ResetTriggerSize();
			return;
		}

		gameObject.layer = 2;
		BoxCollider2D box = GetComponent<BoxCollider2D>();
		box.isTrigger = true;
		float width = Vector3.Distance(ren.bounds.min, new Vector3(ren.bounds.max.x, ren.bounds.min.y, ren.bounds.min.z));
		float height = Height();
		float heightOffset = Vector3.Distance(ren.bounds.min, new Vector3(ren.bounds.min.x, ren.bounds.center.y, ren.bounds.min.z));
		box.size = new Vector2(width / 4, height);
		box.offset = (id > 1) ? new Vector2(0, height / 2 - heightOffset) : Vector2.zero;
	}

	float Height()
	{
		float value = 0;
		SpriteRenderer[] ren = GetComponentsInChildren<SpriteRenderer>();

		foreach(SpriteRenderer r in ren)
		{
			value += Vector3.Distance(r.bounds.min, new Vector3(r.bounds.min.x, r.bounds.max.y, r.bounds.min.z));
		}

		return value;
	}

	void ResetTriggerSize()
	{
		BoxCollider2D box = GetComponent<BoxCollider2D>();
		box.size = Vector2.one;
		box.offset = Vector2.zero;
	}

	public void RemoveSection()
	{
		SpriteRenderer last = GetLast();

		if(last != null)
		{
			#if UNITY_EDITOR
			DestroyImmediate(last.gameObject);
			#else
			Destroy(last.gameObject);
			#endif
		}

		CalculateTriggerSize(GetLast(), transform.childCount + 1);
	}

	public void ClearAll()
	{
		GameObject[] childs = new GameObject[transform.childCount];

		for(int i = 0; i < childs.Length; i++)
		{
			childs[i] = transform.GetChild(i).gameObject;
		}

		foreach(GameObject obj in childs)
		{
			#if UNITY_EDITOR
			DestroyImmediate(obj);
			#else
			Destroy(obj);
			#endif
		}

		ResetTriggerSize();
	}
}

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

Вертикальные лестницы [2D платформер]

Добавим кнопки в инспектор:

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

[CustomEditor(typeof(Ladder))]

public class LadderEditor : Editor {

	public override void OnInspectorGUI()
	{
		DrawDefaultInspector();
		Ladder e = (Ladder)target;
		GUILayout.Label("Управление секциями:", EditorStyles.boldLabel);
		GUILayout.BeginHorizontal();
		if(GUILayout.Button("Add Section")) e.AddSection();
		if(GUILayout.Button("Remove Section")) e.RemoveSection();
		if(GUILayout.Button("Clear All")) e.ClearAll();
		GUILayout.EndHorizontal();
	}
}
#endif

Кидаем этот скрипт в папку к остальным.



Теперь, подготовим персонажа.
Создадим дочерний пустой объект в нем и размещаем по центру модели, навезем этот объект center.

Важно! Управление персонажем должно быть через Rigidbody2D. У персонажа должен быть установлен стандартный тег 'Player' и слой, например, с таким же названием!

Далее, добавляем пустой объект на сцену и вешаем скрипт:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LadderManager : MonoBehaviour {

	[Header("Основные настройки:")]
	[SerializeField] private Rigidbody2D playerRigidbody; // компонент персонажа
	[SerializeField] private Transform playerCenterPoint; // дочерний пустой объект, который размещается по центру модели/спрайта персонажа
	[SerializeField] private float playerRaySize = 1f; // луч из центра и до точки касания "земли", настраивается визуально
	[Header("Управление персонажем:")]
	[SerializeField] private string verticalAxis = "Vertical";
	[SerializeField] private float speed = 1f;
	public static bool isLadder { get; private set; } // true - если персонаж на лестнице, можно использовать для переключения анимации
	public static bool isMove { get; private set; } // true - если персонаж движется, находясь на лестнице
	private static LadderManager _internal;
	private bool isTrigger;
	private Bounds ladderBounds;
	private int layerMask;

	void OnDrawGizmosSelected()
	{
		if(playerCenterPoint == null) return;
		Gizmos.color = Color.green;
		Gizmos.DrawRay(playerCenterPoint.position, Vector3.down * playerRaySize);
	}

	void Awake()
	{
		isMove = false;
		isLadder = false;
		layerMask = 1 << playerRigidbody.gameObject.layer | 1 << 2;
		layerMask = ~layerMask;
		_internal = this;
	}

	public static void SetLadderBounds(Bounds bounds)
	{
		_internal.SetLadderBounds_internal(bounds);
	}

	public static void ResetStatus()
	{
		_internal.ResetStatus_internal();
	}

	void SetLadderBounds_internal(Bounds bounds)
	{
		ladderBounds = bounds;
		isTrigger = true;
	}

	void ResetStatus_internal()
	{
		isTrigger = false;
		isMove = false;
	}

	bool IsGround()
	{
		RaycastHit2D hit = Physics2D.Raycast(playerCenterPoint.position, Vector3.down, playerRaySize, layerMask);
		if(hit.collider) return true;
		return false;
	}

	void MoveUp()
	{
		if(playerCenterPoint.position.y > ladderBounds.max.y + (playerRaySize/2))
		{
			UnLock();
			return;
		}

		if(!isLadder) Lock();
		playerRigidbody.transform.Translate(Vector3.up * speed * Time.deltaTime);
		isMove = true;
	}

	void MoveDown()
	{
		if(playerCenterPoint.position.y < ladderBounds.center.y && IsGround())
		{
			UnLock();
			return;
		}

		if(!isLadder) Lock();
		playerRigidbody.transform.Translate(Vector3.down * speed * Time.deltaTime);
		isMove = true;
	}

	void Lock()
	{
		isLadder = true;
		playerRigidbody.velocity = Vector2.zero;
		playerRigidbody.isKinematic = true;
		playerRigidbody.transform.position = new Vector3(ladderBounds.center.x, playerRigidbody.transform.position.y, playerRigidbody.transform.position.z);
	}

	void UnLock()
	{
		isMove = false;
		isLadder = false;
		playerRigidbody.isKinematic = false;
	}

	void Update()
	{
		if(!isTrigger) return;

		if(Input.GetAxis(verticalAxis) > 0) MoveUp();
		else if(Input.GetAxis(verticalAxis) < 0) MoveDown();
		else if(Input.GetAxis(verticalAxis) == 0) isMove = false;
	}
}

Это класс управления, когда персонаж находится на лестнице.

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

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

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

Офлайн
Здорово!
А как правильно реализовать анимацию карабкания по лестнице? Я пока что смог только через переключение Layer в Animator, т.е. во время isMove && isLadder. Есть ли другие методы?
Офлайн
Light 13 февраля 2017
Федор Николаев, в смысле правильно? Предусмотрены переменные isMove и isLadder для переключения анимации, как их использовать, каждый решает сам.
Офлайн
Sershum 15 февраля 2017
Федор Николаев,
Ну это же пример, в контроллере Вашего Плауера пропишите нужную Вам функцию

Light,
пропиши камменты не только к переменным))) Нутак для одареннb!х
Офлайн
Ринальдо 22 февраля 2017
Ооо, отличная реализаций той старой идеи! Респект автору!!
Офлайн
wurdalak 20 мая 2018
шикарный эффект получается, если стоя на последней ступеньке прыгнуть )))) а вообще пример хорош. спасибо.
Офлайн
dennyroot 3 октября 2018
у меня одного забагованно работает. Например получается бесконечный прыжок если залез на лесницу
Офлайн
l1pton 13 мая 2020
Можете для чайника объяснить:)
создаю спрайт с лестницей,кидаю на него rigidbody2d,кидаю спрайт на него.А с пустышкой что надо сделать?создать ее и поместить под ladder в иерархии?
и еще вопрос. кидаю скрипт на лестницу,пишет вот это.что подскажете?
The name 'LadderManager' does not exist in the current context
Офлайн
Light 15 мая 2020
Цитата: l1pton
The name 'LadderManager' does not exist in the current context

Скрипт LadderManager нужно добавить на сцену, на любой пустой объект или какой-то другой, который не будет уничтожен во время игры, допустим, на камеру.
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика