17.05.2017
88418
Казуальные игры в жанре match 3 (три в ряд) — одни из самых популярных на рынке. Многие играют в Candy Crush, Bejeweled и прочие. У этих игр простая цель: перемещать мозаичные элементы до тех пор, пока три одинаковых элемента не окажутся рядом. Когда это происходит, совпавшие элементы исчезают, а на их месте появляются другие. Игрок при этом набирает баллы.
В этом руководстве будут освещаться следующее:
Примечание. Предполагается, что вы уже знаете, как пользоваться редактором Unity, как редактировать код, и что у вас есть базовые знания по части C#.
Скачайте Match 3 how-to starter project, извлеките файл в определенное место.
Откройте Starter Project в Unity. Все активы содержатся в папках:
Animations: анимационный эффект, сообщающий о том, что игра завершена.
Audio: содержит музыку и звуковые эффекты, которые используются в игре.
Fonts: содержит шрифты.
Prefabs: содержит различные менеджеры, UI и префабы для элементов.
Scenes: папка с меню и сценариями игры.
Scripts: содержит скрипты игры. BoardManager.cs и Tile.cs — это то, что вы будете редактировать.
Sprites: содержат активы UI и различные спрайты, которые будут использоваться в качестве элементов мозаики.
Откройте сцену Game и кликните play. Простой фон со счетом и счетчиком движений.
Для начала создайте пустой объект игры и назовите его BoardManager.
BoardManager будет отвечать за генерирование досок и сохранение досок с элементами.
Сохраните BoardManager.cs в Scripts\Board and Grid в окне Project. Переместите в пустой объект 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;
}
}
}
Поместите свои спрайты символа в Sprites\Characters в окне проекта. Выберите BoardManager в окне иерархии. В окне-инспектор (Inspector) измените значение Character Size для компонента скрипта BoardManager на 7. Семь элементов добавятся в массив Characters и слоты для них высветятся в окне инспектор.
Теперь переместите каждый символ character в пустые слоты. И наконец, поместите префаб Tile в папку Prefabs, перетяните ее в слот Tile.
Ваша сцена должно выглядеть так:
Снова выберите BoardManager . В компоненте BoardManager в окне инспектор установите X Size — 8, а Y Size — 12. Это размер доски.
Кликните play. Доска появляется, но выходит за пределы экрана.
Элементы сместились вправо и вверх, первый элемент оказался на позиции BoardManager.
Исправьте позицию BoardManager.
Выберите BoardManager X — 2.66, и Y — 3.83.
Нажмите play. Так уже лучше, но нужно, чтоб элементы мозаики не были одинаковыми.
Откройте скрипт BoardManager и добавьте линии кода в метод CreateBoard, под tiles[x, y] = newTile;:
newTile.transform.parent = transform; // 1
Sprite newSprite = characters[Random.Range(0, characters.Count)]; // 2
newTile.GetComponent<SpriteRenderer>().sprite = newSprite; // 3
Эти линии выполняют 3 функции:
После запуска игры у вас должна появиться следующая доска:
Доска располагает элементы вверх и вправо. Поэтому, чтобы избежать «автоматического» совпадения, придется узнать, какой спрайт находится слева от нового элемента, и какой спрайт ниже нового элемента. Для этого понадобится создать две переменные Sprite в методе CreateBoard
Sprite[] previousLeft = new Sprite[ySize];
Sprite previousBelow = null;
Эти переменные устанавливают связь с соседними элементами, таким образом вы можете заменить символы characters.
Произвольный набор символов извлекается из списка и присоединяется к элементам слева и внизу. Это исключает вероятность появления трех одинаковых символов с самого начала.
Для этого добавьте следующие линии над 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);
Затем, замените эту линию:
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 будет присвоен элементам слева и снизу от текущего для следующей итерации.
Запустите игру и проверьте динамическую решетку с неповторяющимися элементами!
Основная механика геймплея игр три в ряд: мозаичные элементы выстраиваются в линии из трех элементов, когда пользователь перемещает их на экране.
Но для этого нужна дополнительная работа со скриптами. Вначале нужно выбрать элемент.
Откройте Tile.cs в редакторе кода. Для удобства скрипт уже содержит несколько переменных и два метода: Select и Deselect.
Select сообщает игре, что определенный элемент выбран, меняет цвет элемента и воспроизводит звуковой эффект. Deselect возвращает спрайту оригинальный цвет и сигнализирует, что никакой объект не выбран.
Чего недостает, так это возможности для игрока взаимодействовать с элементами.
Левый клик мыши представляется подходящей опцией управления.
В 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
}
}
}
Сохраните скрипт и вернитесь в редактор.
Теперь у вас должна быть возможность выбора/отмены элементов кликом левой кнопки мыши.
Теперь можно добавить механизм замены.
Откройте Tile.cs, и добавьте следующий метод — SwapSprite ниже метода 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
}
Этот метод заменит спрайты двух элементов. Вот как это работает:
Если они одинаковы, ничего не делайте, поскольку нет смысла менять два идентичных спрайта.
Метод SwapSprite реализован, теперь вы можете его вызвать из OnMouseDown.
Добавьте эту линию над previousSelected.Deselect(); в других операторах метода 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;
}
Этот метод вернет соседний элемент за счет отправки raycast в 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();
}
}
Сохраните скрипт и вернитесь в редактор Unity.
Воспроизведите игру, проверьте, все ли работает правильно. Заменяться должны только два соседних элемента.
Этот процесс можно разбить на несколько шагов:
Откройте 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
}
Добавьте следующий булев в верхнюю часть файла, над методом 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
}
}
Метод обнаружит все совпавшие элементы на заданном пути, затем очистит совпадение.
Теперь, когда вы нашли совпадение, необходимо очистить элементы. Добавьте следующий метод ниже метода 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 для каждого из направлений, справа и слева, или сверху вниз.
Если вы обнаружите совпадение, либо горизонтальное, либо вертикальное, тогда установите спрайт как null, переустановите matchFound как false, и воспроизведите эффект совпадающих элементов.
Для всей этой работы вам понадобится вызвать ClearAllMatches(), чтобы узнать, произошла ли замена элементов.
В методе OnMouseDown добавьте следующую линию перед линией previousSelected.Deselect();
previousSelected.ClearAllMatches();
Теперь поместите следующий код непосредственно перед previousSelected.Deselect();
ClearAllMatches();
Необходимо вызвать ClearAllMatches в previousSelected, а также текущий элемент, поскольку есть вероятность их совпадения.
Сохраните этот скрипт и перейдите к редактору. Нажмите кнопку play и протестируйте совпадающую механику, если вы выстроите 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 уже не будет. Вы можете проигнорировать эту ошибку, поскольку дальше вы добавите сопрограмму.
Эта сопрограмма по всей доске будет искать элементы с null. Когда будет найден пустой элемент, запустится еще одна сопрограмма 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-позицию и delay. X и Y определяют, какой элемент нужно переместить. Если нужно чтобы элементы сместились вниз, X останется неизменным, а Y изменится.
Сопрограмма выполняет следующее:
Когда найдено совпадение, необходимо остановиться и запустить сопрограмму FindNullTiles.
Сохранить скрипт BoardManager и открыть Tile.cs. Добавить следующие линии в метод ClearAllMatches(), над SFXManager.instance.PlaySFX(Clip.Clear);:
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();
}
}
После всей этой кропотливой работы, необходимо проверить, что все работает как надо.
Сохраните свою работу (Save) и запустите игру (Run). Начните замену элементов, доска будет постоянно заполняться новыми элементами.
Откройте GUIManager.cs (находится под Scripts\Managers) в редакторе кода. Этот скрипт отвечает за UI-аспекты игры, в т.ч. счетчик движений и очков.
Добавьте эту переменную в верх файла, ниже private int score;:
private int moveCounter;
Теперь добавьте это в верхнюю часть метода Awake(), чтобы установить число движений, которые может совершать игрок:
moveCounter = 60;
moveCounterTxt.text = moveCounter.ToString();
Теперь понадобится инкапсулировать данные, чтобы обновлять UI Text всякий раз при обновлении значения. Добавьте следующий код над методом 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(), но так будет лучше для производительности.
Пришло время добавлять баллы и отслеживать движения.
Сохраните скрипт и откройте BoardManager.cs. Добавьте следующий метод ShiftTilesDown, непосредственно над yield return new WaitForSeconds(shiftDelay);:
GUIManager.instance.Score += 50;
Счет будет увеличиваться при появлении пустого элемента.
В Tile.cs, добавьте следующую линию ниже SFXManager.instance.PlaySFX(Clip.Swap); в методе ClearAllMatches:
GUIManager.instance.MoveCounter—;
Это будет понижать MoveCounter всякий раз, когда спрайт будет заменяться.
Сохраните свою работу и проверьте, работают ли корректно счетчики движения и баллов. Каждое совпадение должно добавлять какие-то очки.
Игра должна завершаться, когда счетчик движений достигает 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());
Так все комбинации будут учтены до завершения игры.
Сохраните скрипты, запустите игру и подсчитывайте комбинации:
Скачать файл проекта можно по этой ссылке.
В дальнейшем вы сможете добавлять временные режимы, различные уровни с досками разных размеров, бонусные очки за комбинации, или же анимационные эффекты.
Подпишись на рассылку
16.09.2016
121522
Wish — хорошая платформа для мобильной коммерции; приложение компании ориентировано на любителей скидок и представляет собой показательный пример убеждения...
28.10.2016
12968
На китайский рынок не всегда удается пробиться даже тем, кто прежде успешно работал в Европе и США. Для компаний-разработчиков...