Лестница в 2D платформере

Итак, попробуем научить нашего персонажа, лазать по лестницам, если говорить точнее, лестницам вертикального типа. Персонаж должен взаимодействовать с ней, чего легко добиться, используя триггер. Однако, тут возникают некоторые проблемы, нужно чтобы наш герой «знал» в какой ситуации можно полсти только вверх, а когда вниз, потому как что вверху что и внизу у нас поверхность пол/потолок, на который нужно взобраться. Кромке того, персонаж должен уметь цепляться на лету и спрыгивать с лестницы в любой момент. За основу мы возьмем ранее опубликованный скрипт для 2D платформера и модифицируем его под наши задачи.


Создание лестницы:

Создаем пустышку, а в нее вкладываем спрайты лестницы. После того как построен нужный размер, на родительский объект добавляем 2D бокс коллайдер в режиме триггера. Далее, переключаем слой родителя на Ignore Raycast, чтобы лучи не взаимодействовали с этим объектом. Теперь, нужно добавить еще пару дочерних пустышек, одну двигаем вверх лестницы, другую вниз, и называем их сосуществующие up, down. Однако верхнюю точку нужно установить чуть ниже поверхности, например:

Лестница в 2D платформере

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

На родителя лестницы вешаем простенький скрипт Ladder:

using UnityEngine;
using System.Collections;

public class Ladder : MonoBehaviour {

	public Transform up, down;
}

И указываем в нем ранее добавленные точки.

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

Теперь, скрипт для персонажа Character2D:

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Rigidbody2D))]

public class Character2D : MonoBehaviour {

	public float speed = 1.5f; // скорость движения
	public float acceleration = 100; // ускорение
	public float jumpForce = 5; // сила прыжка
	public float jumpDistance = 0.75f; // расстояние от центра объекта, до поверхности (определяется вручную в зависимости от размеров спрайта)
	public bool facingRight = true; // в какую сторону смотрит персонаж на старте?
	public KeyCode jumpButton = KeyCode.Space; // клавиша для прыжка
	public string ladderTag = "GameController"; // тег лестниц

	private int layerMask;
	private Rigidbody2D body;
	private Vector3 upLadder, downLadder, ladderPos, direction;
	private bool isLadder;
	
	void Start () 
	{
		body = GetComponent<Rigidbody2D>();
		body.freezeRotation = true;
		layerMask = 1 << gameObject.layer | 1 << 2;
		layerMask = ~layerMask;
	}

	void OnTriggerStay2D(Collider2D other)
	{
		if(other.tag == ladderTag && !isLadder)
		{
			Ladder ladder = other.GetComponent<Ladder>();
			upLadder = ladder.up.position;
			downLadder = ladder.down.position;
			ladderPos = other.transform.position;
			isLadder = true;
		}
	}

	void OnTriggerExit2D(Collider2D other)
	{
		if(other.tag == ladderTag)
		{
			isLadder = false;
			body.isKinematic = false;
		}
	}

	bool GetJump() // проверяем, есть ли коллайдер под ногами
	{
		bool result = false;

		RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector3.down, jumpDistance, layerMask);
		if(hit.collider)
		{
			result = true;
		}

		return result;
	}
	
	void FixedUpdate()
	{
		if(!body.isKinematic) body.AddForce(direction * body.mass * speed * acceleration);
		
		if(Mathf.Abs(body.velocity.x) > speed)
		{
			body.velocity = new Vector2(Mathf.Sign(body.velocity.x) * speed, body.velocity.y);
		}
	}
	
	void Flip() // отражение по горизонтали
	{
		facingRight = !facingRight;
		Vector3 theScale = transform.localScale;
		theScale.x *= -1;
		transform.localScale = theScale;
	}
	
	void Update () 
	{	
		Debug.DrawRay(transform.position, Vector3.down * jumpDistance, Color.red); // подсветка, для визуальной настройки jumpDistance

		float h = Input.GetAxis("Horizontal");
		float v = Input.GetAxis("Vertical");

		if(Input.GetKeyDown(jumpButton) && GetJump() || Input.GetKeyDown(jumpButton) && isLadder && v == 0)
		{
			body.isKinematic = false; 
			body.velocity = new Vector2(0, jumpForce); 
		}

		if(isLadder) LadderMode(v);
		
		direction = new Vector2(h, 0); 
		
		if(h > 0 && !facingRight) Flip(); else if(h < 0 && facingRight) Flip();
	}

	void LadderMode(float vertical)
	{
		if(transform.position.y < upLadder.y && vertical > 0)
		{
			body.isKinematic = true;
		}
		else if(transform.position.y > downLadder.y && vertical < 0 && transform.position.y > upLadder.y)
		{
			body.isKinematic = true;
		}
		else if(vertical < 0 && GetJump() && transform.position.y < upLadder.y)
		{
			body.isKinematic = false;
		}

		if(body.isKinematic)
		{
			transform.Translate(new Vector2(0, speed * vertical * Time.fixedDeltaTime)); // движение по лестнице
			float xPos = Mathf.Lerp(transform.position.x, ladderPos.x, 10 * Time.deltaTime);
			transform.position = new Vector2(xPos, transform.position.y); // плавное выравнивание по центру лестницы
		}
	}
}

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

Скачать пример:

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

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

Офлайн
chudaky 14 июня 2016
ето под какую версию ? просто у меня на 4.5.3 не хочет принемать скрипт для персонажа Character2D

да и сцену не грузит
Онлайн
Light 15 июня 2016
chudaky, всё представленное на сайте, под 5-тую версию.
Офлайн
chudaky 15 июня 2016
спасибо, на 5 версии все работает.
Офлайн
Как можно добавить сход с лестницы посередине не прыжком, а просто движением вбок? Добавление "float horizontal" в LadderMode и использование ее там приносит неадекватный результат.
Онлайн
Light 20 июня 2016
Ринальдо, ну например, можно так попробовать, заменить:

if(isLadder) LadderMode(v);

На:

if(isLadder) LadderMode(v, h);

Далее, заменить функцию LadderMode на:

void LadderMode(float vertical, float horizontal)
{
	if(transform.position.y < upLadder.y && vertical > 0)
	{
		body.isKinematic = true;
	}
	else if(transform.position.y > downLadder.y && vertical < 0 && transform.position.y > upLadder.y)
	{
		body.isKinematic = true;
	}
	else if(vertical < 0 && GetJump() && transform.position.y < upLadder.y)
	{
		body.isKinematic = false;
	}

	if(body.isKinematic && Mathf.Abs(horizontal) > 0)
	{
		transform.Translate(new Vector2(speed * horizontal * Time.fixedDeltaTime, 0));
	}
	else if(body.isKinematic && Mathf.Abs(vertical) > 0)
	{
		transform.Translate(new Vector2(0, speed * vertical * Time.fixedDeltaTime)); // движение по лестнице
		float xPos = Mathf.Lerp(transform.position.x, ladderPos.x, 10 * Time.deltaTime);
		transform.position = new Vector2(xPos, transform.position.y); // плавное выравнивание по центру лестницы
	}
}
Офлайн
Light,
Спасибо, точно!!!
А то я пытался добавить переменную horizontal в строчку с вертикальным движением по лестнице... Т_Т
Офлайн
Light,
Еще вопрос.. Почему движение по лестнице, как и прыжки, вынесено в метод Update(), а не оставлено вместе с общим движением в FixedUpdate()? Fixed же вроде принято использовать для физики, а лестницы тоже должны входить в это понятие.. При этом операций не так много, чтобы не успевать выполнять их в фиксированные промежутки FixedUpdate.
Также, почему для общего движения используются .AddForce и .velocity (задействуя массу, ускорение и прочее), если можно использовать .Translate, как вы показали в ответе в мой вопрос про лестницу? Только для создания более реалистичной физической модели объекта?
И еще..
В LadderMode(), в строчке:
"else if(transform.position.y > downLadder.y && vertical < 0 && transform.position.y > upLadder.y)"
зачем нужно условие "transform.position.y > downLadder.y"? Если мы хотим зайти на лестницу сверху, мы в любом случае будем выше "нижней точки", нет? И в таком случае, зачем вообще нужна нижняя точка?

Заранее благодарю за ответы.
Онлайн
Light 21 июня 2016
Цитата: Ринальдо
Почему движение по лестнице, как и прыжки, вынесено в метод Update()

Обычно там ловят нажатие клавиш и т.п.

Цитата: Ринальдо
а лестницы тоже должны входить в это понятие

Нет, это триггер.

Цитата: Ринальдо
если можно использовать .Translate

Тогда будет игнорироваться физика со стороны персонажа, чего не нужно.

Цитата: Ринальдо
зачем нужно условие "transform.position.y > downLadder.y"?

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

Вообще, я подумываю прокачать проект и сделать некоторые изменения + добавить лестницу по диагонали. Но пока не уверен нужно ли это...
Офлайн
Спасибо!
Цитата: Light
В следствии чего, персонаж остановится, когда дойдет до "земли", иначе он бы продолжил спуск.

Но ведь касание "земли" ловится и в третьем условии:
(vertical < 0 && GetJump() && transform.position.y < upLadder.y)
Просто когда я вообще убрал нижнюю точку (и условие, связанное с ним), физика движений не изменилась.

Еще вопрос.. В этом примере, когда персонаж оказывается над лестницей, у него выключается .isKinematic и он начинает стоять на земле, которая расположена за лестницей. А как можно реализовать вариант, где лестница - независимый объект, который располагается РЯДОМ с поверхностью, т.е. стоять требуется на ней самой? В данный момент персонаж просто проваливается сквозь лестницу..... Я пробовал добавить коллайдер к точке "up", на котором персонаж смог бы стоять, когда он оказывается сверху, но этот коллайдер мешает зайти на лестницу, если она короткая (в рост персонажа).
Онлайн
Light 21 июня 2016
Цитата: Ринальдо
но этот коллайдер мешает зайти на лестницу, если она короткая (в рост персонажа).

Активировать его только когда перс над лестницей, т.е. выше верхней точки.
Офлайн
Dinv 6 января 2017
Light,

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


Привет, очень нужно! В моем прототипе есть лестницы, обычные, как ступеньки в доме, по которым персонаж должен вроде как подниматься. Как в старых играх - ступеньки на заднем плане, но если нажать, к примеру стрелку вверх, игрок поднимется на этаж выше.

Поднимается он вполне успешно, если сделать треугольный коллайдер, но потом упирается в потолок головой, да и с физическим коллайдером лестницу на задний план не упрятать. Думал менять положение лестницы в плоскости z, но не очень представляю, как это сделать. Надеюсь тут мне помогут, спасибо.
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика