Игры в стиле Tower Defense невероятно популярны и не удивительно, что нет ничего более захватывающего, чем наблюдение за вашей защитой, уничтожающей злых захватчиков!
В этом учебнике мы создадим неприступную оборону из монстров (выполняющих роль башен) в Unity! Вы узнаете, как ...
- Создать волны врагов и научить их следовать по путевым точкам (вейпоинтам).
- Создать и модернизировать монстров и с их помощью измельчать врагов на пиксели.
В конце концов, вы получите фреймворк для этого игрового жанра, который в дальнейшем сможете усовершенствовать!
Вы должны знать основы Unity, например, как добавить игровые ассеты и компоненты, понимать, что такое префаб и знать основны C#.
Я использую версию OS X версию Unity, но вы можете и работать и под Windows.
Описание 2D игры на Unity в стиле Tower Defense
В этом учебнике вы построите оборону из башен от врагов (маленьких жучков), ползущих к печенью, которое принадлежит вам и вашим миньонам-монстрам! Вы можете разместить монстров в стратегически важных точках и проапгредить их за золото.
Игрок должен убить букашек прежде чем они начнут пировать на печенье, причем каждую новую волну врагов труднее победить. Игра заканчивается когда вам удалось пережить все атаки (Победа!), или когда пять врагов достигнут печенья (Поражение!). Вот скриншот готовой игры:
Начало работы с Unity
Если вы еще не установили Unity 5, или более новую версию, загрузите ее с официального сайта Unity. Кроме того, скачайте этот стартовый проект, разархивируйте и откройте проект TowerDefense-Part1-Starter в Unity.
Стартовый проект включает в себя изображения и звуковые ассеты, а также заготовки анимации и несколько полезных скриптов. Скрипты не относятся конкретно к играм такого плана, поэтому они не будут описаны здесь. Также этот проект содержит префабы, которые в дальнейшем вам понадобятся для создания персонажей игры. И, наконец, проект включает в себя сцену с фоном и пользовательский интерфейс.
Откройте GameScene, найденную в папке Scenes, и выберите соотношение сторон 4:3
, чтобы все умещалось на экране. Вы должны увидеть следующее во вкладке Game view:
Работаем с монстрами
Монстры могут быть размещены только на местах, отмеченных x. Для того, чтобы добавить их на сцену, перетащите Images\Objects\Openspot из Project Browser на вкладку Scene (Сцена). На данный момент, положение не имеет значения.
Выберите в Иерархии Openspot, затем добавьте компонент, нажав на Add Component в Инспекторе и выберите Physics 2D\Box Collider 2D. Unity отобразит бокс-коллайдер с зеленой линией на вкладке Сцена. Вы будете использовать этот коллайдер для обнаружения щелчков мыши на этом месте.
Теперь добавьте компонент Audio\Audio Source в Openspot. Установите источники звука AudioClip в tower_place
, которые вы можете найти в папке Audio и отключите Play On Awake.
Вам нужно создать еще 11 мест. Несмотря на искушение проделать эти шаги снова, Unity имеет более элегантное решение: префабы!
Перетащите Openspot из Иерархии в папку Prefabs в Project Browser (Браузере проекта). Его имя станет синим в Иерархии, чтобы показать, что он подключен к префаб:
Теперь, когда у вас есть префаб, вы можете создать столько копий, сколько вам нужно. Просто перетащите Openspot из папки Prefabs в Браузере проекта во вкладку Сцена. Проделайте это 11 раз, чтобы на сцене было 12 объектов Openspot.
Теперь используйте Инспектор, чтобы установить положение этих 12 объектов в следующие позиции: (-5.2, 3.5, 0)
, (-2.2, 3.5, 0)
, (0.8, 3.5, 0)
, (3.8, 3.5, 0)
, (-3.8, 0.4, 0)
, (-0.8, 0.4, 0)
, (2.2, 0.4, 0)
, (5.2, 0.4, 0)
, (-5.2, -3.0, 0)
, (-2.2, -3.0, 0)
, (0.8, -3.0, 0)
, (3.8, -3.0, 0)
.
Когда вы закончите, ваша сцена должна выглядеть следующим образом:
Размещаем монстров
Для удобства, в папке Prefab есть префаб Monster.
На данный момент она состоит из пустого игрового объекта с тремя различными спрайтами и их анимаций в качестве подобъектов.
Каждый спрайт представляет собой монстра с разным уровнем мощности. Префаб также содержит компонент Audio Source, который вы будете запускать для воспроизведение звука, когда монстр стреляет лазером.
Создадим скрипт, который может поместить Monster на Openspot.
В Браузере проекта выберите Openspot в папке Prefabs. В Инспекторе нажмите на Add Component (Добавить Компонент), затем выберите New Script (Новый Скрипт) и назовите его PlaceMonster. Выберите C Sharp как Language и нажмите на Create and Add (Создать и Добавить). Т.к. вы добавили скрипт в префаб Openspot все Openspot’ы будут также содержать этот скрипт.
Дважды щелкните по скрипту, чтобы открыть его в MonoDevelop. Затем добавьте эти две переменные:
public GameObject monsterPrefab;
private GameObject monster;
Вы создадите копию объекта, хранящегося в monsterPrefab
, чтобы создать монстра и хранить его в monster
для управления им во время игры.
Один монстр на локацию
Добавьте следующий метод, чтобы в каждой локации был только один монстр:
private bool canPlaceMonster() {
return monster == null;
}
В canPlaceMonster()
можно проверить, является ли переменная monster
пустой (null
). Если все еще да, то значит, что монстра нет, и можно разместить еще одного.
Теперь добавьте следующие строки, благодаря которым монстр будет появляться, когда пользователь будет нажимать на этот игровой объект:
//1
void OnMouseUp () {
//2
if (canPlaceMonster ()) {
//3
monster = (GameObject)
Instantiate(monsterPrefab, transform.position, Quaternion.identity);
//4
AudioSource audioSource = gameObject.GetComponent<AudioSource>();
audioSource.PlayOneShot(audioSource.clip);
// СДЕЛАТЬ: уменьшить золото
}
}
Этот код помещает монстра по щелчку мыши или касанию экрана. Так, как это работает?
- Unity автоматически вызывает
OnMouseUp
когда игрок нажимает на физический коллайдер игрового объекта.
- При вызове этот метод помещает нового монстра, если canPlaceMonster() возвращает
true
.
- Монстра можно создать с помощью метода
Instantiate
, который создаёт образец префаба с определенной позицией и вращением. В таком случае можно скопировать monsterPrefab
, применить к нему текущую позицию и вращение игрового объекта, прикрепить результат к GameObject
и сохранить его в monster
.
- И, наконец, вы вызываете
PlayOneShot
, чтобы проиграть звуковой эффект привязанный к компоненту объекта AudioSource
.
Теперь ваш скрипт PlaceMonster
может поместить нового монстра, но вам все еще нужно обозначить префаб.
Использование нужного Префаба
Сохраните файл и переключитесь обратно в Unity. Для того, чтобы назначить переменную monsterPrefab сперва выберите Openspot в папке Prefabs в Браузере проекта. В Инспекторе кликните на кружок справа от компонента PlaceMonster (Script) в поле Monster Prefab и выберите Monster в появившемся диалоговом окне.
Запустите сцену и создавайте монстров в различных точках, помеченных х, с помощью щелчка или касания экрана.
Прокачка монстров в Unity-игре
На рисунке ниже вы видите, как ваши монстры выглядят все более ужасающими на более высоких уровнях.
Скрипт служит основой для применения системы уровней монстров. Он отслеживает насколько сильный монстр должен быть на каждом уровне, и, конечно же, нынешний уровень монстра. Добавьте этот скрипт.
Выберите Prefabs/Monster в Браузере проекта. Добавьте новый C#-скрипт по имени MonsterData. Откройте его в MonoDevelop и добавьте следующий код перед классом MonsterData
.
[System.Serializable]
public class MonsterLevel {
public int cost;
public GameObject visualization;
}
Мы создали класс MonsterLevel
. В нем хранится стоимость (в золоте, которое мы пропишем позже) и внешний вид различных типов монстров.
Чтобы объекты можно было редактировать в инспекторе, добавьте строку [System.Serializable]
на самый верх класса. Это позволяет быстро изменить все значения в классе Level - даже тогда, когда игра запущена. Это невероятно полезно для придания сбалансированности вашей игре.
Определение уровня монстра
В этом случае, вы будете хранить предопределенный MonsterLevel
в List<T>
.
Почему бы просто не использовать MonsterLevel[]
? Хотя бы потому, что нам понадобятся индексы объектов MonsterLevel
несколько раз. Код для этого написать не так и сложно, поэтому, вы будете использовать IndexOf()
, который реализует функциональные возможности для Lists
. Нет никакой необходимости изобретать колесо. :]
В самом верху MonsterData.cs, добавьте оператор using
:
using System.Collections.Generic;
Это дает вам доступ к общим структурам данных, так что вы можете использовать класс List<T>
в вашем скрипте.
Дженерик-функции являются большой частью C#. С их помощью можно определять структуры типобезопасных данных без обращения к их типу. Это очень удобно для классов вроде list и set.
Теперь добавьте следующую переменную MonsterData
для хранения списка MonsterLevel
:
public List<MonsterLevel> levels;
С помощью дженерик-функций можно удостовериться, что levels List
может содержать только объекты MonsterLevel
. Сохраните файл и переключитесь в Unity для настройки каждого уровня монстра. Выберите Prefabs/Monster в Браузере проекта. В Инспекторе вы можете увидеть папку Levels в компоненте MonsterData (Script). Установите size в 3
.
Затем установите cost для каждого уровня:
- Element 0:
200
- Element 1:
110
- Element 2:
120
Теперь присвоить значения дя поля visualization. В окне проектов разверните папку Prefabs/Monster , чтобы увидеть подпапки. Перетащите Monster0 в поле visualization элемента Element 0.
Повторим то же самое для присвоения Monster1 к Element 1, а Monster2 к Element 2. GIF-картинка, размещенная ниже, наглядно демонстрирует этот процесс:
Когда вы выбираете Prefabs/Monster, префаб должен выглядеть следующим образом:
Определение текущего уровня
Переключитесь обратно на MonsterData.cs в MonoDevelop и добавьте еще одну переменную MonsterData
.
private MonsterLevel currentLevel;
В приватной переменной currentLevel
у нас будет записан… секундочку… текущий уровень монстра. Не ожидали? :]
Теперь задайте currentLevel
и сделайте его доступным для других сценариев. Добавьте следующий код в MonsterData
, вместе с объявлениями переменных экземпляра:
//1
public MonsterLevel CurrentLevel {
//2
get {
return currentLevel;
}
//3
set {
currentLevel = value;
int currentLevelIndex = levels.IndexOf(currentLevel);
GameObject levelVisualization = levels[currentLevelIndex].visualization;
for (int i = 0; i < levels.Count; i++) {
if (levelVisualization != null) {
if (i == currentLevelIndex) {
levels[i].visualization.SetActive(true);
} else {
levels[i].visualization.SetActive(false);
}
}
}
}
}
Совсем немного C#, не так ли? Вот, что мы здесь делаем:
- Определяем свойство для приватной переменной
currentLevel
. Когда свойство определено, можно его вызывать как любую переменную: либо как CurrentLevel
(изнутри класса), либо как monster.CurrentLevel
(извне). Можно задать поведение в геттерах и сеттерах свойства. Используя только геттер, только сеттер, либо используя их оба, можно делать свойство доступным только для чтения, только для записи, либо доступным для обоих этих действий.
- В геттере, вы возвращает значение
currentLevel
.
- В сеттере вы назначаете новое значение
currentLevel
. Далее вы получите индекс текущего уровня. Наконец, вы перебираете все levels и установить визуализацию в активном или неактивном состоянии, в зависимости от currentLevelIndex
. Это здорово, потому что это означает, что всякий раз, когда кто-то устанавливает currentLevel
, спрайт обновляется автоматически. Свойства здесь очень пригождаются!
Добавьте следующее в OnEnable:
void OnEnable() {
CurrentLevel = levels[0];
}
Здесь устанавливаем CurrentLevel
при размещении, убедившись в том, что он показывает только нужный спрайт.
Очень важно инициализировать свойство OnEnable
вместо OnStart
, потому что методы вызываются, когда созданы префабы.
OnEnable
будет вызываться сразу при создании префаба (если префаб был сохранен во включенном состоянии), но OnStart
не вызывается до тех пор, пока объект не запущен.
Вам нужно проверить эти данные, прежде чем разместить монстра, для этого запускаемOnEnable
.
Сохраните файл и переключитесь на Unity. Запустите проект и расставьте монстров. Теперь они показывают правильно.
Апгрейд монстров
Вернитесь в MonoDevelop и добавьте следующий метод в MonsterData:
public MonsterLevel getNextLevel() {
int currentLevelIndex = levels.IndexOf (currentLevel);
int maxLevelIndex = levels.Count - 1;
if (currentLevelIndex < maxLevelIndex) {
return levels[currentLevelIndex+1];
} else {
return null;
}
}
В getNextLevel
вы получаете индекс currentLevel
и индекс максимального уровня. Если монстр еще не полностью прокачен, возвращаем индекс следующего уровеня, в противном случае - null
.
Вы можете использовать этот метод, чтобы выяснить возможен ли апгрейд монстра. Добавьте следующий метод для повышения уровня монстра:
public void increaseLevel() {
int currentLevelIndex = levels.IndexOf(currentLevel);
if (currentLevelIndex < levels.Count - 1) {
CurrentLevel = levels[currentLevelIndex + 1];
}
}
Здесь вы получаете индекс текущего уровня и проверяете меньше ли он levels.Count - 1
. Если да, - монстра еще можно прокачать, поэтому мы присваиваем CurrentLevel
следующий уровень.
Тестирование возможности апгрейда
Сохраните файл, а затем переключитесь на PlaceMonster.cs в MonoDevelop и добавьте этот новый метод:
private bool canUpgradeMonster() {
if (monster != null) {
MonsterData monsterData = monster.GetComponent<MonsterData> ();
MonsterLevel nextLevel = monsterData.getNextLevel();
if (nextLevel != null) {
return true;
}
}
return false;
}
Здесь мы смотрим, есть ли монстр, которого можно модернизировать, путем проверки переменной monster на null
. Если это так, то получаем текущий уровень монстра из MonsterData
. После этого мы проверяем доступен ли более высокий уровень, который при getNextLevel()
не возвращает null
. Если "прокачка" монстра возможна, возвращаем true
, в противном случае - false
.
Включить апгрейд с помощью золота
Чтобы включить опцию апгрейда, добавьте оператор else if
к OnMouseUp
:
if (canPlaceMonster ()) {
// Здесь код остается прежним
} else if (canUpgradeMonster()) {
monster.GetComponent<MonsterData>().increaseLevel();
AudioSource audioSource = gameObject.GetComponent<AudioSource>();
audioSource.PlayOneShot(audioSource.clip);
// Предстоит сделать: золотишка стало меньше
}
Проверьте, является ли апгрейд возможен с помощью canUpgradeMonster()
. Если да, то вы получаете доступ к компоненту MonsterData с GetComponent()
и вызываете increaseLevel()
, что повышает уровень монстра. Наконец, вы запускаете звук для монстра AudioSource. Сохраните файл и вернитесь обратно в Unity. Запустите игру, расставьте и модернизируйте как можно больше монстров... пока это возможно.
На следующем занятии по Tower Defense на Unity мы введем в игру игровую валюту, поработаем на маршрутом для монстров и др.