sobota, 23 kwietnia 2016

System logowania i rejestracji

Środek semestru niestety przyniósł ze sobą testów, do których nauka zajęła mi sporo czasu, ale mam nadzieję, że teraz będę miał więcej czasu na pisanie.

Dzisiejszy wpis poświęcę tworzeniu systemu umożliwiającego rejestrowaniu się i logowaniu na serwerze. Dotychczas do gry mogła dołączać się dowolna liczba osób, ale gra nie oferowała możliwości ponownego dołączenia się do gry - nawet jeśli do gry dołączał ten sam gracz, to gra traktowała go jako zupełnie nowego gracza. Teoretycznie można było sprawdzać adres IP graczy, ale nie pozwalało to na rozróżnienie graczy, jeśli korzystali z tej samej sieci. Potrzebowałem więc jakiegoś prostego systemu rejestracji i logowania.

Interfejs

Unity oferuje nam sporo narzędzi ułatwiających tworzenie menu i interfejsu. Jednym z takich narzędzi jest GameObject który zwie się InputField. Uproszczając jest to pole tekstowe, które można dowolnie wypełniać, posiadające sporo wbudowanych funkcji. Aby je utworzyć wystarczy wchodzić w kolejno: GameObject -> UI -> InputField. W podobny sposób tworzymy Button, który służyć nam będzie jako przycisk.

Jeśli na scenie nie istnieje obiekt typu Canvas, to przy tworzeniu elementów UI zostaje utworzony, aby elementy UI zostały do niego podpięte. Canvas jest obiektem na którym umieszcza się elementy służące jako interfejs. W edytorze sceny widzimy go jako prostokąt, który reprezentuje ekran użytkownika, dzięki czemu możemy na nim łatwo rozmieścić wszystkie elementy interfejsu.

Potrzebujemy 6 pól tekstowych i 2 przyciski, które potem przypiszemy do prefabu. Dla czytelności można je przypiąć do pustych obiektów, których nazwa będzie nas informowała o tym, które elementu będą należeć do logowania, a które do rejestracji. EventSystem jest obiektem, który umożliwia poprawne funkcjonowanie UI:


Teraz tworzymy nowy skrypt i przypisujemy go do prefabu. Dodajemy do niego zmienne w których będziemy zawierać odnośniki do poszczególnych elementów interfejsu, aby mieć do nich łatwy dostęp:

 public GameObject LoginNickname;
 public GameObject LoginPassword;
 public GameObject LoginButton;
 public GameObject RegisterNickname;
 public GameObject RegisterEmail;
 public GameObject RegisterPassword;
 public GameObject RegisterConfirmPassword;
 public GameObject RegisterButton;

Następnie w inspektorze Unity przypisujemy do zmiennych odpowiadające im obiekty poprzez przeciągnięcie ich:

Alternatywnym rozwiązaniem jest wyszukanie tych obiektów z poziomu skryptu za pomocą funkcji find. Kwestia gustu.

Co dalej: dodajemy do skryptu zmienne typu string (ciągi znaków), które będą przechowywać informację o tekście znajdującym się w InputField. Aby tekst był automatycznie przekazywany do zmiennych musimy dodać własną funkcję, jak np.:

 public void ChangeLoginNickname () {
  LoginNicknameText = LoginNickname.GetComponent<InputField> ().text;
 }

Jednak funkcja ta będzie pozostawała bezużyteczna, jeśli jej nie wywołamy. Na szczęście w InputFieldach znajduje się komponent, który umożliwia automatycznie aktywowanie funkcji podczas edytowania tekstu. Aby skorzystać z tej właściwości musimy dodać do listy nowy element zawierający informację o obiekcie posiadający interesujący nas skrypt, oraz wskazać na funkcję z tego skryptu:

W podobny sposób postępujemy z innymi polami tekstowymi oraz przyciskami. Prócz tego w polach haseł jako ContentType ustawiamy Password, dzięki czemu wpisywany w nich ciąg znaków będzie przedstawiany na ekranie jako ciąg gwiazdek.

Logowanie się i rejestracja


Rolą przycisków jest przekazanie zawartości pól tekstowych na serwer i aby to zrobić musimy znaleźć nasz utworzony wcześniej PlayerPrefab i uruchomić na nim funkcję:

 PlayerScript PScript;

 void Start () {
  PScript = GameObject.Find ("MyPlayer").GetComponent<PlayerScript> ();
 }

 public void Register () {
  PScript.CmdRegister (RegisterNicknameText, RegisterEmailText, RegisterPasswordText);
 }

Zaś funkcja w PlayerPrefabie wygląda u mnie tak:

 [Command]
 public void CmdRegister (string Nickname, string Email, string Password) {
  if (!Directory.Exists (@"C:/TokenBattle")) {
   Directory.CreateDirectory (@"C:/TokenBattle");
  }
  if (!File.Exists (@"C:/TokenBattle/" + Nickname + ".txt")) {
   File.WriteAllText (@"C:/TokenBattle/"+Nickname+".txt",Nickname+"\r\n"+Email+"\r\n"+Password);
  }
 }

Musieliśmy zastosować tutaj atrybut Command, ponieważ funkcja jest wywoływana na kliencie i ma być wykonana na serwerze. Jej argumentami są 3 zmienne reprezentujące ciągi znaków. Pierwszą wykonywaną rzeczą jest sprawdzenie, czy na serwerze istnieje folder TokenBattle na dysku C i jeśli nie istnieje, to zostaje on utworzony. Standardowo przed wyrażeniem Directory trzeba napisać System.IO, ale żeby tego nie robić dodałem na początku klasy tą bibliotekę, dzięki czemu pisanie kodu staje się łatwiejsze, a bibliotekę można dodać za pomocą linii: using System.IO;

Kolejną rzeczą którą wykonuje ta funkcja jest sprawdzenie, czy w folderze TokenBattle znajduje się plik tekstowy o nazwie użytkownika, jeśli nie istnieje, to zostaje utworzony. Funkcja File.WriteAllText zawiera 2 argumenty: lokalizację pliku tekstowego, oraz ciąg znaków który ma być w niej umieszczony. Ciągi znaków możemy ze sobą łączyć za pomocą dodawania, a nową linię tworzymy umieszczając w ciągu znaków wyrażenie \r\n. Zazwyczaj korzystając ze stringów wystarczy użyć tylko \n, ale przy zapisie ich do pliku musimy wyjątkowo dodać \r.

Nasz skrypt potrzebuje jeszcze funkcji odpowiadającej za logowanie się na serwer:

 [Command]
 public void CmdLogin (string Nickname, string Password) {
  if (File.Exists (@"C:/TokenBattle/" + Nickname + ".txt")) {
   string [] Lines;
   Lines = File.ReadAllLines (@"C:/TokenBattle/" + Nickname + ".txt");
   if (Lines [2] == Password) {
    ConnectPlayer (Nickname);
    RpcSpawnCObject ();
    DownloadData ();
   }
  }
 }

W przeciwieństwie do poprzedniej funkcji ta nie potrzebuje adresu e-mail, wymaga ona tylko nazwy użytkownika i hasła. Najpierw skrypt sprawdza, czy na serwerze istnieje plik z danymi użytkownika - jeśli taki plik nie istnieje, to znaczy że użytkownik nie jest zarejestrowany. Następnie tworzona jest tablica ciągów znaków i przypisywana jest do niej cała zawartość pliku. Kolejną rzeczą jest sprawdzenie, czy podane przez użytkownika hasło jest zgodne z drugą linijką pliku tekstowego użytkownika, czyli hasła (linie są numerowane od zera). Jeśli wszystko się zgadza, to uruchamiane są 3 stworzone przeze mnie funkcje, które kolejno: przekazują informację o podłączeniu się gracza, tworzą na podłączonym kliencie obiekt z danymi i przekazują dane o partii z serwera na klienta.

Skrypt tworzący obiekt z danymi klienta wygląda następująco:

 [ClientRpc]
 public void RpcSpawnCObject () {
  if (name == "MyPlayer") {
   Instantiate (Resources.Load ("PreCObject")).name = "CObject";
   Destroy (GameObject.Find ("PreLogin"));
  }
 }

Sprawdza on, czy nazwa PlayerPrefabu brzmi "MyPlayer" (nazwa ta była ustawiana tylko na kliencie przy tworzeniu prefabu, więc na innych klientach będzie miała inną nazwę) i jeśli następuje zgodność, to tworzony jest nowy prefab i usuwane jest tło. Dotychczas obiekt klienta z danymi był tworzony tuż po podłączeniu się użytkownika na serwer, ale opóźniłem to aby pojawiał się dopiero po zalogowaniu.

System ten można dalej rozbudowywać w zależności od potrzeb. W przyszłości dodane zostaną fragmentu kodu sprawdzające zawartość pól tekstowy, czyli ich długość, rodzaj znaków, czy zgodność obu haseł. Prócz tego przydatne byłoby komunikaty informujące o błędach przy logowaniu/rejestracji, oraz różnego rodzaju systemy szyfrujące dane, ale w fazie testów tego nie potrzebuję, więc zostawię to na później.

Brak komentarzy:

Prześlij komentarz