Pseudo 3D Grafik und Map-Abfrage



  • Hallo,

    ich bin gerade dabei ein kleines Spiel im Stil von "Eye of the Beholder" bzw. "Dungeon Master" (wer nicht weiß was gemeint ist, einfach mal bei Google-Bilder suchen) zu programmieren. Das klappt eig. gut soweit, das Spiel läuft sogar schon. Die Map ist ein 2D-Array, aus dem wiederum ein kleinerer 2D-Array, der den Bereich umfasst, den man gerade sieht, "ausgeschnitten" und dann die einzelnen Zellen per If-Abfrage auf Wände überprüft und dann ggf. auf den Bildschirm gezeichnet. Der kleinere 2D-Array für das Sichtfeld ist 3x4 Felder groß (also man sieht max. 4 Felder nach vorn und dann eben noch rechts, mitte und links) und wird je nach Blickrichtung auch auf der Map gedreht. Ich hoffe mal das ist einigermaßen verständlich ^^. Auf jedem PC reicht die Leistung dafür natürlich locker aus und das ganze läuft ausreichend schnell. Da das Spiel aber auch auf meinem uralt Palm (und vllt. sogar auch mal auf meinem grafischen Taschenrechner) laufen soll, muss das ganze mit bissl weniger Code auskommen, denn so wie ich es momentan mache dauert das auf dem Gerät zu lang. Assembler wäre natürlich eine Möglichkeit, aber mich da nochmal einzuarbeiten... dazu fehlt mir gerade die Zeit und die Lust. Die oben genannten Spiele (und ähnliche) liefen ja schon auf dem alten Atari und C64.

    Ich brauche also einen Lösungsansatz wie ich mein Sichtfeld auf der Map geschickter abfrage.
    Des weiteren würde ich gerne wissen ob es geschickter ist die Map so:

    xxxxxxxxxx
    xoxxoxxxxx
    xoooooooox
    xxxoxxxxxx
    xxxooooxxx
    xxxxxxxxxx
    

    X = Wand; O = frei

    oder so:

    oooooooooo
    oooooooooo
    ooiiTiiioo
    ooooiooooo
    ooii+ioooo
    ooooiooooo
    

    + = Kreuzung
    T = T-Kreuzung
    i = gerader Gang

    aufzubauen.
    Was wäre da von der Abfrage geschickter und schneller zu realisieren?

    So, das ist jetzt erstmal ne ganze Menge ^^. Ich hoffe ich hab mein Problem einigermaßen verständlich geschildert, wenns noch Fragen gibt, einfach fragen 😉

    Ich hoffe ihr könnt mir helfen.

    Lg Metal_Zone



  • das mit den o's und x'en ist einfacher zu lesen als mensch und was einfacher in einer datei zu speichern (man muss sich z.B. die rotationen von den T-Kreuzungen nicht merken oder angeben)

    aber wenn du so eine datei einliest, wäre es wahrscheinlich einfacher wenn man das mit T's und 's macht, da man sonst im nachhinein noch zwischen normalen weg und kreuzung unterscheiden müsste, was dann nur per if geht



  • Oder so:

    nnnnn   nnnnn   nnnnn
    w     e w     e w     e
    w     e w     e w     e
    w     e w     e w     e
     sssss   sssss   sssss
    
     nnnnn   nnnnn   nnnnn
    w     e w     e w     e
    w     e w     e w     e
    w     e w     e w     e
     sssss   sssss   sssss
    
     nnnnn   nnnnn   nnnnn
    w     e w     e w     e
    w     e w     e w     e
    w     e w     e w     e
     sssss   sssss   sssss
    

    Das soll eine 3x3 Map darstellen.
    Wobei
    nnnnn = Wand die nach Norden zeigt
    sssss = Wand die nach Süden zeigt
    www = Wand die nach Westen zeigt
    eee = Wand die nach Osten zeigt

    Bei zwei übereinander liegenden Zellen sind die "sssss" Wand der oberen Zelle und die "nnnnn" Wand der unteren Zelle im Prinzip die selbe Wand, nur dass die "sssss" Wand der oberen Zelle zu sehen ist wenn man von unten (Süden) nach oben (Norden) guckt, und die "nnnnn" Wand der unteren Zelle zu sehen ist wenn man umgekehrt ("nach unten") guckt.

    Jede Map-Zelle wäre dann ein struct aus 4 ints (oder 4 shorts oder sogar chars), wo man z.B. die Textur-Nummer für den Teil der Wand reinschreiben kann.

    Man könnte auch in bestimmte Bits des int/short/char z.B. eine Tür-Nummer, Schalter-Nummer oder allgemein "Special Wall" Nummer reinschreiben, und über diese eine Verknüpfung mit den Tür/Schalter/... Objekten herstellen.

    Es verschwendet zwar etwas Platz gegenüber anderen Möglichkeiten, aber vermutlich nicht so viel dass es wirklich weh tut, und es sollte recht einfach auszulesen sein.



  • Da bekomme ich dann leider ein Speicherprolem 😞 und vom Abfragen her wird's auch nicht viel einfacher, z.B. wenn ich nach Osten schaue, muss ich das ganze ja auch irgendwie "drehen"...

    Aber danke für eure Antworten! Über das mit den T's usw. muss ich nochmal nachdenken und bissl ausprobieren ob sich da was vereinfachen lässt...



  • Vier Byte pro Feld sollten eigentlich nicht so viel Platz darstellen - und das mit der Drehung lässt sich sicher auch mit Modulo-Arithmetik erreichen:

    struct feld
    {
      char mauern[4];//Mauern in Reihenfolge N/E/S/W
    };
    
    ->
    mauer_front = feld[i][j].mauern[richtung];
    mauer_rechts= feld[i][j].mauern[(richtung+1)%4];
    mauer_links = feld[i][j].mauern[(richtung+3)%4];
    


  • Achja. In dem von dir vorgeschlagenen Format ist auch nicht wirklich Platz für Texturen/Zusatzinfos.
    Falls keine benötigt werden, dann kommst du mit "meiner" Variante auch mit 1 Byte/Zelle aus, nämlich indem du für jede Wand ein Bit verwendest.

    Drehung kann da genau so wie im Beispiel von CStoll über Modulo-Arithmetik gemacht werden

    void foo()
    {
        unsigned char const frontBit = 1 << direction;
        unsigned char const leftBit = 1 << ((direction + 1) % 4);
        unsigned char const rightBit = 1 << ((direction + 3) % 4);
    
        for (...)
        {
            unsigned char const cell = map[x][y];
            if (cell & frontBit)
                ...
            if (cell & leftBit)
                ...
            if (cell & rightBit)
                ...
        }
    


  • unsigned char const rightBit = 1 << ((direction + 3) % 4);
    

    Was macht denn das "<<" und wofür ist das "% 4"? Sowas hab ich noch nie gesehen 😕



  • << ist ein Bit-Shift - das verschiebt den Wert (als Binärzahl betrachtet) um die angegebene Stellenanzahl nach links. 1<<n ergibt eine Zahl, bei der nur das n-te Bit gestzt ist - sehr praktisch im Einsatz mit Flag-artigen Daten.
    % ist der Rest bei der ganzzahligen Division.



  • Entschuldigt, wenn es auch schon etwas länger her ist, aber ich denke, ich habe eine Lösung für dieses Problem hier:

    Metal_Zone schrieb:

    xxxxxxxxxx
    xoxxoxxxxx
    xoooooooox
    xxxoxxxxxx
    xxxooooxxx
    xxxxxxxxxx
    

    X = Wand; O = frei

    Ist die einfachere Methode und lässt später noch Türen, Leitern usw. zu.
    Kreuzungen ergeben sich bereits aus dem Wegverlauf.

    Metal_Zone schrieb:

    Da bekomme ich dann leider ein Speicherprolem und vom Abfragen her wird's auch nicht viel einfacher, z.B. wenn ich nach Osten schaue, muss ich das ganze ja auch irgendwie "drehen"...

    Ja, aber nur den sichtbare Bereich muss "gedreht" werden und dass lässt sich so lösen:

    Nehmen wir mal an, du hast eine Sichtweite von 3 Schritten nach vor und jeweils einen Schritt links und rechts und der Spieler blickt nach Osten.

    xxxxxxxxxx
    xoXXOxxxxx
    xoOOOoooox
    xxXOXxxxxx
    xxxooooxxx
    xxxxxxxxxx
    

    Die Großbuchstaben zeigen jetzt dein Sichtfeld, du stehst vor der Kreuzung und siehst nach Westen.

    Zum ersten, drehen muss man nur den sichtbaren Bereich und zwar so, daß man immer nach Norden sieht. Bei der Darstellung am Bildschirm sieht man auch immer nach Norden, da man die richtige Himmelsrichtung nicht wirklich bestimmen kann und es auch das Zeichnen auch wesentlich einfacher gestaltet 🙂 .
    (Natürlich kann man auch ein N, W, O od. S irgendwo anzeigen 😃 )

    Dazu braucht man jetzt in diesem Fall ein Array von 9 Feldern welches wie folgt aussieht:

    01 02 03
    04 05 06
    07 08 09
    

    Jetzt überträgt man den sichtbaren Kartenausschnitt auf das Array, wobei die Position, wo der Spieler selbst steht immer 08 ist.
    07 ist jetzt der Kartenteil links vom Spieler und 09 ist der Kartenteil rechts vom Spieler.
    05 ist der Kartenteil direkt einen Schritt vor ihm usw.

    So erreicht man jetzt eine "Drehung" nach Norden.

    Jetzt geht es an den Ausgabebildschirm:

    Nun muß von hinten nach vorne gezeichnet werden, wobei, hat man ein X, so muß man eine Wand zeichnen, hat man ein O, so braucht man nichts zeichnen, da normalerweise bei jedem Schritt der Ausgabebildschirm gelöscht und immer neu gezeichnet wird, so bleibt der Bereich also frei und der dahinter liegende Bereich wird gezeigt.

    Bei der hintersten Ebene (1,2,3) sind die Tiles also am niedrigsten. Man zeichnet also die entsprechenden Tiles etwas ober der Mitte des Bildschirms.

    _____________________
    |                     |
    |                     |
    |---------------------|
    |---------------------|
    |                     |
    |_____________________|
    

    Bei der mittleren Ebene (4,5,6) sind die Tiles höher als die Tiles der hintersten Ebenen. Folglich zeichnet man Tiles etwas über der ersten Ebenen.
    Handelt es sich um eine Wand, so wird also die hinterste Ebene übermalen.

    _____________________
    |                     |
    |---------------------|
    |                     |
    |                     |
    |---------------------|
    |_____________________|
    

    Bei der vordersten Ebene (7,8,9) sind die Tiles genau so hoch wie der Ausgabebereich und kann man von einfach zu zeichnen beginnen.

    _____________________
    |                     |
    |                     |
    |                     |
    |                     |
    |                     |
    |_____________________|
    

    Vom Speicher- bzw. Geschwindigkeitsbedarf her, lässt sich hier sicher noch einiges optimieren, aber der Weg bleibt, denke ich immer der gleiche.
    Vorteil hierbei habe ich noch, das Sichtfeld lässt sich beliebig erweitern oder verkleinern.
    Wenn sich jetzt die Sichtweite ändert, so muß man natürlich auch die Ebenen am Ausgabebildschirm anpassen.

    Ich hoffe, ich konnte diesen Lösungsansatz verständlich und von Programmiersprachen unabhängig wie möglich beschreiben.

    Grüße.


Anmelden zum Antworten