Создаем игру жанра «Три в ряд» на Unity
В игре «3 в ряд» цель проста: меняйте местами фигурки, пока их не станет 3 или более в один ряд. Когда совпадение сделано, эти плитки удаляются, а пустые места заполняются новыми. Это позволяет игрокам набирать потенциальные комбо и тонны очков!
В этом уроке вы научитесь делать следующее:
- Создавать доску, заполненную игровыми плитками
- Выбирать и отменять выбор плитки при нажатии мыши
- Определять соседние тайлы с помощью лучей Raycast
- Менять тайлы местами
- Проверять совпадение 3х или более блоков с помощью Raycast
- Заполнять пустые плитки после найденных совпадений (комбо) из 3 или более плиток
- Вести счет и считать ходы
Примечание. В этом руководстве предполагается, что вы знакомы с редактором Unity, знаете, как редактировать код в редакторе кода, и что у вас есть базовые знания C#.
Изучите некоторые другие наши уроки по Unity, если вам ещё нужно оттачивать свои навыки в Unity
Начнём
Скачайте стартовый проект Match 3 и извлеките его в каталог по вашему выбору.
Откройте стартовый проект в Unity. Активы отсортированы в нескольких папках:
- Animations: Содержит анимацию над панелью, когда игра заканчивается. Если вам нужно освежить в памяти знания про работу с анимацией, ознакомьтесь с нашим туториалом Введение в Unity Animation.
- Audio: Содержит музыку и аудио эффекты, используемые в игре.
- Fonts: Здесь лежат шрифты, используемые в игре.
- Prefabs: Тут находятся разные менеджеры, пользовательский интерфейс и префабы листов.
- Scenes: Содержит меню и игровую сцену.
- Scripts: Содержит скрипты, используемые в игре. cs и Tile.cs — это те, которые вы будете редактировать.
- Sprites: Содержит ресурсы пользовательского интерфейса и различные спрайты персонажей, которые будут использоваться в качестве фишек на доске.
Создаём игровую доску
Если он еще не открыт, откройте игровую сцену и нажмите кнопку воспроизведения. Это просто синий фон со счетом набранных очков и сделанных ходов. Время это исправить!
Сначала создайте пустой игровой объект и дайте ему имя BoardManager.
BoardManager отвечает за создание доски и ее заполнение тайлами.
Затем найдите BoardManager.cs в папке «Сценарии \ Доска и сетка» в окне «Проект». Перетащите его на пустой игровой объект BoardManager в окошко иерархии.
Теперь у вас должно получиться как на скриншоте:
Пришло время погрузиться в некоторый код. Откройте BoardManager.cs взгляните на код, там уже есть несколько сценариев и скриптов:
public static BoardManager instance; // 1
public List<Sprite> characters = new List<Sprite>(); // 2
public GameObject tile; // 3
public int xSize, ySize; // 4
private GameObject[,] tiles; // 5
public bool IsShifting { get; set; } // 6
void Start ()
{
instance = GetComponent<BoardManager>(); // 7
Vector2 offset = tile.GetComponent<SpriteRenderer>().bounds.size;
CreateBoard(offset.x, offset.y); // 8
}
private void CreateBoard (float xOffset, float yOffset)
{
tiles = new GameObject[xSize, ySize]; // 9
float startX = transform.position.x; // 10
float startY = transform.position.y;
for (int x = 0; x < xSize; x++) { // 11
for (int y = 0; y < ySize; y++) {
GameObject newTile = Instantiate(
tile,
new Vector3(startX + (xOffset * x), startY + (yOffset * y), 0), tile.transform.rotation);
tiles[x, y] = newTile;
}
}
}
1. Другим сценариям потребуется доступ к BoardManager.cs, поэтому сценарий использует шаблон Singleton со статической переменной с именем instance, что позволяет вызывать его из любого сценария.
2. characters – является полным списком спрайтов, которые вы будут использоваться в качестве фрагментов плиток.
3. Игровой объект tile будет служить в качестве префаба, то есть это плитка игрового объекта, будет создана при генерации игрового поля.
4. xSize и ySize — это размеры X и Y платы.
5. Также есть двумерный массив с именами плиток, который будет использоваться для хранения плиток на доске.
6. Инкапсулированный bool IsShifting также очень важный элемент; он сообщит игре, когда совпадение найдено, и игровое поле пополняется новыми плитками.
7. Метод Start () устанавливает синглтон со ссылкой на BoardManager.
8. Вызовите CreateBoard (), передавая границы размера спрайта тайла.
9. В CreateBoard () исходя из названия, будут созданы плитки 2D-массива.
10. Найдите стартовые позиции для генерации доски.
11. Циклически перебирайте xSize и ySize, создавая экземпляр newTile каждую итерацию, чтобы получить сетку из строк и столбцов.
Затем найдите свои спрайты персонажей в разделе «Спрайты \ персонажи» в окне «Проект». Выберите BoardManager в окне Иерархия.
В окне «Инспектора» измените значение «Размер символа» для компонента сценария BoardManager на 7. Это добавит 7 элементов в массив «characters» и отобразит слоты для них в окне «Инспектора».
Теперь перетащите каждого персонажа в пустые слоты. Наконец, найдите префаб Tile внутри каталога Prefabs и перетащите его в слот Tile.
Когда закончите работу, ваша сцена будет выглядеть следующим образом:
Далее снова выбирайте BoardManager. В компоненте BoardManager в окне «Инспектора» задайте «Размер X» на 8 и «Размер Y» на 12. Это размер плитки, с которым вы будете работать в этом учебном пособии.
Нажмите кнопку Играть. Доска генерируется, но она странным образом выходит за пределы экрана:
Это потому, что ваша доска генерирует плитки вверх и вправо, причем первая плитка начинается с позиции BoardManager.
Чтобы это исправить, отрегулируйте положение BoardManager так, чтобы оно находилось внизу слева от поля зрения вашей камеры. Установите позицию X BoardManager на —2,66, а позицию Y на —3,83.
Запускайте игру. Уже выглядит лучше, но игра не будет интересной, если все плитки одинаковы. К счастью, есть простой способ рандомизировать доску.
Вносим элемент случайности в нашу доску
Откройте скрипт BoardManager и добавьте эти строки кода в метод CreateBoard, прямо под плитками [x, y] = newTile ;:
newTile.transform.parent = transform; // 1
Sprite newSprite = characters[Random.Range(0, characters.Count)]; // 2
newTile.GetComponent<SpriteRenderer>().sprite = newSprite; // 3
Эти строки делают три ключевых вещи:
- Добавляют все тайлы в BoardManager, чтобы сохранить иерархию в редакторе.
2. Случайным образом выбирают спрайт из тех, которые вы ранее уже перетаскивали.
3. Устанавливают спрайт вновь созданной плитки на случайно выбранный спрайт.
Запустите игру, и вы увидите рандомизированную доску:
Как вы возможно заметили, ваша доска может генерировать подходящую комбинацию из 3 в начале, и это невероятно круто
Предотвратите повторение плиток
Плитки генерируется вверх, а затем вправо, поэтому для исправления «автоматического» комбинирования из 3х плиток, вам необходимо определить, какой спрайт находится слева от вашей новой плитки, и какой под вашей новой плиткой.
Для этого вам потребуется создать две переменные Sprite в методе CreateBoard прямо над двойными циклами for:
Sprite[] previousLeft = new Sprite[ySize];
Sprite previousBelow = null;
Эти переменные будут использоваться для хранения ссылки на соседние плитки, чтобы вы могли заменить их characters.
Посмотрите на изображение ниже:
Цикл перебирает все плитки снизу слева и поднимается по одной плитке за раз. Каждая итерация получает символ, отображаемый слева и под текущей плиткой, и удаляет его из списка новых возможных characters.
Случайный персонаж вытаскивается из этого списка и назначается как левой, так и нижней плитке.
Это гарантирует, что с самого начала уровня, на доске не будет строки из 3 одинаковых плиток.
Для этого вам нужно добавить следующие строки прямо над Sprite newSprite = characters [Random.Range (0, characters.Count)] ;
List<Sprite> possibleCharacters = new List<Sprite>(); // 1
possibleCharacters.AddRange(characters); // 2
possibleCharacters.Remove(previousLeft[y]); // 3
possibleCharacters.Remove(previousBelow);
- Создайте список возможных characters для этого спрайта.
2. Добавьте все characters в список.
3. Удалите characters слева а также снизу от текущего спрайта из списка всех позиций characters.
Замените этот код:
Sprite newSprite = characters[Random.Range(0, characters.Count)];
На этот:
Sprite newSprite = possibleCharacters[Random.Range(0, possibleCharacters.Count)];
Код выберет новый спрайт из списка возможных значений и сохранит его.
Наконец, добавьте эти строки под newTile.GetComponent (). Sprite = newSprite ;:
previousLeft[y] = newSprite; previousBelow = newSprite;
Этот параметр установит newSprite для плитки слева и снизу от текущей для использования следующей итерации цикла.
Запустите игру и проверьте вашу новую динамическую сетку с плитками, которые не повторяются.
Меняем плитки местами
Самая важная игровая механика в играх «3 в ряд» — это выбор и замена смежных плиток, чтобы вы могли выстроиться в ряд 3 или 4 подряд. Для достижения этого вам нужно будет сделать некоторые дополнительные сценарии. Сначала выбираем плитку.
Откройте Tile.cs в редакторе исходного кода. Для удобства, этот скрипт уже был размечен с несколькими переменными и двумя методами: Select и Deselect.
Select сообщает игре, что этот фрагмент плитки выбран, меняет цвет плитки и воспроизводит звуковой эффект выбора. Отмена выбора возвращает спрайт к его первоначальному цвету и сообщает игре, что в данный момент объект не выбран.
Вам ещё не хватает способа взаимодействия игрока с плитками. Кажется, левый щелчок мыши является разумным вариантом для элементов управления.
В Unity есть встроенный метод поведения MonoBehaviour, готовый для использования: OnMouseDown.
Добавьте следующий метод в Tile.cs, прямо под методом Deselect:
void OnMouseDown() {
// 1
if (render.sprite == null || BoardManager.instance.IsShifting) {
return;
}
if (isSelected) { // 2 Is it already selected?
Deselect();
} else {
if (previousSelected == null) { // 3 Is it the first tile selected?
Select();
} else {
previousSelected.Deselect(); // 4
}
}
}
- Убедитесь, что игра разрешает вам выбрать любую плитку. Иногда нужно сделать так, чтобы игроки не могли взаимодействовать с плитками, например, когда игра заканчивается или если плитка пуста.
2. Событие (isSelected) определяет, выбрать или отменить выбор плитки. Если он уже выбран, отмените выбор.
3. Проверьте, не выбрана ли уже другая плитка. Если значение previousSelected равно нулю, оно является первым, поэтому выберите его.
4. Ну а если это был не первый выбранный вариант, отмените выбор всех плиток.
Сохраняйте свой скрипт и возвращайтесь в редактор.
Теперь вы сможете выбирать и отменять выбор плиток, щелкая их левой кнопкой мыши.
Порядок? Теперь вы можете добавить механизм для свапа плиток.
Меняем расположение плиток
Начните с открытия Tile.cs и добавления следующего метода с именем SwapSpriteunderneath к методу OnMouseDown:
public void SwapSprite(SpriteRenderer render2) { // 1
if (render.sprite == render2.sprite) { // 2
return;
}
Sprite tempSprite = render2.sprite; // 3
render2.sprite = render.sprite; // 4
render.sprite = tempSprite; // 5
SFXManager.instance.PlaySFX(Clip.Swap); // 6
}
Этот метод поменяет местами спрайты 2 тайлов. Вот как это работает:
- Установите значение SpriteRenderer с именем render2 в качестве параметра, который будет использоваться вместе с render для обмена спрайтами.
2. Проверьте render2 против SpriteRenderer текущей плитки. Если они одинаковые, ничего не делайте, так как обмен двух одинаковых спрайтов не имеет большого смысла в игровом процессе.
3. Создайте tempSprite для хранения спрайта render2.
4. Поменяйте местами второй спрайт, установив его на первый.
5. Поменяйте местами первый спрайт, установив для него второй (который был добавлен в tempSprite.
6. Воспроизведите звуковой эффект.
С реализованным методом SwapSprite, вы можете вызывать его из OnMouseDown
Добавьте эту строку прямо над previousSelected.Deselect (); в операторе else метода OnMouseDown:
SwapSprite(previousSelected.render);
Данный метод поменяет плитки местами, как только вы выбрали вторую из них.
Сохраняйте ваш прогресс и заходите обратно в редактор.
Запускайте вашу игру и попробуйте немного поиграть! Теперь вы можете выбрать две разные плитки и поменять их местами:
Находим соседние плитки
Вы наверное, заметили, что можете поменять любые две плитки на доске. Это делает игру слишком простой. Вам понадобится чекер, чтобы убедиться, что плитки можно поменять местами только с соседними.
Но как легко найти плитки рядом с данной плиткой?
Откройте Tile.cs и добавьте следующий метод под методом SwapSprite:
private GameObject GetAdjacent(Vector2 castDir) {
RaycastHit2D hit = Physics2D.Raycast(transform.position, castDir);
if (hit.collider != null) {
return hit.collider.gameObject;
}
return null;
}
Этот метод получает информацию а соседней плитке, отправляя лучевую трансляцию в цель, указанную castDir. Если в этом направлении найден тайл, он вернёт GameObject.
Затем добавьте следующий метод ниже метода GetAdjacent:
private List<GameObject> GetAllAdjacentTiles() {
List<GameObject> adjacentTiles = new List<GameObject>();
for (int i = 0; i < adjacentDirections.Length; i++) {
adjacentTiles.Add(GetAdjacent(adjacentDirections[i]));
}
return adjacentTiles;
}
Этот метод использует GetAdjacent () для генерации списка плиток, окружающих текущую плитку. Это перебирает все направления и добавляет любые соседние, найденные к соседним направлениям, которые были определены в верхней части скрипта.
С помощью новых удобных методов, которые вы только что создали, теперь вы можете принудительно менять плитку только с соседними плитками.
Замените следующий код в методе OnMouseDown:
else {
SwapSprite(previousSelected.render);
previousSelected.Deselect();
}
этим кодом:
else {
if (GetAllAdjacentTiles().Contains(previousSelected.gameObject)) { // 1
SwapSprite(previousSelected.render); // 2
previousSelected.Deselect();
} else { // 3
previousSelected.GetComponent<Tile>().Deselect();
Select();
}
}
- Вызовите GetAllAdjacentTiles и проверьте, находится ли игровой объект previousSelected в списке возвращенных соседних плиток.
2. Поменяйте местами спрайт плиток.
3. Плитка не находится рядом с ранее выбранной, отмените выбор предыдущей и выберите новую выбранную плитку.
Сохраните этот скрипт и вернитесь в редактор Unity.
Поиграйте в свою игру и проверьте, что все работает, как задумано. Теперь вы должны поменять местами только две плитки, которые находятся рядом друг с другом.
Далее вам нужно разобраться с самым важным элементом игры — комбо!
Комбинирование тайлов / плиток
Соответствие может быть разбито на несколько ключевых шагов:
- Найдите 3 или более одинаковых спрайтов рядом друг с другом и сочтите это как валидное совпадение 3 в ряд.
- Удалите подходящие плитки.
- Переместите плитки вниз, чтобы заполнить пустое пространство.
- Заполните пустые плитки вдоль верхней части.
- Проверьте на другое совпадение.
- Повторяйте, пока больше не будет найдено совпадений.
Откройте файл Tile.cs и добавьте следующий метод ниже метода GetAllAdjacentTiles:
private List<GameObject> FindMatch(Vector2 castDir) { // 1
List<GameObject> matchingTiles = new List<GameObject>(); // 2
RaycastHit2D hit = Physics2D.Raycast(transform.position, castDir); // 3
while (hit.collider != null && hit.collider.GetComponent<SpriteRenderer>().sprite == render.sprite) { // 4
matchingTiles.Add(hit.collider.gameObject);
hit = Physics2D.Raycast(hit.collider.transform.position, castDir);
}
return matchingTiles; // 5
}
Итак, вас интересует что здесь происходит?
- Этот метод принимает Vector2 в качестве параметра, который будет направлением, в котором будут запускаться все лучи к тайлам.
2. Создайте новый список GameObjects для хранения всех соответствующих плиток.
3. Запустите луч из плитки в направлении castDir.
4. Продолжайте запускать новые лучи, пока ваша лучевая трансляция не достигнет тайла, либо спрайт тайлов будет отличаться от возвращенного спрайта объекта. Если оба условия соблюдены, тогда это корректное совпадение, добавляйте его в свой список.
5. Верните список совпадающих спрайтов.
Сохраняйте темп и добавьте следующий логический элемент в начало файла, прямо над методом Awake:
private bool matchFound = false;
Когда совпадение найдено, для переменной будет установлено значение true.
Теперь добавьте следующий метод под методом FindMatch:
private void ClearMatch(Vector2[] paths) // 1
{
List<GameObject> matchingTiles = new List<GameObject>(); // 2
for (int i = 0; i < paths.Length; i++) // 3
{
matchingTiles.AddRange(FindMatch(paths[i]));
}
if (matchingTiles.Count >= 2) // 4
{
for (int i = 0; i < matchingTiles.Count; i++) // 5
{
matchingTiles[i].GetComponent<SpriteRenderer>().sprite = null;
}
matchFound = true; // 6
}
}
Этот метод находит все совпадающие плитки по заданным путям, а затем очищает совпадения соответственно.
- Возьмите массив путей Vector2; это пути, по которым плитка будет транслироваться.
2. Создайте список GameObject для хранения совпадений.
3. Выполните итерацию по списку путей и добавьте любые совпадения в список соответствия.
4. Продолжайте работу, до тех пор, пока не нашли 2 или более совпадения с плитками. Вы можете задаться вопросом, почему здесь достаточно двух подходящих плиток, потому что третье совпадение — ваша начальная плитка.
5. Переберите все подходящие плитки и удалите их спрайты, установив для него значение null.
6. Установите для флага matchFound значение true.
Теперь, когда вы нашли совпадение, вам нужно очистить плитки. Добавьте следующий метод ниже метода ClearMatch:
public void ClearAllMatches() {
if (render.sprite == null)
return;
ClearMatch(new Vector2[2] { Vector2.left, Vector2.right });
ClearMatch(new Vector2[2] { Vector2.up, Vector2.down });
if (matchFound) {
render.sprite = null;
matchFound = false;
SFXManager.instance.PlaySFX(Clip.Clear);
}
}
Это запустит метод домино. Он вызывает ClearMatch как для вертикального, так и для горизонтального соответствия. ClearMatch будет вызывать FindMatch для каждого направления, влево/вправо, либо вверх и вниз.
Если вы найдете совпадение по горизонтали или вертикали, тогда вы устанавливаете текущий спрайт на ноль, сбрасываете matchFound на false и воспроизводите соответствующий звуковой эффект.
Чтобы все это работало корректно, вам нужно вызывать ClearAllMatches () всякий раз, когда вы делаете свап.
В методе OnMouseDown добавьте следующую строку непосредственно перед предыдущей Selected.Deselect ();
нужный вам код:
previousSelected.ClearAllMatches();
Теперь добавьте следующий код непосредственно после previousSelected.Deselect ();
строчка кода:
ClearAllMatches();
Вам нужно вызывать ClearAllMatches для предыдущего выбранного, а также для текущего тайла, потому что есть шанс, что оба могут иметь совпадение.
Сохраните этот скрипт и вернитесь в редактор. Нажмите кнопку воспроизведения и проверьте механику матча, если вы выстроите 3 плитки одного типа в ряд, они должны исчезнуть.
Чтобы заполнить пустое пространство, вам нужно сдвинуть и снова заполнить доску.
Перемещение, и автозаполнение игрового поля новыми плитками
Прежде чем вы сможете сдвинуть плитки, вам нужно найти пустые.
Откройте BoardManager.cs и добавьте следующую сопрограмму ниже метода CreateBoard:
public IEnumerator FindNullTiles() {
for (int x = 0; x < xSize; x++) {
for (int y = 0; y < ySize; y++) {
if (tiles[x, y].GetComponent<SpriteRenderer>().sprite == null) {
yield return StartCoroutine(ShiftTilesDown(x, y));
break;
}
}
}
}
Примечание: После добавления этой сопрограммы вы получите сообщение об отсутствии ShiftTilesDown. Вы можете спокойно игнорировать эту ошибку, так как добавите сопрограмму в следующий раз!
Эта сопрограмма будет проходить по всей доске в поисках кусочков плитки с нулевыми спрайтами. Когда он находит пустую плитку, он запускает другую сопрограмму ShiftTilesDown для обработки фактического смещения.
Добавьте следующую сопрограмму под предыдущей:
private IEnumerator ShiftTilesDown(int x, int yStart, float shiftDelay = .03f) {
IsShifting = true;
List<SpriteRenderer> renders = new List<SpriteRenderer>();
int nullCount = 0;
for (int y = yStart; y < ySize; y++) { // 1
SpriteRenderer render = tiles[x, y].GetComponent<SpriteRenderer>();
if (render.sprite == null) { // 2
nullCount++;
}
renders.Add(render);
}
for (int i = 0; i < nullCount; i++) { // 3
yield return new WaitForSeconds(shiftDelay);// 4
for (int k = 0; k < renders.Count - 1; k++) { // 5
renders[k].sprite = renders[k + 1].sprite;
renders[k + 1].sprite = null; // 6
}
}
IsShifting = false;
}
Функционал ShiftTilesDown принимает в позиции X, Y позиции и задержку между ними. X и Y используются для определения того, какие плитки поменять местами. Вам нужно, чтобы плитки перемещались вниз, поэтому значения для X останется постоянным, а Y будет изменится.
Функционал сопрограммы:
- Перебирает плитки и находи, сколько пробелов нужно для смещения вниз.
2. Сохраняет количество пробелов в целом числе с именем nullCount.
3. Повторяет цикл, чтобы начать фактическое переключение.
4. Пауза в секундах — shiftDelay.
5. Переберет каждый SpriteRenderer в списке рендеров.
6. Меняет местами каждый спрайт с тем, который был найден над ним, до тех пор, пока не будет достигнут конец и последний спрайт не будет установлен на ноль
Теперь вам нужно остановиться и запустить сопрограмму FindNullTiles всякий раз, когда найдено совпадение.
Сохраните скрипт BoardManager и откройте файл Tile.cs. Добавьте следующие строки в код:
StopCoroutine(BoardManager.instance.FindNullTiles()); StartCoroutine(BoardManager.instance.FindNullTiles());
Это остановит сопрограмму FindNullTiles и снова запустит ее с самого начала..
Сохраните этот скрипт и вернитесь к редактору. Сыграйте в игру снова и сделайте выбейте несколько совпадений, если вы заметите, что на доске заканчиваются плитки, когда вы получаете матчи. Чтобы сделать нескончаемую доску, вам нужно ее заново заполнять, когда она очищается.
Открывайте BoardManager.cs, сюда вам нужно добавить новый метод под ShiftTilesDown:
private Sprite GetNewSprite(int x, int y) {
List<Sprite> possibleCharacters = new List<Sprite>();
possibleCharacters.AddRange(characters);
if (x > 0) {
possibleCharacters.Remove(tiles[x - 1, y].GetComponent<SpriteRenderer>().sprite);
}
if (x < xSize - 1) {
possibleCharacters.Remove(tiles[x + 1, y].GetComponent<SpriteRenderer>().sprite);
}
if (y > 0) {
possibleCharacters.Remove(tiles[x, y - 1].GetComponent<SpriteRenderer>().sprite);
}
return possibleCharacters[Random.Range(0, possibleCharacters.Count)];
}
Этот фрагмент создает список всех возможных значений, которыми может быть заполнен спрайт. После чего, он использует серию операторов if, чтобы убедиться, что вы не выходите за пределы. Теперь открывайте операторы if, и удаляете возможные совпадения, которые могут привести к рандомному совпадению, когда будет выбран новый спрайт. Наконец, вы возвращаете случайный спрайт из списка возможных.
В сопрограмме ShiftTilesDown замените код:
renders[k + 1].sprite = null;
на:
renders[k + 1].sprite = GetNewSprite(x, ySize - 1);
Это гарантирует, что доска будет всегда заполнена тайлами.
Когда состязание сделано и сдвинуты фигуры, есть шанс, что может быть сформировано другое совпадение. Теоретически, это может продолжаться вечно, поэтому вам нужно продолжать проверять, пока доска не найдет все возможные совпадения.
Комбо
Повторно проверив все плитки после того, как совпадение найдено, вы сможете найти любые возможные комбинации, которые могли быть созданы во время процесса сдвига.
Откройте BoardManager.cs и найдите метод FindNullTiles ().
Добавьте следующий код для в нижней части метода, под циклом for:
for (int x = 0; x < xSize; x++) {
for (int y = 0; y < ySize; y++) {
tiles[x, y].GetComponent<Tile>().ClearAllMatches();
}
}
После всей этой тяжелой работы, пришло время убедиться, что все работает как и задумывалось.
Сохраните свою работу и запустите игру. Начните менять плитки местами и обратите внимание на то, как бесконечный запас новых плиток, будет держать игровое поле заполненным, пока вы играете.
Настроим счетчик для ведение счета
Пришло время отслеживать результаты игры, за ходами игрока и его счетом.
За это у нас будут отвечать параметры Score и MoveCounter.
Откройте GUIManager.cs, расположенный в папке Scripts \ Managers в вашем любимом редакторе кода. Этот скрипт обрабатывает аспекты пользовательского интерфейса игры, в том числе счетчик ходов и хранитель счета.
Чтобы начать, добавьте эту переменную в начало файла, под private int Score;
private int moveCounter;
Теперь добавьте код в начало метода Awake (), чтобы инициализировать количество возможных ходов, которые может сделать игрок:
moveCounter = 60;
moveCounterTxt.text = moveCounter.ToString();
Теперь вам нужно инкапсулировать оба целых числа, чтобы вы могли обновлять текст интерфейса каждый раз, когда обновляете значение. Добавьте следующий код прямо над методом Awake ():
public int Score {
get {
return score;
}
set {
score = value;
scoreTxt.text = score.ToString();
}
}
public int MoveCounter {
get {
return moveCounter;
}
set {
moveCounter = value;
moveCounterTxt.text = moveCounter.ToString();
}
}
Это гарантирует, что при каждом изменении переменных значений Score и MoveCounter, текстовые компоненты, представляющие их, также будут обновляться. Вы могли бы поместить текстовое обновление в метод Update (), но сделать это таким способом намного лучше для производительности, особенно потому, что это включает обработку строк.
Время начинать добавлять очки и отслеживать ходы игрока! Каждый раз, когда игрок выбывает комбо из 3х плиток, он получает вознаграждение в виде нескольких очков
Сохраняйте этот скрипт и откройте BoardManager.cs. Пропишите следующий код в методе ShiftTilesDown, прямо над yield return new WaitForSeconds (shiftDelay);
GUIManager.instance.Score += 50;
Это событие, будет увеличивать счет каждый раз, когда будет найдена пустая плитка.
В Tile.cs добавьте следующую строку ниже SFXManager.instance.PlaySFX (Clip.Swap); в методе ClearAllMatches:
GUIManager.instance.MoveCounter--;
Это будет уменьшать значение MoveCounter каждый раз при замене спрайта.
Сохраните свою работу и проверьте, правильно ли работают счетчики ходов и очков. Каждый ход должен вычитать счетчик ходов, а каждое совпадение должно давать несколько очков.
Настройка экрана конца игры – Game Over
Игра должна закончиться, когда счетчик хода достигнет 0. Откройте GUIManager.cs и добавьте следующий оператор if в установщик MoveCounters, прямо под moveCounter = value ;
if (moveCounter <= 0) {
moveCounter = 0;
GameOver();
}
В общем все должно работать, если вы всё правильно настроили. Поскольку GameOver () вызывается прямо на последнем ходу, комбо не засчитываются в счет окончательного результата. За такую игру, вам максимум поставят одну звезду в отзыве!
Чтобы предотвратить это, вам нужно создать сопрограмму, которая ожидает, пока BoardManager.cs завершит все свои изменения. Затем вы можете вызвать GameOver ().
Добавьте следующую сопрограмму в GUIManager.cs под методом GameOver ()
private IEnumerator WaitForShifting() {
yield return new WaitUntil(()=> !BoardManager.instance.IsShifting);
yield return new WaitForSeconds(.25f);
GameOver();
}
Теперь замените следующую строку в установщике MoveCounter:
GameOver();
этим кодом:
StartCoroutine(WaitForShifting());
Это гарантирует, что все комбо будут подсчитаны до окончания игры.
Теперь сохраните весь ваш процесс, запускайте игру и получайте очки! :]
Что дальше?
Вы можете скачать готовый проект.
Теперь вы знаете, как сделать базовую игру «Матч 3» с помощью Unity! Я призываю вас продолжать работать над этим учебником! Попробуйте добавить следующее самостоятельно:
- Синхронизированный режим
- Различные уровни с различными размерами доски
- Бонусные баллы за комбо
- Добавьте несколько частиц, чтобы сделать крутые эффекты
Если вам понравился урок, посмотрите книгу Unity Games by Tutorials, которая обучает созданию 4 игр, скриптингу в C# и многому другому.
Дополнительные ресурсы для изучения:
- Unity scripting tutorials: видео руководства, обучающие основам скриптинга в Unity.
- Введение в Unity UI: серия уроков по Unity UI.
- Introduction To Unity Particle Systems: изучите как работает система частиц, которая позволяет делать красивые эффекты в играх.
Если у вас есть вопросы или замечания, пишите их в комментариях.
Здорово, но в самом начале жирнющий минус!!! — Скачайте стартовый проект… Блин, ну зачем? Просто с нуля начните и все. Ну нафига скачивать готовый проект и по нему пытаться учиться((( Это же как система ЕГЭ — думать не надо, надо знать правильный ответ. Считаю в корне не верным такой подход к изучению чего-либо!
В стартовом проекте есть ассеты — это удобно, чтобы сконцентрироваться на главном.
Пусть будет выбор )
Не работает пункт «Находим соседние плитки». Список заполняется объектом на который кликнули мышкой. Помогите. Заранее Спасибо!
Внимательно проверьте все. Либо скачайте итоговый проект и сверьтесь с ним.
Я сам проверял, все работает.
Все проверил. Итоговый проект скачивать не стал. Проблему решил отключением коллайдера у объекта по которому идет нажатие. Но все равно спасибо!
Можешь пожалуйста более точно расписать что ты сделал?
“Edit -> Project Settings -> Physcis 2D” and OFF the “Queries Start in Colliders” box.
Я извиняюсь но вопрос а где скачать готовый проект я его в упор не вижу ссылку?
Когда соединяешь плитки так, что и первая образует ряд, и вторая, то происходит баг, при котором опускается больше плиток чем необходимо и заменяют друг друга
Добрый день, создал два проекта в один импортировал Финальную часть в другой делал всё по инструкции, в итоге во второй перестали перетягиваться обьекты, не собираются блоки, почему так? я уже папку assets из финальной части полностью переместил во вторую часть проекта
Здравствуйте.
Попробуйте не перемещать, а скопировать проводником (не в интерфейсе Unity) и вставить также проводником в другую папку.
Уточните, пожалуйста, что значит «перестали перетягиваться», покажите скрин ошибки, если что-то выводится в консоли.
Подтверждаю — даже при создании нового проекта и копировании туда всех элементов из папки Assets предоставленного Вами готового проекта при запуске тайлы выделяются при нажатии мышкой, но не меняются местами вообще, даже если рядом нужный. Такая же ситуация происходит когда пошагово следуешь инструкциям урока: тайлы меняются местами когда это еще можно делать в произвольном месте (с любым тайлом), но как только добавляешь код, какой проверяет, чтобы тайлы для замены были обязательно рядом — замена просто перестает работать ((
*Дополняю: если загрузить Ваш готовый проект полностью (а не копировать только файлы из папки Assets), то работает все как надо — теперь вообще ума не приложу где собака зарыта (((
Нашел решение проблемы в комментариях оригинальной статьи: необходимо перейти в “Edit -> Project Settings -> Physics 2D” и убрать галочку где “Queries Start in Colliders”
Застопорилась на том, где нужно вставить код
StopCoroutine(BoardManager.instance.FindNullTiles());
StartCoroutine(BoardManager.instance.FindNullTiles());
Куда конкретно в Tiles.cs его надо вставить? Если в конце, то выдаёт ошибку.
Код скопирован из готового объекта, но проблема на уровне нахождения соседних плиток и перемещения. Если выключаю коллайдер, то плитки перемещаются, но комбо не ищет
Такой вопрос, иногда после сыгровки могут не появится некоторые новые плитки и просто пустой поле, в чём может быть проблема?
При сыгровке иногда не появляются некоторые новые плитки, просто пустое место, в чём может быть проблема?
Ошибка вылезает когда запускаю проект
Assets\Match 3 Starter\Scripts\Managers\GUIManager.cs(24,19): error CS0234: The type or namespace name ‘UI’ does not exist in the namespace ‘UnityEngine’ (are you missing an assembly reference?)
С родным не работает потому что 2015 си шарп не ставится
Юнити 2022
В итоге запустил проект в безоапасном режиме, поставил пакет Unity UI и дальше юнити сама запустилась и потом проект открылся.
решение нашёл тут
https://stackoverflow.com/questions/57071884/the-type-or-namespace-name-ui-does-not-exist-in-the-namespace-unityengine
Причина здесь, в частности, заключается в том, что в новых версиях Unity весь пользовательский интерфейс и графический интерфейс редактора были полностью обновлены, а теперь «наследие» UnityEngine.UIперенесено в пакет в PackageManager … таким образом, пространство имен просто больше не существует, если соответствующий пользовательский интерфейс Пакет не установлен для вашего проекта.
если это урок для начинающих то мне как начинающему не понятно с смамого начала — а как создать обект — что нажимать?
Ошибка — не появляются тайттлы в первом ряду после совпадения тайтлов и удаления их с доски
Сделал правки в этом файле
C:\unity project\Match 3 Game Starter\Assets\Match 3 Starter\Scripts\Board and Grid\BoardManager.cs
И после этого первый ряд тайтлов после исчезновения стал появляться, убил кучу времени чтобы разобраться.
private IEnumerator ShiftTilesDown(int x, int yStart, float shiftDelay = .03f)
{
IsShifting = true;
List renders = new List();
int nullCount = 0;
for (int y = yStart; y < ySize; y++)
{ // 1
SpriteRenderer render = tiles[x, y].GetComponent();
if (render.sprite == null)
{ // 2
nullCount++;
}
renders.Add(render);
}
for (int i = 0; i < nullCount; i++)
{
GUIManager.instance.Score += 50;
// 3
yield return new WaitForSeconds(shiftDelay);// 4
for (int k = 0; k < renders.Count; k++) //5 for (int k = 0; k < renders.Count — 1; k++) {
{
renders[k].sprite = GetNewSprite(x, ySize — 1);
}
}
IsShifting = false;
}