Multiplayer: Unterschied zwischen den Versionen
Markierung: 2017-Quelltext-Bearbeitung |
K Änderungen von Langenfeld (Diskussion) wurden auf die letzte Version von LeonH zurückgesetzt Markierung: Zurücksetzung |
||
Zeile 125: | Zeile 125: | ||
<source lang=csharp> | <source lang=csharp> | ||
myNetClient.SendMessage(buffer, NetChannel.UnreliableInOrder1); | myNetClient.SendMessage(buffer, NetChannel.UnreliableInOrder1); | ||
</source> | |||
== Multiplayer mittels Windows Live == | |||
=== PacketReader & PacketWriter === | |||
Instanzen dieser Klassen dienen zum bequemen Schreiben bzw. Lesen von Netzwerkdaten. Die ''PacketWriter'' Klasse verfügt über eine merfach überladene ''Write()'' Methode für verschiedenste Datentypen. Ebenso einfach können diese Datentypen über die entsprechend benannten Read() Methoden der ''PacketReader'' Klasse wieder gelesen werden. Auch hier gilt, dass die Reihenfolge des Lesens und Schreibens die Gleiche sein sollte. | |||
=== SendDataOptions === | |||
Das Windows Live System bietet ebenfalls die 4 grundlegenden Transportwege an. Diese können beim Senden durch Setzen eines Wertes des Typ SendDataOptions ausgewählt werden. Diese Enum enthält unter anderem folgende Definitionen: | |||
* '''None''' - Nachrichten kommen entweder beim Empfänger an oder auch nicht | |||
* '''InOrder''' - Auslieferung der Nachrichten ebenfalls nicht garantiert, falls eine Nachricht zu spät kommt, d.h. eine neuere Nachricht ist vorher eingetroffen, so wird die ältere gedroppt | |||
* '''Reliable''' - Garantierte Auslieferung der Nachrichten, allerdings nicht notwendigerweise in der Reihenfolge in der sie gesendet wurden | |||
* '''ReliableInOrder''' - Alle Nachrichten kommen in der Reihenfolge in der sie gesendet wurden beim Empfänger an | |||
=== Server === | |||
Starten des Servers | |||
<source lang=csharp> | |||
const int maxGamers = 16; | |||
const int maxLocalGamers = 4; | |||
try | |||
{ | |||
NetworkSession myNetworkSession = NetworkSession.Create(NetworkSessionType.SystemLink, | |||
maxLocalGamers, maxGamers); | |||
// hook event handlers | |||
myNetworkSession.GamerJoined += GamerJoinedEventHandler; | |||
myNetworkSession.SessionEnded += SessionEndedEventHandler; | |||
} | |||
catch (Exception e) | |||
{ | |||
errorMessage = e.Message; | |||
} | |||
</source> | |||
Die Eventhandler | |||
<source lang=csharp> | |||
void GamerJoinedEventHandler(object sender, GamerJoinedEventArgs e); | |||
void SessionEndedEventHandler(object sender, NetworkSessionEndedEventArgs e) | |||
{ | |||
errorMessage = e.EndReason.ToString(); | |||
myNetworkSession.Dispose(); | |||
myNetworkSession = null; | |||
} | |||
</source> | |||
Empfangen von Clientdaten | |||
<source lang=csharp> | |||
// Keep reading as long as incoming packets are available. | |||
while (gamer.IsDataAvailable) | |||
{ | |||
NetworkGamer sender; | |||
// Read a single packet from the network. | |||
gamer.ReceiveData(packetReader, out sender); | |||
if (!sender.IsLocal) | |||
{ | |||
// Read the packetdata available in packetReader | |||
} | |||
} | |||
</source> | |||
Daten an Clients senden | |||
<source lang=csharp> | |||
LocalNetworkGamer server = (LocalNetworkGamer)myNetworkSession.Host; | |||
server.SendData(packetWriter, SendDataOptions.InOrder, client); | |||
myNetworkSession.Update(); | |||
</source> | |||
<source lang=csharp> | |||
LocalNetworkGamer server = (LocalNetworkGamer)myNetworkSession.Host; | |||
server.SendData(packetWriter, SendDataOptions.InOrder); | |||
myNetworkSession.Update(); | |||
</source> | |||
=== Client === | |||
Client starten | |||
<source lang=csharp> | |||
const int maxLocalGamers = 4; | |||
try | |||
{ | |||
// Search for sessions. | |||
using (AvailableNetworkSessionCollection availableSessions = NetworkSession.Find( | |||
NetworkSessionType.SystemLink, maxLocalGamers, null)) | |||
{ | |||
if (availableSessions.Count == 0) | |||
{ | |||
errorMessage = "No network sessions found."; | |||
return; | |||
} | |||
// Join the first session we found. | |||
myNetworkSession = NetworkSession.Join(availableSessions[0]); | |||
// hook event handlers | |||
myNetworkSession.GamerJoined += GamerJoinedEventHandler; | |||
myNetworkSession.SessionEnded += SessionEndedEventHandler; | |||
} | |||
} | |||
catch (Exception e) | |||
{ | |||
errorMessage = e.Message; | |||
} | |||
</source> | |||
Daten vom Server empfangen | |||
<source lang=csharp> | |||
// Keep reading as long as incoming packets are available. | |||
while (gamer.IsDataAvailable) | |||
{ | |||
NetworkGamer sender; | |||
// Read a single packet from the network. | |||
gamer.ReceiveData(packetReader, out sender); | |||
// Read the data from packetReader.. | |||
} | |||
</source> | |||
Daten zum Server senden | |||
<source lang=csharp> | |||
gamer.SendData(packetWriter, SendDataOptions.InOrder, myNetworkSession.Host); | |||
</source> | </source> | ||
Aktuelle Version vom 5. August 2021, 11:21 Uhr
Netzwerk
Da die Implementierung von Netzwerkfunktionalität für Multiplayer-Unterstützung nicht ganz trivial umzusetzen ist wird sie gern als optionales Feature für die spätere Entwicklung angedacht. Das Problem hierbei ist, dass Netzwerkfunktionalität einen entscheidenden Einfluss auf die gesamte Architektur hat. Wenn Netzwerkfunktionalität geplant ist sollte diese daher von vornherein als festes Feature eingeplant werden.
Wenn man sich dafür entscheidet, Netzwerkfunktionalität zu implementieren, sollte man wissen, dass es für das XNA Framework zwei grundlegend verschiedene Möglichkeiten gibt:
- Man kann die Netzwerk-Funktionalitäten Nutzen, die das XNA-Framework zur Verfügung stellt. Hierzu sollte man folgende Fakten zu XNA Framework Networking and LIVE Requirements kennen.
- Wenn man die Einschränkungen der ersten Möglichkeit nicht hinnehmen will und/oder nicht mit Xbox LIVE bzw. Games for Windows® - LIVE arbeiten möchte, so kann man mit .NET eigene Netzwerkfunktionalität programmieren oder versuchen eine .NET Network-Library zu integrieren. Auch wenn man bei dieser Alternative mehr flexibilität erreichen kann, ist das definitiv die schwerere Variante. Mögliche Einstiegspunkte in das Thema:
Davon unabhängig sind für die Realisierung eines klassischen Client/Server Konzepts folgende Schritte notwendig:
Auf Server Seite:
- Socket öffnen & Clientverbindungen annehmen
- Daten gemäß einem definierten Protokoll transportieren
Auf Client Seite:
- Mit Server Socket verbinden
- Daten gemäß einem definierten Protokoll transportieren
Protokoll
Ein Netzwerkprotokoll legt fest, wie und in welcher Form Daten von einem Rechner in einem Netzwerk zu einem anderen Rechner übertragen werden. Bei der Definition eines (Netzwerk-)Protokolls muss auf sehr viele verschiedene Dinge geachtet werden. Eines der wichtigsten ist die zur Verfügung stehende Datenübertragungsrate des Netzwerks. Ein echtzeit-multiplayer Spiel muss seinen Spielzustand mit allen verbundenen Spielern zu jeder Zeit synchron halten. Das heißt, jede Änderung des Spielzustandes eines Spielers muss ebenfalls in den Spielzuständen aller anderen Spieler durchgenommen werden.
Eine Möglichkeit, dies zu lösen, ist, den gesamten Spielzustand z.B. einmal pro Sekunde zu übertragen. Allerdings kommt hier besagte maximale Datenübertragungsrate zum Tragen, denn ein Spielzustand, in dem sich 500 Spielobjekte befinden und ihre Attribute ändern, kann je nach Wahl der Architektur und des Protokolls so groß sein, dass eine 100 MBit/s Übertragungsrate nicht mehr ausreicht. Da die Datenübertragungsrate im Internet meist deutlich geringer als 100 MBit/s ist, ist Multiplayer über das Internet ist mit diesem "naiven" Protokoll fast immer undenkbar.
Wie die Übertragung von Spielzuständen möglichst effizient erreicht werden kann, das heißt, dass nur die wirklich notwendigen Daten übertragen werden, die für die Änderung des Spielzustands wichtig sind, ist eine "Kust für sich" und hängt stark von der Softwarearchitektur des Spiels ab. Überlegen Sie sich bei der Definition eines Netzwerkprotokolls daher genau, welche Daten Sie übertragen müssen, damit das Spiel im Netzwerk spielbar ist und bleibt, und wie Sie diese Daten übertragen.
Multiplayer mit der Lidgren Bibliothek
Die NetBuffer Klasse
Eine Instanz der NetBuffer Klasse erhält man stets durch Aufruf der Methode CreateBuffer() auf einem NetServer respektive NetClient Objekt.
Die Klasse stellt zum Schreiben von Nutzdaten die Methode Write() mit unzähligen Overloads für die einzelnen Datentypen zur Verfügung. Wenn die XNA Extension genutzt wird können auch direkt XNA Datentypen wie z.B. Vector3 in einen NetBuffer geschrieben werden.
Um die Nutzdaten aus einem NetBuffer Objekt auszulesen werden die entsprechend benannten Read() Methoden verwendet. So gibt es z.B. ReadString(), ReadBoolean(), ReadFloat() und falls die XNA Extension eingebunden ist auch Methoden wie ReadVector3().
Zu beachten hierbei ist, dass die Daten in einem Buffer in der gleichen Reihenfolge wieder gelesen werden müssen in der sie auch geschrieben wurden.
Network Channels
Es gibt 4 verschiedene virtuelle Netzwerk Kanäle die beim versenden von Paketen mittels der Lidgren Library angegeben werden können:
- UnreliableUnordered - Nachrichten kommen entweder beim Empfänger an oder auch nicht
- UnreliableInOrder - Auslieferung der Nachrichten ebenfalls nicht garantiert, falls eine Nachricht zu spät kommt, d.h. eine neuere Nachricht ist vorher eingetroffen, so wird die ältere gedroppt
- ReliableUnordered - Garantierte Auslieferung der Nachrichten, allerdings nicht notwendigerweise in der Reihenfolge in der sie gesendet wurden
- ReliableInOrder - Alle Nachrichten kommen in der Reihenfolge in der sie gesendet wurden beim Empfänger an
Server
Starten des Servers
NetPeerConfiguration config = new NetPeerConfiguration("MyApp");
config.Port = 4096;
NetServer myNetServer = new NetServer(config);
myNetServer.Start();
Empfangen von Clientdaten
NetIncomingMessage msg;
while ((msg = server.ReadMessage()) != null)
{
switch (msg.MessageType)
{
case NetIncomingMessageType.VerboseDebugMessage:
case NetIncomingMessageType.DebugMessage:
case NetIncomingMessageType.WarningMessage:
case NetIncomingMessageType.ErrorMessage:
Console.WriteLine(msg.ReadString());
break;
case NetIncomingMessageType.Data:
// data sent by client..
// buffer contains the userdata from the client..
// sending client is specified in sender
break;
}
}
Daten an Clients senden
myNetServer.SendMessage(buffer, client, NetChannel.UnreliableInOrder1);
myNetServer.SendToAll(buffer, NetChannel.UnreliableInOrder1);
Client
Client starten
NetConfiguration config = new NetConfiguration("MyApp");
NetClient myNetClient = new NetClient(config);
myNetClient.SetMessageTypeEnabled(NetMessageType.ConnectionRejected, true);
myNetClient.DiscoverLocalServers(4096);
Daten vom Server empfangen
NetBuffer buffer = myNetClient.CreateBuffer();
NetMessageType type;
while (myNetClient.ReadMessage(buffer, out type))
{
switch (type)
{
case NetMessageType.ServerDiscovered:
// just connect to any server found!
myNetClient.Connect(buffer.ReadIPEndPoint());
break;
case NetMessageType.ConnectionRejected:
Console.WriteLine("Rejected: " + buffer.ReadString());
break;
case NetMessageType.DebugMessage:
case NetMessageType.VerboseDebugMessage:
Console.WriteLine(buffer.ReadString());
break;
case NetMessageType.StatusChanged:
string statusMessage = buffer.ReadString();
NetConnectionStatus newStatus = (NetConnectionStatus)buffer.ReadByte();
Console.WriteLine("New status: " + newStatus + " (" + statusMessage + ")");
break;
case NetMessageType.Data:
// userdata sent from the server is now in the buffer
break;
}
}
Daten zum Server senden
myNetClient.SendMessage(buffer, NetChannel.UnreliableInOrder1);
Multiplayer mittels Windows Live
PacketReader & PacketWriter
Instanzen dieser Klassen dienen zum bequemen Schreiben bzw. Lesen von Netzwerkdaten. Die PacketWriter Klasse verfügt über eine merfach überladene Write() Methode für verschiedenste Datentypen. Ebenso einfach können diese Datentypen über die entsprechend benannten Read() Methoden der PacketReader Klasse wieder gelesen werden. Auch hier gilt, dass die Reihenfolge des Lesens und Schreibens die Gleiche sein sollte.
SendDataOptions
Das Windows Live System bietet ebenfalls die 4 grundlegenden Transportwege an. Diese können beim Senden durch Setzen eines Wertes des Typ SendDataOptions ausgewählt werden. Diese Enum enthält unter anderem folgende Definitionen:
- None - Nachrichten kommen entweder beim Empfänger an oder auch nicht
- InOrder - Auslieferung der Nachrichten ebenfalls nicht garantiert, falls eine Nachricht zu spät kommt, d.h. eine neuere Nachricht ist vorher eingetroffen, so wird die ältere gedroppt
- Reliable - Garantierte Auslieferung der Nachrichten, allerdings nicht notwendigerweise in der Reihenfolge in der sie gesendet wurden
- ReliableInOrder - Alle Nachrichten kommen in der Reihenfolge in der sie gesendet wurden beim Empfänger an
Server
Starten des Servers
const int maxGamers = 16;
const int maxLocalGamers = 4;
try
{
NetworkSession myNetworkSession = NetworkSession.Create(NetworkSessionType.SystemLink,
maxLocalGamers, maxGamers);
// hook event handlers
myNetworkSession.GamerJoined += GamerJoinedEventHandler;
myNetworkSession.SessionEnded += SessionEndedEventHandler;
}
catch (Exception e)
{
errorMessage = e.Message;
}
Die Eventhandler
void GamerJoinedEventHandler(object sender, GamerJoinedEventArgs e);
void SessionEndedEventHandler(object sender, NetworkSessionEndedEventArgs e)
{
errorMessage = e.EndReason.ToString();
myNetworkSession.Dispose();
myNetworkSession = null;
}
Empfangen von Clientdaten
// Keep reading as long as incoming packets are available.
while (gamer.IsDataAvailable)
{
NetworkGamer sender;
// Read a single packet from the network.
gamer.ReceiveData(packetReader, out sender);
if (!sender.IsLocal)
{
// Read the packetdata available in packetReader
}
}
Daten an Clients senden
LocalNetworkGamer server = (LocalNetworkGamer)myNetworkSession.Host;
server.SendData(packetWriter, SendDataOptions.InOrder, client);
myNetworkSession.Update();
LocalNetworkGamer server = (LocalNetworkGamer)myNetworkSession.Host;
server.SendData(packetWriter, SendDataOptions.InOrder);
myNetworkSession.Update();
Client
Client starten
const int maxLocalGamers = 4;
try
{
// Search for sessions.
using (AvailableNetworkSessionCollection availableSessions = NetworkSession.Find(
NetworkSessionType.SystemLink, maxLocalGamers, null))
{
if (availableSessions.Count == 0)
{
errorMessage = "No network sessions found.";
return;
}
// Join the first session we found.
myNetworkSession = NetworkSession.Join(availableSessions[0]);
// hook event handlers
myNetworkSession.GamerJoined += GamerJoinedEventHandler;
myNetworkSession.SessionEnded += SessionEndedEventHandler;
}
}
catch (Exception e)
{
errorMessage = e.Message;
}
Daten vom Server empfangen
// Keep reading as long as incoming packets are available.
while (gamer.IsDataAvailable)
{
NetworkGamer sender;
// Read a single packet from the network.
gamer.ReceiveData(packetReader, out sender);
// Read the data from packetReader..
}
Daten zum Server senden
gamer.SendData(packetWriter, SendDataOptions.InOrder, myNetworkSession.Host);