Dynamisches auslesen von Bildern (mit INI Dateien)

Übersicht BlitzMax, BlitzMax NG FAQs und Tutorials

Neue Antwort erstellen

M0rgenstern

Betreff: Dynamisches auslesen von Bildern (mit INI Dateien)

BeitragDo, Sep 02, 2010 22:00
Antworten mit Zitat
Benutzer-Profile anzeigen
Hallo Leute.
Ich hab mich heute mal dazu durchgerungen mein erstes Tutorial zu schreiben.
Die Vorraussetzungen die man mitbringen sollte: Man sollte einigermaßen mit dem schreiben und lesen von Dateien umgehen können und somit auch Befehle wie Readline, Writeline, OpenStream und Closestream kennen und wenigstens wissen was ein Type ist.
Aber alles in allem versuche ich das Tutorial so zu schreiben, dass auch Neulinge gut damit klar kommen, vor allem die Types werde ich ausführlich behandeln, da es nur einer ist.

Zu erstmal:

Worum gehts?

Ich weiß, dass der Titel wohl zuersteinmal verwirrend wirkt. Also hier erstmal eine Erklärung:
Ich hab neulich mal drüber nachgedacht, wie folgendes zu realisieren wäre:
Wenn man ein Spiel programmiert und dem Spieler die Möglichkeit geben will selbst Objekte hinzuzufügen oder es sich selbst einfach machen will, ohne jede Grafik einzeln laden zu müssen.
Im vorraus einfach mal zig TImage Instanzen zu deklarieren fand ich zu undynamisch.
Also hab ich mir folgendes überlegt: Die Bilder werden nach einem bestimmten Schema benannt. Danach kann man den Namen auseinandernehmen und entsprechend in einer INI Datei speichern. Dann kann man das ganze schon ganz schön auslesen.
Zusätzlich hab ich noch einen kleinen Type zur Bildverwaltung geschrieben, damit man alle Informationen auf einmal hat.

Das Tutorial ist aufgespalten in 3 Teile.
Da die Bilder, und in diesem Fall auch der Type der sie hier verwaltet, grundlegend sind werden wir heute damit anfangen.
In den nächsten Tagen wird folgen:
2. Teil: Bilderordner durchsuchen, die Namen auseinanderschneiden und alle Infos in einer INI Datei speichern.
3. Teil: Die INI Datei auslesen und entsprechend die Bilder laden


Also, los gehts mit

Teil 1: Die Bilddateien und der zugehörige Type

Zuerst sollte man sich für die Bilddateien eine anständige und einheitliche Notation überlegen, die man genau so bei allen Bildern die man so bearbeiten möchte nutzt.
Ich hab folgende Notation gewählt:
Bildname ! BreiteProFrame x HöheproFrame % FrameAnzahl & AnimationsTimer . png
Die Leerzeichen hab ich nur zur Verdeutlichung eingefügt.
Kleine Erklärung: BreiteProFrame, Höheproframe, FrameAnzahl und Animationstimer sind einfach Zahlen.

BreiteProFrame gibt die Breite eines einzelnen Frames in Pixeln an.
HöheProFrame gibt die Höhe eines einzelnen Frames in Pixeln an.
FrameAnzahl gibt die Gesamtzahl aller Frames im Bild an.
Animationstimer gibt die Zeit in Millisekunden zwischen zwei Frames an.

Als konkretes Beispiel hier mal zwei Namen meiner Bilddateien:
Bild1:
Zitat:
bullet!16x16%4&200.png

Bild2:
Zitat:
kleinerGrabstein_grau!32x32%0&0.png


Wie ihr sehen könnt hat Bild1 folgende Eigenschaften:
Bildname = bullet
BreiteProFrame = 16
HöheProFrame = 16
FrameAnzahl = 4
Animationstimer = 200

Und Bild2 hat folgende Eigenschaften:
Bildname = kleinerGrabstein_grau
BreiteProFrame = 32
HöheProFrame = 32
FrameAnzahl = 0 (Also, es ist ein Einzelbild und somit keine Animation vorhanden)
Animationstimer = 0 (Da es hier kein Animationsbild ist, ist das ja nur logisch)

Damit wäre der Aufbau der Dateien geklärt.

Jetzt folgt der zugehörige Type.
Bevor wir einen neuen Type anlegen, müssen wir uns im klaren darüber sein, welche Attribute (Felder) er haben muss.
Also, erstmal brauchen wir eine Liste in der alle Einträge gespeichert werden.
Dann brauchen wir eine Variable in der das Bild gespeichert werden kann (Timage).
Zusätzlich brauchen wir für jede Information aus den Bildern noch eine Variable, also:
Maximale Frameanzahl, der Name, der Pfad, Breite eines Einzelbildes, Höhe eines Einzelbildes und die Animationszeit.
Zusätzlich brauchen wir noch eine Variable die aussagt welcher Frame im Moment angezeigt wird und eine die uns sagt wann die letzte Animation stattgefunden hat.
Daraus ergiebt sich erstmal folgender Type:

BlitzMax: [AUSKLAPPEN]
Type TMyMainIMage
Global tlAllMyFirstImages:TList = New TList 'Die Liste aller Instanzen
Field tiImage:TImage 'Hier wird das geladenen Bild gespeichert
Field iMaxFrames:Int 'Die maximale Frameanzahl des Bildes
Field sName:String, sPath:String 'name und Pfad des Bildes
Field fCellWidth:Float, fCellHeight:Float 'Höhe und Breite der einzelnen Frames (in Pixeln)
Field iFrame:Int 'Momentaner Frame
Field iTimer:Int 'Die Animationszeit (in Millisekunden)
Field iLastFrameUpDate:Int 'Zeit der letzten Animation (in Millisekunden)
End Type


Dann brauchen wir einen sogenannten Konstruktor, der uns eine neue Typeinstanz erstellt.
Diesem Konstruktor müssen wir die wichtigsten Dinge übergeben.
Er muss den Pfad kennen, aus dem das Bild geladen wird. Im Prinzip müssen wir dem Konstruktor alles übergeben, was wir aus der Bilddatei auslesen können.
Daraus ergiebt sich folgende Grundstruktur:

BlitzMax: [AUSKLAPPEN]
	Function Create:TMyMainImage(pPath:String, pMaxFrames:Int = 0, pCellWidth:Float = 0, pCellHeight:Float = 0, PTimer:Int = 0, pName:String = "")
Local MyIMage:TMyMainImage = New TMyMainImage
Return Myimage
End Function


Das ganze muss noch erweitert werden, so dass Werte zugewiesen werden.
Damit komment wie auf folgende Funktion:

BlitzMax: [AUSKLAPPEN]

Function Create:TMyMainImage(pPath:String, pMaxFrames:Int = 0, pCellWidth:Float = 0, pCellHeight:Float = 0, PTimer:Int = 0, pName:String = "")
Local MyIMage:TMyMainImage = New TMyMainImage

MyIMage.fCellWidth = pCellWidth
MyIMage.fCellHeight = pcellheight
If pMaxFrames > 0 Then 'wenn es KEIN Einzelbild ist
MyIMage.tiimage = LoadAnimImage(pPath, pCellWidth, pCellHeight, 0, pMaxFrames) 'Dann muss man ein animiertes Bild laden
ElseIf pMaxFrames = 0 Then 'Ansonsten
MyIMage.tiimage = LoadImage(pPath) 'Läd man ein nicht animiertes Bild
EndIf
MyIMage.iMaxFrames = pMaxFrames
MyIMage.iTimer = PTimer
MyIMage.iFrame = 0 'immer beim ersten Frame anfangen
MyIMage.iLastFrameUpDate = MilliSecs() 'Wir nehmen Millisecs() Als Grundlage für unsere Animation
MyIMage.sName = pName
MyIMage.sPath = pPath

Return Myimage
End Function


So, kleine Zwischenbilanz.
Was kenne wir nun? Den Aufbau der Bilddateien. UNd die Grundlagen des Verwaltungstypes.
Wir haben aber noch keine Möglichkeit die Typeinstanzen in die Liste zu bekommen, bzw sie da wieder raus zu bekommen.
Dazu gibt es zwei Funktionen.
Die erste heißt BlitzMax: [AUSKLAPPEN]
New

Und vielleicht fällt euch auf, dass sie in der ersten Zeile unseres Konstruktors steckt:
BlitzMax: [AUSKLAPPEN]
Local MyIMage:TMyMainImage = New TMyMainImage


Nun brauchen wir noch die Funktion dazu, welche wie folgt aussieht:

BlitzMax: [AUSKLAPPEN]
	Method New()
tlAllMyFirstImages.AddLast(Self)
End Method


Die Instanz die wir im Konstruktor erstellt haben, wird hier in die Liste eingefügt.
Schön und gut, aber, wie bekommen wir sie da wieder raus, wenn die INstanz vielleicht nicht mehr benötigt wird?
Ganz einfach, mit einem Destruktor:

BlitzMax: [AUSKLAPPEN]
	Method Destroy()
tlAllMyFirstImages.Remove(Self)
End Method


Dieser entfernt den zugehörigen Listeneintrag einfach.
Aufgerufen wird der Destruktor wie folgt:
BlitzMax: [AUSKLAPPEN]
Global MeinBild:TMyMainImage = TMyMainImage.Create(hier die Parameter)
MeinBild.Destroy() 'Und raus ist der Eintrag und somit auch das zugehörige Bild


Kleiner Zwischenstopp um den bisherigen Type anzuschauen:

BlitzMax: [AUSKLAPPEN]
Type TMyMainIMage
Global tlAllMyFirstImages:TList = New TList
Field tiimage:TImage
Field iMaxFrames:Int
Field sName:String, sPath:String
Field fCellWidth:Float, fCellHeight:Float
Field iFrame:Int
Field iTimer:Int, iLastFrameUpDate:Int

Function Create:TMyMainImage(pPath:String, pMaxFrames:Int = 0, pCellWidth:Float = 0, pCellHeight:Float = 0, PTimer:Int = 0, pName:String = "")
Local MyIMage:TMyMainImage = New TMyMainImage

MyIMage.fCellWidth = pCellWidth
MyIMage.fCellHeight = pcellheight
If pMaxFrames > 0 Then
MyIMage.tiimage = LoadAnimImage(pPath, pCellWidth, pCellHeight, 0, pMaxFrames)
ElseIf pMaxFrames = 0 Then
MyIMage.tiimage = LoadImage(pPath)
EndIf
MyIMage.iMaxFrames = pMaxFrames
MyIMage.iTimer = PTimer
MyIMage.iFrame = 0
MyIMage.iLastFrameUpDate = MilliSecs()
MyIMage.sName = pName
MyIMage.sPath = pPath

Return Myimage
End Function

Method New()
tlAllMyFirstImages.AddLast(Self)
End Method

Method Destroy()
tlAllMyFirstImages.Remove(Self)
End Method

End Type


Weiter gehts.
"Von Hand" animieren ist out. Das kann der Type schön selbst machen.
Also, hier der Code, die Beschreibung gibts als Kommentar:

BlitzMax: [AUSKLAPPEN]
	Method Animate()
If iMaxFrames <> 0 Then 'Wenn das Bild kein Einzelbild ist
If ((iTimer + iLastFrameUpDate) < MilliSecs()) Then 'Und wenn genug Zeit seit der letzten Animation vergangen ist
'Kleine erklärung: Ilastframeupdate ist die Zeit von Millisecs() (Gibt die Laufzeit des Betriebsystems an) zum
'Zeitpunkt der letzten Animation und Millisecs() ist die Momentane Laufzeit.
'ITimer ist eine bestimmte Konstante die wir ja vorher schon festgelegt haben. Das heißt:
'Wenn seit dem letzten mal genug Zeit vergangen ist, dass Millisecs() jetzt größer ist als beide Variablen zusammen
'Also, LetzteLaufzeit + Konstante, dann darf erst wieder animiert werden
iFrame = (iFrame + 1) Mod iMaxFrames 'Neuen Frame berechnen. Also ein Frame weiter
'+1 und darauf auchten dass man nicht aus dem Frame bereich gerät Mod Imaxframes
iLastFrameUpDate = MilliSecs() 'Variable auf die momentane Laufzeit setzen, damit die
'Animation konstant bleibt
EndIf
End If
End Method


Natürlich ist es auch ab und zu nötig ein Bild manuell zu animieren. Also, wenn man zum Beispiel eine Animation nur bei einem bestimmten Ereigniss wünscht.
Dann ist die Animationszeit nicht relevant und fällt somit weg.
Was noch übrig bleibt ist folgende Funktion, welche wie ich denke selbsterklärend ist:

BlitzMax: [AUSKLAPPEN]
	Method AnimateManual()
If iMaxFrames <> 0 Then
iFrame = (iFrame + 1) Mod iMaxFrames
End If
End Method


Unser Type sieht also nun so aus:

BlitzMax: [AUSKLAPPEN]
Type TMyMainIMage
Global tlAllMyFirstImages:TList = New TList
Field tiimage:TImage
Field iMaxFrames:Int
Field sName:String, sPath:String
Field fCellWidth:Float, fCellHeight:Float
Field iFrame:Int
Field iTimer:Int, iLastFrameUpDate:Int

Function Create:TMyMainImage(pPath:String, pMaxFrames:Int = 0, pCellWidth:Float = 0, pCellHeight:Float = 0, PTimer:Int = 0, pName:String = "")
Local MyIMage:TMyMainImage = New TMyMainImage

MyIMage.fCellWidth = pCellWidth
MyIMage.fCellHeight = pcellheight
If pMaxFrames > 0 Then
MyIMage.tiimage = LoadAnimImage(pPath, pCellWidth, pCellHeight, 0, pMaxFrames)
ElseIf pMaxFrames = 0 Then
MyIMage.tiimage = LoadImage(pPath)
EndIf
MyIMage.iMaxFrames = pMaxFrames
MyIMage.iTimer = PTimer
MyIMage.iFrame = 0
MyIMage.iLastFrameUpDate = MilliSecs()
MyIMage.sName = pName
MyIMage.sPath = pPath

Return Myimage
End Function

Method New()
tlAllMyFirstImages.AddLast(Self)
End Method

Method Destroy()
tlAllMyFirstImages.Remove(Self)
End Method

Method Animate()
If iMaxFrames <> 0 Then
If ((iTimer + iLastFrameUpDate) < MilliSecs()) Then
iFrame = (iFrame + 1) Mod iMaxFrames
iLastFrameUpDate = MilliSecs()
EndIf
End If
End Method

Method AnimateManual()
If iMaxFrames <> 0 Then
iFrame = (iFrame + 1) Mod iMaxFrames
End If
End Method

End Type


Aus persönlichen Gründen hab ich noch eine Methode gebraucht, die mir alle Frames nebeneinander aufgereiht anzeigt. Da diese Methode aber nicht für dieses Thema relevant ist werde ich sie nicht weiter kommentieren und einfach nur einfügen:

BlitzMax: [AUSKLAPPEN]
	Method DrawAllFramesInARow(px:Int, py:Int)
Local fscalex:Float, fscaley:Float
GetScale(fscalex, fscaley)
Local iWidth:Int = imaxframes * fcellwidth
Local iwidthhalf:Int = iwidth / 2
Local iFirstPosition:Int = px - iwidthhalf
For Local i:Int = 0 Until iMaxFrames
DrawImage(tiimage, iFirstPosition + (fCellWidth * fscalex) * i, py, i)
Next
End Method


Somit sieht unser Type endgültig so aus:

BlitzMax: [AUSKLAPPEN]
Type TMyMainIMage
Global tlAllMyFirstImages:TList = New TList
Field tiimage:TImage
Field iMaxFrames:Int
Field sName:String, sPath:String
Field fCellWidth:Float, fCellHeight:Float
Field iFrame:Int
Field iTimer:Int, iLastFrameUpDate:Int

Function Create:TMyMainImage(pPath:String, pMaxFrames:Int = 0, pCellWidth:Float = 0, pCellHeight:Float = 0, PTimer:Int = 0, pName:String = "")
Local MyIMage:TMyMainImage = New TMyMainImage

MyIMage.fCellWidth = pCellWidth
MyIMage.fCellHeight = pcellheight
If pMaxFrames > 0 Then
MyIMage.tiimage = LoadAnimImage(pPath, pCellWidth, pCellHeight, 0, pMaxFrames)
ElseIf pMaxFrames = 0 Then
MyIMage.tiimage = LoadImage(pPath)
EndIf
MyIMage.iMaxFrames = pMaxFrames
MyIMage.iTimer = PTimer
MyIMage.iFrame = 0
MyIMage.iLastFrameUpDate = MilliSecs()
MyIMage.sName = pName
MyIMage.sPath = pPath

Return Myimage
End Function

Method New()
tlAllMyFirstImages.AddLast(Self)
End Method

Method Destroy()
tlAllMyFirstImages.Remove(Self)
End Method

Method DrawAllFramesInARow(px:Int, py:Int)
Local fscalex:Float, fscaley:Float
GetScale(fscalex, fscaley)
Local iWidth:Int = imaxframes * fcellwidth
Local iwidthhalf:Int = iwidth / 2
Local iFirstPosition:Int = px - iwidthhalf
For Local i:Int = 0 Until iMaxFrames
DrawImage(tiimage, iFirstPosition + (fCellWidth * fscalex) * i, py, i)
Next
End Method

Method Animate()
If iMaxFrames <> 0 Then
If ((iTimer + iLastFrameUpDate) < MilliSecs()) Then
iFrame = (iFrame + 1) Mod iMaxFrames
iLastFrameUpDate = MilliSecs()
EndIf
End If
End Method

Method AnimateManual()
If iMaxFrames <> 0 Then
iFrame = (iFrame + 1) Mod iMaxFrames
End If
End Method

End Type



Uff...
Wenn ihr bis hier her gelant seit: Grautlation. Ihr habt den ersten Teil schadlos (hoffe ich doch) überstanden.
Teil 2 und 3 folgen in den nächsten Tagen.
Bis dahin wäre ich über Kritik oder Kommentare wirklich sehr froh, da ich sicher bin, dass ich einiges verbessern könnte (leider weiß ich selbst nicht was). UNd das möchte ich dann auch tun. Es soll ja schließlich helfen.

Liebe Grüße, M0rgenstern.

M0rgenstern

Betreff: Teil2

BeitragDo, Sep 02, 2010 22:01
Antworten mit Zitat
Benutzer-Profile anzeigen
Hallo Leute.
Heute gehts dann weiter mit:

2. Teil: Bilderordner durchsuchen, die Namen auseinanderschneiden und alle Infos in einer INI Datei speichern.

Im ersten Teil haben wir alle vorbereitungen getroffen, eine bestimmte auslesbare Bilddatei zu erstellen.
Heute geht es dann damit weiter diese Dateien auch auszulesen und schön sortiert in eienr Datai zu speichern.

Los gehts:
Zuersteinmal brauchen wir eine Funktion, die in einem bestimmten Ordner nach allen Dateien sucht und diese dann temporär speichert. Später wird diese FUnktion auch die INI Datei schreiben, aber wir gehen wieder schrittweise vor.

Zuersteinmal deklarieren wir wieder einige benötigte Variablen.
Wir brauchen den Ordnerpfad in dem die Bilder stecken (als relativer Pfad), eine Variable für das ausgelesene Verzeichnis, eine Variable für die Datei die momentan gelesen wird, ein Array in dem alle relevanten Bilddateien gespeichert werden und eine Variable die die Größe des Arrays angiebt.
Somit sieht unsere Funktion anfangs so aus:

BlitzMax: [AUSKLAPPEN]
Function GetObjectImages()
Local sPath:String = "Data\GFX\Surrounding\" 'Der Pfad in dem die Bilddateien stecken
Local iDir:Int 'Das Verzeichnis, was damit passiert seht ihr gleich
Local sFile:String 'Die momentan gelesene Datei
Local sAllFiles:String[] 'Alle relevanten Bilddateien
Local iAmount:Int = 0 'Größe des Arrays
End Function


Nun gehen wir erst einmal durch das Verzeichnis und zählen wie viele relevante Dateien überhaupt in diesem Verzeichnis liegen.
Die Erklärung gibts hier anhand von Kommentaren:

BlitzMax: [AUSKLAPPEN]
	iDir = ReadDir(spath) 'Wir öffnen den angegebenen Pfad zum auslesen der Dateien
Repeat 'eine Schleife in der die Dateien gelesen werden
sFile = NextFile(iDir) 'Wir suchen uns zu beginn der Schleife immer die "nächste" Datei im Verzeichnis
'Damit stellen wir sicher, dass jede Datei nur einmal "angesehen" wird
'Der Dateiname wird somit in sFile zwischengespeichert
If FileType(sPath + sFile) = 1 Then 'Wenn die Datei kein Ordner sondern eine Datei ist (2 wäre Ordner, 0 nicht existent)
If Instr(sFile, "!") Then 'Wenn in dem Dateinamen ein Ausrufezeichen vorkommt
If Instr(sFile, "%") Then 'Und wenn ein Prozentzeichen vorkommt
If Instr(sFile, ".png") Then 'Und wenn die Zeichenkette .png vorkommt
iAmount:+1 'Dann ist die Datei eine der gesuchten und wir erhöhen den Zähler um eins
EndIf
EndIf
End If
EndIf
Until sFile = "" 'Wenn der Dateiname leer ist sind wir alle Dateien im Ordner durchgegangen und die
'Schleife wird abgebrochen
CloseDir(iDir)'Wir schließen das Verzeichnis wieder


Somit sieht unsere Funktion nun so aus:

BlitzMax: [AUSKLAPPEN]
Function GetObjectImages()
Local sPath:String = "Data\GFX\Surrounding\"
Local iDir:Int
Local sFile:String
Local sAllFiles:String[]
Local iAmount:Int = 0

iDir = ReadDir(spath)
Repeat
sFile = NextFile(iDir)
If FileType(sPath + sFile) = 1 Then
If Instr(sFile, "!") Then
If Instr(sFile, "%") Then
If Instr(sFile, ".png") Then
iAmount:+1
EndIf
EndIf
End If
EndIf
Until sFile = ""
CloseDir(iDir)
End Function


Jetzt wiederholen wir das ganze, nur mit dem Unterschied, dass wir alle relevanten Dateien in einem Array speichern.
Dazu geben wir unserem vorher deklarierten Array erstmal seine Größe mit:

BlitzMax: [AUSKLAPPEN]
	sAllFiles = New String[iAmount] 'Ein neues Stringarray mit einer bestimmten Größe erstellen
iAmount = 0 'Anzahl der Dateien auf 0 setzen, damit wir damit im Array durchzählen können


Und nun folgt fast die gleiche Schleife wie vorher. Ich werde nur noch die neuen Teile kommentieren:

BlitzMax: [AUSKLAPPEN]
iDir = ReadDir(sPath)
Repeat
sFile = NextFile(iDir)
If FileType(sPath + sFile) = 1 Then
If Instr(sFile, "!") Then
If Instr(sFile, "%") Then
If Instr(sFile, "&") Then
sAllFiles[iAmount] = sFile 'Den Dateinamen im Array speichern
iAmount:+1 'Arrayzähler um 1 erhöhen
EndIf
End If
End If
EndIf
Until sFile = ""
CloseDir(iDir)


Unsere Funktion sieht jetzt folgendermaßen aus:

BlitzMax: [AUSKLAPPEN]
Function GetObjectImages()
Local sPath:String = "Data\GFX\Surrounding\"
Local iDir:Int
Local sFile:String
Local sAllFiles:String[]
Local iAmount:Int = 0

iDir = ReadDir(spath)
Repeat
sFile = NextFile(iDir)
If FileType(sPath + sFile) = 1 Then
If Instr(sFile, "!") Then
If Instr(sFile, "%") Then
If Instr(sFile, ".png") Then
iAmount:+1
EndIf
EndIf
End If
EndIf
Until sFile = ""
CloseDir(iDir)

sAllFiles = New String[iAmount]
iAmount = 0

iDir = ReadDir(sPath)
Repeat
sFile = NextFile(iDir)
If FileType(sPath + sFile) = 1 Then
If Instr(sFile, "!") Then
If Instr(sFile, "%") Then
If Instr(sFile, "&") Then
sAllFiles[iAmount] = sFile
iAmount:+1
EndIf
End If
End If
EndIf
Until sFile = ""
CloseDir(iDir)
End Function


Nun, das ist wieder ein gutes Stück gewesen. Kleine Zwischenbilanz. Was haben wir bis jetzt?
1. Bilddateien die in einem bestimmten Muster benannt sind
2. Einen Type der unsere Bilder verwalten kann
3. Wir haben schonmal alle gültigen Bilddateien ausgelsen und die Namen in einem Array gespeichert.

Jetzt folgt das Auseinanderschneiden der Dateinamen und das Speichern.

Das Problem ist, dass hier beides immer pro Datei einmal passiert. Wie schneiden sie auseinander und speichern das direkt ab um dann die nächste Datei in Bearbeitung zu nehmen.
Also gibts jetzt erstmal ein paar Hilfsfunktionen, die uns die Dateien entsprechen schneiden.

Zuerst die einfachste und kürzeste Funktion dieser drei Funktionen:
Wie bekommen wir den Namen des Bildes? (Ihr erinnert euch: Alles was vor dem ! steht.)

BlitzMax: [AUSKLAPPEN]
Function GetNameFromImage:String(pImageFile:String, pName:String Var)
Local ipos:Int 'Variable die die Position des ! in dem String angeben wird
ipos = Instr(pImageFile, "!") 'Wir lassen uns die Position des Ausrufezeichens innerhalb des Strings in
'einer Variable speichern
pName = Mid(pImageFile, 0, iPos) 'Hier wird der Name "herausgetrennt". pImageFile gibt den kompletten
'Dateinamen an. 0 Gibt die Startposition an, ab der geschnitten werden soll und iPos gibt die Endposition an.
'iPos gibt also die Stelle an an der das Ausrufezeichen sitzt. Zu beachten ist hier, dass immer eine Position DAVOR
'aufgehört wird. Also das Ausrufezeichen kommt nicht mehr in den Namen.
End Function


Kleine Erklärung zu der Function: GetNameFromImage:STRING bedeutet, dass diese Funktion einen String zurückliefert. pImageFile:String ist der komplette Name der Bilddatei den man der Funktion übergiebt.
Und pName ist kein eigentlicher Parameter, sondern ein Rückgabewert.
Das erkennt man, daran, dass dahinter noch ein Var steht.
Durch solche Parameter kann man der Funktion sagen, welche Variablen sie zurückgeben soll.

Warum braucht man sowas?
Nun, normalerweise können wir der Funktion mit Return sagen, welche Werte sie zurückgeben soll. Mit Return kann man aber nur jeweils einen Wert zurückgeben. Ihr werdet gleich sehen, dass wir zwei Funktionen dieser Art haben, die zwei Parameter zurückgeben müssen. Dann hilft uns Return nicht weiter.
Man gibt der Funktion also beim Aufruf eine vorher deklarierte Variable mit. Nach dem Aufruf der Funktion hat die Variable dann den Wert der ihr in der Funktion zugeteilt wurde (Später nochmal im Code zu sehen).

Nun die Funktion, die uns die Höhe und Breite des Bildes leifert:

BlitzMax: [AUSKLAPPEN]
Function GetSizeFromImage:String(pImageFile:String, pWidth:String Var, pHeight:String Var)
Local iPos:Int, iLastPos:Int 'Wieder die Positionen von bestimmten Zeichen
iPos = Instr(pImageFile, "!") 'Wir suchen wieder das Ausrufezeichen
iLastPos = Instr(pImageFile, "x", iPos) 'Als zweites suchen wir das x das zwischen unseren beiden Zahlen steht
'Zu beachten: Wir suchen nicht vom Anfang des Strings sondern erst ab der Position des Ausrufezeichens
'Das sieht man an dem zusätzlichen iPos am Ende.
pWidth = Mid(pImageFile, iPos + 1, iLastPos - iPos - 1) 'Die Breite ist das, was zwischen dem Ausrufezeichen
'und dem x liegt. Also ipos+1 (hinter dem !) und die Position des x - die Position des ! - 1 (sonst hätten wir das x mit ausgeschnitten)
iPos = Instr(pImageFile, "%", iLastPos) 'Wir suchen die Position des Prozentzeichens, aber angefangen
'bei dem x
pHeight = Mid(pImageFile, iLastPos + 1, iPos - iLastPos - 1) 'und schneiden hier die Höhe aus.
End Function


Wie ihr sehen könnt hat diese Funktion sogar zwei Rückgabewerte: pWidth und pHeight
Logisch, sie gibt ja auch die Breite UND die Höhe zurück.

Und zu guter letzt die Funktion, die uns die Frameanzahl und die Animationszeit ausgiebt.
Dazu brauche ich eigentlich nichts mehr zu sagen, da sie genauso wie die vorhergegangene Funktion funktioniert, nur dass nach anderen Anhaltspunkten gesucht wird:

BlitzMax: [AUSKLAPPEN]
Function GetFramesFromImage:String(pImageFile:String, pFrames:String Var, pTimer:String Var)
Local iPos:Int, iLastPos:Int
iPos = Instr(pImageFile, "%")
iLastPos = Instr(pImageFile, "&", iPos)
pFrames = Mid(pImageFile, iPos + 1, iLastPos - iPos - 1)
ipos = Instr(pImageFile, ".", ilastpos)
ptimer = Mid(pimagefile, ilastpos + 1, ipos - ilastpos - 1)
End Function


Jetzt haben wir die Funktionen die und die Dateiinfos aus den Dateinamen geben.
Nun müssen wir alles zusammen speichern.

Dazu machen wir in unsere Funktion vom Anfang weiter, also in Zitat:
GetObjectImages()


Zuersteinmal legen wir eine neue INI Datei an.
Das geht so (dabei bitte darauf achten, dass alle angegebenen Ordner im Pfad vorhanden sind. Gegebenenfalls bitte abändern):

BlitzMax: [AUSKLAPPEN]
	Local sIniPath:String = "Bin\Objects.ini" 'Der Pfad
Local sIniFile:TStream = WriteStream(sinipath)'Eine neue Datei erstellen und zum schreiben öffnen


Dann müssen wir uns erstmal im Klaren sein wie unsere INI datei aussehen soll.
Ich habe folgende Anordnung gewählt:

Zitat:
[Global]
Anzahl = (Variable)
Namen der Bilder....

[Bildname1]
Pfad = (Pfad)
Breite = (Breite)
Höhe = (Höhe)
Frames = (Frames)
Zeit = (Time)

[Bildname2]
.
.
.


Ich denke, der Aufbau sollte damit klar sein.
Jetzt schreiben wir erstmal unsere Sektion, also das in den Eckigen Klammern und die Anzahl, sowie alle Bildnamen in die INI Datei.
Also:

BlitzMax: [AUSKLAPPEN]
WriteLine(sIniFile, "[Global]") 'Die Sektion schreiben
WriteLine(sIniFile, "Amount = " + iAmount) 'Anzahl aller Bilder
For Local i:Int = 0 Until iAmount 'Für jedes Bild im Array
Local sName:String
GetNameFromImage(sAllFiles[i], sName) 'Wir führen eine unserer Funktionen von vorher aus
'Hier sieht man genau: Wir übergeben die Variable sName als Rückgabeparameter
'sName entspricht jetzt dem Namen des Bildes, also alles vor dem !
WriteLine(sIniFile, sName) 'Und den Namen schreiben wir jetzt in die Datei
Next


Und jetzt folgt der ganze Rest der in die Datei gehört.
Das sieht dann so aus:

BlitzMax: [AUSKLAPPEN]
	For Local i:Int = 0 Until iAmount 'Für alle Bilder im Array
Local sImageWidth:String, sImageHeight:String, sFrames:String, sName:String, sTimer:String 'Benötigte Variablen
GetSizeFromImage(sAllFiles[i], sImageWidth, sImageHeight) 'Die Größe des momentanen BIldes
GetFramesFromImage(sAllFiles[i], sFrames, sTimer) 'Die Frames und Animationszeit
GetNameFromImage(sAllFiles[i], sName) 'Der Name
WriteLine(sIniFile, "") 'Zuerst immer zwei Zeilen PLatz lassen
WriteLine(sIniFile, "") 'Die zweite freie Zeile
WriteLine(sIniFile, "[" + sName + "]") 'EIne neue Sektion, die wie das Bild heißt
WriteLine(sIniFile, "Path = " + "Data\GFX\Surrounding\" + sAllFiles[i]) 'Der Pfad des Bildes (mit vollem Bildnamen)
'Achtung: Gegebenenfalls muss auch hier die Ordnerangabe geändert werden.
WriteLine(sIniFile, "Width = " + sImageWidth) 'Breite desBildes speichern
WriteLine(sIniFile, "Height = " + sImageHeight) 'Höhe speichern
WriteLine(sIniFile, "Frames = " + sFrames) 'Frames speichern
WriteLine(sIniFile, "Timer = " + sTimer) 'Animationszeit speichern
Next


Und zum schluss schließen wir die Datei wieder:

BlitzMax: [AUSKLAPPEN]
	CloseStream(sIniFile)


Unsere Funktion sieht jetzt folgendermaßen aus:

BlitzMax: [AUSKLAPPEN]
Function GetObjectImages()
Local sPath:String = "Data\GFX\Surrounding\"
Local iDir:Int = ReadDir(spath)
Local sFile:String

Local sAllFiles:String[]
Local iAmount:Int = 0

Repeat
sFile = NextFile(iDir)
If FileType(sPath + sFile) = 1 Then
If Instr(sFile, "!") Then
If Instr(sFile, "%") Then
If Instr(sFile, ".png") Then
iAmount:+1
EndIf
EndIf
End If
EndIf
Until sFile = ""
CloseDir(iDir)
sAllFiles = New String[iAmount]
iAmount = 0
iDir = ReadDir(sPath)
Repeat
sFile = NextFile(iDir)
If FileType(sPath + sFile) = 1 Then
If Instr(sFile, "!") Then
If Instr(sFile, "%") Then
If Instr(sFile, "&") Then
sAllFiles[iAmount] = sFile
iAmount:+1
EndIf
End If
End If
EndIf
Until sFile = ""
CloseDir(iDir)

Local sIniPath:String = "Bin\Objects.ini"
Local sIniFile:TStream = WriteStream(sinipath)
WriteLine(sIniFile, "[Global]")
WriteLine(sIniFile, "Amount = " + iAmount)
For Local i:Int = 0 Until iAmount
Local sName:String
GetNameFromImage(sAllFiles[i], sName)
WriteLine(sIniFile, sName)
Next

For Local i:Int = 0 Until iAmount
Local sImageWidth:String, sImageHeight:String, sFrames:String, sName:String, sTimer:String
GetSizeFromImage(sAllFiles[i], sImageWidth, sImageHeight)
GetFramesFromImage(sAllFiles[i], sFrames, sTimer)
GetNameFromImage(sAllFiles[i], sName)
WriteLine(sIniFile, "")
WriteLine(sIniFile, "")
WriteLine(sIniFile, "[" + sName + "]")
WriteLine(sIniFile, "Path = " + "Data\GFX\Surrounding\" + sAllFiles[i])
WriteLine(sIniFile, "Width = " + sImageWidth)
WriteLine(sIniFile, "Height = " + sImageHeight)
WriteLine(sIniFile, "Frames = " + sFrames)
WriteLine(sIniFile, "Timer = " + sTimer)
Next
CloseStream(sIniFile)
End Function


Sooo...
Jetzt wieder eine Zusammenfassung:
Wir haben die Bilder die wir benötigen entsprechend genannt.
Wir haben unseren Verwaltungstype.
Wir haben Funktionen die uns ganz spezielle Infos aus dem Dateinamen herausfiltern.
Und wir haben Die Funktion, die uns alles in einer INI Datei speichert.

In Teil 3 werden wir dann noch eine Funktion schreiben, die uns die INI Datei ausliest und entsprechende Bilder in den Speicher läd.

Lg, M0rgenstern
  • Zuletzt bearbeitet von M0rgenstern am Sa, Sep 04, 2010 12:24, insgesamt 2-mal bearbeitet

M0rgenstern

Betreff: Teil 3

BeitragDo, Sep 02, 2010 22:02
Antworten mit Zitat
Benutzer-Profile anzeigen
Hallo an alle.
Heute kommen wir endgültig zum 3. und letzten Teil des Tutorials.

3. Teil: Die INI Datei auslesen und entsprechend die Bilder laden

Zuerst benötigen wir einen INI Parser.
Meinen habe ich mir nach dem Lesen von Midimasters Tutorial zusammengebastelt und stelle ihn jetzt hier einfach als festen Code ein.
Wer mehr über INI Parser wissen will, den verweise ich auf Midimasters Tutorial: https://www.blitzforum.de/foru...hp?t=33651

Hier der Parser:

BlitzMax: [AUSKLAPPEN]


'Gibt den Wert einer Zeile zurück (Wert, der rechts neben dem '=' in der Datei steht)
Function GetValue:String(sLine:String)

If Instr(sLine, "=") > 0 Then

Return Right(sLine, Len(sLine) - Instr(sLine, "="))

Else

Return ""

EndIf

End Function

'Gibt den linken Teil einer Zeile wieder, also die Bezeichnung
Function GetLeftPart:String(sLine:String)

If Instr(sLine, "=") > 0 Then

Return Left(sLine, Instr(sLine, "=") - 1)

Else

Return ""

EndIf

End Function

'Sucht nach einem bestimmten Suchwort in der Datei und gibt den zugehörigen Wert zurück
Function SearchValue:String(sSearchTerm:String, fileINI:TStream, sFile:String)

Local sValue:String, sLine:String

While Not Eof(fileINI)

sLine = ReadLine(fileINI)
If Lower(Trim(GetLeftPart(sLine))) = Lower(sSearchTerm) Then

Return GetValue(sLine)

EndIf
If Left(sLine, 1) = "[" Then RuntimeError("Wert '" + sSearchTerm + "' konnte in dieser Sektion nicht gefunden werden")

Wend
RuntimeError("Wert '" + sSearchTerm + "' in '" + sFile + "' konnte nicht gefunden werden")

End Function

'Sucht nach einem bestimmten Suchwort in der Datei unter einer bestimmten Sektion
Function SearchInFile:String(sSection:String, sSearchTerm:String, sFile:String)

Local sValue:String, sLine:String, fileINI:TStream

sSection = "[" + sSection + "]"

If FileType(sFile) = 1 Then

fileINI = ReadFile(sFile)

While Not Eof(fileINI)

sLine = ReadLine(fileINI)
If Lower(sLine) = Lower(sSection) Then
sValue = SearchValue(sSearchTerm, fileINI, sFile)
CloseFile fileINI
Return Trim(sValue:String)
EndIf

Wend
RuntimeError("Sektion '" + sSection + "' in '" + sFile + "' konnte nicht gefunden werden")

Else

RuntimeError "Datei '" + sFile + "' existiert nicht."

EndIf

End Function

'Überprüft ob die Datei vorhanden ist.
Function FileExists:Int(sFile:String)

If FileType(sFile) = 1 Then
Return True
EndIf

End Function


So, weiter gehts.
Wir brauchen eine Funktion die unsere INI Datei automatisch ausließt.
Hört sich vielleicht ein wenig komplizierter an als es ist.
Die komplette Funktion sieht so aus:

BlitzMax: [AUSKLAPPEN]
Function MakeImagesFromIni()
Local iAmount:Int, sAllFiles:String[] 'Wieder unser Array und die Anzahl
Local sinipath:String = "Bin\Objects.ini" 'Unser Pfad in dem die INI steckt
Local sIniFile:TStream = ReadStream(sinipath) 'Wie öffnen die INIDatei zum lesen

iAmount = Int(SearchInFile("Global", "Amount", sinipath)) 'SearchInFile ist eine Funktion aus dem Parser
'Diese sucht in einer bestimmten Sektion (und nur in dieser) nach einem bestimmten Suchwort und gibt den Wert
'rechts vom Gleichheitszeichen aus
'Achtung: Die Sektion wird zwar [Global] beschrieben, die Funktion benötigt aber nur des Wort innerhalb
'der Klammern
'Der zweite Parameter ist das Suchtwort, das links vom = steht und der dritte Parameter ist der Pfad der Datei
'Das Int() in dem die Funktion steckt, bedeutet, dass der string, den die FUnktion liefert, in eine Zahl umgewandelt wird

sAllFiles = New String[iAmount]'Wir vergrößern unser Array auf den ausgelesenen Wert

ReadLine(sIniFile) 'Die ersten beiden Zeilen der Datei sind jetzt unwichtig, also lesen wir sie aus ohne
ReadLine(sIniFile) 'sie zu speichern. Damit kommen wir zur dritten Zeile

For Local i:Int = 0 Until iAmount 'Wir füllen das Array...
sAllFiles[i] = ReadLine(sIniFile) '...mit den Bildnamen (ihr erinnert euch: Nur das was vor dem ! stand
Next

For Local i:Int = 0 Until iAmount 'Und hier lesen wir für jedes Bild die INfos ein
Local sImagePath:String, iWidth:Int, iHeight:Int, iFrames:Int, iTimer:Int 'Unsere Variablen

'ihr erinnert euch: Die Sektionen tragen alle den Namen des Bildes
'sAllFiles[i] ist somit immer die Sektionsangabe

sImagePath = SearchInFile(sAllFiles[i], "Path", sinipath) 'Der Pfad wird ausgelesen
iWidth = Int(SearchInFile(sAllFiles[i], "Width", sinipath)) 'Die Breite wird ausgelesen
iHeight = Int(SearchInFile(sAllFiles[i], "Height", sinipath)) 'Die Höhe auslesen
iFrames = Int(SearchInFile(sAllFiles[i], "Frames", sinipath)) 'Die Framezahl auslesen
iTimer = Int(searchinfile(sAllFiles[i], "Timer", sinipath)) 'Die Animationszeit auslesen

If FileType(sImagePath) = 1 Then 'Jetzt überprüfen wird, ob das Bild auch wirklich noch existiert
TMyMainIMage.Create(sImagePath, iFrames, iWidth, iHeight, iTimer, sAllFiles[i]) 'Wenn ja
'dann erstellen wir eine neue Typeinstanz
'Hier kommt also endlich der Type ins Spiel
EndIf
Next
End Function


So.
Das wars auch schon.
Ich hab natürlich noch ein Anwendungsbeispiel:

BlitzMax: [AUSKLAPPEN]
Import brl.timer
Import brl.random
Import brl.PNGLoader
SuperStrict
SeedRnd MilliSecs()
AutoMidHandle(1)
Include "ImageTypes.bmx"
Include "INIParser.bmx"

Graphics 1024, 768

SetBlend(ALPHABLEND)

GetObjectImages() 'Wir rufen unsere Funktionen auf
MakeImagesFromIni()

'Erstellen zwei Arrays die die Bildpositionen angeben sollen:
Local iXPositions:Int[TMyMainIMage.tlAllMyFirstImages.count()], iYPositions:Int[TMyMainIMage.tlAllMyFirstImages.count()]

'Eine Schleife, die uns die Arrays füllt. Wir gehen für jedes Bild von 64*64 Pixel pro Frame aus
'Wenn wir also 16 Bilder in einer Reihe haben, rutschen wir zur nächsten Reihe.
Local ix:Int = 1, iy:Int = 1
For Local i:Int = 0 Until TSurroundingObject.iMaxObjects
iXPositions[i] = ix * 64
iYPositions[i] = iy * 64
ix:+1
If ix >= 16 Then
iy:+1
ix = 0
End If
Next

While Not (KeyHit(KEY_ESCAPE) Or AppTerminate())
WaitTimer(FTimer)

Cls

Local i:Int = 0
For Local Local Image:TMyMainImage = EachIn TMyMainImage.tlAllMyFirstImages
If image.iMaxFrames > 0 Then
image.Animate()
EndIf
DrawImage(image.tiimage, iXPositions[i], iYPositions[i], image.iframe)
i:+1
Next
Flip 0
Wend



Und nochmal: Ihr müsst natürlich im Code die richtigen Ordner angeben, sonst funktioniert das nicht.

So.
Ich hoffe dass das Tutorial irgendjemandem hilft.

Und bitte: Wenn ihrs lest, dann gebt mit hier in dem Thread bitte ne Rückmeldung. Was ihr gut fandet, oder was scheiße war. Würd mir echt viel helfen.

Lg, M0rgenstern

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG FAQs und Tutorials

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group