Как сделать стрельбу в юнити
Как сделать выстрелы в Unity?
Как и всегда, есть несколько путей решения проблемы. Какой выбрать, решать вам. Например, Вы хотите что-бы пуля была большой и летела медленно, тогда ваш вариант это физическая пуля. Нагрузка больше из-за расчетов физики, но эффект броска снежка или камня есть. А вот если вы делаете автомат и пуль много, их все равно не видно, можно сделать через рейкаст, что собственно все и делают, а физику полета пули рассчитывают заранее, видим мы только эффект от прилетевшей пули, иногда с эффектом трастера.
Для начала рассмотрим все в теории, а потом уже возьмемся за реализацию и тестирование.
Теория
Физическое тело в виде снаряда
Итак, у нас есть задача бросить камень, надеюсь не в меня) Для этого делается следующее:
Как же быть? Для таких целей обычно используют рейкаст
Рейкаст для реализации выстрела
В простейшем случае работает это таким образом:
Для расчета падения пули можем провести расчет по скорости и дальности, затем опустив попадание ниже. У такого подхода есть свой минус. Пуля может пролететь сквозь стену если мы ее не просчитаем сами.
Как решить данную проблему?
Можно после получения конечной точки пустить еще один луч и проверить соприкосновения еще раз, это не совсем правильно но не так затратно по ресурсам.
Можно запустить симуляцию всего полета «на паузе». Расчет требует времени и соответственно ресурсов, однако точно просчитает полет пули. Это почти так же как и в варианте с физическим объектом, но расчет ведется отдельно от текущего времени и выдается только результат.
Так же выполняется создание обьекта и его пинок в нужном направлении но с игнорированием реального времени.
Либо по школьной физике просчитываем полет тела сами, но зачем? Все равно каждую точку проверять нужно на столкновения.
Практика
Физический снаряд
Начнем практику с кидания камней в стену, стену сделаем разрушаемой, для большего веселья.
Стрелять будем в точку, без поворота камеры.
Тут все просто, как и описывалось выше, создается объект и мы его пинаем вперед, все.
Первый выстрел
Последствия
Путь луча
Принцип описан выше, сцена та же самая, код чуть ниже. Для Визуализации я буду создавать «пулю» в точке соприкосновения луча и объекта. В реальном проекте, там нужно оставлять следы от пуль, искры, осколки, все что захотите.
Итоги
В данной статье я привел лишь примеры того как можно сделать выстрелы в unity. Надеюсь она вам поможет определиться как действовать дальше и у вас получиться хорошая стрелялка =)
2D игра на Unity. Подробное руководство. Часть 3
Снаряд
Сделайте масштаб таким (0,75, 0,75, 1) для лучшего отображения. Теперь, нам нужно установить новый параметр в «Инспекторе» (Inspector). для этого в «Box Collider 2D» поставьте галочку напротив свойства «IsTrigger». Триггер коллайдера создает событие при столкновении, но не используется при моделирования физики. Это значит, что выстрел пройдет сквозь объект при соприкосновении — никакого «реального» взаимодействия не будет. А вот у другого коллайдера это спровоцирует событие «OnTriggerEnter2D».
Та-дам! У нас появился выстрел. Теперь настало время немного поскриптить. Создайте скрипт, назвав его «ShotScript»:
Прикрепите «ShotScript» к спрайту. Также добавьте «MoveScript», т.к. ваши снимки будут двигаться. Теперь перетащите объект выстрел в панель «Проект» для создания Префаба. Он нам совсем скоро понадобится. Вы должны иметь следующую конфигурацию:
Если вы запустите игру с помощью кнопки «Play», вы увидите, что выстрел движется.
Столкновения и повреждения
Тем не менее, выстрел (пока) не наносит повреждений. Ничего удивительного, ведь мы не сделали скрипт обработки повреждений. Создадим его, назвав «HealthScript»:
Добавьте «HealthScript» на префаб спрута.
Внимание: Лучше всего поработать непосредственно с префабом. При этом каждый экземпляр врага, участвующий в сцене, будет модифицирован так, чтобы отражать префаб. В данном случае это особенно важно, потому что в нашей сцене будет много врагов. Если вы сосредоточили усилия на экземпляр игрового объекта вместо префаба, не волнуйтесь: нажав на кнопку «Применить» сверху вкладки «Инспектор», вы добавите эти изменения и в префаб.
Убедитесь, что выстрел и спрут находятся на одной линии, чтобы проверить столкновение. Напоминаю, что 2D движок ничего не знает про ось Z, поэтому ваши 2D коллайдеры всегда будут в той же плоскости. А теперь, запустите нашу сцену. Вы должны увидеть следующее:
Здоровье врага превосходит урон от выстрела, поэтому он выживет. Попробуйте изменить значение hp в «HealthScript» врага:
Стрельба
Удалите выстрел из сцены. Теперь, когда мы с ним закончили, ему нечего там делать. Нам нужен новый скрипт для стрельбы. Создайте его под именем «WeaponScript». Этот скрипт мы будем использовать везде (игроки, враги и т.д.) Его цель заключается в to instantiate снаряда перед игровым объектом, к которому он привязан. Вот полный код, больше, чем обычно. Объяснения ниже:
Прикрепите этот скрипт к игроку. Скрипт делится на три части:
Переменные во вкладке «Inspector»
Выберите игрока в сцене «Hierarchy». В компоненте «WeaponScript», вы можете увидеть свойство «Shot Prefab» со значением «None». Перетащите префаб «Shot» на это место:
Unity автоматически дополнит скрипт это информацией. Удобно, не так ли?
Переменная shootingRate имеет значение по умолчанию, установленное в коде. Мы не будем менять его на данный момент. Но вы можете начать игру и экспериментировать с ним, чтобы узнать на что она влияет.
Будьте осторожны: изменение значения переменной во вкладке «Инспектор» в Unity не приводит к сохранению этих значений в скрипте. Если добавите этот скрипт в другой объект, значение по умолчанию будет таким, которое написано в скрипте. Если же вы хотите сохранить отредактированные параметры, вы должны открыть свой редактор кода и записать эти значения там.
Оружие обладает определенной частотой выстрелов. Без этого параметра можно было бы выпускать неограниченное количество патронов в каждом кадре.
3. Публичный метод создания атаки
Главная цель этого скрипта – активироваться через другой скрипт. Поэтому для создания снаряда мы используем публичный метод.
Создав екземпляр снаряда, мы извлекаем скрипты объекта выстрела и оверрайдим некоторые переменные.
Давайте вернемся к нашему «PlayerScript».
В функции Update() добавьте этот кусочек кода:
На данном этапе неважно, поставите вы его перед или после движения.
Запустите игру с помощью кнопки «Play». Вот что вы должны получить:
Вражеский снаряд
Если вы так же ленивы, как я, продублируйте префаб «PlayerShot», переименуйте его в «EnemyShot1» и измените спрайт, как описано выше.
Для дублирования создайте экземпляр, перетащив его на сцену, переименовав созданный игровой объект и, наконец, сохранив его как `Prefab’.
Или можно просто продублировать Prefab напрямую внутри папки с помощью ярлыков cmd+D (OS X) или ctrl+D (для Windows).
Если вы не выбираете легких путей, вы можете создать новый спрайт с параметром rigibody, коллайдером с триггером и т.д.
Вот, что у вас должно получиться.
При нажатии «Play» произойдет выстрел, который потенциально может уничтожить врага. Это из-за свойств «ShotScript» (которые по умолчанию плохо совместимы с Poulpi).
Не изменяйте ничего. Помните наш «WeaponScript»? Он то и установит правильные значения.
У нас есть префаб «EnemyShot1». Удалите экземпляры со сцены, если они есть.
Прикрепите этот скрипт к осьминогу. У вас должно получиться следующее (заметьте, что частота стрельбы немного звеличилась до 0.75 ):
Попробуйте сыграть и посмотреть!
Итак, мы сделали то, что хотели и теперь и по нам тоже стреляют.
Если повернуть врага, вы можете сделать его стреляющим в его слева, но, хм. спрайт повернулся вверх ногами, а нам это не нужно.
Давайте исправим это недоразумение.
Стрельба в любом направлении.
«WeaponScript» был написан особым образом: вы можете выбрать направление стрельбы, просто вращая прикрепленный игровой объект. Мы уже видели это раньше, когда вращали спрайт врага. Суть в том, чтобы создать пустой игровой объект как ребенка префаба врага. Итак, нам нужно:
Если вы проделали это все на игровом объекте, а не на префабе, то не забудьте нажать на кнопку «Применить» для сохранения изменений. Вот, что у нас получилось:
However, we have a small change to make on the «EnemyScript» script.
В своем нынешнем состоянии вызов GetComponent () в «EnemyScript» возвращает null. В самом деле, «WeaponScript» больше не привязан к одному объекту игры.
Наконец, нужно обновить скорость выстрела путем настройки публичной пременной «MoveScript» из префаба «EnemyShot1». Скорость выстрела должна быть больше скорости движения спрута:
Мы сделали великого и ужасного осьминога. А давайте еще реализуем стрельбу в двух направлениях?
Стрельба а двух направлениях
Эта задача реализуеся всего в пару кликов. Для этого не нужны никакие скрипты:
Нанесение урона игроку
Просто добавьте «HealthScript» на игрока. Убедитесь, что сняли галку с поля «IsEnemy».
Запустите игру и почувствуйте разницу:
Бонус
Вот вам некоторые советы для улучшения аспекта стрельбы в вашей будущей игре. Вы можете пропустить эту часть, если вас не интересуют подробности, относящиеся к жанру шмапа.
Солкновения игрока с врагом
Массив снарядов
Когда вы играете, вы можете наблюдать ва вкладке «Иерархия» (Hierarchy), что игровые объекты создаются и удаляются только через 20 секунд (если они не сталкиваются с игроком или врагом).
Если ваша цель создание огневой завесы для которой требуется МНОГО пуль, эта техника вряд ли подойдет.
Один из способов увеличить количество пуль – использовать массив. По сути, это набор пуль ограниченного размера. Когда массив заполнен, удалите старый объект и замените его на новый.
Мы не будем использовать его здесь, но в этом нет ничего сложного. Мы использовали ту же технику для скрипта рисования.
Кроме того, можно сократить время жизни пули и тогда она исчезнет быстрее.
Имейте в виду, что использование метода Instantiate довольно затратное удовольствие. Применяйте его осторожно.
Поведение пули
В хорошем шутере должны быть запоминающиеся боевые сцены.
Некоторые библиотеки вроде BulletML значительно упрощают определение сложных и зрелищных bullet patterns.
Если вы хотите сделать полную версию игры в жанре Shoot’Em Up, ознакомьтесь с нашим плагином BulletML for Unity
Задержка выстрела
Добавьте несколько вооруженных противников в сцену и запустите игру. Вы увидете как синхронны все враги.
Скорость врагов также может определяться случайной величиной.
Еще раз, это зависит от вас. Все зависит исключительно от того, чего вы хотите достичь с вашим геймплеем.
В следующем уроке
Создание игры Tower Defense в Unity: башни и стрельба по врагам
[Первая и вторая части туториала]
Туториал создавался в Unity 2018.3.0f2.
Зададим врагам жару.
Создание башни
Содержимое тайла
Префаб
Три куба, образующих башню.
Турель будет поворачиваться, и поскольку она имеет коллайдер, её будет отслеживать физический движок. Но нам не нужно быть такими точными, потому что мы используем коллайдеры башен только для выбора ячеек. Это вполне можно делать приблизительно. Удалите коллайдер куба турели и измените коллайдер куба башни, чтобы он покрывал оба куба.
Коллайдер куба башни.
Скрытый куб лазерного луча.
Создадим для лазерного луча подходящий материал. Я просто использовал стандартный полупрозрачный чёрный материал и отключил все отражения, а также придал ему красный испускаемый цвет.
Материал лазерного луча.
Проверьте, чтобы у лазерного луча не было коллайдера, а также отключите у него отбрасывание и получение теней.
Лазерный луч не взаимодействует с тенями.
Завершив создание префаба башни, добавим его в фабрику.
Размещение башен
В Game.HandleTouch при зажатии клавиши shift переключаться будут не стены, а башни.
Блокирование пути
Пока блокировать поиск пути могут только стены, поэтому враги движутся сквозь башни. Давайте добавим в GameTileContent вспомогательное свойство, обозначающее, блокирует ли содержимое путь. Путь блокируется, если это стена или башня.
Используем это свойство в GameTile.GrowPathTo вместо проверки типа содержимого.
Теперь путь блокируют и стены, и башни.
Заменяем стены
Скорее всего, игрок будет часто заменять стены башнями. Ему будет неудобно сначала убирать стену, и к тому же в этот временно появившийся промежуток смогут проникать враги. Можно реализовать прямую замену, заставив GameBoard.ToggleTower проверять, находится ли в данный момент на тайле стена. Если да, то нужно сразу заменить её на башню. В таком случае нам не придётся искать другие пути, потому что тайл по-прежнему их блокирует.
Целимся во врагов
Башня может выполнять свою задачу только тогда, когда найдёт врага. После нахождения врага она должна решить, в какую его часть нужно целиться.
Точка прицеливания
Для обнаружения целей мы будем использовать физический движок. Как и в случае с коллайдером башни, нам не нужно, чтобы коллайдер врага обязательно совпадал с его формой. Можно выбрать простейший коллайдер, то есть сферу. После обнаружения врага мы будем использовать позицию игрового объекта с присоединённым к нему коллайдером как точку для прицеливания.
Добавим к кубу префаба врага компонент и коллайдер. Это заставит башни целиться в центр куба. Используем сферический коллайдер с радиусом 0.25. Куб имеет масштаб 0.5, поэтому истинный радиус коллайдера будет равен 0.125. Благодаря этому враг должен будет визуально пересечь круг дальности башни, и только спустя какое-то время становиться настоящей цель. На размер коллайдера также влияет случайный масштаб врага, поэтому его размер в игре тоже будет немного варьироваться.
Враг с точкой для прицеливания и коллайдером на кубе.
Слой врагов
Башни волнуют только враги, и они не целятся ни во что другое, поэтому мы поместим всех врагов в отдельный слой. Воспользуемся слоем 9. Измените его название на Enemy в окне Layers & Tags, которое можно открыть через опцию Edit Layers в раскрывающемся меню Layers в правом верхнем углу редактора.
Слой 9 будет использоваться для врагов.
Этот слой нужен только для распознавания врагов, а не для физических взаимодействий. Давайте укажем на это, отключив их в Layer Collision Matrix, которая находится в панели Physics параметров проекта.
Матрица коллизий слоёв.
Убедитесь, что игровой объект точки прицеливания находится на нужном слое. Остальная часть префаба врага может быть на других слоях, но проще будет всё согласовать и поместить весь префаб в слой Enemy. Если вы измените слой корневого объекта, то вам будет предложено изменить слой и для всех его дочерних объектов.
Враг на нужном слое.
Давайте добавим утверждение о том, что TargetPoint действительно находится на нужном слое.
Обновление содержимого тайлов
Заставим Tower переопределить его, пусть пока просто выводит в консоль, что ищет цель.
В нашем туториале обновлять нужно только башни. Изменим ToggleTower так, чтобы он при необходимости добавлял и удалял содержимое. Если обновление потребуется и другому содержимому, то нам понадобится более общий подход, но пока достаточно и этого.
Дальность прицеливания
Дальность прицеливания 2.5.
Гизмо дальности прицеливания.
Захват цели
Мы гарантированно получим правильные точки прицеливания, если будем учитывать коллайдеры только на слое врагов. Это слой 9, поэтому передадим соответствующую маску слоя.
Можно визуализировать захваченную цель, отрисовав линию-гизмо между позициями башни и цели.
Фиксация на цели
В результате башни фиксируются на цели, пока она не достигнет конечной точки и не будет уничтожена. Если вы используете врагов многократно, то вместо этого нужно проверять правильность ссылки, как это делается со ссылками на фигуры, обрабатываемые в серии туториалов Object Management.
Однако этот код не учитывает радиус коллайдера. Поэтому в результате башня может потерять цель, затем снова захватить её, только для того чтобы прекратить отслеживать её в следующем кадре, и так далее. Мы можем избежать этого, прибавив к дальности радиус коллайдера.
Это даёт нам правильные результаты, но только если масштаб врага не изменён. Так как мы даём каждому врагу случайный масштаб, нужно учитывать его при изменении дальности. Для этого мы должны запоминать масштаб, данный Enemy и открывать его при помощи свойства-геттера.
Теперь мы можем проверять в Tower.TrackTarget правильную дальность.
Синхронизируем физику
Похоже, всё работает хорошо, но башни, которые могут целиться в центр поля, способны захватывать цели, которые должны находиться вне пределов дальности. Им не будет удаваться отслеживать эти цели, поэтому они фиксируются на них только на один кадр.
Это происходит, потому что состояние физического движка неидеально синхронизовано с состоянием игры. Экземпляры всех врагов создаются в точке начала координат мира, который совпадает с центром поля. Затем мы перемещаем их в точку создания, но физический движок узнаёт об этом не сразу.
Игнорируем высоту
По сути, наш игровой процесс происходит в 2D. Поэтому давайте изменим Tower так, чтобы при прицеливании и отслеживании он учитывал только координаты X и Z. Физический движок работает в 3D-пространстве, но по сути мы можем выполнять проверку AcquireTarget в 2D: растянем сферу вверх, чтобы она покрывала все коллайдеры, вне зависимости от их позиции по вертикали. Это можно сделать, использовав вместо сферы капсулу, вторая точка которой будет в нескольких единицах над землёй (допустим, в трёх).
Избегаем выделения памяти
Недостаток использования Physics.OverlapCapsule заключается в том, что на каждый вызов он выделяет новый массив. Этого можно избежать, выделив массив один раз и вызывая альтернативный метод OverlapCapsuleNonAlloc с массивом в качестве дополнительного аргумента. Длина передаваемого массива определяет количество получаемых результатов. Все потенциальные цели за пределами массива отбрасываются. Мы всё равно будем использовать только первый элемент, поэтому нам хватит массива длиной 1.
Вместо массива OverlapCapsuleNonAlloc возвращает количество произошедших столкновений, вплоть до максимально допустимого, и именно это число мы будем проверять вместо длины массива.
Стреляем во врагов
Прицеливаемся турелью
Чтобы направить турель на цель, классу Tower нужно иметь ссылку на компонент Transform турели. Добавим для этого поле конфигурации и подключим его в префабе башни.
Стреляем лазером
Для позиционирования лазерного луча классу Tower тоже нужна ссылка на него.
Подключили лазерный луч.
Чтобы превратить куб в настоящий лазерный луч, нужно сделать три шага. Во-первых, его ориентация должна соответствовать ориентации турели. Это можно сделать, скопировав её поворот.
Во-вторых мы отмасштабируем лазерный луч так, чтобы его длина была равна расстоянию между локальной точкой начала координат турели и точкой прицеливания. Мы масштабируем его по оси Z, то есть локальной оси, направленной в сторону цели. Чтобы сохранить исходный масштаб по XY, запишем исходный масштаб при пробуждении (Awake) турели.
В-третьих, расположим лазерный луч посередине между турелью и точкой прицеливания.
Стрельба лазерными лучами.
Это работает, пока турель фиксирована на цели. Но когда цели нет, лазер остаётся активным. Мы можем отключить отображение лазера, присвоив его масштабу в GameUpdate значение 0.
Простаивающие башни не стреляют.
Здоровье врагов
Пока наши лазерные лучи просто касаются врагов и больше никак на них не влияют. Нужно сделать так, чтобы лазер наносит врагам урон. Мы не хотим уничтожать врагов мгновенно, поэтому дадим Enemy свойство здоровья. В качестве здоровья можно выбрать любое значение, поэтому давайте возьмём 100. Но будет логичнее, чтобы у крупных врагов было больше здоровья, поэтому введём для этого коэффициент.
Урон в секунду
Теперь нам нужно определить, сколько урона будет наносить лазер. Для этого добавим к Tower поле конфигурации. Так как лазерный луч наносит непрерывный урон, мы выразим его как урон в секунду (damage per second). В Shoot приложим его к компоненту Enemy цели с умножением на дельту времени.
Урон каждой башни — 20 единиц в секунду.
Прицеливание случайным образом
Так как мы всегда выбираем первую доступную цель, поведение прицеливания зависит от порядка, в котором физический движок проверяет пересекающиеся коллайдеры. Эта зависимость не очень хороша, потому что мы не знаем подробностей, не можем ею управлять, к тому же выглядеть это будет странно и непоследовательно. Часто такое поведение приводит к сосредоточенному огню, но так бывает не всегда.
Вместо того, чтобы полностью полагаться на физический движок, давайте добавим немного случайности. Это можно сделать, увеличив количество получаемых коллайдерами пересечений, например до 100. Возможно, этого не будет достаточно для получения всех возможных целей на густо заполненном врагами поле, но этого будет достаточно для улучшения прицеливания.
Теперь вместо выбора первой потенциальной цели мы будем выбирать из массива случайный элемент.