Гейм-девелопер
2,4
рейтинг
13 февраля 2014 в 01:15

Разработка → Основы создания 2D персонажа в Unity 3D 4.3. Часть 3: прыжки (и падения) tutorial

Часть 1: заготовка персонажа и анимация покоя
Часть 2: бегущий персонаж
Часть 3: прыжки (и падения)

Всем привет. Продолжаем серию уроков по созданию 2D персонажа. В предыдущих двух частях мы сделали персонажа, который может находиться в двух состояниях: покой и бег вправо-влево. Возможно, для очень простой игры этого будет достаточно. Но мы не будем останавливаться на достигнутом и сегодня научим нашего персонажа прыгать.

Наша цель звучит просто, но в ее реализации будут некоторые тонкости. Во-первых, нам надо как-то определить, что персонаж находится в состоянии прыжка. Это будет немного сложнее, чем определение состояния бега. Во-вторых, нужно прикрутить соответствующую анимацию. Здесь мы могли бы поступить аналогично анимациям покоя и бега — циклично воспроизводить соответствующие кадры анимации, пока персонаж находится в состоянии прыжка. Но мы реализуем более гибкий вариант. Состояние прыжка на самом деле состоит из двух состояний: взлет и свободное падение. Кроме того, бывают частные случаи — только взлет и только падение. Если мы прикрутим одну общую анимацию для этих двух состояний (например, поджатые ноги и развевающийся на ветру плащ) — это может выглядеть нормально, но не совсем реалистично. В реальности, когда человек прыгает, положение его тела, рук, ног отличается при взлете и при падении. Мы создадим такую анимацию, которая будет учитывать скорость взлета/падения и в зависимости от нее переключать соответствующий кадр анимации.

Загрузим наш проект и сцену. В папке Assets Sprites у нас остался последний неиспользованный спрайт Jump. Проделаем с ним уже знакомую операцию по нарезке спрайта на коллекцию изображений. Затем в окне Hierarchy выберем Character и перейдем в окно Animation. Для прыжка нам понадобиться несколько файлов анимаций, а точнее — семь. Это равно числу кадров в спрайте Jump. Давайте создадим эти анимации, называя их Jump1, Jump2 и т.д.



Теперь добавим в каждую анимацию по одному изображению из спрайта Jump, по порядку: спрайт Jump_0 в анимацию Jump1, спрайт Jump_1 в анимацию Jump2



В окне Animator (не Animation!) у нас автоматически создались элементы для новых анимаций, но они нам теперь не понадобятся. Выделим их и удалим клавишей Delete (анимации также удалятся из соответствующего списка в окне Animation, но чуть позже мы их вернем). Создадим в Animator'е два новых параметра: Ground с типом Bool и vSpeed с типом Float. Первый будет обозначать, находится ли персонаж на земле или в прыжке, а второй будет хранить текущее значение скорости персонажа по оси Y, то есть скорость взлета/падения. В зависимости от нее мы будем применять соответствующую анимацию из наших семи анимаций прыжка.



Теперь кликнем правой кнопкой по любому свободному месту в окне Animator и выберем Create StateFrom New Blend Tree. В окне Inspector переименуем созданный элемент как Jump.



Создадим два перехода между анимациями: Any State -> Jump и Jump -> Idle. То есть, из любого состояния мы можем перейти в состояние прыжка, а из состояния прыжка перейти в состояние покоя. Напоминаю, как делаются переходы: клик правой кнопкой по первой анимации, Make Transition, клик левой кнопкой по второй анимации. Для первого перехода зададим условие Ground false, для второго Ground true.



Теперь кликнем дважды по Jump. Откроется элемент Blend Tree. В него мы добавим наши семь анимаций прыжка, а переключение между ними будут происходить в зависимости от значения параметра vSpeed. По умолчанию в Blend Tree сейчас установлен параметр Speed — поменяем его на vSpeed в окне Inspector.



В этом же окне нажмем на плюсик и выберем Add Motion Field. Это нужно проделать семь раз. Создадутся семь полей для наших семи анимаций. Снимем флаг Automate Thresholds, чтобы можно было вручную устанавливать значения параметра vSpeed, при достижении которых будет производиться смена анимации в Blend Tree. Перетащим в каждое поле по анимации и зададим значения vSpeed. Вот что должно получиться (кстати, после этих действий удаленные из списка в окне Animation анимации Jump1-Jump6 вновь появятся):



С анимацией пока закончим. Теперь нам надо научится определять, когда персонаж находится на земле, а когда — в воздухе, то есть в прыжке. Перейдем в окно Scene. Создадим пустой игровой объект, назовем его GroundCheck. Выделяем его в окне Hierarchy и перетаскиваем мышью на Character (все в том же окне Hierarchy!). Теперь объект GroundCheck будет дочерним по отношению к Character и будет перемещаться вместе с ним, а объект Character приобретет соответствующую стрелку, скрывающую дочерние объекты. Кликнем по стрелке и вновь выберем объект GroundCheck. Назначим ему иконку, чтобы видеть объект на сцене. На скриншоте я указал, как это сделать:



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



Теперь нам надо определить, а что же является землей? Очевидно, это наша платформа, но игра об этом пока не знает. Кстати, давайте создадим копию платформы для тестирования прыжка (лучшим способ будет создание префаба на основе платформы, но сейчас давайте просто выделим Platform и нажмем Ctrl+D). Разместим вторую платформу правее и выше первой.



Выделим любую из платформ и обратим внимание на верхнюю часть окна Inspector. Там есть поле Layer со значение Default. Оно обозначает принадлежность объекта к тому или иному слою. Кликнем на Default, выберем Add Layer, зададим в поле User Layer 8 имя Ground. Снова выделим платформу, и вместо слоя Default установим слой Ground. Для второй платформы тоже установим слой Ground.



Таким образом мы определили, что наши платформы будут землей. Объект GroundCheck будет проверяться на предмет пересечения с объектами слоя Ground, т.е. с нашими платформами. Если будет обнаружено пересечение — персонаж находится на земле. Делаться это будет в скрипте CharacterControlleScript, созданном в предыдущей части. Давайте откроем его на редактирование.

В начало скрипта добавим новые переменные в добавок к уже существующим:
	//находится ли персонаж на земле или в прыжке?
	private bool isGrounded = false;
	//ссылка на компонент Transform объекта
	//для определения соприкосновения с землей
	public Transform groundCheck;
	//радиус определения соприкосновения с землей
	private float groundRadius = 0.2f;
	//ссылка на слой, представляющий землю
	public LayerMask whatIsGround;


А в начало метода FixedUpdate следующие строки:
		//определяем, на земле ли персонаж
		isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundRadius, whatIsGround); 
		//устанавливаем соответствующую переменную в аниматоре
		anim.SetBool ("Ground", isGrounded);
		//устанавливаем в аниматоре значение скорости взлета/падения
		anim.SetFloat ("vSpeed", rigidbody2D.velocity.y);
        //если персонаж в прыжке - выход из метода, чтобы не выполнялись действия, связанные с бегом
        if (!isGrounded)
            return;


Теперь добавим в скрипт метод Update, в котором будем обрабатывать нажатие клавиши прыжка. Мы будем делать это в методе Update для большей точности управления — этот метод вызывается каждый фрейм игры, в отличии от FixedUpdate, который вызывается через одинаковое определенное время и обычно, при хорошем FPS, вызовы происходят реже вызовов Update.
	private void Update()
	{
		//если персонаж на земле и нажат пробел...
		if (isGrounded && Input.GetKeyDown (KeyCode.Space)) 
		{
			//устанавливаем в аниматоре переменную в false
			anim.SetBool("Ground", false);
			//прикладываем силу вверх, чтобы персонаж подпрыгнул
            rigidbody2D.AddForce(new Vector2(0, 600));				
		}
	}


Итого, полностью скрипт будет выглядеть так:
CharacterControllerScript
using UnityEngine;
using System.Collections;

public class CharacterControllerScript : MonoBehaviour
{
    //переменная для установки макс. скорости персонажа
    public float maxSpeed = 10f; 
    //переменная для определения направления персонажа вправо/влево
    private bool isFacingRight = true;
    //ссылка на компонент анимаций
    private Animator anim;
	//находится ли персонаж на земле или в прыжке?
	private bool isGrounded = false;
	//ссылка на компонент Transform объекта
	//для определения соприкосновения с землей
	public Transform groundCheck;
	//радиус определения соприкосновения с землей
	private float groundRadius = 0.2f;
	//ссылка на слой, представляющий землю
	public LayerMask whatIsGround;

    /// <summary>
    /// Начальная инициализация
    /// </summary>
	private void Start()
    {
        anim = GetComponent<Animator>();
    }
	
    /// <summary>
    /// Выполняем действия в методе FixedUpdate, т. к. в компоненте Animator персонажа
    /// выставлено значение Animate Physics = true и анимация синхронизируется с расчетами физики
    /// </summary>
	private void FixedUpdate()
    {
		//определяем, на земле ли персонаж
		isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundRadius, whatIsGround); 
		//устанавливаем соответствующую переменную в аниматоре
		anim.SetBool ("Ground", isGrounded);
		//устанавливаем в аниматоре значение скорости взлета/падения
		anim.SetFloat ("vSpeed", rigidbody2D.velocity.y);
        //если персонаж в прыжке - выход из метода, чтобы не выполнялись действия, связанные с бегом
        if (!isGrounded)
            return;
        //используем Input.GetAxis для оси Х. метод возвращает значение оси в пределах от -1 до 1.
        //при стандартных настройках проекта 
        //-1 возвращается при нажатии на клавиатуре стрелки влево (или клавиши А),
        //1 возвращается при нажатии на клавиатуре стрелки вправо (или клавиши D)
        float move = Input.GetAxis("Horizontal");

        //в компоненте анимаций изменяем значение параметра Speed на значение оси Х.
        //приэтом нам нужен модуль значения
        anim.SetFloat("Speed", Mathf.Abs(move));

        //обращаемся к компоненту персонажа RigidBody2D. задаем ему скорость по оси Х, 
        //равную значению оси Х умноженное на значение макс. скорости
        rigidbody2D.velocity = new Vector2(move * maxSpeed, rigidbody2D.velocity.y);

        //если нажали клавишу для перемещения вправо, а персонаж направлен влево
        if(move > 0 && !isFacingRight)
            //отражаем персонажа вправо
            Flip();
        //обратная ситуация. отражаем персонажа влево
        else if (move < 0 && isFacingRight)
            Flip();
    }

	private void Update()
	{
		//если персонаж на земле и нажат пробел...
		if (isGrounded && Input.GetKeyDown (KeyCode.Space)) 
		{
			//устанавливаем в аниматоре переменную в false
			anim.SetBool("Ground", false);
			//прикладываем силу вверх, чтобы персонаж подпрыгнул
            rigidbody2D.AddForce(new Vector2(0, 600));				
		}
	}

    /// <summary>
    /// Метод для смены направления движения персонажа и его зеркального отражения
    /// </summary>
    private void Flip()
    {
        //меняем направление движения персонажа
        isFacingRight = !isFacingRight;
        //получаем размеры персонажа
        Vector3 theScale = transform.localScale;
        //зеркально отражаем персонажа по оси Х
        theScale.x *= -1;
        //задаем новый размер персонажа, равный старому, но зеркально отраженный
        transform.localScale = theScale;
    }
}



Сохраняем скрипт, возвращаемся в Unity. В Hierarchy выделяем Character. Перетаскиваем объект GroundCheck в поле Ground Check скрипта CharacterControllerScript, а в поле What Is Ground устанавливаем Ground.



Итак, в нашем скрипте, в методе FixedUpdate проверяется пересечение объекта GroundCheck с объектами, принадлежащими слою Ground. Это достигается при помощи метода Physics2D.OverlapCircle. В аргументах этого метода также задан радиус определения пересечения. Затем, в переменную аниматора Ground устанавливается результат определения нахождения на земле, а в переменную vSpeed — текущая скорость персонажа по оси Y. В зависимости от этой скорости применяется та или иная анимация прыжка из семи анимаций в Blend Tree. Сам прыжок задается в методе Update при нажатии на пробел, путем придания вертикальной силы компоненту Rigidbody2D.
Прежде чем запускать игру давайте немного изменим значение гравитации, а то скорость прыжков получится медленной, как на Луне. Заходим в меню Edit Project SettingsPhysics 2D. В окне Inspector устанавливаем значение Gravity Y = -30. Запускаем игру! Если все сделано правильно, увидим примерно следующее:



Капитан Коготь умеет бегать и прыгать. При прыжке, в зависимости от состояния (взлет/падение) и от скорости применяется соответствующая анимация. На видео хорошо заметно, что при прыжке в окне Animator происходят переключения между анимациями в элементе Blend Tree. При этом, если персонаж просто падает с платформы (т.е. в прыжке нету фазы взлета, пробел не нажимался) — работают только те анимации, для переключения которых заданы отрицательные значения скорости, что делает поведение персонажа более реалистичным. Конечно, для лучшего эффекта необходимо больше анимаций и более тонкая настройка значений скорости.

Что ж, на этом все. Основа для 2D персонажа создана. Какие к ней прикрутить дополнительные возможности — зависит уже от конкретной игры. Спасибо за внимание!
Вячеслав Ильин @Charoplet
карма
16,0
рейтинг 2,4
Гейм-девелопер
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (16)

  • –2
    Спасибо за статью, жду продолжения.
  • +1
    Некисло так посты про Юньку попёрли! Чуть ли не через день новый. )

    Еще с 2d фичами не разбирался, но курс неплохой получается.
  • 0
    Спасибо Вам за труд.
  • 0
    Уже третья часть, а вы всё не хотите собрать проект под web-плеер. Ну дайте уже поиграться с капитаном Когтем, пожалуйста.
    • 0
      В такую «игру» надоест играть через минуту. Лучше уж оригинал установить) В принципе, могу собрать под веб, только надо еще этому научиться…
  • 0
    Смотрю на уроки и становится одновременно и познавательно и обидно.
    Изучаю Unity и делаю всё гораздо грубее :(
    Например с тем же прыжком я кидал рэйкастом вниз на расстояние чуть ниже спрайта. А тут вот как удобнее и проще можно. Спасибо!
    А с направление лево-право вообще 2 набора спрайтов делал X_x

    А ещё хочется работать unity разработчиком. Чувствую очень долго я буду учиться.
    • +1
      Да, программисту нужно постоянно учиться) И в написании своих велосипедов нет чего-то ужасного — это приучает самостоятельно решать поставленные задачи, а не просто копипастить готовое решение. Продолжайте учиться — и проекты будут получаться все лучше.
    • 0
      Ну я бы назвал озвученное в статье решение для прыжка особо хорошим для реального случая. Рейкаст гораздо гибче и надежнее как минимум потому, что можно получить точную позицию, а не просто факт того, что-то в определенном радиусе что-то есть.
      • 0
        *не назвал
  • 0
    Unity не интересен, но листаю ваши уроки в самый низ ради видео с результатом. Уж очень здорово у вас получается :)
    • 0
      Спасибо, тем, у кого научился)
  • 0
    Ещё интересно было бы почитать как создавать таких вот персонажей, в какой программе дорисовывать им движения для спрайтов
    • 0
      Это уже к Unity прямого отношения не имеет :)
      Я вообще клепаю спрайты в Photoshop (подойдёт любой его аналог, кроме стандартного Paint, очевидно) с компьютера и у меня ещё стоит IsoPix на планшете.
  • 0
    В окне Animator (не Animation!) у нас автоматически создались элементы для новых анимаций, но они нам теперь не понадобятся. Выделим их и удалим клавишей Delete.

    При удалении в Animator, в окне Animation они так же автоматом удаляются
    • 0
      Да, но после добавления анимаций в Blend Tree они снова отобразятся в окне Animation. Я добавил соответствующие пояснения в статью.
  • 0
    Я добавил анимацию на объект, и потом дублировал его несколько раз (объект — бонусы, которые можно собирать). Но вот в чём проблема. Если у одного из объектов срабатывает переход на новое состояние анимации, то у всех объектов начинает проигрываться та же анимация. А хотелось бы, чтобы анимация начиналась только у одного объекта. Как это можно было бы сделать?
    Я так понимаю, что объект Animator Controller у всех объектов одинаковый, поэтому изменение у него состояния сказывается на всех объектах, которыми он управляет. Не хотелось бы создавать кучу аниматоров, по одному для каждого объекта…

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