Мозаика / нарезка изображения

Способ разбивки изображения на части, на основе чего, представлена фактически готовая игра. Обычная мозаика, где любая картинка разбивается на указанное число частей, в дальнейшем каждую часть надо разместить на свою позицию, с помощью мышки и сложить исходный рисунок. Нарезка текстуры, реализована следующим образом. На сцене, размещается оригинальное изображение, затем, размеры объекта переносятся в пространство экрана. На основе этих значений, рассчитывается размер для части/паззла. Далее, делается скриншот каждой части экрана. После, получившиеся текстуры, конвертируются в спрайты и добавляются на сцену, каждая часть на свое место, плюс, за каждым паззлом закрепляется оригинальная позиция, что в дальнейшем используется для определения правильности расстановки паззлов.


Подготовка сцены.
Нужно переключить камеру в режим Orthographic, так как скрипт работает только с этим режимом. Потом добавляем на сцену меш, обычный Quad и назначаем материал с шейдером Unlit, чтобы не заморачиваться с освещением. Ну и указываем текстуру для нашего "холста".

Теперь на какой-нибудь пустой объект, вешаем скрипт:

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

public class Puzzle : MonoBehaviour {

	public MeshRenderer original; // холст, с которого будут генерироваться пазлы
	public float targetDistance = 0.5f; // дистанция пазла от точки своего назначения, чем больше это значение, тем больше допускается неточность расположения
	public string puzzleTag = "GameController"; // тег для пазлов
	public int columns = 5; // столбцы
	public int lines = 3; // линии
	public float smooth = 5; // сглаживание, во время соединения всех пазлов

	private int puzzleCounter, sortingOrder;
	private List<SpriteRenderer> _puzzle = new List<SpriteRenderer>();
	private List<Vector3> _puzzlePos = new List<Vector3>();
	private Transform current;
	private Vector3 offset;
	private bool isWinner;

	void Start()
	{
		Invoke("NewGame", 5); // запуск игры, через пять секунд
	}

	void NewGame()
	{
		original.gameObject.SetActive(true);
		Clear();
		StartCoroutine(Generate());
	}

	int CheckPuzzle(float distance) // проверка всех пазлов, относительно точек назначения
	{
		int i = 0;
		for(int j = 0; j < _puzzle.Count; j++)
		{
			if(Vector3.Distance(_puzzle[j].transform.position, _puzzlePos[j]) < distance)
			{
				i++;
			}
		}
		return i;
	}

	void Update()
	{
		if(isWinner)
		{
			if(CheckPuzzle(0.1f) == _puzzle.Count)
			{
				Clear();
				original.gameObject.SetActive(true);
			}
			else
			{
				for(int j = 0; j < _puzzle.Count; j++)
				{
					_puzzle[j].transform.position = Vector3.Lerp(_puzzle[j].transform.position, _puzzlePos[j], smooth * Time.deltaTime);
				}
			}
		}
		else
		{
			if(Input.GetMouseButtonDown(0))
			{
				GetPuzzle();
			}
			else if(Input.GetMouseButtonUp(0) && current)
			{
				current.GetComponent<SpriteRenderer>().sortingOrder = sortingOrder;
				current = null;

				if(CheckPuzzle(targetDistance) == _puzzle.Count)
				{
					isWinner = true;
					Debug.Log("!WIN!");
				}
			}	
		}

		if(current)
		{
			Vector3 position = Camera.main.ScreenToWorldPoint(Input.mousePosition);
			current.position = new Vector3(position.x, position.y, current.position.z) + new Vector3(offset.x, offset.y, 0);
		}
	}

	void Clear()
	{
		isWinner = false;
		puzzleCounter = 0;
		foreach(Transform child in transform)
		{
			Destroy(child.gameObject);
		}
		_puzzle = new List<SpriteRenderer>();
		_puzzlePos = new List<Vector3>();
	}

	void Randomize() // раскидываем пазлы по случайным точкам
	{
		float[] x = new float[_puzzle.Count];
		float[] y = new float[_puzzle.Count];

		for(int j = 0; j < _puzzle.Count; j++)
		{
			x[j] = _puzzlePos[j].x;
			y[j] = _puzzlePos[j].y;
		}

		float minX = Mathf.Min(x);
		float maxX = Mathf.Max(x);
		float minY = Mathf.Min(y);
		float maxY = Mathf.Max(y);

		foreach(SpriteRenderer e in _puzzle)
		{
			e.transform.position = new Vector3(Random.Range(minX, maxX), Random.Range(minY, maxY), e.transform.position.z);
		}
	}

	IEnumerator Generate() // создание пазлов / нарезка текстуры
	{
		// переносим размеры холста в пространство экрана
		Vector3 posStart = Camera.main.WorldToScreenPoint(new Vector3(original.bounds.min.x, original.bounds.min.y, original.bounds.min.z));
		Vector3 posEnd = Camera.main.WorldToScreenPoint(new Vector3(original.bounds.max.x, original.bounds.max.y, original.bounds.min.z));

		int width = (int)(posEnd.x - posStart.x);
		int height = (int)(posEnd.y - posStart.y);

		// определяем размеры пазла
		int w_cell = width/columns;
		int h_cell = height/lines;

		// учитываем рамку, т.е. неиспользуемое пространство вокруг холста
		int xAdd = (Screen.width - width)/2;
		int yAdd = (Screen.height - height)/2;

		yield return new WaitForEndOfFrame();

		for(int y = 0; y < lines; y++)
		{
			for(int x = 0; x < columns; x++)
			{
				// делаем снимок части экрана
				Rect rect = new Rect(0, 0, w_cell, h_cell);
				rect.center = new Vector2((w_cell * x + w_cell/2) + xAdd, (h_cell * y + h_cell/2) + yAdd);
				Vector3 position = Camera.main.ScreenToWorldPoint(rect.center);
				Texture2D tex = new Texture2D(w_cell, h_cell, TextureFormat.ARGB32, false);
				tex.ReadPixels(rect, 0, 0);
				tex.Apply();

				// создание нового объекта и настройка его позиции
				GameObject obj = new GameObject("Puzzle: " + puzzleCounter);
				obj.transform.parent = transform;
				position = new Vector3(((int)(position.x * 100f))/100f, ((int)(position.y * 100f))/100f, puzzleCounter/100f);
				obj.transform.localPosition = position;

				// конвертируем текстуру в спрайт
				SpriteRenderer ren = obj.AddComponent<SpriteRenderer>();
				int unit = Mathf.RoundToInt((float)Screen.height/(Camera.main.orthographicSize * 2f)); // формула расчета размера спрайта (только для режима камеры Оrthographic)
				ren.sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), unit);
				obj.AddComponent<BoxCollider2D>();
				obj.tag = puzzleTag;

				_puzzlePos.Add(position);
				_puzzle.Add(ren);
				puzzleCounter++;
			}
		}

		original.gameObject.SetActive(false);
		Randomize();
	}

	void GetPuzzle()
	{
		// массив рейкаста, чтобы фильтровать спрайты по глубине Z (тот что ближе, будет первым элементом массива)
		RaycastHit2D[] hit = Physics2D.RaycastAll(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);
		if(hit.Length > 0 && hit[0].transform.tag == puzzleTag)
		{
			offset = hit[0].transform.position - Camera.main.ScreenToWorldPoint(Input.mousePosition);;
			current = hit[0].transform;
			sortingOrder = current.GetComponent<SpriteRenderer>().sortingOrder;
			current.GetComponent<SpriteRenderer>().sortingOrder = puzzleCounter + 1;
		}
	}
}

Нужно отметить важную деталь. Тут не изменяется сама текстура картинки, а делаются снимки экрана. То есть, всё равно что сфотографировать. Это значит, что холст, где представлено исходное изображение, не должен перекрываться другими объектами и/или элементами UI. С начало должна произойти генерация спрайтов, а уже потом, можно добавить на холст какие-либо элементы, если это необходимо.

Скачать проект:

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

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

Офлайн
Топ проект.
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Дешевый хостинг
  • Яндекс.Метрика