zatzen hat geschrieben:Dein Speichermanager wäre aber dennoch einmal interessant, allerdings habe ich bisher noch keine Not danach, was auch daran liegt dass ich bisher kein großartig komplexes Programm geschrieben habe. Aber auch den Speichermanager würde ich verstehen wollen und müssen, damit ich damit richtig umgehen kann.
Naja, so kompliziert ist der nicht. Anfangs hatte ich ihn bytegenau gemacht - was man auch machen kann. Dann habe ich aber zusätzlich einen Typ eingeführt, der nur aus einem Word besteht (um an ganzen Segmentgrenzen zu beginnen) und habe den so umgebaut, daß ALLE Pointer an Segmentgrenzen landen (also immer Offset=0). Das verschwendet manchmal ein paar Bytes, aber macht das Ganze einfacher für den "Segment-Only"-Typ. Trotzdem wird die Größe in Bytes zurückgegeben, wenn man danach fragt. Vor jedem reservierten Bereich ist ein Header, der die Adresse der Variable (Pointer, Word, o.ä.) enthält und die Größe und Typ usw. Das ist wichtig, weil, wenn sich die Speichergrößen ändern, die Bereiche verschoben werden und dann die Pointer, die auf die Bereiche zeigen, mit geändert werden müssen. Das ist eigentlich das ganze "Geheimnis" dahinter. Wie man so etwas dann in Code umsetzt, ist dann kein Problem mehr.
zatzen hat geschrieben:Sprachausgabe:
Es ist absolut klar und schlüssig, dass soetwas für ein Spiel generell eine ziemliche Nebensache ist. Etwas anders kann das aber durchaus bei Adventures aussehen. Da kann die Sprachausgabe zu einem reelleren Erlebnis beitragen. Zudem hatte ich da mit Freunden schon einen riesen Spaß, diese Sprachausgabe zu manipulieren und die Charaktere mitunter völligen Blödsinn oder übertrieben albern reden zu lassen - sicher nicht jedermanns Humor, aber immerhin mancher eins.
Mag ja alles richtig sein - hat nur nichts mit meiner Aussage zu tun: Das Spiel würde auch ohne Sprachausgabe funktionieren. (Es wäre nicht so lustig, etc. pp. ...) Aber es gibt eben Sachen, ohne die das Spiel NICHT funktionieren würde und bevor diese nicht funktionieren/eingebaut sind, kann man sich nicht über die "Zusatzfeatures" hermachen. Wenn die zwar funktionieren, aber für die unbedingt zur Funktion notwendigen Sachen kein Speicher/Rechenzeit/usw. mehr da ist, bekommt man das Programm/Spiel nicht fertig. Deshalb ist es so wichtig, erst sicherzustellen, daß das Grundgerüst, aus dem das Spiel besteht, lauffähig zu bekommen, bevor man überlegt, wieviel Speicher/Rechenzeit usw. man für die "Süßigkeiten" einsetzen will.
zatzen hat geschrieben:Wie gesagt war mein letztes ernsthaftes und vollendetes Spiel Kotzman II, und das ist jetzt 24 Jahre her. Programmiert habe ich in der Zwischenzeit nur Versuche, kleine Demos oder auf tiefgehenderer Ebene Tools.
So geht's mir ja auch. Mein letztes großes Spielprojekt (von den kleinen Spielchen mal abgesehen, die ich nicht ernstnehme) war dieses Xpyderz, und das ist nicht einmal fertig geworden.
zatzen hat geschrieben:Eine gut Überlegte Herangehensweise an ein Spiel wäre für mich nach wie vor eher neu. Aber ich würde natürlich ein Spiel, in das ich Arbeit und Mühe stecke, relativ ernst aufziehen und eben nicht mit der Tapete anfangen um die ich die Wände baue,
Ja, das meine ich: Wenn man sich so viel Mühe macht, wäre es schade, wenn man da ungeplant herummurkst und dann hat man ein halbfertiges Chaos da, in das man vielleicht 3 Jahre Mühe und Arbeit reingesteckt hat und das dann keiner spielen kann.
Weil ich da schon so Erfahrungen gemacht habe, bin ich ja jetzt so "vorsichtig" geworden und plane vorher (leider sehr viel), um so etwas zu vermeiden. Wenn man schon, wenn man 10% des Spiels fertig hat, damit anfangen muß, sein selbstprogrammiertes Zeug (Units, Subroutinen) zu "workarounden", weil sie nicht so funktionieren, wie man sich's gedacht hat oder brauchen würde, dann ufert das immer mehr aus und man verzettelt sich ständig nur in Nebensächlichkeiten, das Zeug zum Funktionieren "hinzupatchen" und kommt vor lauter Gemurks gar nicht mehr dazu, die Sachen zu machen, die man eigentlich im Spiel haben wollte.
zatzen hat geschrieben:Furzman war schon keine schlechte Idee eigentlich, aber generell bemerke ich bei mir eine gewisse Prokrastination, weil ich mich derzeit noch so sehr auf Dinge wie Grafikkompression konzentriere, die für ein kleines Spiel eigentlich gar nicht nötig sind. Aber sowas macht mir eben Spaß.
Ja, ich prokrastiniere hier ja auch schon seit Monaten/Jahren. Immer wieder gibt es irgendwas, das mir noch nicht so gefällt. Erst letztens habe ich noch diesen einen Befehl in GameSys2 eingebaut usw. Immer ist irgendwas. Am liebsten würde ich meine ganzen Sprite- und Level-Routinen (die sind 15 Jahre alt!) schon wieder neu schreiben, bin in Assembler ja auch etwas besser geworden... Aber irgendwann will ich den ganzen gemachten Kram auch mal irgendwo einsetzen und deshalb zwinge ich mich dazu, diese Routinen nicht anzufassen. Kann man später immer noch machen, NACHDEM man mal wieder ein Spiel gemacht hat - ich baue das Zeug ja immer modularer auf, so daß man auch später noch Routinen gegen bessere austauschen könnte.
zatzen hat geschrieben:Metadaten für den flexiblen Zugriff auf gepackte Sprites:
Darin habe ich bisher kein Problem gesehen, da ich auch bei Kotzman II einfach nur die Grafik in den Bildschirmspeicher "geklatscht" habe, ohne dass weitere Zugriffe auf die Quelle nötig waren.
Also denke ich, dass die Quelle auch beliebig gepackt sein kann.
Naja - für Xpyderz (Draufsicht) brauchte ich ja z.B. die Drehbarkeit der Sprites und das aus "block-gepackten" Daten zu extrahieren/darzustellen...
SO irre bin ich dann doch nicht. Ich komme später noch darauf zurück.
zatzen hat geschrieben:Schätzung von Speicher/Leistung:
Vorab: Ich ziele nicht darauf ab, das bestmögliche an Leistung aus dem System herauszuholen. Dazu reizt mich derzeit die Idee der Datenkompression zu sehr. [...]
Ich sehe die Vorteile der Grafikkompression für mich als bedeutsamer an, als den Verlust der Performance. Beispiel: Ich würde eher kleine/wenige Sprites auf dem Screen verwenden, aber mit vielen Animationphasen. Denkbar jedenfalls.
Ja, die muß auch erstmal jemand pixeln. Sieht schön aus, so viele Animationsphasen - wenn man's kann und die Zeit/Muße hat.
Andererseits - mal kurz überlegen: Viele Animationsphasen sind ja dazu da, flüssige Bewegungen darzustellen. Wenn man viele Animationsphasen hat, aber wegen Performanceproblemen gar nicht die Framerate erzeugen kann, um diese ruckelfrei darzustellen, sondern deshalb Phasen auslassen muß, wird das Ganze wie eine Schlange, die sich selbst in den Schwanz beißt...
zatzen hat geschrieben:Ob ich überhaupt künstlerisch genug bin so viel zu pixeln sei mal dahingestellt.
Ich bin es schonmal nicht. Ich bin froh, wenn ich überhaupt mal ansehbares Zeug hinkriege. Die "Grafik" von Xpyderz und auch meinen sonstigen Spielen ist ja eher primitiv.
zatzen hat geschrieben:Du hast meine ZSM-Moonwalker Demo gesehen, da habe ich einiges an Sprites aufgefahren und es funktionierte so in Dosbox mit 20000 Cycles, während XPYDERZ damit sehr zäh läuft.
Stimmt. Dazu siehe weiter unten.
zatzen hat geschrieben:Vielleicht ist diese Demo einfach noch zu simpel was die Berechnung abgesehen von der Grafikdarstellung angeht. 100% sicher kann ich Dir das mit XPYDERZ als Benchmark Feedback gerade nicht sagen, ich habe gerade nur Zugriff auf meinen Laptop, der die 20000 Cycles nicht stabil emuliert. Wir können später noch einmal darauf zurückkommen.
EDIT: Wie dumm von mir. Du hast bestimmt selber einen fähigen Rechner, der 20000 Dosbox
Cycles hinbekommt.
Hab ich - und mit 20000 Cycles ist es... zwar noch spielbar, aber eben schon, wie Du sagtest, ziemlich zäh.
zatzen hat geschrieben:Mir fällt da nur gerade ein: Du hast mir mal XLAT empfohlen. Ich wollte das in der ZSM Engine verwenden, habe dann aber mal im ASM Guide nachgelesen und es hatte deutlich mehr Takte als ein normaler Zugriff auf eine Tabelle. Ich weiss nicht wieviel Du XLAT benutzt,
aber vielleicht verlangsamt das manches von Dir.
Naja, der Einsatz/Sinn eines Befehls besteht nicht nur in den Zyklen, aus denen er selbst besteht, sondern auch daraus, was man "drumherum" anstellen muß, um sich auf die Benutzung des Befehls vorzubereiten.
Beispiel: Die STOSx, MOVSx-Befehle brauchen immer mehrere (festgelegte) Register, zusammen mit REP noch mehr. D.h. ihr Nutzen ergibt sich erst ab einer bestimmten Datenmenge und wenn man diese auch ohne "Zwischenprüfungen" ausnutzen kann.
Der XLAT-Befehl braucht ein paar Zyklen, das ist richtig. Dafür ist es der einzige Befehl, bei dem man mit einem BYTE-Register indirekt indizieren kann (und es auf sich selbst zurückspeichern) und zwar, ohne das Basisregister dafür zu verändern.
XLAT ersetzt also quasi eine Befehlsfolge wie (beispielsweise) diese:
Und wenn ich EINEN Befehl habe, der das alles macht, spare ich vielleicht DOCH Zyklen.
Natürlich sollte man nicht alles "um einen XLAT herum" programmieren. "Empfohlen" habe ich XLAT nicht, lediglich erwähnt, daß ich ihn in ISM benutze. Natürlich benutze ich ihn nur da, wo es sinnvoll ist.
Daß Xpyderz so langsam ist, liegt NICHT an XLAT - in Xpyderz wird kein XLAT benutzt. Es gibt mehrere Gründe für die Langsamkeit von Xpyderz:
1. Jedes Sprite wird mit der gleichen Routine dargestellt, die Fähigkeiten wie indirekte Paletten, 4bit-Pixel, Drehbarkeit und Skalierbarkeit enthält.
2. Weil Xpyderz ursprünglich mal Multiplayer werden sollte, sind die Levels größer als der Bildausschnitt - UND ALLE "Figuren" (ja auch die kleinen Schrapnelle von Explosionen sind Figuren und alle Schüsse usw.) werden gesteuert von in Pascal geschriebenen Routinen (da gabs noch kein GameSys2, noch nichtmal GameSys1). Diese Steuerung enthält auch Kollisionsabfragen für Figur-Figur-Kollisionen und Figur-Level-Kollisionen. Da gibt es zwar auch schon meine Kollisionsmatrix (da auch noch in Pascal), aber diese reduziert nur die Kollisionsabfragen auf ein erträgliches Maß (ohne diese wären es Fakultät(Anzahl_Figuren) Abfragen (also Anzahl! ). Zum Vergleich: Ein Taschenrechner, der auf 100 Stellen genau rechnet, steigt bei 70! aus, 69! ist die letzte, die er noch anzeigt, also eine 100-stellige Zahl. So viele (theoretische) Abfragen wären es bei 69 Figuren OHNE meine Kollisionsmatrix.
3. Es sind aber nicht 69 Figuren, sondern schon nach kurzer Zeit (1-2 Minuten) sind schon mehrere hundert freibewegliche Objekte unterwegs mit jeweils eigener Steuerung, die auf andere Objekte und das Level reagieren müssen.
Dafür, daß die Steuerung (inkl. Kollisionen) komplett in Pascal ist und die Sprite-Routinen etwas mehr können als bloßes "Hinkopieren", finde ich die erreichte Performance selbst heute noch einigermaßen OK.
Nochmal zu XLAT: In ISM verwende ich ja BX dann als Tabellenstart (XLAT geht ja nur mit BX/AL). Eigentlich sind es aber mehrere Tabellen. Ich brauche dann nur BH erhöhen/vermindern, um auf weitere 256er-Tabellen zu referenzieren, ohne anderen Code zu ändern usw. Also: Nicht bei einem einzelnen Befehl wie XLAT nur auf die Taktzyklen gucken, die dieser eine Befehl braucht, ohne zu sehen, was ringsherum passiert.
zatzen hat geschrieben:Levelhintergrunddaten: Da plane ich derzeit nach wie vor ein 8x8 Pixel Raster, 320x200 wären dann 1000 Byte, also könnte ich bis zu 65 Bildschirme für ein Level anlegen.
Wie ich auch im anderen Thread erwähnte: Um den Speicher für's Level mache ich mir weniger Sorgen. 65 Bildschirme für 'n Level klingt gut - aber so ein großes Level muß man auch erstmal bauen! Meist wird es eher weniger sein.
zatzen hat geschrieben:Ich glaube auch, man sollte nicht das Spektakel machen mit merken, was von den Sprites überschrieben wurde und dann nur das ersetzen. Naja müsste man sehen, ich könnte in meinem 8x8 Raster ein Bit als Flag setzen, Notfalls auch Words benutzen statt Bytes, um mehr Flags und Werte zu haben. Aber spontan habe ich da bis zuletzt eher "klassisch" gedacht - einfach alles neu zeichnen, notfalls zugunsten der Performance und des Speichers den aktiven Screen verkleinern.
Ja, es diente ja nur der Veranschaulichung. Die immer wieder Zeichnen/Löschen-Methode ist auch nicht so meins. Sobald sich Sprites überdecken sollen, fangen die Probleme an - und wenn sie sich NICHT überdecken dürfen, weil's die Routine nicht hergibt, muß man die ganze Zeit bei der Steuerung darauf achten, daß das nicht passiert - was nerven würde. Außerdem bräuchte man für jede Figur einen spritegroßen (nicht packbaren) Puffer - weil man ja vorher nicht wissen kann, was da grad im Hintergrund wäre... Und man müßte beim Setzen/Löschen in umgekehrter Reihenfolge vorgehen, sobald es Überdeckung gibt - was ab einer gewissen Anzahl zu flackernden Sprites führen würde... Alles Dinge, denen ich gern aus dem Weg gehe.
zatzen hat geschrieben:Ich merke gerade, Speicher verschwenden, und dann Performance kompromittieren um komprimierte Daten zu verwenden... Du hast schon recht, aber naja, ich mach das nur zum Spaß. Ich muss selber meinen Holzweg gehen...
Naja, Machbarkeitsstudien können schon nützlich sein. Wenn es funktioniert, ist es egal, wie es gemacht ist oder ob jemand die Methode gut findet oder was auch immer. Das ist sowieso mein Credo: Bewiesen ist, was funktioniert. Wenn irgendwer eine theoretisch tolle Idee hat, die in der Umsetzung so aber nicht machbar ist oder nicht so funktioniert wie gedacht, kann die Idee noch so toll sein - sie ist in der Praxis unbrauchbar. Wenn dann jemand mit einer viel simpleren Idee kommt, die am Ende aber funktioniert, dann stehts 1:0 für diese Idee. So seh ich das.
zatzen hat geschrieben:Beim Sound stimme ich Dir mehr oder weniger zu. Mit 44100 kann es natürlich zum Worst Case kommen, und das sollten vielleicht dann nur Leute mit nem Pentium so
nutzen. Allerdings habe ich in ZSMPLAY bei der Rate und ner Framerate von ca. 20 fps überhaupt keine Probleme, und die Dosbox läuft auf 20000 Cycles, was einem eher langsamen 486 entspricht.
Wie gesagt: War nur ein Gedankenexperiment, das verdeutlichen sollte, daß Dinge, die im Einzelnen prima funktionieren, das nicht unbedingt immer noch tun, wenn sie alle "gleichzeitig" laufen sollen. Und ja, deshalb wäre es vielleicht keine schlechte Idee, gewisse Parameter (wie auch z.B. die Soundframerate) einstellbar zu machen.
zatzen hat geschrieben:Die Sache ist, unabhängig von der Grafikframerate, bleibt die Sound-Rate pro Sekunde immer gleich.
Naja, mit 44100 Hz müssen aber doppelt so viele Daten pro Sekunde berechnet werden als mit 22050 Hz - das sollte doch klar sein, oder? Also auch doppelt so viele pro Frame.
zatzen hat geschrieben:Wenn man Dein Beispiel auf 20 fps umrechnet, dann sind es in einem Frame nur noch 17640 Byte Sounddaten, gegenüber (ja auch Worst Case, wobei kopieren in den Bildpuffer nicht mal mit drin ist) 64000 Byte Bilddaten.
Es geht ja nicht um "pro Frame" - auch wenn das komisch klingt.
Man muß hier von der allgemeinen Systemleistung ausgehen. Und das bedeutet einfach: Wieviele Grafikdaten und Sounddaten kann man gleichzeitig berechnen, damit diese "rechtzeitig" zur Verfügung stehen - und das im Durchschnitt über einen längeren Zeitraum. Außerdem machen sehr kurze Soundpuffer das Ganze langsamer - denn wie man ja weiß, ist bei jedem Aufruf so einer Generator-Routine noch ein gewisser Overhead an "Vorbereitungscode" dabei vor der eigentlichen Generatorschleife, so daß häufigere Aufrufe für kürzere Puffer den Anteil Overhead erhöhen. (Zu) kurze Puffer haben auch den kleinen Nachteil, daß man mehr Aufwand hat, darauf zu achten, daß der Puffer nicht "leerläuft", bevor man dazu kommt, den neuen zu generieren. Wenn Grafik-/Steuerroutinen zu lange brauchen, fängt es an zu stottern... PC-Spiele-Programmierung ist schon ein Kreuz...
zatzen hat geschrieben:Nochmal EDIT: Kann man wiederum schlecht abschätzen, da bei ZSM die Sample-Datenrate an sich auch unabhängig von der Mix-Samplerate ist. Wie in einem vergangenen Thread beschrieben, ergibt eine Verdopplung der Mix-Samplerate nicht eine Verdopplung der Rechenbeansprung.
Ja, und was viel entscheidender ist: Eine Halbierung der Mix-Samplerate ergibt eben NICHT eine Halbierung der Rechenzeit! Das ist es, was ich im vorigen Absatz ausdrücken wollte.
zatzen hat geschrieben:Spiel mit fester, durch Soundkarten-IRQ getriggerter Framerate:
Ja, das ist natürlich irgendwie blöd. Aber es übersteigt wie oft schon gesagt derzeit meine Programmierfähigkeiten, soetwas dynamisch aufzuziehen. Ich kann es nur so handhaben, dass Headroom
Max? ... <scnr>
zatzen hat geschrieben:verschwendet wird, indem ich leistungsmäßig kleine Brötchen backe, so dass das bescheidene Spiel was ich dann mache eben einen 486er braucht,[...]
Ja, solange es überhaupt ein spielbares Spiel wird, wäre es ja schon mal gut. Auf 486er laufen ist immer noch besser als gar kein Spiel.
Und - mein Zeug braucht wahrscheinlich auch 486er. Hab das ja auch schon erwähnt: Auf 386er performt mein Zeug auch eher bescheiden.
zatzen hat geschrieben:Größere Schritte der Figuren: Ich würde intern feinere Schritte rechnen (ohne Sprites dabei anzuzeigen) und dann eben nur jeden fünften Schritt oder so anzeigen. Ist mein Gedanke, hab ich nie praktisch probiert. Dann hätte ich bei 20 angezeigten fps intern 100 fps.
Dann müsste ich die Steuerung entweder intern auch so schnell tickern oder sie bleibt so ungenau, bei den internen feineren Schritten würde es mir um Kollisionen gehen, vor allem gegenüber Level-Blöcken.
Der Gedanke von mir war aber eigentlich auch, nicht mit 100 Hz zu tickern, sondern einfach zwischen jedem Bild 5 (Objektpositions-)Berechnungen zu machen, damit sich die Elemente effektiv nur in 1 Pixel Schritten bewegen. Letztlich werden wohl auch 3 reichen.
Es scheint, daß Du langsam zu begreifen anfängst, wie das bei mir (und mit GameSys2) funktioniert. Dann kann ich ja vielleicht doch noch mal versuchen, es zu erklären:
Es gibt eine - ungetimete! - Spielschleife, die die ganze Zeit (vereinfacht) das macht:
* Schleifenstart
* Steuerung berechnen, X-mal (und X Nullsetzen)
* Grafik berechnen+Anzeigen (ausgehend von der "Situation" in den Steuerdaten)
* Sound berechnen (abgespielt wird im IRQ/DMA)
* [eventuell auf Vertical Retrace warten]
* Zurück zum Schleifenstart
Wo ist das Timing?
In der Schleife: nirgends!
Denn:
Nebenher läuft ein Timer-Interrupt, der macht folgendes in jedem Tick:
* X um 1 erhöhen.
* Steuerdaten des Spielers abfragen und in einen Ringpuffer werfen
Jedesmal also, wenn die Spielschleife an die Stelle kommt, wo die Steuerung berechnet wird, fragt sie X ab und berechnet dann X-mal (idealerweise ist X<=1) die Bewegung der Figuren. Aber NICHT für jede Figur gleich X "Schritte": Sondern für jede Figur EINEN Schritt. Und das ganze für alle Figuren. Und dann wiederholen. Solange, bis das alles X-mal passiert ist.
Dann hat sich eine "End-Situation" in den Figurendaten (Koordinaten usw) ergeben und die Grafik wird daraus berechnet. Die "Zwischenschritte" davor werden für die Grafik ignoriert - so wird damit umgegangen, wenn keine volle Framerate erreicht werden kann, weil der Rest in der Schleife (Grafikberechnung usw) zu lange braucht.
Dann wird z.B. geprüft, ob neue Sounddaten gebraucht werden, falls ja, wird der Generator mit Füllen eines neuen Puffers beauftragt.
Das Warten auf Retrace kann man abschaltbar machen - das kann eingeschaltet werden, falls X<=1 wird, d.h. volle Framerate erreicht wird - dann kann man eben flüssig scrollen/bewegen und den Bildrefresh des Monitors drauf abstimmen. Wenn man dafür sowieso zu langsam ist, würde das Warten auf den nächsten Retrace nur weiter verlangsamen, daher abschaltbar. (Xpyderz hat da Möglichkeiten AN/AUS/AUTO. Bei AUTO prüft das Spiel live/dynamisch die erreichte Leistung und schaltet automatisch V-Retrace-Scan an/aus.)
Daß auch die Abfrage der Spielereingabe im Timer-Tick erfolgt hat den Zweck, daß immer genauso viele Steuer-Daten des Spielers zur Verfügung stehen, wie gebraucht werden UND daß der Spieler unabhängig von der Framerate (die je nach Spielsituation variabel ist) trotzdem immer gleichschnell steuern kann und ALLE seine Steuerungen sich auch auf das Spiel auswirken (also keine Tastendrücke usw. "ausgelassen"/"nicht erkannt" werden, falls das Spiel mal langsamer macht).
(Die Steuerdaten werden nicht wirklich im Timer-Tick GENERIERT, sondern nur eingespeichert. Vor-Generiert werden sie im Keyboard-Interrupt: Da wird immer, wenn eine Taste gedrückt ist, das Feld (Bit, Byte) der Taste auf 1 gesetzt und wenn eine Taste losgelassen wird, wird wieder 0-gesetzt. Im Timer-Tick werden nur die gerade gedrückten Tasten gegen die "gesetzten" 8, 16, 32.... Tastennummern getestet und die Bits in einer Bitmatrix dazu gesetzt.)
Das Ganze funktioniert deshalb, weil das menschliche Gehirn Einzelbilder mit Objekten an unterschiedlichen, aufeinanderfolgenden Positionen eigenständig zu einer Bewegung umrechnet - auch wenn diese mal weniger Bilder hat (sonst würde z.B. Fernsehen und Film nicht funktionieren). D.h. für den Spieler sieht auch eine ruckelige Bewegung noch wie eine Bewegung aus und sein Steuerverhalten bezieht sich auf die ganze Bewegung - interessanterweise auch auf die Szenen, die bei geringer Performance gar nicht grafisch dargestellt werden (die interpoliert das Gehirn). Deshalb meine Wahl der Steuerungs-Abfrage im Interrupt. D.h. ABGEFRAGT wird die Steuerung im Ticker, aber BENUTZT außerhalb des Tickers, in der Steuersubroutine, die das dann aus dem Ringpuffer holt.
Natürlich ist es klar, daß es für jede Framerate eine "nicht mehr spielbare" Untergrenze gibt, ab wo etwas nicht mehr als Bewegung wahrgenommen wird und ab wo zu viele fehlende Zwischenbilder die Steuerung des Spiels unmöglich machen. Außerdem kann so ein Ringpuffer auch mal "überlaufen", wenn nichts mehr abgeholt wird. Wenn der Ringpuffer für 1 Sekunde Daten reicht, ist das mehr als ausreichend. Warum? Naja, ich denke, mit 1 FPS wird keiner mehr spielen... Wenn ein Steuer-Bitfeld 32bit wäre, wären es bei einem 50-Schritte-Pro-Sekunde-Spiel 201 Bytes, die der Ringpuffer haben müßte. (Es werden aber wohl eher 8 oder 16bit sein.)
Das mit den Bits hatte ich schonmal erklärt: Es ist quasi wie bei den Pins eines alten Digitalpads/Digitaljoysticks gedacht: Jede für das Spiel relevante Taste aktiviert eins der Bits - die anderen Tasten machen gar nichts mit dem Steuer-Bitfeld. Auf diese Art hat man zwei Vorteile: 1.) Durch diese Indirektheit kann man dem Spieler die Option geben, selbst seine Tastenbelegung zu wählen. 2.) Das Spiel braucht nicht "wissen", welche Tasten oder Steuergeräte der Spieler nutzt, man kann das so programmieren, daß man sich immer darauf verlassen kann, daß das jeweilige Bit im Bitfeld genau das macht, was seiner Bitposition entspricht, z.B. Bits 0 bis 5: Hoch/Runter/Links/Rechts/Sprung/Feuer.
Mein "Tastenfeld" reicht für 255 Tasten - so viele gibt es in Wirklichkeit nicht. In die freiwerdenden Stellen habe ich quasi "Pseudo-Tasten" eingefügt, die abhängig von einem/zwei Joysticks am Joystickport ($201) digital umgerechnet und gesetzt/gelöscht werden. So kann man auf einfache Art auch einen Joystick benutzen und die gleichen Steuerroutinen wie für Tastatureingaben benutzen. Entsprechende Mausabfrage folgt vielleicht auch noch.
zatzen hat geschrieben:Okay, nun zu "ZVID2", ich stelle mir das bislang so vor:[...]
Palettenstring.[...]
Meine Idee dazu: Die Blocks scannen und als erstes die Blocks mit den meisten unterschiedlichen Farben nehmen. Grund: Die braucht man sowieso alle "auf einem Fleck", damit es funktioniert. Und die mit den wenigeren Farben, die aber teilweise noch auf die anderen passen, kann man dann dazwischen einsortieren.
zatzen hat geschrieben:Warum sich ZVID(2) gerade bei Sprites lohnt:
Die sind meistens ja in der Farbgebung von Frame zu Frame sehr ähnlich bis gleich. Wenn man letztlich vielleicht 20 Animationsphasen hat, fällt selbst ein etwas größerer Palettenstring kaum noch ins Gewicht.
Naja, warum ICH meine Sprites UND meine Paletten NICHT (bzw. Sprites höchstens 8zu4bit) packe, liegt daran, daß ich mir meine Features erhalten will:
Bei den Sprites: Drehbarkeit, Spiegelbarkeit, Skalierbarkeit, Ditherbarkeit
Bei den Paletten: Ich kann einem Spriteimage mehrere Paletten geben, d.h. mit dem gleichen Image verschiedene (verschiedenfarbige) Dinge darstellen. So wird es z.B. mit den kleinen Spinnen bei Xpyderz gemacht. Die haben je 4 Bewegungsphasen, es gibt aber 9 verschiedene kleine Spinnen - trotzdem brauche ich nicht 36, sondern nur 8 Images. Die schwarze ist "besonders", weil leicht andere Zeichnung mit Kreuz usw. (4 Images). Die "bunten" haben nur verschiedene Paletten, aber das gleiche Image (also nur 4 Images insgesamt für die anderen 8 Spinnen.)
Ich habe ein (selbstgebautes) Programm hier, das auch für Xpyderz genutzt wird und die Images selbst nach Größe in die "Imagematrix" einpaßt (256 Pixel breit, erwähnte ich mal), wenn nicht paßt oder quer weniger Platz als längs braucht, dann quer. Die erkennt auch gleiche Images, die nur andere Farben haben (nimmt dann den gleichen Offset, speichert sie also nur 1x ab) und erkennt auch für unterschiedliche Images, ob Paletten wiederverwendet werden können. Für meine Zwecke finde ich das mehr als ausreichend.
zatzen hat geschrieben:Spiegeln ist kein Problem, man teilt der Anzeigeroutine das einfach mit. Da es 4x4 Blöcke sind habe ich vorab aber schon daran gedacht, eine Zeile direkt über ein 32 Bit Register in den Speicher zu hauen, das müsste man beim Spiegeln dann anders machen, entweder erst verdrehen (ror ax,8; ror eax,16; ror ax,8), oder doch Byte für Byte mit std.
Blöcke mit Transparenz müssten sowieso Byte für Byte gehandhabt werden.
Naja, ich arbeite sowieso Byte für Byte - schon weil allgemein immer von "gedreht" ausgegangen wird (auch wenn's 0° sind).
Und - so traurig das jetzt vielleicht klingt - auch
Du wirst wohl Byte für Byte arbeiten müssen. Wieso? Weil Du das DWord (EAX) ja nicht irgendwo auslesen und reinballern kannst, sondern weil die Daten (Bits 4,3,2,1...) im 4x4-Block ja nur Referenzen auf Farbnummern im Farbpalettenstring sind. Wenn Du dann die einzelnen Bytes von EAX (auch die "oberen beiden", an die man so schlecht "einzeln drankommt" mit den Farben beladen willst, nur um am Ende EVENTUELL (falls kein "Loch" drin ist) ein STOSD machen zu können - da bezweifle ich ernsthaft, ob das wirklich noch einen Geschwindigkeitsvorteil bringen wird.
zatzen hat geschrieben:Selbst wenn ich am Ende nur 8 KB Grafikdaten habe find ich das dann irgendwie toll, besser als wenn der gleiche Kram 30 KB belegen würde und Luft drin hätte. Auch schön das Gefühl, dass man alles von Strichmännchen bis hin zu "fotorealistisch" kreiren kann und immer nur so viel Speicher wie nötig belegt wird.
Ja, stimmt natürlich. Daher hatte ich ja die 4-Bit-Idee.
Aber, wie ich ja erwähnte, versuche ich da immer den Spagat zu schaffen zwischen Performance und Speicher. Am meisten Speicher sparen könnte man, wenn man jede Grafik aus einer beliebig komplexen Formel berechnen läßt. Für die Performance wäre das dann gleichzeitig der Worst Case. Umgekehrt könnte man am meisten performen, wenn man jedes Sprite ungepackt in ein 256x256-Feld klatscht ohne den überstehenden Rest zu berücksichtigen. Nur wäre dann der Speicher bald voll. Und meiner Meinung nach ist ein Mittelweg da am besten. Man schafft es nicht, soviel Speicher freizumachen (bzw. es macht keinen Sinn für ein simples DOS-2D-Game) und andererseits kommt man auch leicht an die Grenzen der Performance, wenn es interaktiv werden soll. Aber, wo man welche Grenze zieht, muß natürlich jeder selbst wissen.
Wie ich ja schon erwähnte, bin ich kein Freund davon im "laufenden Prozeß" vom RealMode aus auf XMS zuzugreifen, weil 1.) die Performance-Einschränkungen da unvorhersehbar sind (zu sehr von der Gerätekonfiguration abhängig) und 2.) man nicht weiß, ob und wieviel XMS überhaupt eingebaut ist. Deshalb wird das für mich immer nur für Editoren oder als Zusatzfeature gelten, aber nie für reguläre Funktionen in einem Spiel.
zatzen hat geschrieben:Mehrere gleiche Byte hintereinander bzw. ein ganzes Byte wo auch zwei Bit reichen würden empfinde ich als Verschwendung, und auch wenn es irgendwie sinnlos ist da genug Speicher da ist und es extra Performance kostet, ist es in meinem Rahmen des Programmierens für mich ein Ansporn. Ich habe ja keinen Auftrag für eine Firma zu erledigen sondern kann ganz
alleine bestimmen wie meine Software funktioniert, und diese Spielerei mit der Datenkompression will ich mir nicht nehmen lassen, es macht einfach Spaß.
Ja, ich kenne das. Habe früher auch viele Formate erfunden und manche davon waren auch recht komplex. (In meinem Fall ging es z.B. um serielle Übertragungsprotokolle.) Irgendwann ist mir dann aufgefallen, daß ich quasi gar nichts mehr gespart habe - weil die Routinen, die die Daten am Ende gepackt und verpackt haben, dermaßen viel eigenen Speicher brauchten und durch ihre Komplexität immer anfälliger für unbehebbare Fehler wurden, daß ich dann eingesehen habe, daß die ganze Mühe vom Ergebnis her eher "nach hinten losgegangen" ist und ich damit kaum etwas erreicht habe. Irgendwann war alles nur noch "um diese Subfunktion herum" angeordnet und alles hatte sich der Bereitstellung dieser Subfunktion unterzuordnen - obwohl es eben nur der Multiplayer-Datenlink war. Das hat mir dann nicht mehr gefallen.
Inzwischen ist mir auch klargeworden, daß ein klein wenig "Verschwendung" zwei Vorteile haben kann:
1.) Mitunter kann so ein unvollständig gepacktes Zeug wesentlich besser performen UND da die Leseroutinen viel einfacher sind, spart man zwar weniger an den Daten, spart dafür aber Speicher UND Performance bei den Routinen selbst.
2.) Ungenutzte Bytes oder Bits (die man standardmäßig nullsetzt) können nützlich sein, um das Zeug später
abwärtskompatibel erweitern zu können, ohne neue Formatnamen/-Erweiterungen für jede neue Version erfinden zu müssen und so, daß neue Routinen neue und alte Versionen lesen können, ohne extreme Verzweigungen haben zu müssen.
Natürlich alles nur wieder meine persönliche Ansicht dazu.
Ich habe schon öfter mal bereut, manches von meinem alten Zeug "zu dicht komprimiert" zu haben, so daß kein Platz für Erweiterungen mehr da war und habe mich andererseits jedes Mal gefreut, wenn ein altes Format noch Dinge "offen gelassen" hatte, so daß ich es noch weiter verwenden und erweitern konnte.
zatzen hat geschrieben:Ich bin wohl irgendwie nicht so der "Performer", vielleicht auch wegen des Speichermangels in Qbasic ein gebranntes Kind was das alles angeht, keine Ahnung, oder einfach nur ein "sich Dateiformate ausdenken"-Idiot.
Naja, die ganzen Formate und Protokolle, die ich mir schon ausgedacht habe, zähle ich schon gar nicht mehr. Irgendwann, Jahre später, merkt man dann, was davon sich bewährt hat (was man immer noch, bzw. immer wieder verwendet) und anderes, was mal eine "Schnapsidee" war, die gut aussah, aber in der Praxis nicht wirklich verwendbar war (entweder zu groß und sperrig, zu kompliziert einzubauen oder auch zu nichts im Mindesten kompatibel außer zu sich selbst). Wenn ich heutzutage ein Format benutze, will ich NICHT mehr (wie leider früher manchmal!) alles "um das Format", bzw. "um diese Routine herum" bauen müssen. Da finde ich es inzwischen besser, Zeug so zu machen, daß man es "nur noch einzubauen" braucht und es quasi alles, was es braucht, selbst macht, bzw. "von selbst mitbringt". Nur das schafft es (zumindest für mich), daß ich meine Zeit dann auch wieder auf etwas anderes, "neues" aufwenden kann - wenn ich mich um einmal "fertiges" Zeug wirklich "nicht mehr kümmern" muß, weil es quasi wie ein Baustein nur noch "mit anderen Bausteinen" zu einer funktionierenden Einheit zusammengebaut werden muss.
zatzen hat geschrieben:Letztlich würden mit ZVID größere Grafikteile schneller eingerechnet werden als wenn man die Routine zig mal kachelweise aufruft.
Meine Levelroutinen arbeiten nicht kachelweise. Es ist zwar gekachelt angeordnet, aber die Grafik wird spaltenweise generiert und entsprechend Mode-X dann "4-Spalten-weise", so daß die "Plane" so selten wie möglich gewechselt werden muß.
zatzen hat geschrieben:Der magische Würfel 486er:
Ich hab immerhin meine Programmierkarriere auf einem 286er begonnen. Klar, Du auch auf dem C64.
Angefangen habe ich Mitte der 80er auf einem KC85/1 (in einer außerschulischen "Computer-AG"). Da hatte ich noch keinen eigenen Computer. Das habe ich 3 Jahre lang gemacht, DANN erst habe ich einen eigenen (teuren) KC85/4 bekommen. (Falls Du Dich fragst: Das sind Modelle aus der DDR.) und erst in der Wendezeit kam dann der C64. Zu der Zeit hatte ich schon knapp 5 Jahre (allerdings ausschließlich in BASIC) programmiert.
zatzen hat geschrieben:Aber auf dem 286er war das PUT von Basic noch so langsam, selbst bei kleinen Sprites... Das waren noch die Zeiten in denen man monolithisch programmiert hat in dem Sinne, dass sehr viele Spiele auf einem Rechner > 286 zu schnell liefen.
Naja, daß für "flächige Dinge" (wie Sprites, Rechtecke/Polygone) der fortwährende Zugriff auf eine generalisierte Pixelroutine (die jedes Mal aus Koordinaten die Pixelposition ausrechnet und für den Grafikmode entsprechend die Daten setzt für den Pixel) eine Scheißidee (weil langsam) war, habe ich schon recht schnell herausgefunden. Sprites aus einzelnen Pixel(X,Y,C) oder PUT (wasauchimmer) zusammenzusetzen - so verrückt war ich nie.
zatzen hat geschrieben:Und dann hatte ich einen 486er, [...] und an die Grenzen des 486ers bin ich selber durch eigene Programme gar nicht gestoßen, [...] als ich einen Pentium hatte, und den hab ich auch nicht ausreizen können, dann kam nach vier Jahren ein schneller Windows-Rechner, nach 5 Jahren noch einer usw...
Naja, wie ja bereits mehrmals erwähnt: Um zu merken, daß es wirklich funktioniert, reicht es nicht, zu sehen, ob die Steuerung alleine oder die Grafik alleine oder der Sound alleine funktionieren. Erst, wenn
ALLES ZUSAMMEN arbeitet und immer noch funktioniert,
DANN wird's ein brauchbares Spiel. Und der PC (im Gegensatz zu Konsolen, C64 u.ä.) hat nunmal keine dedizierten Spezialchips für Sound Effekte, grafische Sprites/Fonts usw. - da muß das alles fast 100%ig und "quasi-gleichzeitig" durch die CPU bewerkstelligt werden.
Daß ich
JETZT so einen "Aufwand" betreibe, einen einigermaßen gescheiten "Rahmen" für Spiele zu schaffen, liegt daran, daß zum Schluß nicht bei jeder neuen Spielidee wieder von Null angefangen werden soll, im Sinne von: Wo krieg ich den ganzen Speicher her? Wie baue ich das Zeug so zusammen, daß es funktioniert ohne abzuschmieren oder rumzuruckeln? Also quasi soll dieser ganze Kram schon so "getestet und für gut befunden" sein, daß man ein Spiel drauf entwickeln kann OHNE jedes Mal komplett neue Routinen bauen zu müssen, die eigentlich das gleiche machen wie die des letzten Spiels. Viele Dinge, die ich so mache oder bei einem Spiel mache/machen würde, würde ich immer wieder auf die gleiche Art und Weise machen - da macht's schon Sinn, wenn das Code-Zeug auch mal (und wenn's nur teilweise ist) eine problemlose Wiederverwendbarkeit hat.
zatzen hat geschrieben:Ich habe mir für DOSbox aktuell die 20000 Cycles festgelegt und bis jetzt bin ich da selbst mit den extremsten ZSMs noch nicht an die Grenzen gestoßen.
Naja, wie ich auch schon erwähnte: DOSBox ist ein wundervolles Tool und - Respekt! an die Leute, die das gemacht haben. Aber für reale Performance-Tests ist es trotzdem nur mäßig geeignet.
zatzen hat geschrieben:Ich habe wohl noch nicht richtig gelernt, wie man ein Spiel auf korrekte Weise "time't", bei Kotzman II hab ich es so gemacht wie ich es jetzt etwas anders auch vor habe, nämlich indem ich in die Hauptschleife ein SOUND 0, .1 oder ähnliches eingefügt habe. Oder war es doch eine Verzögerungsschleife? Jedenfalls statisch und wie ich meine schlimmer als warten auf einen Interrupt, weil sich ja die Grafikrechenzeit und die Warteschleife addiert haben, was besonders bei langsameren Rechnern und höherem Grafik- aufkommen bemerkbar gemacht hat.
Ja, das entspräche dem (bei sowieso zu langsamer Maschine sinnlosem) Warten auf den Vertical Retrace (Rasterstrahlrücklauf, bzw. Bildaufbauende) - siehe oben.
zatzen hat geschrieben:Von daher wäre Interrupt-Timing für mich erstmal die nächst bessere Erfahrung...
Ja, wie Du es machen willst/würdest - keine Ahnung.
ICH fahre mit der Kombination "ungetimete Schleife" mit "Ticker für Steuersignale/Schrittzähler" bisher ganz gut. Grund ist, daß das Verhalten eines Spiels nie "monolithisch" ist: Mal ist mehr, mal weniger los. Mit so "Warte-States (Sound 0,x usw...) kann man ja immer nur auf den Worst Case timen und verschenkt so leider die ganze Zeit, wo das Spiel auch flüssiger laufen könnte.
Meiner Erfahrung nach scheinen sehr viele (spätere) Coder, vor allem im 3D-Bereich, das so wie ich zu machen: Die FPS-Raten der Spiele sind variabel und hängen davon ab, wie aufwendig gerade berechnet werden muß. So gibt es eben auch mal "Engstellen", wo die Performance einbricht (wenn man das Maschinen-Limit erreicht hat bei mittelschnellen Kisten), aber dann ist es eben nur
DANN mal kurz lahm - und nicht die ganze Zeit. Meiner Meinung nach immer noch die bessere Option.
Anders - und allgemeiner - ausgedrückt: Die internen Berechnungen sind mal aufwendiger, mal weniger aufwendig, was die FPS variabel werden läßt und trotzdem wirkt das Spiel von außen her trotzdem so, als würde zumindest der "Spielfortschritt" bzw. das "Spielgeschehen" immer durchschnittlich "gleich schnell" ablaufen.
Diese Idee hatte ich damals auch bei Xpyderz - und der Grund dafür war eigentlich ein komplett anderer, aber ich bin froh darüber, daß ich dadurch angeregt war zu meiner Idee. Grund war, daß - wie erwähnt - das Ding mal Multiplayer werden sollte. Und bei Multiplayer - auf mehreren Kisten gleichzeitig -
MUß das Spiel ja überall "gleich schnell" ablaufen, weil es sonst unsynchron wird! Das war der ursprüngliche Knackpunkt, den ich damals bewältigen wollte, weil ich wußte, daß Multiplayer nicht funktionieren kann, wenn die (durchschnittliche) Spielgeschwindigkeit auf jeder Kiste unterschiedlich ist. Kleine Abweichungen dabei sind hinzunehmen - für leichte Asynchronität wurden schließlich Ringpuffer erfunden, die das ausgleichen. Aber im Allgemeinen muß die Geschwindigkeit eingehalten werden.
Und, auch wenn man kein Multiplayer-Spiel macht, finde ich, daß eine vorhandene Grundgeschwindigkeit Teil des Spielerlebnisses ist. Wie schnell sich die eigene Figur oder die anderen Figuren pro Sekunde bewegen und damit das Spielgeschehen/-erlebnis beeinflussen, sollte der Spielemacher festlegen - NICHT die Leistung der ausführenden Maschine.
zatzen hat geschrieben:Kleines Update bzgl. ZV2: [...]
Kurz gesagt haben sich bei obigem Bild mit der Greifhand 1801 individuelle Paletten ergeben, was ein wenig ernüchternd ist, da sich ja die Anzahl nur etwas mehr als halbiert hat.
Bei ZVID(1) war das alles kein Problem, dort habe ich die Bitbreite UND den Palettenoffset direkt im Header deklariert, aber diesmal soll der Header pro Block ja nur ein Byte groß sein.
Wie auch immer, man kann daraus schliessen dass sich ZVID2 nur für Sprites eignen wird - oder nur für extrem spärlich mit unterschiedlichen Farben besetzte Vollbilder.
Naja, im Gegensatz zu Deiner Vorgehensweise habe ich von Anfang an für Sprites, (Level-) Hintergründe, große Logos usw. immer andere, jeweils auf den jeweiligen Zweck zugeschnittene Routinen gebaut. Meine Sprite-Routinen wären für Bilder/Level denkbar ungeeignet, meine Level-Routinen wären für Sprites nicht zu gebrauchen. Aber das, was sie können/können sollen, können sie dafür ziemlich gut.
Generalisierte Routinen benutze ich eher gern dort, wo NICHT absolute Echtzeit gebraucht wird: Z.B. in Editoren, die viel Platz für Daten lassen sollen und daher Speicher sparen - dort können so generalisierte "Eine kann alles"-Routinen gut Speicher sparen - und müssen ja sowieso keine "Voll-Frame-Rate" erreichen. Die sind dann langsamer, aber universeller einsetzbar und man braucht nur
eine einfache Routine statt 3 oder 4 komplexe.
zatzen hat geschrieben:Erklärung des Begriffs "Set": ZVID2 soll, ähnlich wie ZVID(1), mehrere "Frames" enthalten, die sich dann eine Metapalette und eine Zuordnungstabelle (in die die Header-Bytes weisen) teilen. Dadurch verringert sich im Verhältnis der Speicher-Overhead für die Kompression.
Ja, wie ich oben erwähnte, gehe ich hier von anderen Voraussetzungen aus. Sprite-Images stehen immer für sich allein - aber können durchaus eine gleiche (gemeinsam genutzte) Palette belegen, das erkennt das Programm, das die Spritedaten zusammenstellt, automatisch. Das erlaubt es auch, der gleichen Figur (deren gesamte Phasen die gleiche Palette nutzen) einmalig eine komlett neue Palette zuzuweisen, um eine bewegliche Figur in komplett anderen Farben haben zu können. Mein Beispiel-TGAME.EXE nutzt diesen Effekt beispielsweise aus: 8 unterschiedliche "Männchen" - jedes mit anderer Haut-/Augen-/Haarfarbe und anderen Klamotten. Und obwohl es immer das gleiche Sprite-Image (bzw. Images für die Animation) ist, wirkt es so, als hätte man mehr... - Es ist wirklich erstaunlich, was man durch simples Umfärben erreichen kann.
zatzen hat geschrieben:Den Farbstring zu optimieren ist eine Sache für sich - wer da eine zündende Idee hat möge sich bitte melden, ich werde mich aber auch nochmal in den ZVID(1) Code einlesen, wie ich das da halbwegs gut gelöst habe.
Ja, wie ich ganz oben erwähnt habe: Zuerst mit den vielfarbigen Blocks anzufangen scheint mir hier die beste Idee zu sein, die in absehbarer Zeit gute Ergebnisse bringen sollte. Klar, das "Überlappen" von Paletten (an den Enden) könnte noch etwas mehr 'rausholen, da könnte man noch "herumsortieren" um noch geringfügig Platz zu sparen. Wenn man aber "nur" 256 Farben hat, werden realistisch gesehen kaum je mehr als 16-32 Farben gleichzeitig im gleichen Spriteimage verwendet werden, weil nun mal kein gescheit aussehendes Sprite so ALLE Farben des vorhandenen Farbspektrums gleichzeitig benutzen wird.
zatzen hat geschrieben:Vielleicht nenne ich das Format am Ende lieber ZSPR, da es ja wirklich nur für Sprites gedacht ist.
Ja, Hauptsache Zxxx, oder...?
Ja, vieles von meinem Zeug ist auch mit Ixx benannt. (I für Imperial Games) Meist nutze ich eher nur 3 Buchstaben/Zeichen - so kann man es auch gleich als Filenamen-Erweiterung verwenden...
Mir ist nur aufgefallen, daß ich für mein Logo-Bitmap Format (senkrecht gespeicherte/gepackte Bitmaps) das BM für Bitmap nicht mit I kombinieren konnte (oder wollte), weil ich mein Format weder IBM (International Business Machines) noch BMI (Body-Mass-Index) nennen wollte - so heißen sie nun DBM (Downwards BitMap)...
zatzen hat geschrieben:Eine solche Datei soll möglichst nur mit einem Pointer auf dem Heap adressiert werden. ZVID(1) erforderte etliche Pointer und Variablen für eine Datei.
Ja, seit einigen Jahren neige ich auch eher zu so kompakten Formen/Formaten, die möglichst einfach in Assembler auszulesen/darstellbar sind.
zatzen hat geschrieben:Das wird noch eine Herausforderung, vor allem mit dem Clipping, wahrscheinlich schreibe ich das erstmal alles in Pascal.
Das mache ich oft so - habe ich früher noch öfter gemacht. In Pascal ist Zeug leichter zu debuggen, so daß man erst einmal die grundlegende Funktionalität sicherstellen kann, bis es genau das macht, was es soll. Es dann in Assembler umzusetzen ist dann eher eine Übung, das unter Ausnutzung der (im Gegensatz zu Hochsprachen vorhandenen) Möglichkeiten der Register und Flags den ganzen Kram klein und performant zu kriegen,
zatzen hat geschrieben:Decodiervorgang:
1. Header-Byte lesen
2. An entsprechender Position aus der Zuweisungstabelle die Bitbreite und den Palettenstring-Offset auslesen
3. Blockdaten entsprechend Bitbreite interpretieren und jeweilige Farbe an Position des Palettenstring-Offsets
+ jeweiligem Blockdatum lesen, Pixel in Zieldatenfeld schreiben
Klar, genau so habe ich das auch die ganze Zeit verstanden.
Du sagtest ja, um zu sparen, soll jeder Block nur ein Byte als Header haben. Allerdings verweist dieses Byte auf ein Word - und wenn man Pech hat, braucht jeder Block sein eigenes Word, so daß man quasi in Wirklichkeit insgesamt 3 Byte Header hat, nur daß 2 davon "woanders stehen".
Und, wie Du schon sagtest, kann es passieren, daß man nicht mehr "safe" ist, weil man evtl. mehr Verweise braucht, als es Bytes gibt.
zatzen hat geschrieben:Tja, alles im Grunde nicht nötig, man müsste überhaupt erstmal genug Material pixeln um unkomprimiert 300K an Sprites zu füllen - aber es macht Spaß, eine kleine Etüde...
"Etüde" - der Musiker wieder...
Aber ja, es kann durchaus lehrreich sein, sich mit solchen Dingen zu befassen. Ob man sie später einsetzt oder nicht, kann man ja immer noch entscheiden. Aber es kann nicht schlecht sein, bei aufkommender Drohung von Speichermangel schon vorbereitet zu sein und einen "Plan B" in der Tasche zu haben, um dann Daten schrittweise zu packen. Meine Xpyderz-Sprites, die sowieso recht klein und auch nicht gerade "kakelbunt" sind, auf 16 Farben zu "reduzieren", hat mir nicht besonders wehgetan (ursprünglich hatte das Spiel INSGESAMT nur 16 Farben) - mit der 256er Palette haben die Figuren usw. jetzt
IMMER NOCH je 16 Farben, nur eben 16 aus beliebigen 256. (Eigentlich 15 aus 255, Keyfarbe und so.) Die Figuren sehen für meine Ansprüche an das Spiel immer noch gut aus - und ich konnte den Speicherbedarf für die Sprites auf einen Schlag auf fast genau die Hälfte reduzieren - wo die Sprites vorher 8bit waren und kaum jemals an 16 Farben pro Sprite je herangekommen waren - eher so 8 bis 12.
zatzen hat geschrieben:Ich habe heute mal provisorisch den ersten Decoder in Freepascal geschrieben und dabei noch den Encoder debuggt.
Ich finde die Kompressionsrate erfreulich und das decodieren gestaltet sich so auch ersteinmal einfach.
Hier nochmal an einem Beispiel:
64x64.png
Als PNG 1874 Bytes.
Die ZV2 Datei ist 2637 Bytes groß.
Klingt so erstmal nach nichts besonderem, allerdings muss man bedenken, dass bei einem Set von Grafiken, also mehrere Sprites in einer ZV2, die Farbtabelle und der Farbstring relativ zu den Blockdaten schrumpfen.
Ja, kenne ich - ist bei meinen auch so.
Und, daß natürlich ein blockbasiertes Format Grafiken (mit begrenzter Farbanzahl) gut packt, liegt daran, daß in Flächen Farben in geometrischer Nähe zueinander viel häufiger ähnlich oder gleich sind als Farben, die zeilenweise gespeichert werden. Da sind ein paar nebeneinanderliegende Farben zwar ähnlich, aber die Zeilen selbst haben quasi nichts miteinander zu tun. Für so Flächen könnte man noch so eine "Zähler"-Option einbauen, daß die häufigste Farbe nicht angegeben werden muß, sondern immer wenn ein "Zähler" Byte/Datum auftritt, soviele Pixel von der "häufigsten" Farbe des Blocks gepixel werden... - ABER... das würde nur Sinn machen bei 16x16er oder VIELLEICHT noch 8x8er Blocks. Bei 4x4er würde der Speicherbedarf dafür meiner Schätzung nach höher sein als der Platzgewinn (also würde kaum wirklich Packen).
zatzen hat geschrieben:Auseinanderdividiert sind die Blockdaten inkl. Headerdaten nur 1462 Bytes groß, angenommen man hat dann 16 Stück dieser Sprites mit je 1500 Bytes -> 24000 Bytes, und wenn die restlichen Daten nur unwesentlich anwachsen, sagen wir auf 2 KB, dann hat man, in diesem Fall von 16x16 Blöcken d.h. 64x64 Pixel, 16 * 64 * 64 Bytes = 65536 auf 26000 Bytes reduziert, also auf knapp 40%.
Naja gut, von so "angenommen"-Daten gehe ich nie aus, um irgendwelche Statistiken zu "verschönern" oder wasauchimmer. Realistisch ist es für mich, wenn ich es wirklich SO in einem Spiel verwenden würde.
Wenn ich z.B. Schüsse habe, die rund und 5x5 Pixel groß wären, bräuchte man 4 (oder 3, weil unten rechts wegen der Rundung nichts gebraucht) solcher 4x4er Blocks dafür. In MEINEM Format wäre es kaum besser, weil die immer mit ganzen 16er Positionen gespeichert werden, somit also 80 Bytes (5 Zeilen, 16 Bytes/Zeile). Wenn 16-farbig, wäre es nicht weniger, weil die Pixeldopplung nach rechts (und nicht nach unten) geht und wenn man etwas Rundes speichert, braucht es quer und längs die gleiche Anzahl Pixel und auch als 4-Bit wären es noch 80 Bytes. Nun ist das aber ein Sonderfall und nicht etwas, das andauernd auftritt. Außerdem könnte ich diesen Schuß dann in allen Farbkombinationen speichern, mittels zusätzlicher Paletten.
Und ja. Da packe ich WESENTLICH weniger effizient als Du! Mir geht es ja unter anderem auch darum, alle Sprite-Features verwenden zu können und auch auf einfache Weise "vom Spiel aus" auf die Daten zugreifen zu können, um z.B. einer Figur eine andere Palette verpassen zu können oder die Figur gespiegelt, skaliert, gedreht anzeigen zu können ohne zusätzliche Image-Daten zu brauchen. Nur weiß ich natürlich, daß z.B. die "gedreht"-Option in Spielen mit seitlicher Ansicht gar nicht so oft gebraucht werden würde (als es z.B. in der Xpyderz-mäßigen Draufsicht der Fall ist) - und trotzdem wird diese Option quasi "mitgeschleppt". Da würde eine einfachere, spiegelfähige, aber NICHT drehfähige Spriteroutine sicher SO EINIGES an Performance bringen.
Hier sieht man wieder den erwähnten Nachteil generalisierter Routinen: Sie können "alles" - machen "alles" aber leider oft auch, wenn es gar nicht gebraucht wird...
zatzen hat geschrieben:Tja, zugegeben, an diesem Beispiel zeigt sich eher dass sich hier eine simple Reduktion auf 4 Bit eher auszahlen würde. Allerdings wäre man auf 16 Farben eingeschränkt - dieses Beispiel hier hat 60 Farben.
Ich müsste noch ein Beispiel anbringen von wirklich typischen Sprites die man als 4 Bit speichern *könnte*, wie sich ZV2 da schlägt.
Naja, dieser Bildausschnitt ist nur (meiner groben Meinung nach) kaum ein Beispiel für einen typischen Sprite. 60 Farben - d.h. fast ein Viertel der vorhandenen 256er Palette - wird meiner bescheidenen Meinung nach kaum je ein einzelnes Spriteimage belegen.
zatzen hat geschrieben:Ok... geschrieben, getan:
guyb.png
(das ist der VGA-Guybrush mit konkret 12 Farben, nicht EGA wo vielleicht nur 8 verwendet werden)
PNG (mit nur 16 Farben eingestellt): 445 Bytes
ZV2 (komplett mit Tabelle und Farbstring): 344 Bytes
Diesmal hätte also klar ZV2 Vorteile, selbst schon bei nur einem Sprite.
Ja, wie erwähnt: Hier zeigt sich der Vorteil blockbasierter Speicherung zum "Zusammenfassen" von Farben, weil diese in geometrischer, flächiger X/Y-Nähe zueinander viel häufiger "passend" auftreten als z.B. zeilenweise. Ich weiß nur nicht auswendig, ob PNG nicht auch blockweise packt. JPG tut es bekanntlich definitiv - aber JPG ist ja kein realistischer Vergleich, weil es (teilweise stark) verlustbehaftet arbeitet und daher meiner Meinung nach für Sprites in Größenordnungen, wie wir "320er" sie benutzen, sowieso ungeeignet wäre. Je gröber die Auflösung, umso bescheidener sieht JPG aus...
zatzen hat geschrieben:Die reinen Blockdaten sind 240 Bytes groß, das Sprite ist 6x11 Blöcke also 66 * 16 Bytes = 1056 Bytes groß, und rein so gesehen ergibt sich eine Kompression auf 23%.
Naja, das ist jetzt EIN Beispiel-Sprite.
Man müßte da mal so ganze Sprite-Sammlungen haben. (Gibts übrigens im Internet. Es scheint viele Leute zu geben, die Sprites in allen Animationsphasen aus alten 2D-Spielen herausrippen und in großen Bitmapgrafiken zusammenfassen.) Und die gleichen Routinen auf quasi so mehrere VERSCHIEDENE Sammlungen anwenden und DANN aus ALLEM eine durchschnittliche Kompressionsrate errechnen, das wäre dann realistischer. Allerdings sind viele dieser Sprite-Sammlungen auch schon eher auf 16bit- oder 24bit-Grafik eingestellt, so daß hier nach Farbnummer packen schon nicht mehr möglich/sinnvoll ist.
Ich muß aber auch dazu sagen, daß oft mit viel größerer Farbtiefe gearbeitet wird als überhaupt nötig. Ein 16x16-Sprite kann z.B. schon rein mathematisch nie mehr als insgesamt 256 Farben haben, in der Realität hat es meist kaum ein Sechstel davon - und trotzdem wird dann Schwachsinn betrieben wie solche Sprites in 24bit (oder 32bit) anzulegen. Habe da schon mal ein Spiel von einem Typen gespielt, das da kaum auf meinen einen Rechner lauffähig zu kriegen war, weil dauernd "Speichermangel" bzw. Windows mit Swappen gar nicht mehr hinterherkam. Tja, wenn man eigentlich Kachel-/Arcadegame-mäßige Hintergründe als scrollende 24bit-Vollgrafik speichert und Sprites, egal wie groß/klein ebenfalls als 24bit und zwar IMMER 200x200 Pixel (bei kleineren war dann eben ringsherum riesiger Leerraum)... Dann funktioniert es zwar, frißt aber Speicher UND Performance und weil die Sprites auch noch GEPIXELT waren, brauchten sie eigentlich niemals 24bit, sondern waren kaum 16-farbig... (aus 16777216 Farben!) ... Wenn man so etwas sieht und sich dann "die Leute nicht erklären können, wieso das ruckelt oder auf manchen Computern mit Grund Speichermangel nicht starten will"... - da fällt einem auch echt GAR NICHTS mehr ein, was man da noch sagen soll... - Klar, Respekt, daß überhaupt noch Leute selbst Spiele machen... - Aber wenn man selber codet, tut einem sowas schon weh.
zatzen hat geschrieben:Zum Thema GIF: Habe es auch mal als GIF gespeichert, 16 Farben, ergibt 343 Bytes, also nur um 1 Byte kleiner, und wie gesagt sind als Sprite-Set noch deutlich kleinere Durchschnittswerte (siehe 240 Bytes reine Blockdaten) zu erwarten.
Naja, fairerweise muß man sagen: "GIF87a" bzw "GIF89a" sind 6 Bytes. Die "Sequenzblock-Header" und "Subsequenzblock-Header" brauchen insgesamt auch nochmal bei der Gesamtgröße schätzungsweise so 10 Bytes... - Und dann kommt noch dazu: Wenn man GIF "interlaced" speichert, wird es evtl. etwas schlechter packen als "nicht interlaced" (progressiv).
Und ja, wie schon erwähnt: Hier zeigt sich der Vorteil blockweisen Packens (geometrische Nähe gleicher/ähnlicher Pixel). Andererseits wird vielleicht GIF evtl. wieder besser abschneiden, wenn man ein Bild nimmt, dessen Abmessungen keine Vielfachen von 4 sind, weil blockbasierte Speicherung bekanntlich immer dann "zum Problem wird", wenn man angeschnittene Blocks trotzdem als volle Blocks speichern muß.
zatzen hat geschrieben:Die Farbstrings können noch geringfügig optimiert werden, sagen wir um ca. 10% verkleinert, allzuviel kann man da nicht herumschieben, sonst bräuchte man wieder mehr Tabelleneinträge (diese enthalten Offsets in den Farbstring zusammen mit der Bitbreite).
Ja, man könnte hier auch STUNDENLANG herumrechnen lassen, um den optimalsten Palettenstring zu finden, indem quasi "alles mit allem" kombiniert wird... aber da denke ich, für die 10-20 Bytes, die man da realistisch gesehen am Ende gewinnen wird, wäre es den Aufwand kaum wert. Das macht vielleicht auf einem C64 Sinn, wo man wirklich alles (Code, Grafik, Level, Sound) in die 64kByte quetschen muß... - Natürlich ist es schön, zu wissen, daß man das absolute Optimum an Spritedaten gewonnen hat. Allerdings... naja. Jedes Mal, wenn man vielleicht mal andere Sprites benutzen will, müßte man das neu berechnen, es wäre also jedes Mal ein Daten-"Unikat", von dem man nie vorher wüßte, wie groß es am Ende werden wird und wie viel Speicher man dafür zu reservieren, d.h. freizuhalten hat.
Gut, dann habe ich also auch zu DIESEM Thread endlich meinen schon länger geplanten Senf abgelassen. (Ja, hier in der Gegend wird bekannter Senf hergestellt...)