Перетяжка, Премия ТПрогер, 13.11
Перетяжка, Премия ТПрогер, 13.11
Перетяжка, Премия ТПрогер, 13.11

Пишем сапёр на Unity. Настройка

Аватар Типичный программист
Отредактировано

17К открытий19К показов
Пишем сапёр на Unity. Настройка

Логические игры часто включают в себя клеточные поля, при этом клетки имеют определенные свойства и модели поведения. В этой серии уроков мы покажем вам, как создать простую версию классической игры Сапёр. Прилагаем список статей:

  1. Пишем сапёр на Unity. Настройка.
  2. Пишем Сапёр на Unity. Взаимодействие.
  3. Пишем Сапёр на Unity. Обработка конца игры.

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

  1. Настройка проекта.
  2. Механика мяча и платформы.
  3. Поведение блоков, префабы и дизайн уровней.
  4. Добавление звуков и новых уровней.

Правила игры

Цель игры — найти все мины и не взорваться. Мины спрятаны в клетках, а сами поля бывают разных размеров от 9×9 (легкий уровень) до 16×30 (сложный уровень). Впрочем, никто не запрещает использовать любой понравившийся размер поля.

При нажатии на клетку вы «раскрываете» её. Если там мина — вы проиграли. Если же мины есть в рядом стоящих клетках, то на месте клетки, на которую вы нажали, появляется число, означающее количество мин вокруг нее. А если вокруг безопасно, то все близлежащие «безопасные» клетки раскроются.

Если вы уверены, что в какой-то клетке находится мина, смело жмите на нее правой кнопкой — клетка отметится флажком и вы не сможете «раскрыть» клетку, пока не снимите флажок. Это делается для того, чтобы вы случайно не открыли клетку, в которой точно находится мина.

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

Базовая клетка

Создайте новый проект Unity, добавьте на сцену кубик (Cube) и назовите его Tile. Перетащите его в папку с проектом для того, чтобы превратить его в префаб. Кубик пока ничего не умеет, но мы воспользуемся им, чтобы построить игровое поле, а затем добавим ему новых возможностей.

Генератор клеток

Создайте новый пустой объект и назовите его Grid. Так же как и кубик, сделайте его префабом (перетащив в папку с проектом). Этот объект — наш будущий генератор клеток, который и будет создавать игровое поле.

Создайте новый сценарий (в качестве языка программирования будем на этот раз использовать JavaScript) и также назовите его Grid. Прикрепите его к нашему генератору и пропишите в скрипте:

			public var tilePrefab: GameObject;
public var numberOfTiles: int = 10;
public var distanceBetweenTiles: float = 1.0;
 
function Start()
{
    CreateTiles();
}
 
function CreateTiles()
{
 
}
		

Сохраните скрипт и перетащите наш кубик Tile в поле Tile Prefab компонента Script объекта Grid. У вас должно получится вот так:

Пишем сапёр на Unity. Настройка 1

Названия переменных говорящие: numberOfTiles позволяет задать количество клеток на игровом поле, а distanceBetweenTiles задает расстояние между клетками.

В настоящий момент генератор клеток ничего не делает. Давайте добавим несколько строчек кода в метод CreateTiles:

			var xOffset: float = 0.0;
 
for(var tilesCreated: int = 0; tilesCreated < numberOfTiles; tilesCreated += 1)
{
    xOffset += distanceBetweenTiles;
    Instantiate(tilePrefab, Vector3(transform.position.x + xOffset, transform.position.y, transform.position.z), transform.rotation);
}
		

Если вы нажмете на кнопку Play, то увидите нечто подобное:

Пишем сапёр на Unity. Настройка 2

Функция CreateTiles создает копии префаба кубика (столько раз, сколько мы задали) и помещает их в линию, где расстояние между кубиками равно distanceBetweenTiles. Попробуйте подобрать оптимальное расстояние, чтобы будущее поле выглядело красиво.

Но для Сапёра нам нужно поле в виде сетки, а не линии. Чтобы достичь этого, добавьте в сценарий объекта Grid новую переменную, которая будет отвечать за количество кубиков в строке:

public var tilesPerRow: int = 4;

И перепишем метод CreateTiles:

			function CreateTiles()
{
    var xOffset: float = 0.0;
    var zOffset: float = 0.0;
 
    for(var tilesCreated: int = 0; tilesCreated < numberOfTiles; tilesCreated += 1)
    {
        xOffset += distanceBetweenTiles;
         
        if(tilesCreated % tilesPerRow == 0)
        {
            zOffset += distanceBetweenTiles;
            xOffset = 0;
        }
     
        Instantiate(tilePrefab, Vector3(transform.position.x + xOffset, transform.position.y, transform.position.z + zOffset), transform.rotation);
    }
}
		

Запустив игру, вы увидите такую картину:

Пишем сапёр на Unity. Настройка 3

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

Добавление мин

Теперь, когда мы создали основу, давайте поработаем с минами. Создайте новый сценарий, назовите его Tile и прикрепите его к префабу Tile. Добавьте строчку с объявлением переменной:

public var isMined: boolean = false;

Этот параметр нам и скажет, есть ли в клетке мина. Далее нам нужно позволить генератору создавать новый объект, который мы только что создали. Для этого измените тип переменной tilePrefab с GameObject на Tile в скрипте объекта Grid:

public var tilePrefab: Tile;

А теперь добавим новые переменные:

			static var tilesAll: Tile[];
static var tilesMined: Array;
static var tilesUnmined: Array;
		

И не забудем про инициализацию:

			tilesAll = new Tile[numberOfTiles];
tilesMined = new Array();
tilesUnmined = new Array();
		

И немного изменим команду Instantiate:

			var newTile = Instantiate(tilePrefab, Vector3(transform.position.x + xOffset, transform.position.y, transform.position.z + zOffset), transform.rotation);
tilesAll[tilesCreated] = newTile;
		

А в конце сценария выполним метод AssignMines. Вот так будет выглядеть измененный метод CreateTiles:

			function CreateTiles()
{
    tilesAll = new Tile[numberOfTiles];
    tilesMined = new Array();
    tilesUnmined = new Array();
     
    var xOffset: float = 0.0;
    var zOffset: float = 0.0;
     
    for(var tilesCreated: int = 0; tilesCreated < numberOfTiles; tilesCreated += 1)
    {
        xOffset += distanceBetweenTiles;
     
        if(tilesCreated % tilesPerRow == 0)
        {
            zOffset += distanceBetweenTiles;
            xOffset = 0;
        }
     
        var newTile = Instantiate(tilePrefab, Vector3(transform.position.x + xOffset, transform.position.y, transform.position.z + zOffset), transform.rotation);
        tilesAll[tilesCreated] = newTile;
    }
     
    AssignMines();
}
		

Метод AssignMines случайным образом задаст некоторым клеткам мины:

			function AssignMines()
{
    tilesUnmined = tilesAll;
 
    for(var minesAssigned: int = 0; minesAssigned < numberOfMines; minesAssigned += 1)
    {
        var currentTile: Tile = tilesUnmined[Random.Range(0, tilesUnmined.length)];
         
        tilesMined.Push(currentTile);
        tilesUnmined.Remove(currentTile);
         
        currentTile.GetComponent(Tile).isMined = true;
    }
}
		

Как работает функция AssignMines? Дело в том, что все созданные клетки в методе CreateTiles помещаются в массив tilesAll. И уже в методе AssignMines они копируются в массив tilesUnmined. Далее случайным образом отбирается numberOfMines плиток. Параметр isMined отобранных плиток устанавливается в true, а они сами удаляются из массива tilesUnmined и помещаются в tilesMined.

На данный момент обычные клетки никак не отличаются от тех, которые с минами. Этот момент мы обязательно исправим, но вы уже сейчас можете проверить демо-игру, в которой можете по-своему настроить поле и количество мин (красные кубики).

Дизайн плиток

Сейчас наши клетки выглядят как кубики (по сути это и есть кубики). Давайте изменим их внешний вид.

В исходных файлах вы найдете 3D модель puzzleObjects.fbx. Скопируйте файл в папку с проектом для дальнейшего использования (убедитесь в том, что он импортируется с размером, установленным в 1):

Пишем сапёр на Unity. Настройка 4

Перейдите в настройки префаба Tile и поменяйте значение поля Mesh Filter на tileImproved.

Пишем сапёр на Unity. Настройка 5

И здесь же сбросьте значения компонента Box Collider, нажав на него правой кнопкой мыши и выбрав Reset.

Пишем сапёр на Unity. Настройка 6

И наконец, присвойте объекту новый материал, чтобы он не имел стандартный белый цвет.

Пишем сапёр на Unity. Настройка 7

Обратите внимание, мы поменяли параметры префаба лишь раз, но изменениям будут подвергнуты все созданные из него объекты. В этом и есть преимущество этих объектов.

Добавление чисел

Для того, чтобы показывать количество мин вокруг открывшейся клетки, воспользуемся 3D текстом. Создайте, выбрав GameObject -> Create Other -> 3D Text, и добавьте его к объекту Tile. Вот, как это должно выглядеть:

Пишем сапёр на Unity. Настройка 8

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

Пишем сапёр на Unity. Настройка 9
Пишем сапёр на Unity. Настройка 10

Теперь мы должны получить доступ к тексту из кода. Добавьте следующую строчку в скрипт объекта Tile:

public var displayText: TextMesh;

Перетащите объект текста в новое поле компонента Script объекта Tile:

Пишем сапёр на Unity. Настройка 11

Вывод

И на этом наша статья подходит к концу. Мы создали функциональную основу для игры в жанре головоломок. Эта основа может быть использована вами не только для Сапёра.

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

Перевод статьи «Build a Grid-Based Puzzle Game Like Minesweeper in Unity: Setup»

Следите за новыми постами
Следите за новыми постами по любимым темам
17К открытий19К показов