Jump to content
Unity Insider Forum

Multiplayer mit Unity - Teil 2


DoubleM

Recommended Posts

Multiplayer mit Unity - Teil 2

Masterserver und Statesynchronisation

Inhalt:

  1. Kommentar
  2. Etwas über Unity und Multiplayer
  3. Den Masterserver benutzen
  4. Spieler Spawnen
  5. Einfache Statesynchronisation
  6. Dead Reckoning

1. Kommentar

Guten Abend zusammen ;)

Ich entschuldige mich dafür dass dieses Tutorial so lange hat auf sich warten lassen. Ich arbeite aktuell nicht mit dem eingebauten Netzwerksystem von Unity sondern mit einer eigenen Lösung (basierend auf Sockets) und einem C++ Server, die Gründe dafür werde ich in 2 genauer erläutern.

Generell gibt es zu diesem Tutorial zu sagen dass es auf dem Code und dem Wissen vom 1. Tutorial basiert und ich werde demzufolge kein komplettes Programm sondern nur die speziellen Ausschnitte (zum Master Server verbinden, Statesynchronisation...) hier erläutern, da es sonst auch zu lang würde.

Das komplette Projekt ist im Anhang zu finden, und eine Webplayer Demo werde ich auch veröffentlichen.

 

2. Etwas über Unity und Multiplayer

"Unity unterstützt Multiplayer, juhuu, jetzt kann ich endlich ein MMORPG schreiben!"

Das wäre zu schön um wahr zu sein.

Abzusehen davon dass ein MMORPG immernoch sehr viel Arbeit ist, ist Unity für diesen (oder andere Spieltypen) nicht ausgelegt.

Hier mal etwas genauer:

Unity's Multiplayer ist durchaus einfach zu bedienen und funktionstüchtig, das möchte ich ihm nicht absprechen, aber

es ist nur für Spiele geeignet bei dem ein Spieler selber der Server ist, d.h. z.B. FPS oder Strategiespiele.

Es ist grundsätzlich bis zu einem gewissen Grad möglich auch Spiele mit Unity zu realisieren bei denen ein eigenständiger

Server benutzt wird (z.B. bei einem MMORPG), aber dabei sollte man auf andere Server zurückgreifen, z.B. Photon.

Für den Hobby Entwickler liegt das einzige Problem da evtl. beim Preis ;)

Bei dieser Lösung wird Unity dann praktisch nur noch benutzt um beim Client die Grafik/Sound usw. zu verwalten.

Von der Idee den Server auch in Unity zu realisieren sollte man sich fernhalten. Abgesehen davon dass Unity nicht dafür strukturiert ist wird man spätestens beim 2. Level dass der Server parallel verwalten soll auf Probleme treffen. [beispiel: Im Server hat der NetworkView eine andere ID als im Client weil der Server mehrere Szenen auf einmal hat (Wenn die 1. Szene 3 Network Views hat dann hat der 1. Network View der 2. Szene beim Server die ID 3 und im Client die ID 0...)]

Die andere Alternative ist es auf Unitys Netzwerksystem zu verzichten und es selber (z.B. mit Sockets) zu erledigen.

3. Den Masterserver benutzen

Ok, let's go.

(Ich gehe davon aus dass der Quellcode aus dem 1. Tutorial benutzt wird)

Um statt zu einer direkten IP über den Master Server zu verbinden sind einige wenige Änderungen nötig.

Beim Server ist lediglich ein weiterer Funktionsaufruf nötig:

if (GUILayout.Button ("Start Server"))
  {
   	// Status auf  "Starte Server...." setzen
   	Status = "Starte Server...";
   	Debug.Log(Status);
   	// zu bestimmter IP und bestimmtem Port verbinden
  	Network.InitializeServer(32, parseInt(Port), !Network.HavePublicAddress());
  	// Den Server beim Master Server "anmelden"
  	MasterServer.RegisterHost("Multiplayer_Tutorial_2-Test", "Testgame", "free4all");
  }

Der Aufruf von "RegisterHost" tut die ganze Magie auf der Seite des Servers. Dadurch wird der Server in die Serverliste vom Unity Masterserver aufgenommen.

Der Erste Parameter gibt den Typ des Spiels an. Nur Wenn der Server den selben Typ wie der Client hat erscheint der Server auch in der Serverliste des Clients.

Der 2. Parameter gibt den Spielnamen und der 3. ein Kommentar an.

Hier muss ich noch auf NAT hinweisen (durch Network.useNat benutzbar). Falls es Probleme beim Verbinden geben sollte ist es ratsam Nat an/aus zu schalten. Es gibt viele Computer die nur zu Nat Servern verbinden können oder nur zu nicht Nat Servern.

Fortgeschritten ist es möglich die NAT-Möglichkeiten durch Unity herausfinden zu lassen. Link: Test Connection

 

Auf der Seite des Clients wird das ganze schon etwas komplizierter.

Die Serverliste kann man mit

MasterServer.RequestHostList("Multiplayer_Tutorial_2-Test");

abrufen. Dann muss man sie nur noch auswerten.

//in OnGUI
// Serverliste abfragen
var serverList : HostData[] = MasterServer.PollHostList();
// Alle Server durchgehen
for (var server in serverList)
{
	GUILayout.BeginHorizontal();	
       // Name, Spielerzahl/Spielerlimit und Kommentar anzeigen
	GUILayout.Label(server.gameName + " (" + server.connectedPlayers + " / " + server.playerLimit + ") | " + server.comment);	
	GUILayout.FlexibleSpace();
       // Für jeden Server einen Button erstellen
	if (GUILayout.Button("Verbinden"))
	{
           // Bei Nat richten wir uns nach dem Server
		Network.useNat = server.useNat;
           // verbinden
		Network.Connect(server.ip, server.port);			
	}
	GUILayout.EndHorizontal();	
}

Dazu muss man noch sagen dass man RequestHostList öfters aufrufen sollte (über einen "Aktualisieren" Button oder zeitlich alle paar Sekunden) um die Serverliste zu aktualisieren.

Das wars schon um den Master Server zu benutzen.

 

4. Spieler Spawnen

Als nächstes kommen wir zu einem spannenderem Punkt: Wie kann man Spieler in der Welt instanziieren/Steuern usw.

Um etwas instanziieren zu können brauchen wir erstmal ein Prefab.

Dabei habe ich eine einfache Capsule mit Capsule Collider und Rigidbody (Use gravity und Freeze Rotation) genommen, aber das bleibt jedem selbst überlassen.

Dem ganzen kann bzw. sollte man dann noch ein Spielerskript und einen NetworkView (mit Standardeinstellungen) hinzufügen.

Damit sich auch was bewegt kann man in das Spielerskript ein provisorisches Steuerungssystem einbauen:

function Update()
{
// Wenn es mein NetworkView ist
if(networkView.isMine)
{
       // den Spieler bewegen   
	transform.Translate(Vector3(Input.GetAxis("Horizontal"), 0,Input.GetAxis("Vertical")).normalized *Time.deltaTime * 7);
   }
}

 

Dann brauchen wir noch einen Spawner, das ist die Stelle an der die Spieler erstellt werden sollen.

Dazu ein leeres GameObject erstellen und an die Position verschieben an der die Spieler gespawnt werden sollen.

Dann ein neues Skript (z.B. "Spawner.js") erstellen.

// Das Prefab, muss im Inspektor gesetzt werden
var playerPrefab : Transform;

// Wenn wir zum Server verbunden wurden
function OnConnectedToServer()
{
   // das Spielerprefab instanziieren (an der Position des Spawners und mit der Ausrichtung des Spawners)
Network.Instantiate(playerPrefab, transform.position, transform.rotation,0);
}

// Wenn der Server gestartet wurde auch für ihn einen Spieler erstellen
function OnServerInitialized()
{
	// das Spielerprefab instanziieren (an der Position des Spawners und mit der Ausrichtung des Spawners)
Network.Instantiate(playerPrefab, transform.position, transform.rotation,0);
}

// Ein Spieler hat sich ausgeloggt (wird nur auf dem Server aufgerufen)
function OnPlayerDisconnected(player: NetworkPlayer)
{
   // aufräumen
Debug.Log("Der Spieler " + player + " hat sich ausgeloggt, Entferne seine RPCs und Objekte");
   // alle von ihm gebufferten RPCs löschen
Network.RemoveRPCs(player);
   // Seine Spielerobjekte zerstören
Network.DestroyPlayerObjects(player);
}

In diesem Codeschnipsel wird zuerst das Prefab für den Spieler definiert (muss ihm Inspektor zugewiesen werden (!) ) und dann werden 3 verschiedene Events behandelt.

Bei einer Neuverbindung (Serverside/Clientside) wird jeweils ein neuer Spieler erstellt und wenn eine Verbindung beendet wird wird auch der dazugehörende Spieler gelöscht.

[Das Event "OnDisconnectedFromServer" sollte auch behandelt werden ist hier aber unwichtiges. Genaueres dazu ist in der Datei Spawner.js als Kommentar zu finden ;) ]

Network.Instantiate erstellt eine Instanz von dem Prefab an der angegebenen Position mit der angegebenen Ausrichtung auf jedem Client/Server. Dieser Aufruf ist gebuffert, d.h. auch Spieler die sich später verbinden werden diesen Aufruf empfangen.

Wenn man das Ganze jetzt testet sollte Unity die Spielerpositionen standardmäßig synchronisieren und soweit sollte alles funktionieren.

 

5. Einfache Statesynchronisation

Auch wenn Unity im Falle von Position, Skalierung und Rotation selber synchronisiert ist es doch wünschenswert es auch manuell durchführen zu können. Das kann man z.B. gut gebrauchen wenn auch andere Variablen ausgetauscht werden sollten, z.B. Health oder Mana.

Hier ein kleines Beispiel:

 

// Health und Mana als Integer Variablem
var health : int = 100;
var mana   : int = 100;

// Bei jeder Synchronization
function OnSerializeNetworkView(stream : BitStream, info : NetworkMessageInfo)
{
// temporäre Kopien anlegen
var healthCopy : int = 0;
var manaCopy : int  = 0;
   // Es ist unser NetworkView (wir können schreiben)    
  if (stream.isWriting)
{
   	// den temporären Kopien die lokalen Werte zuweisen
	healthCopy = health;
   	manaCopy = mana;
   	// Beides zu dem gleichen NetworkView auf den anderen Clients/Server weiterleiten.
	stream.Serialize(healthCopy);
   	stream.Serialize(manaCopy);
}
else
{
   	// Die beiden temporären Kopien mit den Werten von der Synchronisation füllen
	stream.Serialize(healthCopy);
        stream.Serialize(manaCopy); 	
       // Die lokalen Variablen aktualisieren 
   	health = healthCopy;
   	mana = manaCopy;
}
}

Der Code ist schnell erklärt. OnSerializeNetworkView wird von Unity immer dann aufgerufen, wenn standardmäßig Daten "synchronisiert" werden.

Dabei wird überprüft ob wir Schreibzugriff auf den Stream haben (wenn ja sind wir der Eigentümer, ansonsten nicht) und handeln dann dementsprechend.

Zum Schreiben/Lesen wird die Funktion stream.Serialize benutzt, die abhängig davon wem der NetworkView gehört entweder schreibt oder liest.

Beim Schreiben wird der Wert des ersten Parameters an alle Clients "verteilt", beim Lesen wird der erste Parameter mit dem empfangenen Wert gefüllt.

 

6. Dead Reckoning

Unitys Statesynchronisation ist relativ simpel, die Position wird alle ~15 Frames synchronisiert. Bei einer langsamen Verbindung kann das allerdings zu dem altenbekannten Lag führen. (Wenn Positionsupdates verspätet oder gar nicht ankommen.) Und die pure Positionssynchronisation ist auch oft recht "ruckelig".

Deswegen gibt es eine Technik die sich Dead Reckoning nennt. Der Client bekommt statt der Position die Geschwindigkeit/Richtung des Spielers übertragen und berechnet die neue Position selber um sie nur gegebenenfalls zu berichtigen (und das am besten auch über mehrere Frames verteilt, damit es nicht zu "Sprüngen" kommt).

Das wird Thema dieses Kapitels sein.

 

Es haut bei mir gerade zeitlich nicht hin, ich werde das Tutorial (Kapitel 5 und 6) bald fortsetzen, heute geht leider nicht mehr :unsure:

Hier noch die Webplayer Version vom fertigen Projekt (Anhang) [server muss zuerst laufen].

 

Gute Nacht.

 

Hier die Dateien:

MPT2.html

MPT2.unity3d

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 3 months later...

kleine erweiterung der "Client_Connect.js":

der verbinden Button wird unterbunden bis man als name den "Benutzername" ändert sei es durch zeichen dazu oder weg ;)

 

 

 

 

 

erst mal zeilennummern einschalten bitte ( view/line numbers)

 

53 - 61 original :

 

			if (GUILayout.Button("Verbinden"))
			{
				Debug.Log("useNat: " + server.useNat);
				Debug.Log("IP: "+ server.ip + ", Port: " + server.port);
				Network.useNat = server.useNat;
				Network.Connect(server.ip, server.port);			
			}
			GUILayout.EndHorizontal();	
		}

 

 

53 - 61 neu :

 

               if (PlayerPrefs.GetString("Player Name") == "Benutzername" || PlayerPrefs.GetString("Player Name") == "benutzername" ){
			GUILayout.Label("Bitte nicht > Benutzername < als Name nutzen! ");
			}else{
			if (GUILayout.Button("Verbinden"))
			{
				Debug.Log("useNat: " + server.useNat);
				Debug.Log("IP: "+ server.ip + ", Port: " + server.port);
				Network.useNat = server.useNat;
				Network.Connect(server.ip, server.port);			
			}
			}

 

 

aber auch von mir vielen dank für deine mühe bei dem tutorial konnte viel dadurch lernen

großen respekt an dich für die arbeit:)

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 3 weeks later...

Ja, und nicht nur der Master Server....

Musst halt mal mit Nat rumspielen (bei Unity 3 ist Nat automatisch an wenn du mit GUID connectest (glaube ich^^)).

Bei Servern die hinter ner Firewall liegen (und Nat unterstützen) musst du bei der Erstellung glaube ich Nat einschalten und mit Nat dann verbinden.

Und halt eben Ports richtig freigeben.

@Gassi: Vorerst ist nichts geplant, da ich so aktuell nichts mehr mit Unity zu tuen habe.

Ob ich trotzdem noch was mache weiß ich noch nicht.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Okay, ich dachte du würdest noch Kapitel 5 und 6 evtl machen, naja, dann nich :)

Irgendwas kann ma ja auhc mal selbst rausfinden :D

 

Dem mit dem Server kann ich allgemein zustimmen, nur durch eine Verbindung direkt über die IP zum gewünschten Ziel.

Über den Hauptserver geht es wenn überhaupt im heimischen Netzwerk.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ja, und nicht nur der Master Server....

Musst halt mal mit Nat rumspielen (bei Unity 3 ist Nat automatisch an wenn du mit GUID connectest (glaube ich^^)).

Bei Servern die hinter ner Firewall liegen (und Nat unterstützen) musst du bei der Erstellung glaube ich Nat einschalten und mit Nat dann verbinden.

Und halt eben Ports richtig freigeben.

 

Versteh ich das also richtig?

Ich muss meinen Spielern eine Anleitung geben, wie sie Mutliplayer spielen können...? oO

Hab Dein Tutorial ansonsten durchgearbeitet (sehr schön übrigens), und soweit ich das erkennen kann wird NAT benutzt...

Link zu diesem Kommentar
Auf anderen Seiten teilen

Naja, kommt darauf an.

Ob NAT oder nicht muss ja daran festgemacht werden ob der Server das unterstützt/es braucht.

Das kann man dann ja abfragen bevor man die Verbindung aufbaut.

Und die Ports müssen nur beim Server freigegeben sein.

 

Eine Variante ist auch der ConnectionTester von Unity, aber ich persönlich mag den nicht so^^

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 1 month later...

Hier ist zwar schon lange nix mehr passiert, ich hätte aber trotzdem ne Frage:

 

Ich hab mich mit dem Masterserver-ding befasst und es hat geklappt (ich konnte aber nur von einem Computer aus spielen, da nicht mal mein Lappi mitspielen durfte), jetzt meine Frage. Was muss man ändern, damit sich Unity statt mit dem Masterserver mit einem anderen Server verbindet, ich hab gelesen, dass zum Beispiel RedDwarf für solche Zwecke geeignet und kostenlos sein soll.

 

so long

gamer

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ja, und nicht nur der Master Server....

Musst halt mal mit Nat rumspielen (bei Unity 3 ist Nat automatisch an wenn du mit GUID connectest (glaube ich^^)).

Bei Servern die hinter ner Firewall liegen (und Nat unterstützen) musst du bei der Erstellung glaube ich Nat einschalten und mit Nat dann verbinden.

Und halt eben Ports richtig freigeben.

@Gassi: Vorerst ist nichts geplant, da ich so aktuell nichts mehr mit Unity zu tuen habe.

Ob ich trotzdem noch was mache weiß ich noch nicht.

 

Mit was hast du denn momentan zu tun?^^

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hier ist zwar schon lange nix mehr passiert, ich hätte aber trotzdem ne Frage:

 

Ich hab mich mit dem Masterserver-ding befasst und es hat geklappt (ich konnte aber nur von einem Computer aus spielen, da nicht mal mein Lappi mitspielen durfte), jetzt meine Frage. Was muss man ändern, damit sich Unity statt mit dem Masterserver mit einem anderen Server verbindet, ich hab gelesen, dass zum Beispiel RedDwarf für solche Zwecke geeignet und kostenlos sein soll.

 

so long

gamer

 

Ich habe noch nicht mit dem RedDwarf-Server gearbeitet und kann keine wirklich genauen Informationen geben, deswegen ist das Folgende etwas grob und lückenhaft^^

Als erstes musst du den Server aufsetzen, du findest ihn hier, es lohnt sich auch einen Blick ins Wiki zu werfen (für Tutorials, Installationsanleitung...) ;)

Damit du auf den Server mit Unity connecten kannst kannst du nicht das Unity-Networking benutzen.

Hier findest du einen C# Client und Hier hat jemand diesen etwas modifiziert und mit Unity benutzt.

Wie gesagt, keine genauen Angaben.

 

@Dark: Etwas aus Lw mit DirectX und Esenthel rumprobieren und sonst keine Zeit fürs Programmieren haben ;)

Link zu diesem Kommentar
Auf anderen Seiten teilen

Aktuell mit gar keinem.

 

Ich habe eine Zeit mit dem normalen Unity Networking gearbeitet, davon fand ich die Ergebnisse usw. aber nicht so toll.

Dann habe ich Badumna ausprobiert, auch ganz ok aber nicht so meins.

Und dann habe ich einen eigenen Server in C++/C# geschrieben und mit Unity über C# -> UDP Sockets drauf connected.

Die Methode ist nicht unbedingt die leichteste, aber ich persönlich finde sie durchaus interessant und brauchbar^^

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 3 weeks later...

Ich bekomme immer die Meldung: The connection request to 127.0.0.1:25001 failed. Are you sure the server can be connected to?

Irgendwie unlogisch das ich nicht auf meinen eigenen PC connecten kann, das NAT wird ja in Unity 3.2 nicht mehr gebraucht :/

 

Also das Problem ist folgender Abschnitt:

	if (GUILayout.Button("Server starten/verbinden"))
	{
		// Status auf "Verbinde zu Server...." setzen
		Status = "Verbinde zu Server...";

		Network.Connect(IP_Addresse, parseInt(Port));
		// Den Server beim Master Server "anmelden"

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ok....

Scheinabr sind ein paar Fehler drin...

 

Der Code zum starten des Servers war Müll, hatte versehentlich etwas Code von Server und Client vermischt O,ô

Ich denke du hattest den Fehler deswegen.

Das ist der (hoffentlich) richtige Code zum Starten des Servers:

if (GUILayout.Button ("Start Server"))
  {
       // Status auf  "Starte Server...." setzen
       Status = "Starte Server...";
       Debug.Log(Status);
       // zu bestimmter IP und bestimmtem Port verbinden
       Network.InitializeServer(32, parseInt(Port), !Network.HavePublicAddress());
       // Den Server beim Master Server "anmelden"
       MasterServer.RegisterHost("Multiplayer_Tutorial_2-Test", "Testgame", "free4all");
  }

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ah, so hab ich auch noch versucht es zu lösen, aber hab dann das Connect noch drinnen gelassen. So wies ausschaut, ist man automatisch eh gleich mit dem Server verbunden - praktisch ;)

Zumindest hab ich alles zum Laufen gekriegt, doch etwas viel mir noch auf. Alle Spieler konnten alle Charaktere steuern. Networkview war dran, jeder hatte seine Kamera, aber steuern konnten die alle :/

Mein Laufskript


function Update()
{
if(networkView.isMine)
{
	var controller : CharacterController = GetComponent(CharacterController);

	if (controller.isGrounded) {
		// We are grounded, so recalculate
		// move direction directly from axes
		moveDirection = Vector3(Input.GetAxis("Horizontal"), 0,
		Input.GetAxis("Vertical"));
		moveDirection = transform.TransformDirection(moveDirection);
		moveDirection *= speed;


	// Move the controller
	controller.Move(moveDirection * Time.deltaTime);		
}	
}

Link zu diesem Kommentar
Auf anderen Seiten teilen

So, nach etwas nachforschungne ist es nun so, dass bei 2 Spielern, jeder genau den anderen steuert. Also es ist so, als wäre "networkView.isMine" in wirklichkeit "!networkView.isMine". Bei einem Spieler steuere ich meinen, bei 2 den anderen. Woran liegt denn das?

 

EDIT:Moment mal, es könnt ja sein, dass nur die Kamera vertauscht ist! Wenn ja, wie stelle ich sicher, dass für jeden Spieler die richtige aktiv ist?

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 2 weeks later...

So, das Cam Problem ist ja gelöst aber mir viel noch was auf. Was bestimmt welche Dinge von Unity im Netzwerk automatisch verwaltet werden? Z.b. werden in meinem Spiel aufgenommene Waffen nur vom lokalen Spieler realisiert(ich weiß nicht obs daran liegen könnte, dass sie dem Charakter dann untergeordnet werden) für die anderen Spieler bleiben sie auf dem Boden liegen. Wie kann ich auch solche Objekte positions und rotationsmäßig von Unity verwalten lassen?

Link zu diesem Kommentar
Auf anderen Seiten teilen

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung jetzt entfernen

  Only 75 emoji are allowed.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Lädt...
×
×
  • Neu erstellen...