Пишем игру Lines (Линии, Шарики)

Еще одна простенькая игрушка «Шарики» или «Линии», кому как удобней, в общем. Смысл в том, что есть игровое поле, девять на девять клеток, иногда делают больше или меньше. В случайных клетках появляются шарики, игроку надо собрать по вертикали/горизонтали, подряд пять шариков (либо больше), одинакового цвета. За каждый ход, кроме как когда собрана линия из пяти шаров, на поле будут добавляться по три шара. Когда поле полностью заполнится, засчитывается проигрыш. Задача игрока, продержаться как можно дольше и набрать наибольшее количество очков. Как не трудно догадаться, для создания этой игры, придется поработать с двумерными массивами. Ранее мы уже делали подобные игры, но в этот раз попробуем несколько иной подход.


Создаем в Unity новый 2D проект.
Но перед этим в Photoshop, либо другом редакторе, надо сделать пару картинок. Первая, это обычный квадрат белого цвета, ширина и высота, должны быть одинаковые. Размеры например, 150х150. Другая картинка это будет шарик, тоже белого цвета и с прозрачным фоном, чуть меньшего размера, чем квадрат.



Итак, импортируем изображения в проект и добавляем на сцену.
На спрайт квадрата, надо добавить Box Collider 2D и небольшой скрипт LinesCell:

using UnityEngine;
using System.Collections;

public class LinesCell : MonoBehaviour {

	public int cellID;
	public Lines _lines;

	void onmousedown()
	{
		_lines.FindBall(cellID);
	}
}

Суть в том, что каждый квадратик имеет свой номер, и кликом ЛКМ по нему, мы будем сообщать главному скрипту, к какой клетки обращаемся.

На спрайт шарика, цепляем Balls:

using UnityEngine;
using System.Collections;

public class Balls : MonoBehaviour {

	public Color color;
}

Данный скрипт нужен лишь для хранения цвета шарика.

Перетаскиваем спрайты в папку Prefab. Готово.

Теперь, добавляем на сцену пустой объект и вешаем на него скрипт Lines.
Преступаем к редактированию. Первое, в самом начале подключаем:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;

Объявляем переменные:

public GameObject cellSprite;
public GameObject ballSprite;
public Color[] colors;
public float shift;
public int size = 9;
public Text scoreText;
private int id;
private GameObject[,] cells;
private GameObject[,] tmp;
private GameObject ball;
private bool gameOver;
private int score;
private int scoreOld;

Здесь у на пара префабов, квадрат cellSprite и шарик ballSprite. Так же, массив цветов colors. В переменной shift указывается смещение для квадратов, чтобы найти нужное число, помещаем квадрат по центру (позиция по нулям), заме дублируем его или кидаем второй, и один из них надо сместить по Х или Y так, чтобы между ними получился небольшой зазор. Далее, size - количество клеток по вертикали и горизонтали. А в scoreText обычный текстовый UI.

Продолжаем, старт сцены:

void Start () 
{
	shift = Mathf.Abs(shift);
	tmp = new GameObject[size,size];
	id = 0;
	float posX = -shift*(size-1)/2-shift;
	float posY = Mathf.Abs(posX);
	float Xreset = posX;
	cells = new GameObject[size,size];
	for(int y = 0; y < size; y++)
	{
		posY -= shift;
		for(int x = 0; x < size; x++)
		{
			posX += shift;
			cells[x,y] = Instantiate(cellSprite, new Vector3(posX, posY, 0), Quaternion.identity) as GameObject;
			cells[x,y].GetComponent<LinesCell>()._lines = this;
			cells[x,y].GetComponent<LinesCell>().cellID = id;
			cells[x,y].transform.parent = transform;
			id++;
		}
		posX = Xreset;
	}
	gameOver = false;
	AddBalls();
}

Создается игровое поле, формула расчета позиции такая, что сетка будет размещена по центру, относительно нулевых координат. Иначе говоря, нам остается только после запуска сцены, отрегулировать размер камеры, чтобы вместилась вся конструкция:

Пишем игру Lines (Линии, Шарики)

Сделаем вывод текстовой информации:

void OnGUI () 
{
	scoreText.text = "Текущий Счет:\n" + score + "\n\nПрошлый счет:\n" + scoreOld;
}

Публичная функция, к которой мы обращаемся по клику:

public void FindBall(int ball_id)
{
	if(!gameOver)
	{
		for(int y = 0; y < size; y++)
		{
			for(int x = 0; x < size; x++)
			{
				if(ball == null)
				{
					if(cells[x,y].GetComponent<LinesCell>().cellID == ball_id && tmp[x,y])
					{
						tmp[x,y].GetComponent<SpriteRenderer>().color = tmp[x,y].GetComponent<Balls>().color;
						ball = tmp[x,y];
						tmp[x,y] = null;
					}
				}
				else
				{
					if(cells[x,y].GetComponent<LinesCell>().cellID == ball_id && tmp[x,y] == null)
					{
						Color color = ball.GetComponent<Balls>().color;
						color.a = 0.5f;
						ball.GetComponent<SpriteRenderer>().color = color;
						ball.transform.position = cells[x,y].transform.position;
						tmp[x,y] = ball;
						ball = null;
						FindLines(true);
					}
				}
			}
		}
	}
}

Если попадем по шарику, первый клик - выбор, второй клик - перемещение выбранного шарика в текущую клетку. Осуществляется всё это с помощью двух двумерных массивов. Первый это игровое поле с коллайдерами, а второй по сути копия, но только с шариками. Изменения происходят только во втором массиве, первый - статичен.

Очистка массива с шарами, если засчитан проигрыш:

IEnumerator ClearField()
{
	yield return new WaitForSeconds (1);
	gameOver = false;
	int j = 0;
	while(j < id)
	{
		for(int y = 0; y < size; y++)
		{
			for(int x = 0; x < size; x++)
			{
				j++;
				Destroy(tmp[x,y].gameObject);
				tmp[x,y] = null;
			}
		}
	}
	Debug.Log("New Game");
	AddBalls();
}

Задержка в одну секунду, иначе слишком быстро всё происходит. После, старт новой игры.

Поиск линий:

void FindLines(bool isBall)
{
	List<GameObject> arr = new List<GameObject>();
	bool getBall = true;
	int z = 0;
	int index = 0;
	while(z < id)
	{
		for(int y = 0; y < size; y++)
		{
			for(int x = 0; x < size; x++)
			{
				z++;
				if(x < size-1)
				{
					if(tmp[x,y] && tmp[x+1,y] && tmp[x,y].GetComponent<Balls>().color == tmp[x+1,y].GetComponent<Balls>().color && getBall)
					{
						if(index == 0) 
						{
							index = 2; 
							arr.Add(tmp[x,y]);
							arr.Add(tmp[x+1,y]);
						}
						else 
						{
							index++;
							arr.Add(tmp[x+1,y]);
						}
					}
					else if(index < 5)
					{
						index = 0;
						arr = new List<GameObject>();
					}
					else
					{
						getBall = false;
					}
				}
				
			}
		}
	}
	if(index == 0)
	{
		arr = new List<GameObject>();
		getBall = true;
		z = 0;
		while(z < id)
		{
			for(int y = 0; y < size; y++)
			{
				for(int x = 0; x < size; x++)
				{
					z++;
					if(x < size-1)
					{
						if(tmp[y,x] && tmp[y,x+1] && tmp[y,x].GetComponent<Balls>().color == tmp[y,x+1].GetComponent<Balls>().color && getBall)
						{
							if(index == 0) 
							{
								index = 2; 
								arr.Add(tmp[y,x]);
								arr.Add(tmp[y,x+1]);
							}
							else 
							{
								index++;
								arr.Add(tmp[y,x+1]);
							}
						}
						else if(index < 5)
						{
							index = 0;
							arr = new List<GameObject>();
						}
						else
						{
							getBall = false;
						}
					}
				}
			}
		}
	}
	
	if(isBall)
	{
		if(index < 5) AddBalls(); else StartCoroutine (WaitDestroy(arr));
	}
	else
	{
		if(index >= 5) StartCoroutine (WaitDestroy(arr));
	}
}

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

Уничтожение линий, с небольшой задержкой:

IEnumerator WaitDestroy(List<GameObject> item)
{
	
	yield return new WaitForSeconds (0.2f);
	foreach(GameObject obj in item)
	{
		Destroy(obj.gameObject);
		score++;
	}
}

Добавление новых шариков:

void AddBalls()
{
	int ballCount = 0;
	int e = 0;
	while(e < id)
	{
		for(int y = 0; y < size; y++)
		{
			for(int x = 0; x < size; x++)
			{
				if(tmp[x,y] == null) ballCount++;
				e++;
			}
		}
	}
	if(ballCount > 3) ballCount = 3;
	int i = 0;
	while(i < ballCount)
	{
		int j = Random.Range(0, id);
		for(int y = 0; y < size; y++)
		{
			for(int x = 0; x < size; x++)
			{
				if(cells[x,y].GetComponent<LinesCell>().cellID == j && tmp[x,y] == null)
				{
					tmp[x,y] = Instantiate(ballSprite, cells[x,y].transform.position, Quaternion.identity) as GameObject;
					Color _color  = colors[Random.Range(0, colors.Length)];
					tmp[x,y].GetComponent<Balls>().color = _color;
					_color.a = 0.5f;
					tmp[x,y].GetComponent<SpriteRenderer>().color = _color;
					i++;
				}
			}
		}
	}
	ballCount = 0;
	e = 0;
	while(e < id)
	{
		for(int y = 0; y < size; y++)
		{
			for(int x = 0; x < size; x++)
			{
				if(tmp[x,y] == null) ballCount++;
				e++;
			}
		}
	}
	if(ballCount == 0)
	{
		gameOver = true;
		Debug.Log("Game Over");
		scoreOld = score;
		score = 0;
		StartCoroutine (ClearField());
	}
	FindLines(false);
}

В начале делается проверка, сколько свободных клеток, если больше трех, то только три шара ставим, либо текущее значение. После, нужное число шаров создается в случайных свободных клетках. И далее, идет проверка на "Game Over", если свободных клеток больше нет, чистим массив и запускаем новую игру.

Вот и всё! smile

Скачать проект, если лень делать с нуля:

Внимание! Посетители, находящиеся в группе Гости, не могут скачивать файлы.

Комментариев 2

Офлайн
8Observer8 19 января 2016
В Википедии написано "Для совершения хода необходимо, чтобы между начальной и конечной клетками существовал путь из свободных клеток." Я нашёл реализацию A* алгоритма на Unity. Попробую применить.
Офлайн
sanioooook 1 июня 2019
у меня есть полностью рабочий проект с поиском пути, поиском диагонали, с показом следующих шариков
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика