2D игра на Unity. Подробное руководство. Часть 3

Опубликовано: 11 сентября 2014 г.
  • unity
Наш космический корабль из прошлого урока сталкивается с ужасным осьминогом, но ничего не может сделать... Давайте предоставим ему оружие и боеприпасы! Для этого нам придется еще поскриптить, но, поверьте, это того стоит.

Снаряд

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

Спрайт выстрела

Снаряд – объект, которым мы будем пользоваться очень часто. В игре будет несколько сцен, в которых игрок будет стрелять. Что мы должны использовать в этом случае? Префаб (Prefab), конечно же! Для этого проделайте следующие действия:

  1. Импортируйте текстуру
  2. Создайте новый спрайт в сцене
  3. Установите изображение на спрайт.
  4. Добавьте "Rigidbody 2D" с равныим нулю значениями "Gravity Scale" и "Fixed Angles".
  5. Добавьте "Box Collider 2D" размером (1, 1).

Сделайте масштаб таким (0,75, 0,75, 1) для лучшего отображения. Теперь, нам нужно установить новый параметр в "Инспекторе" (Inspector). для этого в "Box Collider 2D" поставьте галочку напротив свойства "IsTrigger". Триггер коллайдера создает событие при столкновении, но не используется при моделирования физики. Это значит, что выстрел пройдет сквозь объект при соприкосновении — никакого «реального» взаимодействия не будет. А вот у другого коллайдера это спровоцирует событие "OnTriggerEnter2D".

Та-дам! У нас появился выстрел. Теперь настало время немного поскриптить. Создайте скрипт, назвав его "ShotScript":

using UnityEngine;

/// <summary>
/// Поведение снаряда
/// </summary>
public class ShotScript : MonoBehaviour
{
  // 1 – Переменная дизайнера

  /// <summary>
  /// Причиненный вред
  /// </summary>
  public int damage = 1;

  /// <summary>
  /// Снаряд наносит повреждения игроку или врагам?
  /// </summary>
  public bool isEnemyShot = false;

  void Start()
  {
    // Ограниченное время жизни, чтобы избежать утечек
    Destroy(gameObject, 20); // 20 секунд
  }
}

Прикрепите "ShotScript" к спрайту. Также добавьте "MoveScript", т.к. ваши снимки будут двигаться. Теперь перетащите объект выстрел в панель "Проект" для создания Префаба. Он нам совсем скоро понадобится. Вы должны иметь следующую конфигурацию:

Конфигурация Выстрел 1

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

Столкновения и повреждения

Тем не менее, выстрел (пока) не наносит повреждений. Ничего удивительного, ведь мы не сделали скрипт обработки повреждений. Создадим его, назвав "HealthScript":

using UnityEngine;

/// <summary>
/// Handle hitpoints and damages
/// </summary>
public class HealthScript : MonoBehaviour
{
  /// <summary>
  /// Всего хитпоинтов
  /// </summary>
  public int hp = 1;

  /// <summary>
  /// Враг или игрок?
  /// </summary>
  public bool isEnemy = true;

  /// <summary>
  /// Наносим урон и проверяем должен ли объект быть уничтожен
  /// </summary>
  /// <param name="damageCount"></param>
  public void Damage(int damageCount)
  {
    hp -= damageCount;

    if (hp <= 0)
    {
      // Смерть!
      Destroy(gameObject);
    }
  }

  void OnTriggerEnter2D(Collider2D otherCollider)
  {
    // Это выстрел?
    ShotScript shot = otherCollider.gameObject.GetComponent<ShotScript>();
    if (shot != null)
    {
      // Избегайте дружественного огня
      if (shot.isEnemyShot != isEnemy)
      {
        Damage(shot.damage);

        // Уничтожить выстрел
        Destroy(shot.gameObject); // Всегда цельтесь в игровой объект, иначе вы просто удалите скрипт.      }
    }
  }
}

Добавьте "HealthScript" на префаб спрута.

Внимание: Лучше всего поработать непосредственно с префабом. При этом каждый экземпляр врага, участвующий в сцене, будет модифицирован так, чтобы отражать префаб. В данном случае это особенно важно, потому что в нашей сцене будет много врагов. Если вы сосредоточили усилия на экземпляр игрового объекта вместо префаба, не волнуйтесь: нажав на кнопку "Применить" сверху вкладки "Инспектор", вы добавите эти изменения и в префаб.

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

Enemy is shot

Здоровье врага превосходит урон от выстрела, поэтому он выживет. Попробуйте изменить значение hp в "HealthScript" врага:

Enemy is shot but has more HP

Стрельба

Удалите выстрел из сцены. Теперь, когда мы с ним закончили, ему нечего там делать. Нам нужен новый скрипт для стрельбы. Создайте его под именем "WeaponScript". Этот скрипт мы будем использовать везде (игроки, враги и т.д.) Его цель заключается в to instantiate снаряда перед игровым объектом, к которому он привязан. Вот полный код, больше, чем обычно. Объяснения ниже:

using UnityEngine;

/// <summary>
/// Launch projectile
/// </summary>
public class WeaponScript : MonoBehaviour
{
  //--------------------------------
  // 1 – Переменные дизайнера
  //--------------------------------

  /// <summary>
  /// Префаб снаряда для стрельбы
  /// </summary>
  public Transform shotPrefab;

  /// <summary>
  /// Время перезарядки в секундах
  /// </summary>
  public float shootingRate = 0.25f;

  //--------------------------------
  // 2 - Перезарядка
  //--------------------------------

  private float shootCooldown;

  void Start()
  {
    shootCooldown = 0f;
  }

  void Update()
  {
    if (shootCooldown > 0)
    {
      shootCooldown -= Time.deltaTime;
    }
  }

  //--------------------------------
  // 3 - Стрельба из другого скрипта
  //--------------------------------

  /// <summary>
  /// Создайте новый снаряд, если это возможно
  /// </summary>
  public void Attack(bool isEnemy)
  {
    if (CanAttack)
    {
      shootCooldown = shootingRate;

      // Создайте новый выстрел
      var shotTransform = Instantiate(shotPrefab) as Transform;

      // Определите положение
      shotTransform.position = transform.position;

      // Свойство врага
      ShotScript shot = shotTransform.gameObject.GetComponent<ShotScript>();
      if (shot != null)
      {
        shot.isEnemyShot = isEnemy;
      }

      // Сделайте так, чтобы выстрел всегда был направлен на него
      MoveScript move = shotTransform.gameObject.GetComponent<MoveScript>();
      if (move != null)
      {
        move.direction = this.transform.right; // в двухмерном пространстве это будет справа от спрайта
      }
    }
  }

  /// <summary>
  /// Готово ли оружие выпустить новый снаряд?
  /// </summary>
  public bool CanAttack
  {
    get
    {
      return shootCooldown <= 0f;
    }
  }
}

Прикрепите этот скрипт к игроку. Скрипт делится на три части:

  1. Переменные во вкладке "Inspector"

    Здесь у нас есть два члена: shotPrefab и shootingRate.

    Первый необходим для установки выстрела, который будет использоваться с этим оружием.

    Выберите игрока в сцене "Hierarchy". В компоненте "WeaponScript", вы можете увидеть свойство "Shot Prefab" со значением "None". Перетащите префаб "Shot" на это место:

    Использование префаба

    Unity автоматически дополнит скрипт это информацией. Удобно, не так ли?

    Переменная shootingRate имеет значение по умолчанию, установленное в коде. Мы не будем менять его на данный момент. Но вы можете начать игру и экспериментировать с ним, чтобы узнать на что она влияет.

    Будьте осторожны: изменение значения переменной во вкладке "Инспектор" в Unity не приводит к сохранению этих значений в скрипте. Если добавите этот скрипт в другой объект, значение по умолчанию будет таким, которое написано в скрипте. Если же вы хотите сохранить отредактированные параметры, вы должны открыть свой редактор кода и записать эти значения там.
  2. Перезарядка.

    Оружие обладает определенной частотой выстрелов. Без этого параметра можно было бы выпускать неограниченное количество патронов в каждом кадре.

    Поэтому нам нужен простой механизм охлаждения. Если его значение превышает 0, мы просто не можем стрелять. Мы вычитаем прошедшее время из каждого кадра.

    3. Публичный метод создания атаки

    Главная цель этого скрипта – активироваться через другой скрипт. Поэтому для создания снаряда мы используем публичный метод.

    Создав екземпляр снаряда, мы извлекаем скрипты объекта выстрела и оверрайдим некоторые переменные.

    Внимание: С помощью метода GetComponent<TypeOfComponent>() можно создать точный компонент (а значит, и скрипт, потому что скрипт – тоже компонент) объекта. Используйте generic (<TypeOfComponent>) для обозначения конкретного компонента, который вам нужен.
    Кроме того, у нас есть GetComponents<TypeOfComponent>(), вызывающий список вместо первого и т.д.

    Использование оружия с классом игрока

    Если вы запустите сейчас игру, то увидите, что ничего не изменилось. Мы создали оружие, но оно совершенно бесполезно.

    В самом деле, если "WeaponScript" был бы привязан к классу, мы никогда не смогли бы использовать метод Attack(bool).

    Давайте вернемся к нашему "PlayerScript".

    В функции Update() добавьте этот кусочек кода:

      void Update()
      {
        // ...
    
        // 5 - Стрельба
        bool shoot = Input.GetButtonDown("Fire1");
        shoot |= Input.GetButtonDown("Fire2");
        // Замечание: Для пользователей Mac, Ctrl + стрелка - это плохая идея
    
        if (shoot)
        {
          WeaponScript weapon = GetComponent<WeaponScript>();
          if (weapon != null)
          {
            // ложь, так как игрок не враг
            weapon.Attack(false);
          }
        }
    
        // ...
      }
    

    На данном этапе неважно, поставите вы его перед или после движения.

    Что мы сделали?

    1. Мы определяем нажатие кнопки стрельбы (click или ctrl по умолчанию).
    2. Извлекаем скрипт объекта.
    3. Мы запускаем Attack(false).
    Button down: Вы уже наверняка заметили, что мы используем метод GetButtonDown() для обеспечения ввода. "Down" в конце позволяет нам ввести данные при нажании кнопки и только один раз. GetButton() будет выводить true в каждом кадре, пока гирок не отпустит кнопку. В нашем случае нам явно необходимо поведение, обеспечиваемое методом GetButtonDown(). Попробуйте использовать GetButton() и почувствуйте разницу.

    Запустите игру с помощью кнопки "Play". Вот что вы должны получить:

    Стрельба

    Пули летят слишком медленно? Поэкспериментируйте с префабом "Shot" чтобы выбрать ортимальное значение. Попробуйте также добавить вращение игроку: (0, 0, 45). Пули двигаються под углом 45 градусов, даже если вращение спрайта выстрела является некорректным – а ведь мы его не изменили.

    Shooting rotation

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

    Вражеский снаряд

    Мы создадим новый снаряд с помощью этого спрайта:

    Вражеский снаряд

    Если вы так же ленивы, как я, продублируйте префаб "PlayerShot", переименуйте его в "EnemyShot1" и измените спрайт, как описано выше.

    Для дублирования создайте экземпляр, перетащив его на сцену, переименовав созданный игровой объект и, наконец, сохранив его как `Prefab’.

    Или можно просто продублировать Prefab напрямую внутри папки с помощью ярлыков cmd+D (OS X) или ctrl+D (для Windows).
    Если вы не выбираете легких путей, вы можете создать новый спрайт с параметром rigibody, коллайдером с триггером и т.д.

    Правильный масштаб - (0.35, 0.35, 1).

    Вот, что у вас должно получиться.

    настойки для вражеского снаряда

    При нажатии "Play" произойдет выстрел, который потенциально может уничтожить врага. Это из-за свойств "ShotScript" (которые по умолчанию плохо совместимы с Poulpi).

    Не изменяйте ничего. Помните наш "WeaponScript"? Он то и установит правильные значения.

    У нас есть префаб "EnemyShot1". Удалите экземпляры со сцены, если они есть.

    Также, как мы делали для игрока, также нам нужно добавить оружие и врагу, а потом вызывать Attack() чтобы выстрелить. Вот, что нам надо сделать:

    1. Добавьте "WeaponScript" врагу.
    2. Перетащите префаб "EnemyShot1" в переменную "Shot Prefab" скрипта.
    3. Создайте новый скрипт под названием "EnemyScript". Он просто будет запускать стрельбу в каждом кадре. Что-то вроде автострельбы.
    using UnityEngine;
    
    /// <summary>
    /// Enemy generic behavior
    /// </summary>
    public class EnemyScript : MonoBehaviour
    {
      private WeaponScript weapon;
    
      void Awake()
      {
        // Получить оружие только один раз
        weapon = GetComponent<WeaponScript>();
      }
    
      void Update()
      {
        // автоматическая стрельба
        if (weapon != null && weapon.CanAttack)
        {
          weapon.Attack(true);
        }
      }
    }
    

    Прикрепите этот скрипт к осьминогу. У вас должно получиться следующее (заметьте, что частота стрельбы немного звеличилась до0.75):

    Настойка осьминога с оружием

    Замечание: Если вы модифицируете игровой объект в сцене, не забудьте сохранить все изменения в префабе, использовав кнопку "Применить" справа сверху от панели "Инспектор".

    Попробуйте сыграть и посмотреть!

    Атакующий осьминог

    Итак, мы сделали то, что хотели и теперь и по нам тоже стреляют.

    Если повернуть врага, вы можете сделать его стреляющим в его слева, но, хм... спрайт повернулся вверх ногами, а нам это не нужно.

    Перевернутый спрут

    Давайте исправим это недоразумение.

    Стрельба в любом направлении.

    "WeaponScript" был написан особым образом: вы можете выбрать направление стрельбы, просто вращая прикрепленный игровой объект. Мы уже видели это раньше, когда вращали спрайт врага. Суть в том, чтобы создать пустой игровой объект как ребенка префаба врага. Итак, нам нужно:

    1. Создать пустой игровой объект. Назовем его "WeaponObject".
    2. Удалим "WeaponScript", прикрепленный к префабу врага.
    3. Добавим "WeaponScript" к "WeaponObject" и установить свойства префба выстрела как мы это делали раньше.
    4. Повернем "WeaponObject" вот так (0, 0, 180).

    Если вы проделали это все на игровом объекте, а не на префабе, то не забудьте нажать на кнопку "Применить" для сохранения изменений. Вот, что у нас получилось:

    Enemy with a new object

    However, we have a small change to make on the "EnemyScript" script.

    В своем нынешнем состоянии вызов GetComponent<WeaponScript>() в "EnemyScript" возвращает null. В самом деле, "WeaponScript" больше не привязан к одному объекту игры.

    К счастью, в Unity также доступен метод, использующий детскую иерархию игрового объекта, который называется GetComponentInChildren<Type>().

    Для GetComponent<>(), GetComponentInChildren<>() также существует в форме множественного числа: GetComponentsInChildren<Type>(). Обратите внимание на s после "Component". Этот метод возвращает список вместо первого соответствующего компонента.

    На самом деле, просто для удовольствия, мы также добавили возможность управления несколькими видами оружия. Мы просто манипулируем списком вместо одного экземпляра компонента. Взгляните на весь "EnemyScript":

    using System.Collections.Generic;
    using UnityEngine;
    
    /// <summary>
    /// Enemy generic behavior
    /// </summary>
    public class EnemyScript : MonoBehaviour
    {
      private WeaponScript[] weapons;
    
      void Awake()
      {
        // Получить оружие только один раз
        weapons = GetComponentsInChildren<WeaponScript>();
      }
    
      void Update()
      {
        foreach (WeaponScript weapon in weapons)
        {
          // автоматическая стрельба
          if (weapon != null && weapon.CanAttack)
          {
            weapon.Attack(true);
          }
        }
      }
    }
    

    Наконец, нужно обновить скорость выстрела путем настройки публичной пременной "MoveScript" из префаба "EnemyShot1". Скорость выстрела должна быть больше скорости движения спрута:

    Вооруженный и опасный спрут

    Мы сделали великого и ужасного осьминога. А давайте еще реализуем стрельбу в двух направлениях?

    Стрельба а двух направлениях

    Эта задача реализуеся всего в пару кликов. Для этого не нужны никакие скрипты:

    1. Добавьте другое оружие врагу (дублируя первый "WeaponObject").
    2. Измените угол поворота второго "WeaponObject".

    Враг должен сейчас стрелять в двух направлениях. Возможный результат:

    Супер опасный спрут с двуми пушками

    Это хороший пример правильной работы в Unity: создавая независимые скрипты вроде этого и делая публичными некоторые полезные переменные, можно значительно уменшить количество кода. Меньше кода - меньше ошибок.

    Нанесение урона игроку

    Наши осьминоги внушают ужас? Как бы не так! Да, они могут стрелять, но это не наносит повреждения игроку. Может, у них холостые патроны? Давайте разбираться.

    Просто добавьте "HealthScript" на игрока. Убедитесь, что сняли галку с поля "IsEnemy".

    Конфигурация скрипта, отвечающего за здоровье игрока

    Запустите игру и почувствуйте разницу:

    Player hit by an enemy projectile

    Бонус

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

    Солкновения игрока с врагом

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

    //PlayerScript.cs
    //....
    
    void OnCollisionEnter2D(Collision2D collision)
      {
        bool damagePlayer = false;
    
        // Столкновение с врагом
        EnemyScript enemy = collision.gameObject.GetComponent<EnemyScript>();
        if (enemy != null)
        {
          // Смерть врага
          HealthScript enemyHealth = enemy.GetComponent<HealthScript>();
          if (enemyHealth != null) enemyHealth.Damage(enemyHealth.hp);
    
          damagePlayer = true;
        }
    
        // Повреждения у игрока
        if (damagePlayer)
        {
          HealthScript playerHealth = this.GetComponent<HealthScript>();
          if (playerHealth != null) playerHealth.Damage(1);
        }
      }
    

    При столкновении мы наносим урон как врагу, так и игроку благодаря наличию компонента HealthScript. К нему привязано все, что относится к здоровью/урону.

    Массив снарядов

    Когда вы играете, вы можете наблюдать ва вкладке "Иерархия" (Hierarchy), что игровые объекты создаются и удаляются только через 20 секунд (если они не сталкиваются с игроком или врагом).

    Если ваша цель создание огневой завесы для которой требуется МНОГО пуль, эта техника вряд ли подойдет.

    Один из способов увеличить количество пуль – использовать массив. По сути, это набор пуль ограниченного размера. Когда массив заполнен, удалите старый объект и замените его на новый.

    Мы не будем использовать его здесь, но в этом нет ничего сложного. Мы использовали ту же технику для скрипта рисования.

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

    Имейте в виду, что использование метода Instantiate довольно затратное удовольствие. Применяйте его осторожно.

    Поведение пули

    В хорошем шутере должны быть запоминающиеся боевые сцены.

    Некоторые библиотеки вроде BulletML значительно упрощают определение сложных и зрелищных bullet patterns.

    BulletML для Unity

    Если вы хотите сделать полную версию игры в жанре Shoot'Em Up, ознакомьтесь с нашим плагином BulletML for Unity

    Задержка выстрела

    Добавьте несколько вооруженных противников в сцену и запустите игру. Вы увидете как синхронны все враги.

    Можно просто добавить в оружие задержку: поставьте охлаждение на любое значение выше 0. Вы можете использовать алгоритм или просто поставить случайную цифру.

    Скорость врагов также может определяться случайной величиной.

    Еще раз, это зависит от вас. Все зависит исключительно от того, чего вы хотите достичь с вашим геймплеем.

    В следующем уроке

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

    Результат

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

Популярные статьи
2D игра на Unity. Подробное руководство. Часть 1
  • unity
2D игра на Unity. Подробное руководство. Часть 1
Адаптивный слайдер без Javascript на CSS3
  • слайдер
Адаптивный слайдер без Javascript на CSS3
Работа с Unity в 2D
  • unity
Работа с Unity в 2D
2D игра на Unity. Подробное руководство. Часть 3
  • unity
2D игра на Unity. Подробное руководство. Часть 3
2D игра на Unity. Подробное руководство. Часть 4
  • unity
2D игра на Unity. Подробное руководство. Часть 4
2D игра на Unity. Подробное руководство. Часть 5
  • unity
2D игра на Unity. Подробное руководство. Часть 5
2D игра на Unity. Подробное руководство. Часть 6
  • unity
2D игра на Unity. Подробное руководство. Часть 6
Учебник по новому GUI в Unity. Часть 2.
  • unity
Учебник по новому GUI в Unity. Часть 2.
Учебник по новому GUI в Unity. Часть 1.
  • unity
Учебник по новому GUI в Unity. Часть 1.

HTML LESS LESS React
Онлайн журнал для профессиональных веб-дизайнеров и программистов
БлогПлагиныГрафикаИнструменты
CanvasSPAcssjavascriptjqueryphotoshopphpunitywordpress{"fieldValue":[{"image_preview":"","image_demo":"","example":""}],"fieldSettings":{"autoincrement":1}}{"fieldValue":[{"image_preview":"https://cdn-images-1.medium.com/max/800/1*OIBUnA4NokXK14IMR5csTw.jpeg","image_demo":"https://cdn-images-1.medium.com/max/800/1*OIBUnA4NokXK14IMR5csTw.jpeg","example":""}],"fieldSettings":{"autoincrement":1}}Аудио/Видеоаккордеонанимациябазы данныхбраузерные игрыверсткагалереяграфикакартыкнопкименюпараллаксподсказкипопаппрелоадерслайдертаймерформычекбоксыэлементы интерфейса
© 2009-2017 WebSketches.ru