Sprawdzam Sudoku - Kurs Unity - 7. Legenda podpowiedzi, Zapisywanie stanu, Tłumaczenie
W poprzednim kroku zaimplementowaliśmy podpowiedzi. W tym kroku dodamy legendę wyjaśniającą podpowiedzi, dodamy zapisywanie stanu oraz tłumaczenie na inne języki:
Implementacja
Zacznijmy od tłumaczenia na inne języki. Zgodnie z tym tutorialem instalujemy pakiet Localization, dodajemy locale dla języków: angielskiego (domyślny), niemieckiego oraz polskiego.
Poprawiamy nazwy wszystkich napisów w aplikacji, oraz dodajemy ich tłumaczenia.
Dodajemy też opcję w ustawieniach, aby można było ręcznie zmienić język. W tym celu dodajemy grupę przełączników, która zawiera:
- HorizontalLayoutGroup - aby automatycznie rozmieścić przełączniki języków
- ToggleGroup - aby jednocześnie mógł być aktywny tylko jeden język
Każdy przełącznik, to zwykły Toggle, w którym pole tekstowe zastąpiliśmy obrazkiem, z ikoną pobraną z pixabay.com (english-flag, german-flag, polish-flag)
Dodajemy skrypt z właściwością
localeName oraz (na razie pustą) obsługą kliknięcia do przełącznika (LanguageToggle):public class LanguageToggle : MonoBehaviour
{
public string localeName;
...
public void Check()
{
GetComponent<Toggle>().SetIsOnWithoutNotify(true);
}
public void OnValueChanged(bool value)
{
}
}W edytorze dla każdego przełącznika ustawiamy poprawny
localeName oraz obsługę kliknięcia (On Value Changed):Dodajemy skrypt do grupy języków który odczytuje i zapisuje wybrany język (
LanguageToggleGroup):public class LanguageToggleGroup : MonoBehaviour
{
...
private void OnEnable()
{
foreach (LanguageToggle languageToggle in GetComponentsInChildren<LanguageToggle>())
{
if (languageToggle.localeName == LocalizationSettings.SelectedLocale.name)
{
languageToggle.Check();
break;
}
}
}
public static void SetLocaleByName(string name)
{
foreach (Locale locale in LocalizationSettings.AvailableLocales.Locales)
{
if (name == locale.name)
{
LocalizationSettings.SelectedLocale = locale;
break;
}
}
}
}Na koniec implementujemy obsługę kliknięcia przełącznika (
LanguageToggle):public class LanguageToggle : MonoBehaviour
{
public void OnValueChanged(bool value)
{
if (value)
{
LanguageToggleGroup.SetLocaleByName(localeName);
}
}
}Przechodzimy do zapisywania stanu. Do każdego elementu, którego stan chcemy przechować, a więc:
- planszy (
BoardImage/BoardModel) - ustawień audio (dodajemy
MusicToggle/MusicVolumeSlider) - ustawień języka (
LanguageToogleGroup)
public class BoardImage : MonoBehaviour
{
...
private void Awake()
{
...
model = new BoardModel(cellImages);
if (model.IsStoredInPrefs())
{
model.LoadFromPrefs();
} else {
// Set the initial board.
// 9 2 | | 7 5
// 7 | 2 5 | 8
// 4 | 8 9 | 1
// ---------------------
// 2 8 | | 1 6
// | |
// 1 6 | | 5 3
// ---------------------
// 1 | 5 6 | 2
// 2 | 9 4 | 3
// 4 3 | | 8 9
model.SetCellValue(0, 1, 9);
...
model.SetCellValue(8, 7, 9);
}
}
...
public void OnDestroy()
{
SavePrefs();
}
public void SavePrefs()
{
model.SaveToPrefs();
}
}
public class BoardModel
{
...
public bool IsStoredInPrefs()
{
bool result = true;
for (int row = 0; row < NUM_ROWS; ++row)
{
for (int col = 0; col < NUM_COLS; ++col)
{
if (!PlayerPrefs.HasKey("Board" + row + col))
{
result = false;
break;
}
}
if (!result)
{
break;
}
}
return result;
}
public void LoadFromPrefs()
{
for (int row = 0; row < NUM_ROWS; ++row)
{
for (int col = 0; col < NUM_COLS; ++col)
{
SetCellValue(row, col, PlayerPrefs.GetInt("Board" + row + col));
}
}
}
public void SaveToPrefs()
{
for (int row = 0; row < NUM_ROWS; ++row)
{
for (int col = 0; col < NUM_COLS; ++col)
{
PlayerPrefs.SetInt("Board" + row + col, cellImageGrid[row, col].GetValue());
}
}
}
}
public class MusicToggle : MonoBehaviour
{
public AudioSource audioSource;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void LoadPrefs()
{
GetComponent<Toggle>().isOn = PlayerPrefs.GetInt("MusicEnabled", 0) == 1;
}
public void SavePrefs()
{
PlayerPrefs.SetInt("MusicEnabled", audioSource.enabled ? 1 : 0);
}
}
public class MusicVolumeSlider : MonoBehaviour
{
public AudioSource audioSource;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void LoadPrefs()
{
GetComponent<Slider>().value = PlayerPrefs.GetFloat("MusicVolume", 0.25f);
}
public void SavePrefs()
{
PlayerPrefs.SetFloat("MusicVolume", audioSource.volume);
}
}
public class LanguageToggleGroup : MonoBehaviour
{
...
public static void SetLocaleByName(string name)
{
...
SavePrefs();
}
public void LoadPrefs()
{
SetLocaleByName(PlayerPrefs.GetString("LocaleName", LocalizationSettings.SelectedLocale.name));
}
private static void SavePrefs()
{
PlayerPrefs.SetString("LocaleName", LocalizationSettings.SelectedLocale.name);
}
}Plansza sama załadowuje swój stan przy pierwszym wyświetleniu. Dla pozostałych elementów metody wczytujące stan zawołamy w menedżerze gry (
GameManager):public class GameManager : MonoBehaviour
{
public LanguageToggleGroup languageToggleGroup;
public MusicToggle musicToggle;
public MusicVolumeSlider musicVolumeSlider;
// Start is called before the first frame update
IEnumerator Start()
{
musicToggle.LoadPrefs();
musicVolumeSlider.LoadPrefs();
// Wait for the localization system to initialize, loading Locales, preloading etc.
yield return LocalizationSettings.InitializationOperation;
languageToggleGroup.LoadPrefs();
}
...
}Pozostaje nam wyświetlenie legendy podpowiedzi. Do obiektu legendy (
HintImage) dodajemy:- pole tekstowe (
HintText), które będzie wyświetlało podpowiedź (np. "1 to jedyna wartość") - legendy podpowiedzi (np. "
w kolumnie")
Ponieważ mamy już w aplikacji tłumaczenia, dla tego każdy napis musimy zdefiniować w trzech językach i dodać jako właściwość skryptu (
HintImage). Do skryptu dodajemy również metody do pokazywania i ukrywania legendy:using TMPro;
using UnityEngine.Localization;
public class HintImage : MonoBehaviour
{
public static HintImage Instance { get; private set; }
public TextMeshProUGUI hintText;
public LocalizedString selectCellString;
public LocalizedString selectValueString;
public LocalizedString valueIsOnlyString;
public LocalizedString valueIsSelectedString;
public LocalizedString valueIsSuggestedString;
public GameObject hintColumn;
public GameObject hintRow;
public GameObject hintGroup;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(this);
}
else
{
Instance = this;
}
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
private void OnEnable()
{
DisplaySelectCellHint();
}
public void ClearOnlyValueHint()
{
hintText.SetText("");
hintColumn.SetActive(false);
hintRow.SetActive(false);
hintGroup.SetActive(false);
}
public void DisplayOnlyValueHint(int value, List<CellSetType> cellSetTypes)
{
Debug.Assert(cellSetTypes.Count > 0, "Cell set types cannot be empty");
hintText.SetText(valueIsOnlyString.GetLocalizedString(), value);
foreach (CellSetType cellSetType in cellSetTypes)
{
switch (cellSetType)
{
case CellSetType.COLUMN:
hintColumn.SetActive(true);
break;
case CellSetType.ROW:
hintRow.SetActive(true);
break;
case CellSetType.GROUP:
hintGroup.SetActive(true);
break;
}
}
}
public void DisplaySelectCellHint()
{
hintText.SetText(selectCellString.GetLocalizedString());
}
public void DisplaySelectValueHint()
{
hintText.SetText(selectValueString.GetLocalizedString());
}
public void DisplaySelectedValueHint(int value)
{
hintText.SetText(valueIsSelectedString.GetLocalizedString(), value);
}
public void DisplaySuggestedValueHint(int value)
{
hintText.SetText(valueIsSuggestedString.GetLocalizedString(), value);
}
}Wołać te metody będzie wybrana komórka (
CellImage) wraz z roboczą komórką (WorkCellImage):public class CellImage : MonoBehaviour
{
...
public void ClearPossibilityOnlyValue(PossibilityRect possibility)
{
...
HintImage.Instance.ClearOnlyValueHint();
}
public void DisplayPossibilityOnlyValue(PossibilityRect possibility)
{
List<CellSetType> cellSetTypes = model.GetCellSetTypesWithOnlyValue(possibility.Value);
if (cellSetTypes.Count > 0)
{
foreach (CellSetType cellSetType in cellSetTypes)
{
possibility.DisplayOnlyValueForType(cellSetType);
}
HintImage.Instance.DisplayOnlyValueHint(possibility.Value, cellSetTypes);
}
}
...
public void DisplayHint()
{
if(!Selected)
{
HintImage.Instance.DisplaySelectCellHint();
return;
}
int value = GetValue();
if (value != 0)
{
HintImage.Instance.DisplaySelectedValueHint(value);
return;
}
int suggestion = CalculateSuggestion();
if (suggestion != 0)
{
HintImage.Instance.DisplaySuggestedValueHint(suggestion);
return;
}
HintImage.Instance.DisplaySelectValueHint();
}
}
public class WorkCellImage : MonoBehaviour
{
...
public void SelectCell(CellImage cell)
{
...
if (selectedCell != null)
{
...
selectedCell.DisplayHint();
}
...
if (selectedCell == cell)
{
selectedCell = null;
}
else
{
...
selectedCell.Select();
selectedCell.DisplayHint();
...
}
}
public void SelectPossibility(PossibilityRect possibility)
{
...
if (selectedPossibility == possibility)
{
...
selectedCell.DisplayHint();
selectedCell.DisplayPossibilityOnlyValue(possibility);
}
...
else
{
...
selectedCell.ClearPossibilityOnlyValue(selectedPossibility);
selectedCell.DisplayHint();
}
}
}Otrzymujemy aplikację pokazaną we wstępie.
GitHub
Zmiany związane z tym krokiem znajdują się tutaj:
Kolejne kroki
Istniejąca aplikacja jest już prawie gotowa do opublikowania. Pozwala nam rozwiązać sudoku. Pozostaje nam rozwiązać kwestię wprowadzania plansz do aplikacji. Tym zajmiemy się w kolejnym kroku.
Polski | Angielski













Komentarze
Prześlij komentarz