Autor Thema: [Scripting for Dummies] Multiplayer - Teil 1 - Nachrichten  (Gelesen 3374 mal)

0 Mitglieder und 1 Gast betrachten dieses Thema.

Offline FG28_Kodiak

  • Blasenteetrinker
  • *****
  • Beiträge: 1.893
Nachdem wir uns in den letzten Lektionen hauptsächlich mit Singleplayermissionen beschäftigt haben, werden wir langsam auch Multiplayermissionen ins Auge fassen.
In der heutigen Lektion kümmern wir uns darum Nachrichten an die jeweiligen Multiplayerparteien zu schicken, ohne das die andere Seite das mitbekommt ;).

Bislang haben wir GamePlay.gpHUDLogCenter("Nachricht") verwendet, um Meldungen auf dem Bildschirm anzuzueigen. Damit ergibt sich allerdings das Problem das diese Nachrichten von allen Spielern gesehen werden können. Zum Glück stellt uns das Spiel noch drei weitere Methoden gpHUDLogCenter zur Verfügung, nämlich:

void gpHUDLogCenter(Player[] to, string msg)
void gpHUDLogCenter(Player[] to, string msg, object[] parms)
void gpHUDLogCenter(Player[] to, string msg, object[] parms, double lifeTime)

Diesen drei "überladenen" Methoden ist gemein das sie ein Array vom Typ Player als Übergabe erwarten (Player[] to), damit ist es möglich einem Piloten oder einer bestimmten Pilotengruppe eine Meldung zukommen zu lassen, die nur von diesen empfangen wird.
Schauen wir uns zuerst die drei Methoden genauer an.
void gpHUDLogCenter(Player[] to, string msg) erweitert unsere schon bekannte Variante um die Möglichkeit Adressaten anzugeben. 
void gpHUDLogCenter(Player[] to, string msg, object[] parms) hier können zusätzlich noch Objekte übergeben werden die Werte enthalten, kennen wir ja bereits von gpLogServer, die Anwendung erfolgt genauso. Beispiel:
int ZerstörtAnzahl =5;
int GesamtAnzahl = 10;
GamePlay.gpHUDLogCenter(BlauePlayerListe, "Blau hat {0} von {1} Zielen zerstört", new object [] {ZerstörtAnzahl, GesamtAnzahl});
Würde dann an die Blauen Spieler diese Meldung ausgeben:

Bei void gpHUDLogCenter(Player[] to, string msg, object[] parms, double lifeTime) kann man zusätzlich noch eine Anzeigedauer angeben (double lifeTime).

So nun zum Adressaten, gibt man null an, wird die Meldung an alle verschickt.
Beispiel:
GamePlay.gpHUDLogCenter(null, "Blau hat {0} von {1} Zielen zerstört", new object [] {ZerstörtAnzahl, GesamtAnzahl});
Würde die obige Meldung an alle ausgeben.
Es besteht auch die Möglichkeit die Meldung nur einen Piloten zu zeigen und zwar beispielsweise mit:
GamePlay.gpHUDLogCenter(new Player[] { player }, "Nachricht");
Beispiel:
public override void OnAircraftCrashLanded(int missionNumber, string shortName, AiAircraft aircraft)
{
    base.OnAircraftCrashLanded(missionNumber, shortName, aircraft);

    GamePlay.gpHUDLogCenter(new Player[] { aircraft.Player(0) }, "Hauptsache unten");
}

Hiermit würde nur der Bruchpilot selbst die Meldung "Hauptsache unten" bekommen.

So wie kommen wir jetzt aber an das Array mit den Piloten? Glücklicherweise stellt uns das Spiel die Methode gpRemotePlayers() der GamePlay-Klasse zur Verfügung, diese Methode liefert uns genau das Array das wir benötigen, nämlich eins mit sämtlichen Spielern die gerade im Spiel sind. Wir müssen jetzt also nichts anderes tun als die Piloten aus dem Array in ein neues zu kopieren, die die von uns gewünschten Eigenschaften erfüllen.
Möchten wir also z.B. nur die Blauen Piloten haben, gehen wir alle Elemente des Arrays durch und kopieren alle Piloten die als Wert bei Army den Wert 2 haben in ein neues Array oder besser Liste.
Der Code für eine Methode die das leistet sieht folgendermassen aus:
private void sendScreenMessageToBlue(string msg)
{
    if (GamePlay.gpRemotePlayers() == null || GamePlay.gpRemotePlayers().Length <= 0)
    {
        if (GamePlay.gpPlayer() != null && GamePlay.gpPlayer().Army() == 2)
            GamePlay.gpHUDLogCenter(null, msg);
       
        return;       
    }

    List<Player> bluePlayers = new List<Player>();

    foreach (Player p in GamePlay.gpRemotePlayers())
    {
        if (p.Army() == 2)
            bluePlayers.Add(p);
    }
    GamePlay.gpHUDLogCenter(bluePlayers.ToArray(), msg);
}
Schauen wir uns das ganze näher an:
if (GamePlay.gpRemotePlayers() == null || GamePlay.gpRemotePlayers().Length <= 0)
{
    if (GamePlay.gpPlayer() != null && GamePlay.gpPlayer().Army() == 2)
        GamePlay.gpHUDLogCenter(null, msg);
    return;       
}

Mit diesem Codeabschnitt stellen wir fest ob überhaupt Online-Spieler anwesend sind.
Dazu verwenden wir die Abfrage if (GamePlay.gpRemotePlayers() == null || GamePlay.gpRemotePlayers().Length <= 0) mit dieser stellen wir fest ob überhaupt ein Array vorhanden ist GamePlay.gpRemotePlayers() == null wird wahr wenn kein Array zurückgeliefert wird, das bedeutet es ist kein Multiplayerspiel im gange, und mit GamePlay.gpRemotePlayers().Length <= 0 überprüfen wir ob überhaupt jemand auf dem Server ist.
Sollte also eines der beiden Kriterien zutreffen haben wir es entweder mit einem Singleplayerspiel oder einem leeren Server zu tun, um doch eine Meldung abzusetzen (kann ja sein das der Missionsdesigner seine Mission auch testen möchte) überprüfen wir im nächsten Schritt ob ein Spieler vorhanden also GamePlay.gpPlayer() != null ist und ob dieser Spieler der gewünschten Seite (hier blau also 2) angehört ist dies der Fall wird die Meldung an den Spieler ausgegeben. Anschließend wird die Methode mit return beendet.

Haben wir es hingegen mit einem Multiplayerspiel zu tun, in dem sich mehrere Spieler befinden erzeugen wir mit List<Player> bluePlayers = new List<Player>(); eine neue Liste mit Namen bluePlayers in dem wir die Player speichern können, die unserem Kriterium entsprechen.

foreach (Player p in GamePlay.gpRemotePlayers())
{
    if (p.Army() == 2)
        bluePlayers.Add(p);
}
GamePlay.gpHUDLogCenter(bluePlayers.ToArray(), msg);

Mit einer foreach Schleife durchwandern wir die Player im von GamePlay.gpRemotePlayers() gelieferten Array und fügen dann innerhalb der Schleife alle Player die zur blauen Armee (2) gehören an unsere bluePlayers-Liste an. Nach der Schleife schicken wir dann die Meldung mit GamePlay.gpHUDLogCenter(bluePlayers.ToArray(), msg); an die blauen Spieler. Mit ToArray() wandeln wir unsere Liste in ein Array um.

Für die rote Seite kann man ebenfalls eine Methode schreiben, hier werden dann halt die Player kopiert die zur roten Armee (1) gehören:
private void sendScreenMessageToRed(string msg)
{
    if (GamePlay.gpRemotePlayers() == null || GamePlay.gpRemotePlayers().Length <= 0)
    {
        if (GamePlay.gpPlayer() != null && GamePlay.gpPlayer().Army() == 1)
            GamePlay.gpHUDLogCenter(null, msg);
       
        return;       
    }

    List<Player> redPlayers = new List<Player>();

    foreach (Player p in GamePlay.gpRemotePlayers())
    {
        if (p.Army() == 1)
            redPlayers.Add(p);
    }
    GamePlay.gpHUDLogCenter(redPlayers.ToArray(), msg);
}

Man hat natürlich auch die Möglichkeit beide Methode in einer zu vereinen in dem man die Armee als Parameter mit der Methode übergibt also:
private void sendScreenMessageTo(int army, string msg)
{
    if (GamePlay.gpRemotePlayers() == null || GamePlay.gpRemotePlayers().Length <= 0)
    {
        if (GamePlay.gpPlayer() != null && GamePlay.gpPlayer().Army() == army)
            GamePlay.gpHUDLogCenter(null, msg);
       
        return;       
    }

    List<Player> Players = new List<Player>();

    foreach (Player p in GamePlay.gpRemotePlayers())
    {
        if (p.Army() == army)
            Players.Add(p);
    }
    GamePlay.gpHUDLogCenter(Players.ToArray(), msg);
}
Die Anwendung wäre dann sendScreenMessageTo(1, "Meldung") für Rot und sendScreenMessageTo(2, "Meldung") für Blau. Es ist eine Sache des persönlichen Geschmacks welche Art man schlussendlich verwendet sendScreenMessageToRed und sendScreenMessageToBlue haben meiner Meinung nach weniger Verwechslungsgefahr.

So abschließend als Beispiel alles zusammen mit zeitlicher Testausgabe:
using System;
using System.Collections.Generic;
using maddox.game;
using maddox.game.world;


public class Mission : AMission
{

    private void sendScreenMessageToBlue(string msg)
    {
        if (GamePlay.gpRemotePlayers() == null || GamePlay.gpRemotePlayers().Length <= 0)
        {
            if (GamePlay.gpPlayer() != null && GamePlay.gpPlayer().Army() == 2)
                GamePlay.gpHUDLogCenter(null, msg);
           
            return;       
        }

        List<Player> bluePlayers = new List<Player>();

        foreach (Player p in GamePlay.gpRemotePlayers())
        {
            if (p.Army() == 2)
                bluePlayers.Add(p);
        }
        GamePlay.gpHUDLogCenter(bluePlayers.ToArray(), msg);
    }
   
    private void sendScreenMessageToRed(string msg)
    {
        if (GamePlay.gpRemotePlayers() == null || GamePlay.gpRemotePlayers().Length <= 0)
        {
            if (GamePlay.gpPlayer() != null && GamePlay.gpPlayer().Army() == 1)
                GamePlay.gpHUDLogCenter(null, msg);
           
            return;       
        }

        List<Player> redPlayers = new List<Player>();

        foreach (Player p in GamePlay.gpRemotePlayers())
        {
            if (p.Army() == 1)
                redPlayers.Add(p);
        }
        GamePlay.gpHUDLogCenter(redPlayers.ToArray(), msg);
    }


    private void sendScreenMessageTo(int army, string msg)
    {
        if (GamePlay.gpRemotePlayers() == null || GamePlay.gpRemotePlayers().Length <= 0)
        {
            if (GamePlay.gpPlayer() != null && GamePlay.gpPlayer().Army() == army)
                GamePlay.gpHUDLogCenter(null, msg);
           
            return;       
        }

        List<Player> Players = new List<Player>();

        foreach (Player p in GamePlay.gpRemotePlayers())
        {
            if (p.Army() == army)
                Players.Add(p);
        }
        GamePlay.gpHUDLogCenter(Players.ToArray(), msg);
    }

    public override void OnAircraftCrashLanded(int missionNumber, string shortName, AiAircraft aircraft)
    {
        base.OnAircraftCrashLanded(missionNumber, shortName, aircraft);
   
        GamePlay.gpHUDLogCenter(new Player[] { aircraft.Player(0) }, "Hauptsache unten");
    }


    public override void OnTickGame()
    {
        if (Time.tickCounter() % 666 == 665) 
        {
            sendScreenMessageToBlue("sendScreenMessageToBlue: Blaue Testmeldung");
            sendScreenMessageToRed("sendScreenMessageToRed: Rote Testmeldung");
           

        }
        if (Time.tickCounter() % 1000 == 999) 
        {   
            sendScreenMessageTo(1, "sendScreenMessageTo: Rote Meldung");
            sendScreenMessageTo(2, "sendScreenMessageTo: blaue Meldung");   
        }
    }
}

Der Missionsdesigner muss nur die entsprechenden Methoden in seinen Code kopieren und dann an gegebener Stelle aufrufen.

Wie immer bin ich für Kritik und Anregungen dankbar.
« Letzte Änderung: 20.Juni.2011, 13:40 von FG28_Kodiak »

Offline Trantor

  • Blasenteetrinker
  • *****
  • Beiträge: 975
  • Burn Baby Burn!
    • Jagdgruppe Ost
Re: [Scripting for Dummies] Multiplayer - Teil 1 - Nachrichten
« Antwort #1 am: 19.Juni.2011, 20:19 »
1. Danke  8)
2. Ich will ein Kind von Dir  ;D

~S~

Trantor

Offline I/JG27_Zimmi

  • Hallenfeger
  • **
  • Beiträge: 26
    • I. Gruppe Jagdgeschwader 27
Re: [Scripting for Dummies] Multiplayer - Teil 1 - Nachrichten
« Antwort #2 am: 20.Juni.2011, 06:33 »
An der Stelle auch ein großes Lob und Dankeschön von mir für die mühevolle Aufarbeitung der Programmiermöglichkeiten in CloD.
Punkt 2. würde ich so allerdings nicht unterschreiben 


Offline ulyssesOne

  • Blasenteetrinker
  • *****
  • Beiträge: 966
  • No Panic !!!
Re: [Scripting for Dummies] Multiplayer - Teil 1 - Nachrichten
« Antwort #3 am: 20.Juni.2011, 08:38 »
Ein spezielles Danke von mir. Super.

... Warum muss das so kompliziert? :o


Tatsächlich würde ich mir wünschen, dass man seitens Entwickler für bestimmte "einfache" Dinge eine Eingabemaske mit Füllfeldern erhält und das entsprechende Script sozusagen im Hintergrund für mich aufgebaut wird. Zumindestens bei triggerfreien Textnachrichten sollte dies kein Problem darstellen. Was müsste so eine Maske an Daten abfragen?

Textnachrichten-Creator

Speichern unter: Meldung1_Blau

Zeit: Missionsstart + xxxx Sekunden

Empfänger: Blau

Textausgabe: "HQ meldet: Feindverband in PQ A2, Kurs NO"


Mir ist zwar klar, dass das prinzipiell nur Oberflächengeplätscher in punkto Scripting wäre, dennoch wäre es für nahezu jeden Missionsbauer händelbar. Warum man die Spawnzeit nicht wieder ebenfalls ins Objektmenü eingebracht hat, ist ebenso unverständlich, schliesslich wäre es mir, ob im Hintergrund ein überschreibares Script angelegt wird.

Was Scripting angeht, werde ich mich wohl an ehemaligen Politikergrößen orientieren und über Copy und Paste wohl nicht hinauskommen.  ???

Hihi ... vielleicht setzt ja irgendwann jemand ein externes Tool zum einfachen erstellen von Scripts auf, welche man dann per .txt ausgeben lässt und so einfach nur ins Spiel zu kopieren braucht.  ;) Bei IL2 gibt es ja auch tolle Tools, die ich früher garnicht für möglich gehalten hätte.
« Letzte Änderung: 20.Juni.2011, 08:44 von ulyssesOne »
We ask you to react professionally – exactly this type of reaction will help us to fix everything as soon as possible.