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

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


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 16 февраля 2019
glebik1337, удалить сохранения.
Офлайн
glebik1337 16 февраля 2019
А как опять закрыть пройденные уровни?
Офлайн
zerbanius 15 декабря 2018
Было бы неплохо сделать так: на непройденые уровни вешаем одну иконку, на пройденые другую, а на уровень, который проходит игрок - третью. Увы, в этом проекте недоступна третья иконка. Доработайте пожалуйста!
Офлайн
Light,
Огромное спасибо!!!!!
Офлайн
Light 20 февраля 2018
Давуд Ахмедов, это не ошибка, просто счет в массиве начинается с нуля.
Но если хочешь, чтобы сцены шли с номера - 1:

Найди: string level = scenePrefix + id;
Замени на: string level = scenePrefix + (id+1);
Офлайн
В этом скрипте есть ошибка которую я давно заметил и она очень сильно мучает геймдевера.Уровенем 1 она засчитывает уровеньткоторый называется "Префикс"-0.Т.е один она считает за 0.Два считает за Один.И три за два.В чем дело? grin
Офлайн
Light 23 июля 2017
Давуд Ахмедов, все примеры написаны в статье.
Офлайн
Цитата: Light
Давуд Ахмедов, во время теста таких проблем небыло. Ты можешь кинуть мне ссылку на архив своего проекта, я посмотрю в чем проблема.

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

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

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

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

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

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

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

Я имею ввиду что у меня уровни не открываются если я прохожу все уровни подряд.Или открывается только половина тех уровней которых я прошел например прошел 12 уровней а открылись только 6.У меня в игре при финише переходит на следующий уровень.
Офлайн
Light 20 июля 2017
Давуд Ахмедов, как может открыться тот уровень, который уже был открыт и ты его прошел?
Офлайн
Light,
у меня какая то проблема у меня открывается только тот уровень которого я выбрал в меню и прошел.А когда прохожу все уровни подряд он не сохраняет ничего кроме того уровня которого я выбрал в меню
Офлайн
Все я немного изменил скрипт я поменял private int groupCount на значение public
Офлайн
Light,
не работает сделал 8 иконок и 6 страниц там выходит 40 уровней с ограничением.В Build Setings все уровни добавил до 48.И что теперь?Наооборот сделал 6 иконок и 8 страниц выходит 30 уровней...
Офлайн
Light 29 июня 2017
Давуд Ахмедов, в данном примере, группа состоит из 12 иконок. Можно удалить или дублировать еще иконки. Если хочешь 48 уровней, то надо удалить 4 лишние иконки. Получится страничка с 8 иконками. Затем надо указать сколько нужно страниц, переменная groupCount за это отвечает. Значит 6 страниц по 8 иконок на каждой = 48 уровней.

Читай комментарии в скрипте внимательно, там всё указано.
Офлайн
Light,
Как изменить количество уровней?
Уровней у вам там стоит 60 а как сделать на свое усмотрение например 48?
Офлайн
Light,
Почему именно поэтому я иногда вам ответить не могу
Офлайн
Light 20 июня 2017
Давуд Ахмедов, нет, это ограничение останется неизменным.
Офлайн
Light,
Убери пожалуйста ограничение комментариев на сайте
Офлайн
Light 18 июня 2017
Давуд Ахмедов, никаких ошибок нет в данном примере.
Офлайн
Light,
Ошибку дает сказано LevelSelect не содержит определения для IsConplete
Офлайн
Light 17 июня 2017
Давуд Ахмедов, для этого нужно делать проверку перед сохранением, я внес изменения в скрипт. Скачай заново пакет и посмотри TestTest.
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика