Client - Server - Client; Spieler zu Spieler Datensynchronisation

  • GM 8

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • Client - Server - Client; Spieler zu Spieler Datensynchronisation

    Hallo!
    Ich werkle seit ein paar Stunden an einem Problem herum. Undzwar möchte ich, dass die Daten beliebig vieler Spieler untereinander synchronisiert werden, falls diese sich in der Nähe zueinander befinden.
    An sich funktioniert soweit auch alles. Bloß drawt der erste Client, den ich connecten lasse den anderen o_enemy immer auf der selben Stelle wie seinen eigenen o_player. Bei dem zweiten Client wird es richtig angezeigt. Ein Dritter wird erst gar nicht angezeigt. Ich vermute, dass er alle "fremden" Player(enemies) auf der selben Stelle zeichnet, da er sie nicht unterscheiden kann.

    Also im Server heißt das Objekt, welches die Kommunikation zwischen Server und Client verwaltet "player". Ich handhabe das so, dass dieses unsichtbare Objekt "player" zur selben Stelle springt, wie der dazugehörige Spieler im Client. Jetzt wird also im Step-Event von "player" geprüft, ob sich in der Nähe ein anderes "player"-Objekt befindet.
    Hier mal der relevante Code dazu. Ich denke, dass der Fehler darin besteht, dass der Server die verschiedenen "player"-Objekte nicht voneinander unterscheiden kann. Somit nimmt das erste player-Objekt die Distanz zu sich selber und sendet dann auch die eigenen Koordinaten.

    GML-Quellcode

    1. if distance_to_object(player) <=600 {
    2. clearbuffer()
    3. writebyte(msg_enemy)
    4. writeshort(player.x)
    5. writeshort(player.y)
    6. sendmessage(tcp)
    7. }


    Wie kann ich also die Distanz zu anderen "player"-Objekten prüfen lassen, obwohl alle von diesen den selben Namen haben? Es sollte beispielsweise auch mit 11 Playern funktionieren, sodass jeder die Bewegungen der anderen sehen kann.

    Danke schonmal für eure Anregungen! :D
  • Du musst über die Instanzen arbeiten und nicht über den Objektnamen. Außerdem solltest du den Clients beim connecten eine eindeutige ID geben und diese dann auch in den Spielerobjekten eintragen. so lässt sich dann leichter überprüfen, welche Instanz zu welchem Client gehört.

    © 2008 by Teamgrill Productions
  • Okay, danke für deine Antwort!

    Für zwei Spieler habe ich es jetzt zum Laufen gebracht, undzwar wie folgt:

    GML-Quellcode

    1. for(i=0;i<=server_init.players;i+=1) {
    2. if distance_to_object(server_init.o[i]) <=600
    3. {
    4. clearbuffer()
    5. writebyte(msg_enemy)
    6. writeshort(server_init.o[i].x)
    7. writeshort(server_init.o[i].y)
    8. sendmessage(tcp)
    9. }


    Jetzt muss ich nur noch deine Idee mit den ID's einbauen, damit er die Koordinaten der verschiedenen Gegner/Spieler unterscheiden kann.
    Zudem möchte ich den Nicknamen nur einmalig senden und abspeichern lassen. Dann soll jeweils lokal geprüft werden, ob der Nickname schon gesendet wurde (server) bzw. ob er schon eingegangen ist (client). Um ihn nicht in jedem Step senden zu müssen.

    Kann man prüfen lassen, ob zb gerade eine String-Message ankommt? Also dass er readstring() überspringen soll, falls an dieser Stelle kein Wert empanfen wird?

    GML-Quellcode

    1. if readstring() !=0 nickname=readstring()


    So in etwa?


    Hachja.. das ist schon spannend mit einer Online-Funktion zu arbeiten :D.
  • Ich bin mir nicht ganz sicher wie weit du bisher gekommen bist, aber ich erkläre hier kurz mal wie ich das mache.

    Beim Server gibt es ein Objekt dass ständig auf neue Verbindungen von außen eingeht. Verbindet sich ein Client mit dem Server, erstellt dieser ein objPlayer. Er weist diesem eine Eindeutige ID zu(via zählvariable die bei jeder neuen verbindung ansteigt, jedoch bei disconnecten nicht wieder sinkt, im gegensatz zum playercount).

    Der Client besitzt ein objReceive das für ALLE eingehenden messages zuständig ist.

    Jede Message vom Client wird folgendermaßen aufgebaut:
    (byte) - ID
    [...] - content
    Ihr wird also vor dem eigentlichen Inhalt eine ID verschickt, damit die Nachricht eindeutig zuordbar ist.

    Messages vom Server werden zusätzlich mit der Playernummer versehen
    (byte) - ID
    (byte) - objPlayer.PlayerId
    [..] - content

    Erreicht den Server eine Nachricht auf einer der Verbindungen, also in einem der objPlayer(s), sendet er diese per with schleife an die anderen weiter

    GML-Quellcode

    1. with(objPlayer)
    2. {
    3. if (playerID != other.playerID)
    4. {
    5. [...]//hier könnte jetzt noch deine distanz abfrage stehen:
    6. //if (distance_to_object(other)<= 600)
    7. }
    8. }

    Beim Client erreicht jede Nachricht das objReceive und es wird per switch(ID) zwischen den Nachrichten unterschieden.

    Du brauchst also nicht überprüfen ob ein String empfangen wurde. Identifiziere deine Nachrichten durch vorrausgesendete ID's.

    Ich hoffe ich konnte dir ein bisschen helfen :), viel Spaß noch mit der 39sten.
  • Hallo und danke für die Antwort!

    Partik schrieb:

    Jede Message vom Client wird folgendermaßen aufgebaut:
    (byte) - ID
    [...] - content
    Ihr wird also vor dem eigentlichen Inhalt eine ID verschickt, damit die Nachricht eindeutig zuordbar ist.

    Messages vom Server werden zusätzlich mit der Playernummer versehen
    (byte) - ID
    (byte) - objPlayer.PlayerId
    [..] - content
    Genau. So handhabe ich das momentan auch. ^^


    Partik schrieb:

    Beim Client erreicht jede Nachricht das objReceive und es wird per switch(ID) zwischen den Nachrichten unterschieden.

    Du brauchst also nicht überprüfen ob ein String empfangen wurde. Identifiziere deine Nachrichten durch vorrausgesendete ID's.
    An sich benutze ich auch nur die ID's. Aber um Bandbreite/Bytes zu sparen dachte ich mir, ich lasse überprüfen, ob zB. der Nickname schon an den Player versendet wurde, der momentan die Informationen zu einem anderen Player erhält (da sich dieser in dessen Nähe befindet).

    Hier der Code dazu, im Server:

    GML-Quellcode

    1. for(i=0;i<=global.ID;i+=1)
    2. {
    3. if distance_to_object(global.o[i]) <=600
    4. {
    5. clearbuffer()
    6. writebyte(msg_enemy)
    7. writebyte(global.o[i].ID)
    8. writeshort(global.o[i].x)
    9. writeshort(global.o[i].y)
    10. writeshort(global.o[i].rot)
    11. if !nickname[i]=global.o[i].nickname{ //wenn, der im Player-Controller, gespeicherte Nickname nicht gleich dem Nicknamen ist, der im anderen Player-Controller verwendet wird (=wenn der Nickname noch nicht an den anderen Spieler versandt wurde)
    12. writestring(global.o[i].nickname) //versende Nicknamen
    13. nickname[i]=global.o[i].nickname} //und speichere diesen
    14. sendmessage(tcp)
    15. }
    16. }
    Alles anzeigen



    Und im Client:

    GML-Quellcode

    1. ID=readbyte()
    2. xx=readshort()
    3. yy=readshort()
    4. rot=readshort()
    5. if !instance_exists(o[ID]) {o[ID]=instance_create(xx,yy,o_enemy)} //wenn sich noch kein Enemy (anderer Spieler) Objekt im Client befindet
    6. o[ID].x=xx
    7. o[ID].y=yy
    8. o[ID].rot=rot
    9. if o[ID].nickname="unknown" { //falls dessen Nickname noch nicht in einer lokalen Variable gespeichert wurde
    10. o[ID].nickname=readstring()}


    Ich habe keine Ahnung warum, aber durch die allerletzte if-Abfrage jeweils in diesen Codes werden die Namen der anderen Spieler gar nicht mehr angezeigt. Momentan lasse ich den Nicknamen einfach in jedem Step mitsenden. Das würde ich aber natürlich vermeiden wollen.

    Wäre es besser dein vorgeschlagenes With-Statement zu benutzen anstatt meiner For-Schleife, oder würde das auf das Selbe hinausführen?
  • Ich bin jetzt schon ein wenig weiter, wollte aber nicht extra ein neues Thema erstellen.

    Egal wie ich es drehe und wende, es klappt einfach nicht alles so, wie es soll. Sobald ich etwas am Code ändere, dann werden zB zwar die Schüsse der Gegner richtig im Client des anderen angezeigt, jedoch drehen sich die Gegner dann nicht mehr usw.

    Hier ist der besagte Code:

    GML-Quellcode

    1. case msg_enemy:
    2. ID=readbyte()
    3. xx[ID]=readshort()
    4. yy[ID]=readshort()
    5. dir[ID]=readbyte()/255*360
    6. if !instance_exists(o[ID]) {o[ID]=instance_create(0,0,o_enemy)}
    7. o[ID].x=xx[ID]
    8. o[ID].y=yy[ID]
    9. o[ID].direction=dir[ID]
    10. o[ID].nickname=readstring()
    11. break;
    12. case msg_bullet:
    13. ID=readbyte()
    14. with instance_create(xx[ID],yy[ID],o_bullet)
    15. {
    16. dir=other.dir[ID]
    17. direction=dir
    18. speed=6
    19. }
    20. break;
    Alles anzeigen



    Ich habe im Code schon viel umgestellt und ausprobiert. Weiß einer spontan, wie die Kugel im unteren Teil des Codes die richtigen Koordinaten und die richtige Direction des Spielers annimmt? Funktionieren tut es nämlich leider nicht, auch wenn es so aussieht.
  • Wäre es besser dein vorgeschlagenes With-Statement zu benutzen anstatt meiner For-Schleife, oder würde das auf das Selbe hinausführen?

    Probiers doch einfach mal aus :) , kommentier deine Scheife aus, und schreib das With Statement darunter.
    Hier ist der besagte Code:

    Wenn ich das jetzt richtig verstehe, dan liegt der Fehler darin, dass du die variable other.dir(von object o_bullet aus) mit dem Index [...] ID von object o_bullet ausliest. aber vorher setzt du die variable ID in deinem Receive object.

    GML-Quellcode

    1. case msg_bullet:
    2. ID=readbyte();
    3. with instance_create(xx[ID],yy[ID],o_bullet)
    4. {
    5. dir=other.dir[OTHER.ID];<<<<<<<<<<<<<<<<<<<<<<!!//das OTHER natürlich klein, ist nur zur verdeutlichung
    6. direction=dir;//dieser schritt ist unnötig, pack das ganze doch in eine Zeile á la
    7. //direction=other.dir[other.ID]
    8. speed=6;
    9. }
    10. break;
  • Partik schrieb:

    Probiers doch einfach mal aus , kommentier deine Scheife aus, und schreib das With Statement darunter.
    Habe den Code schon auf "With" umgeschrieben. Das sieht ordentlicher aus und man spart sich Variablen^^.

    Leider hat es mit der "other" Änderung auch nicht geklappt :(. Das hatte ich auch schonmal probiert. Kann es sein, dass die Variablen in einem "Case" nur für diesen gelten wie in einem Script? Ich kann mir einfach nicht erklären, weshalb die Kugel nicht die Variablen des Spielers annimmt. Sie erscheint immer bei 0, 0 und fliegt von links nach rechts.


    Edit:
    Okay mir ist gerade etwas seeehr unnormales aufgefallen. Ich habe jetzt im Server, sowie in jedem Objekt die dazugehörige ID drawen lassen, um das mal zu überprüfen. Bei allen Objekten scheint alles in Ordnung zu sein. BIS auf die Kugeln, die haben alle eine ID von 14. Immer und egal von welchem Player, während diese die ID 1...2...3 usw haben. Ich werde meinen Code nochmal checken müssen o_O...

    Edit 2:
    Aaaarrrghhh. Da suche ich mich im Code des Clienten dumm und dämlich und stelle den Code gefühlte 30 mal um..nur um nach 3 Tagen festzustellen, dass der Fehler im Server lag... undzwar an einer kleinen, unscheinbaren Stelle, die ich leicht übersah... . Es fehlte ein einfaches "clearbuffer()". Anstatt die Player ID zu versenden, hat der Server also immer die Message ID versendet (14) -.- ... :headtouch: :sauf:

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von lordvanzed ()

  • Ja, habe es im Code belassen. Danke für den Hinweis :). Jetzt klappt soweit auch alles, was ich bisher eingebaut habe. :troll:

    Jetzt muss ich nur noch eine vernünftige Schadensberechnung einbauen. Da das Spiel RPG-Elemente haben wird, brauche ich dazu immer die Daten beider Cliente (Client1.Atk-Client2.Def als sehr einfaches Beispiel).

    Sollte der Client, der die Kugel abfeuert im Falle einer Kugel-Gegner Kollision den Server "benachrichtigen", oder der Client des getroffenen Spielers? Oder ist die Chance einer Manipulation zu hoch und man sollte soetwas nur im Server berechnen und prüfen lassen?
  • Also, wegen Manipulation solltest Du Dir keine Sorgen machen. Versuch einfach nur, es zum Laufen zu bringen. Das ist schon schwierig genug.
    Hier noch ein paar kleine Hinweise, die sich für mich nützlich erwiesen haben.

    1. Benutze TCP und UDP. UDP für alles was schnell gehen soll, TCP für alles, was wichtig ist.
    2. Schraub den room_speed hoch auf Maximum, und benutze einen alternativen Timer als Game Clock. Es macht einen Riesenunterschied, ob man Netzwerk-Kommunikation mit 60 oder 900 fps taktet. Damit meine ich natürlich das Empfangen, nicht das Senden. Auf keinen Fall soll man die Bewegungen mit 900fps senden.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von Melancor ()