wtorek, 29 marca 2016

Dostosowywanie gry do trybu multi

Na początku trochę o rozłożeniu skryptu na obiektach. Dotychczas miałem w nawyku trzymanie możliwie jak największej liczby informacji w jednym skrypcie, co nie przeszkadzało w grach tworzonych z myślą o single player. Przy zabawia z trybem multi doszedłem do wniosku, że najlepiej będzie, gdy informacje związane ze sceną będą przechowywane tylko na kliencie, co da lekką ulgę serwerowi. PlayerPrefab będzie natomiast zawierał skrypty związane z komunikacją między klientem a sceną. Skrypt umożliwiający wysyłanie serwerowi poleceń musi istnieć zarówno na obiekcie klienta, jak i odpowiedniku tego obiektu na serwerze i dlatego starałem się podzielić główny skrypt na dwie części. Sam schemat będzie wyglądać mniej więcej tak:

Scena


Chcemy mieć dwie sceny: scenę offline i scenę online. Na obu scenach będzie znajdować się kamera i źródło światła, na scenie offline będzie znajdował się obiekt ze skryptem NetworkManager, a na scenie online będzie znajdować obiekt ze skryptem NetworkIdentity z zaznaczą opcją ServerOnly (będzie on służył do przechowywania danych o sytuacji na planszy). Po podłączeniu się na serwer online obiekt ze skryptem NetworkManager przeniesie się ze sceny offline, wiec nie trzeba go tam umieszczać.

Musimy mieć jakiś prefab który umieścimy w skrypcie NetowrkManager jako PlayerPrefab. Do tego prefabu dodałem następujący skrypt:

 using UnityEngine;
 using System.Collections;
 using UnityEngine.Networking;

 public class PlayerScript : NetworkBehaviour {

  NetworkIdentity MyNet;

  void Start () {
   if (isLocalPlayer) {
    name = "MyPlayer";
    MyNet = GetComponent<NetworkIdentity> ();
    Instantiate (Resources.Load ("PreMObject"));
   }
  }

 }

Skrypt ten ma dodaną bibliotekę związaną z sieciami. Skrypt po uruchomieniu odpala funkcję Start, która najpierw sprawdza, czy PlayerPrefab jest naszą własnością - po podłączeniu się innych graczy na naszej scenie pojawi się ich PlayerPrefab, a my nie chcemy aby ten fragment kodu aktywował się na nie-naszych obiektach. Jeśli obiekt jest naszą własnością, to zmieniamy jego nazwę, abyśmy mogli łatwo go znaleźć na scenie. Drugą rzeczą jest przypisanie do zmiennej informacji o tym jaki konkretnie skrypt NetworkIdentity jest do niego przyczepiony. Zmienną tą będziemy czasem przekazywać na serwer, aby móc ją odebrać z pozostałych klientów. Ostatnią rzeczą robioną przez ten fragment kodu jest utworzenie instancji prefabu o nazwie "PreMObject", który utworzyłem przedtem w folderze Resorces. Chciałem aby obiekt ten był tworzony za pośrednictwem skryptu aby mieć pewność, że uruchamiane przez niego funkcje zawsze uruchomią się po pojawianiu się PlayerPrefabu. Gdyby kolejność była inna, to niektóre moje skrypty nie mogłyby odnaleźć naszego gracza i nie zadziałałyby poprawnie.

Mój prefab "PreMObject" zawiera obiekt ze skryptem zajmującym się sceną na kliencie. Ze starego skryptu zostawiłem fragmenty kodu związane z planszą i prócz tego na początku funkcji start dodałem linijkę kodu zmieniającą nazwę obiektu:

 name = "MObject";

Skrypt po staremu tworzy całą planszę, przechowuje o niej informacje i przypisuje do pól skrypt umożliwiający sterowanie, który trzeba nieco zmodyfikować.


Skrypt sterowania


Pierwszą zmianą jest dodanie zmiennej wskazującą na skrypt związany z komunikacją z serwerem, gdzie za typ zmiennej podstawiamy nazwę tamtego skryptu:

 public PlayerScript PScript;

Następnie w funkcji Start dodałem linijkę kodu przypisujący skrypt do tej zmiennej:

 PScript = GameObject.Find ("MyPlayer").GetComponent<PlayerScript> ();

I przekształciłem linijkę kodu wywołującą funkcję tworzącą żeton, aby była ona wywoływana ze wspomnianego skryptu:

 StartCoroutine (PScript.UseToken (x, y));

Komunikacja z serwerem


Teraz powracamy do skryptu PlayerScript. Chcemy aby informacja o postawieniu żetonu była wysłana na serwer, by ten rozkazał klientom, aby utworzyli żeton. Jeśli chcemy wydać serwerowi polecenie z poziomu klienta, musimy zastosować atrybut command. Aby dodać ten atrybut trzeba przed funkcją napisać "[Command]", a za trzy pierwsze znaki nazwy tej funkcji musimy wstawić "Cmd", tak jak na przykładzie:

 [Command]
 public void CmdUseToken (int x, int y) {

 }

Z kolei jeśli z poziomu serwera chcemy wydać polecenie klientom, musimy zastosować atrybut [ClientRpc], a trzema pierwszymi znakami nazwy funkcji muszą być znaki "Rpc", tak jak na przykładzie:

 [ClientRpc]
 public void RpcSpawnToken (int x, int y) {

 }

Z taką wiedzą uzupełniamy skrypt PlayerScript o następujący fragment kodu:

 public IEnumerator UseToken (int x, int y) {
  CmdUseToken (x, y);
  yield return new WaitForSeconds (0.25f);
 }
 [Command]
 public void CmdUseToken (int x, int y) {
  RpcSpawnToken (x, y);
 }
 [ClientRpc]
 public void RpcSpawnToken (int x, int y) {
  MScript MScript = GameObject.Find ("MObject").GetComponent<MScript> ();
  GameObject Clone = Instantiate (Resources.Load ("PreToken")) as GameObject;
  MScript.Token [x, y] = Clone;
  Clone.transform.parent = MScript.Field [x, y].transform;
  Clone.transform.localPosition = new Vector3 (0, 0, -7.5f);
  Clone.transform.localScale = new Vector3 (0.9f, 0.9f, 1);
  Clone.AddComponent<FallingScript> ();
  if (MyNet == GetComponent<NetworkIdentity> ()) {
   Clone.GetComponent<Renderer> ().material.color = new Color (0.65f, 0.9f, 0.55f);
  } else {
   Clone.GetComponent<Renderer> ().material.color = new Color (0.85f, 0.6f, 0.6f);
  }
  Clone = Instantiate (Resources.Load ("PreText")) as GameObject;
  Clone.transform.parent = MScript.Token [x, y].transform;
  Clone.transform.localPosition = new Vector3 (0, 0, -0.01f);
  Clone.transform.localScale = new Vector3 (0.9f, 0.9f, 1);
  Clone.GetComponent<TextMesh> ().text = "2";

}

Ostatnia funkcja wyszukuje na kliencie skrypt z informacjami o scenie, aby móc przypisać do niego informacje o żetonie który tworzy. Warto tu zwrócić uwagę, że funkcja ta będzie wykonywana na każdym kliencie i na każdym kliencie ten skrypt jest wyszukiwany osobno. Kolejną rzeczą której przedtem nie było jest podmiana koloru żetonu: jeśli komponent NetworkIdentity gracza który rozkazał postawienie żetonu jest taki sam co NetworkIdentity klienta, to znaczy że tworzymy żeton na własnym kliencie i wtedy ma kolor zielony, w przeciwnym razie ma kolor czerwony. Wartość koloru jest wyrażana w wartości RGB. Do tego jeszcze dochodzi fragment kodu tworzący tekst wyświetlany nad żetonem.

Teraz możemy już stawiać żetony na różnych klientach i pojawiają się one na każdym z klientów.

Brak komentarzy:

Prześlij komentarz