Меню выбора сцены с сохранением

Такую менюшку можно встретить, например, в казуальных игрушках и им подобным. То есть, когда в игре множество уровней и начиная с первого, игрок по порядку проходит остальные. Если еще нет пройденных уровней, то будет доступен первый в списке, когда он будет пройден, откроется второй уровень и так далее. За прохождение уровня учитывается его сохранение, иначе говоря, если уровень сохранить в любой момент, то сразу будет доступен следующий. Здесь встроена функция сохранения, где можно записать допустим, набранные за уровень очки и время прохождения. Для закрытого и открытого уровня, используются свои иконки. Кроме того, есть возможность для открытых уровней ставить уникальные иконки.


UI меню состоит из пары кнопок "вперед" и "назад".
Еще нужно создать группу кнопок, для выбора сцены.

На каждую кнопку в группе, вешается:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using UnityEngine.SceneManagement;
using System.IO;

public class LevelSelect : MonoBehaviour {

	[SerializeField] private string iconPath = "SceneIcons"; // папка в Resources, где лежат уникальные иконки сцен, их имена должны быть как и у сцен (необязательно)
	[SerializeField] private string fileName = "Levels.data"; // файл для сохранения
	[SerializeField] private string scenePrefix = "Scene_"; // приставка имени сцены, например, может быть так: Scene_1, Scene_2, Scene_3 и т.п.
	[SerializeField] private KeyCode key; // клавиша вкл/выкл меню
	[SerializeField] private GameObject levelMenu; // родительский объект менюшки
	[SerializeField] private RectTransform levelGroup; // группа иконок
	[SerializeField] private int groupCount = 5; // сколько страниц, формула: например, группа иконок содержит 10 объектов, то умножаем на количество страниц = число сцен
	[SerializeField] private Button backButton; // предощущая страница
	[SerializeField] private Button nextButton; // следующая страница
	[SerializeField] private Sprite lockIcon; // иконка, если сцена закрыта
	[SerializeField] private Sprite unlockIcon; // иконка, если сцена открыта
	[SerializeField] private bool dontDestroyOnLoad; // если 'true' - менюшка будет переходить из сцены в сцену
	[SerializeField] private bool hideOnStart = true; // спрятать меню на старте

	private static bool _active;
	private static LevelSelect _internal;
	private static int groupIndex, current;
	private LevelSelectComponent[] comp;
	private LevelData[] data;
	private Sprite[] sceneIcon;

	struct LevelData // параметры для сохранения
	{
		public bool isActive, canUse; // обязательные

		// пользовательские (можно изменять)
		public int score;
		public string time;
	}

	public void SaveScene(int score, string time)
	{
		if(current <= data.Length-1)
		{
			data[current].isActive = true;
			data[current].score = score;
			data[current].time = time;
		}

		StreamWriter writer = new StreamWriter(GetPath());

		foreach(LevelData element in data)
		{
			if(element.isActive)
			{
				writer.WriteLine(element.score + "|" + element.time);
			}
		}

		writer.Close();

		Debug.Log(this + " Cохранение в файл: " + GetPath());

		if(current+1 <= data.Length-1) // после сохранения, открываем следующую сцену, если таковая есть
		{
			data[current+1].canUse = true;
			ButtonUpdate();
		}
	}

	public void LoadNext()
	{
		if(current+1 <= data.Length-1) LoadScene(current+1);
	}

	void SetLevel(string text, int index)
	{
		string[] t = text.Split(new char[]{'|'});

		// загрузка в таком же порядке, что и запись
		int score = Parse(t[0]);
		string time = t[1];

		data[index].isActive = true;
		data[index].score = score;
		data[index].time = time;
	}

	void Load()
	{
		backButton.interactable = false;

		if(!File.Exists(GetPath())) // если файла сохранения еще нет, то будет открыта первая сцена
		{
			if(data.Length > 0)
			{
				data[0].canUse = true;
				ButtonUpdate();
			}
			return;
		}

		StreamReader reader = new StreamReader(GetPath());

		int j = -1;
		while(!reader.EndOfStream)
		{
			j++;
			SetLevel(reader.ReadLine(), j);
		}

		if(j <= data.Length-1)
		{
			data[j+1].canUse = true;
		}

		reader.Close();

		ButtonUpdate();
	}

	public void LoadScene(int id)
	{
		string level = scenePrefix + id;

		if(!Application.CanStreamedLevelBeLoaded(level))
		{
			Debug.Log("[LevelSelect] сцены не существует или она не добавлена в Build Setting: " + level);
			return;
		}

		Hide();

		current = id;

		SceneManager.LoadScene(level);
	}

	public bool isComplete
	{
		get{ return data[current].isActive; }
	}

	public static bool isActive
	{
		get{ return _active; }
	}

	public static int Current
	{
		get{ return current; }
	}

	public static LevelSelect use
	{
		get{ return _internal; }
	}

	static int Parse(string text)
	{
		int value;
		if(int.TryParse(text, out value)) return value;
		return 0;
	}

	void Awake()
	{
		_internal = this;
		if(dontDestroyOnLoad) DontDestroyOnLoad(transform.gameObject);
		sceneIcon = Resources.LoadAll<Sprite>(iconPath);
		groupIndex = 1;
		if(hideOnStart) Hide();
		backButton.onClick.AddListener(() => {Back();});
		nextButton.onClick.AddListener(() => {Next();});
		comp = levelGroup.GetComponentsInChildren<LevelSelectComponent>();
		data = new LevelData[comp.Length * groupCount];
		Load();
	}

	string GetPath()
	{
		return Application.persistentDataPath + "/" + fileName;
	}

	void Show()
	{
		_active = true;
		levelMenu.SetActive(true);
	}

	void Hide()
	{
		_active = false;
		levelMenu.SetActive(false);
	}

	void Back()
	{
		if(groupIndex > 1)
		{
			nextButton.interactable = true;
			groupIndex--;
			ButtonUpdate();
		}

		if(groupIndex == 1) backButton.interactable = false;
	}

	void Next()
	{
		if(groupIndex < groupCount)
		{
			backButton.interactable = true;
			groupIndex++;
			ButtonUpdate();
		}

		if(groupIndex == groupCount) nextButton.interactable = false;
	}

	void LateUpdate()
	{
		if(Input.GetKeyDown(key) && !_active) Show();
		else if(Input.GetKeyDown(key) && _active) Hide();
	}

	Sprite GetSprite(bool isLock, string iconName)
	{
		if(isLock) return lockIcon;

		if(sceneIcon.Length > 0)
		{
			foreach(Sprite element in sceneIcon)
			{
				if(string.Compare(element.name, iconName) == 0)
				{
					return element;
				}
			}
		}

		return unlockIcon;
	}

	void ButtonUpdate()
	{
		int j = (comp.Length * groupIndex) - comp.Length;
		foreach(LevelSelectComponent element in comp)
		{
			if(data[j].isActive || data[j].canUse)
			{
				element.button.interactable = true;
				element.button.image.sprite = GetSprite(false, scenePrefix + j);
			}
			else
			{
				element.button.interactable = false;
				element.button.image.sprite = GetSprite(true, scenePrefix + j);
			}

			element.id = j;

			j++;

			element.buttonText.text = j.ToString();
		}
	}
}

Обратить внимание стоит на функции сохранения и загрузки. Там есть обязательные параметры, которые нельзя изменять. А вот пользовательские уже можно видоизменить или добавить свои, следуя примеру, как это сделано с дефолтными. В самом файле сохранения, каждая строка, считается как открытый уровень, если считать по порядку.

Использование:

if(!LevelSelect.use.isComplete)
{
	// проверка, пройден текущий уровень или нет
}

LevelSelect.use.SaveScene(очки, "время"); // сохранить

LevelSelect.use.LoadNext(); // загрузить следующую сцену

Скачать демо:

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

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

Офлайн
Light 18 июня 2017
Давуд Ахмедов, никаких ошибок нет в данном примере.
Офлайн
Light,
Убери пожалуйста ограничение комментариев на сайте
Офлайн
Light 20 июня 2017
Давуд Ахмедов, нет, это ограничение останется неизменным.
Офлайн
Light,
Почему именно поэтому я иногда вам ответить не могу
Офлайн
Light,
Как изменить количество уровней?
Уровней у вам там стоит 60 а как сделать на свое усмотрение например 48?
Офлайн
Light 29 июня 2017
Давуд Ахмедов, в данном примере, группа состоит из 12 иконок. Можно удалить или дублировать еще иконки. Если хочешь 48 уровней, то надо удалить 4 лишние иконки. Получится страничка с 8 иконками. Затем надо указать сколько нужно страниц, переменная groupCount за это отвечает. Значит 6 страниц по 8 иконок на каждой = 48 уровней.

Читай комментарии в скрипте внимательно, там всё указано.
Офлайн
Light,
не работает сделал 8 иконок и 6 страниц там выходит 40 уровней с ограничением.В Build Setings все уровни добавил до 48.И что теперь?Наооборот сделал 6 иконок и 8 страниц выходит 30 уровней...
Офлайн
Все я немного изменил скрипт я поменял private int groupCount на значение public
Офлайн
Light,
у меня какая то проблема у меня открывается только тот уровень которого я выбрал в меню и прошел.А когда прохожу все уровни подряд он не сохраняет ничего кроме того уровня которого я выбрал в меню
Офлайн
Light 20 июля 2017
Давуд Ахмедов, как может открыться тот уровень, который уже был открыт и ты его прошел?
Офлайн
Цитата: Light
Давуд Ахмедов, как может открыться тот уровень, который уже был открыт и ты его прошел?

Я имею ввиду что у меня уровни не открываются если я прохожу все уровни подряд.Или открывается только половина тех уровней которых я прошел например прошел 12 уровней а открылись только 6.У меня в игре при финише переходит на следующий уровень.
Офлайн
Light 21 июля 2017
Немного обновил скрипт. Сохраняется теперь только выбранный уровень (включая пройденные, перезапись файла).
Офлайн
Light,
но проблема осталась( Если я прохожу все уровни подряд нифига не сохраняет
может это в моем скрипте сохранения ошибка?
Внимание! У Вас нет прав для просмотра скрытого текста.
Офлайн
Light,
Проблема вот в чем.
Я нажимаю уровень 6 прохожу его и уровень 7 открывается сразу и перехожу на него сразу.ПРохожу 8 и 9 уровни и дальше а в меню они не открыты...Если я играю все уровни подряд не сохраняет ничего
Офлайн
Light 21 июля 2017
Давуд Ахмедов, не сохраняет потому что у тебя условие if(!LevelSelect.use.isComplete) убери его.
Офлайн
Цитата: Light
Давуд Ахмедов, не сохраняет потому что у тебя условие if(!LevelSelect.use.isComplete) убери его.

Если я подряд проходить буду оно будет сохранять?
Офлайн
Light 21 июля 2017
Добавил новую функцию, теперь, если например игрок заходит в триггер перехода на другой уровень, то с начало делается сохранение, а потом загрузка сцены:

LevelSelect.use.SaveScene(очки, "время");
LevelSelect.use.LoadNext();
Офлайн
Цитата: Light
Добавил новую функцию, теперь, если например игрок заходит в триггер перехода на другой уровень, то с начало делается сохранение, а потом загрузка сцены:

LevelSelect.use.SaveScene(очки, "время");
LevelSelect.use.LoadNext();

Цитата: Light
Добавил новую функцию, теперь, если например игрок заходит в триггер перехода на другой уровень, то с начало делается сохранение, а потом загрузка сцены:

LevelSelect.use.SaveScene(очки, "время");
LevelSelect.use.LoadNext();

Ну попробую
Офлайн
Light,
Проблема таже самая!Кроме уровня что следует,после уровня что я выбрал в меню больше уровней не открывает
нажал уровень 1 прошел его и перешл в уровень 2 и прошел его (они сохранились) А прохожу уровень 3 и дальше нифига не сохраняет!
Офлайн
Light 22 июля 2017
Давуд Ахмедов, во время теста таких проблем небыло. Ты можешь кинуть мне ссылку на архив своего проекта, я посмотрю в чем проблема.
Офлайн
Цитата: Light
Давуд Ахмедов, во время теста таких проблем небыло. Ты можешь кинуть мне ссылку на архив своего проекта, я посмотрю в чем проблема.

Если у тебя работает то скинт файлы на демо с примером сохранения который у тебя стоит
Офлайн
Light 23 июля 2017
Давуд Ахмедов, все примеры написаны в статье.
Офлайн
В этом скрипте есть ошибка которую я давно заметил и она очень сильно мучает геймдевера.Уровенем 1 она засчитывает уровеньткоторый называется "Префикс"-0.Т.е один она считает за 0.Два считает за Один.И три за два.В чем дело? grin
Офлайн
Light 20 февраля 2018
Давуд Ахмедов, это не ошибка, просто счет в массиве начинается с нуля.
Но если хочешь, чтобы сцены шли с номера - 1:

Найди: string level = scenePrefix + id;
Замени на: string level = scenePrefix + (id+1);
Офлайн
Light,
Огромное спасибо!!!!!
Офлайн
zerbanius 15 декабря 2018
Было бы неплохо сделать так: на непройденые уровни вешаем одну иконку, на пройденые другую, а на уровень, который проходит игрок - третью. Увы, в этом проекте недоступна третья иконка. Доработайте пожалуйста!
Офлайн
glebik1337 16 февраля 2019
А как опять закрыть пройденные уровни?
Офлайн
Light 16 февраля 2019
glebik1337, удалить сохранения.
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика