Individuelles Frametiming (Speicherfrage)

Übersicht BlitzMax, BlitzMax NG Allgemein

Neue Antwort erstellen

Kernle 32DLL

Betreff: Individuelles Frametiming (Speicherfrage)

BeitragSo, Dez 06, 2009 15:20
Antworten mit Zitat
Benutzer-Profile anzeigen
Hiho, für mein Spiel habe ich einen sog. AnimationController gebastelt, der einen FPS unabhängigen Zählalgo liefert. Das Problem dabei ist, das angeommen wird das jeder "Frame" die gleiche länge hat. In anbetracht dessen was ich noch umsetzen will, möchte ich jetzt versuchen mein System auf Individuelles Frametiming umzustellen (so wie es z.b. in Gif Animationen der Fall ist).

Das Problem ist dabei aber nicht wie ich das anstelle, sondern wie ich meine Daten dazu speicher. Mir sind zwei Möglichkeiten eingefallen, und ich würde mal gerne eure Meinung dazu hören.

Dazu habe ich einen kleinen Test geschrieben, der imo gut zeigt wie sich die Systeme unterschieden, genauere Erklärung dazu unten...

BlitzMax: [AUSKLAPPEN]
SuperStrict

'---------------------------

'Creating Individual Framedata for later use

Local FrameBasis:Object[7]

FrameBasis[0] = New Int[2]
Int[](FrameBasis[0])[0] = 10 'Frame duration in ms
Int[](FrameBasis[0])[1] = 0 'Actual frame to display (e.g. frame of an image)

FrameBasis[1] = New Int[2]
Int[](FrameBasis[1])[0] = 20
Int[](FrameBasis[1])[1] = 1

FrameBasis[2] = New Int[2]
Int[](FrameBasis[2])[0] = 13
Int[](FrameBasis[2])[1] = 2

FrameBasis[3] = New Int[2]
Int[](FrameBasis[3])[0] = 60
Int[](FrameBasis[3])[1] = 1

FrameBasis[4] = New Int[2]
Int[](FrameBasis[4])[0] = 60
Int[](FrameBasis[4])[1] = 3

FrameBasis[5] = New Int[2]
Int[](FrameBasis[5])[0] = 4
Int[](FrameBasis[5])[1] = 0

FrameBasis[6] = New Int[2]
Int[](FrameBasis[6])[0] = 100
Int[](FrameBasis[6])[1] = 4

Global AbsoulteFrameTime:Int

For Local I:Int = 0 To FrameBasis.Dimensions()[0]-1
AbsoulteFrameTime:+Int[](FrameBasis[I])[0]
Next

Const TestCount:Int = 1000000

Print "--------------------"
Print "Frames: " + FrameBasis.Dimensions()[0]
Print "Animationtime: " + AbsoulteFrameTime + "ms"
Print "TestsToBeDone: " + TestCount
Print "--------------------"

'---------------------------

'Frame Test 1 - Saving every millisecs as an index of an array

Global FrameTest1:Int[AbsoulteFrameTime]

Local Counter:Int = 0
For Local I:Int = 0 To FrameBasis.Dimensions()[0]-1
For Local J:Int = 0 To Int[](FrameBasis[I])[0]-1
FrameTest1[J+Counter] = Int[](FrameBasis[I])[1]
Next
Counter:+Int[](FrameBasis[I])[0]
Next

'---------------------------

'Frame Test 2 - Saving relative data (Frame goes from ms(n) to ms(m))

Global FrameTest2:Object[FrameBasis.Dimensions()[0]]

Counter = 0
For Local I:Int = 0 To FrameBasis.Dimensions()[0]-1
Local Frame:Int[3]

Frame[0] = Counter
Frame[1] = Counter+Int[](FrameBasis[I])[0]-1
Frame[2] = Int[](FrameBasis[I])[1]

FrameTest2[I] = Frame

Counter:+Int[](FrameBasis[I])[0]
Next

'---------------------------

Print "calculating..."

Global FrameTesting:Int[2]

For Local I:Int = 1 To TestCount
Local GetTime:Int = I Mod AbsoulteFrameTime

Local StartTime:Int = MilliSecs()
GetFrame_Test1(GetTime)
FrameTesting[0]:+MilliSecs()-StartTime

StartTime = MilliSecs()
GetFrame_Test2(GetTime)
FrameTesting[1]:+MilliSecs()-StartTime
Next

Print "--------------------"

'---------------------------

Print "Results:"
Print ""

Print "FrameTest1: "
Print "~tMemory ussage: " + Float(SizeOf(FrameTest1)) + " Byte"
Print "~t " + Float(SizeOf(FrameTest1))/1024.0 + " KByte"
Print "~t " + Float(SizeOf(FrameTest1))/1024.0/1024.0 + " MByte"
Print "~tTiming: " + FrameTesting[0]

Print ""

Print "FrameTest2: "
Print "~tMemory ussage: " + Float(SizeOf(FrameTest2)) + " Byte"
Print "~t " + Float(SizeOf(FrameTest2))/1024.0 + " KByte"
Print "~t " + Float(SizeOf(FrameTest2))/1024.0/1024.0 + " MByte"
Print "~tTiming: " + FrameTesting[1]

Print "--------------------"

'---------------------------

Function GetFrame_Test1:Int(Microtime:Int)
If Microtime < 0 Or Microtime > FrameTest1.Dimensions()[0]-1 Then Return -1
Return FrameTest1[Microtime]
End Function

Function GetFrame_Test2:Int(Microtime:Int)
For Local I:Int = 0 To FrameTest2.Dimensions()[0]-1
Local Frame:Int[] = Int[](FrameTest2[I])
If Microtime >= Frame[0] And Microtime <= Frame[1] Then Return Frame[2]
Next
Return -1
End Function


Mein Test liefert mir dabei folgende Ergebnisse:

Code: [AUSKLAPPEN]
--------------------
Frames: 7
Animationtime: 267ms
TestsToBeDone: 1000000
--------------------
calculating...
--------------------
Results:

FrameTest1:
   Memory ussage: 1068.00000 Byte
                  1.04296875 KByte
                  0.00101852417 MByte
   Timing: 232

FrameTest2:
   Memory ussage: 28.0000000 Byte
                  0.0273437500 KByte
                  2.67028809e-005 MByte
   Timing: 251
--------------------


Also meine beiden Systeme funktionieren wiefolgt:

Das erste System erstellt einfach ein Array mit so viele Einträgen wie die Animation komplett in ms lang ist (siehe dazu AbsoulteFrameTime im Code, ganz oben). Das hat den Vorteil das man zur bestimmung des aktuellen Frame nur auf den Index der übergebenen ms time gehen muss, der Zugriff also quasi sofort ist. Leider wird der Speicherverbrauch sehr schnell übergroß.

System 2 speichert dagegen relative Daten, d.h. für jeden Frame wird ein Indexeintrag erstellt, in dem gespeichert wird von wo bis wo in ms er "aktiv" ist. Z.b. von 0 bis 9 (d.h. der Frame ist 10 ms lang). Vorteil von System 2 ist definitiv der minimale Speicherverbrauch.

Wie man am obrigen Test sieht ist der Geschwindigkeitsvorteil von System 1 zu minimal, der Speicherverbrauch aber schon bei geringen 267ms ca 1kb beträgt (ihr könnt euch vorstellen wie das bei besonders langen Animationen aussieht die 1 Minute gehen).

Wie gesagt, ich würde einfach mal gerne hören was ihr so davon haltet. Oder ob euch selber noch was einfällt. So wie es aussieht würde ich System 2 wählen.

So long,
Kernle
Mein PC: "Bluelight" - Xtreme Gamer PC [Video]
Meine Projekte: Cube-Wars 2010 [Worklog]
Anerkennungen: 1. Platz BCC #7 , 1. Platz BCC #22 , 3. Platz BAC #89
Ich war dabei: NRW Treff III, IV ; Frankfurter BB Treffen 2009

Holzchopf

Meisterpacker

BeitragSo, Dez 06, 2009 16:33
Antworten mit Zitat
Benutzer-Profile anzeigen
Ich hab kurz eine alte BB-Version, wie ich sie mal gebraucht habe, nach BMax umgeschrieben. Kurze Erklärung:
Der aktuelle Frame wird erst erhöht, wenn die Spieldauer diejenige eines Frames überschreitet. Die While-Schleife sorgt dafür, dass, wenn die Spieldauer grösser ist als eine Framedauer, das Frame ganz übersprungen wird, dadurch bleiben Animationen immer schön Synchron. Ob diese Variante optimal ist, wäre noch zu prüfen, da bei jedem Hauptschleifenaufruf geprüft wird, ob das Frame gewechselt werden muss - aber ich stell's mir sowieso schwierig vor, so ein System zu realisieren, bei dem die Zeit direkt einen Index angibt...

Weil ich keine animierten Bildchen zur Verfügung hab', gibt's halt einen fliessenden Graphen:
BlitzMax: [AUSKLAPPEN]
SuperStrict


Type TAnimationFrame
' Index des anzuzeigenden Frames
Field Frame:Int
' Anzeigedauer
Field Duration:Int

' Einzelbild erstellen
Function Create:TAnimationFrame( pFrame:Int, pDuration:Int )
Local Frame:TAnimationFrame = New TAnimationFrame
Frame.Duration = pDuration
Frame.Frame = pFrame
Return Frame
End Function
End Type
Type TAnimation
' Alle Einzelbilder
Field Frames:TAnimationFrame[]
' Aktueller Frame
Field _Frame:Int
' Aktuelle Laufzeit im aktuellen Frame
Field _PlayTime:Int

' Frame hinzufügen
Method Add( pFrame:Int, pDuration:Int )
Local index:Int = Self.Frames.length
Self.Frames = Self.Frames[..index+1]
Self.Frames[index] = TAnimationFrame.Create( pFrame, pDuration )
End Method

' Animation abspielen (um pSteps ms)
Method Play( pSteps:Int )
Self._PlayTime :+ pSteps
' Wenn nötig, Frame wechseln
While Self._PlayTime => Self.Frames[ Self._Frame ].Duration
Self._PlayTime :- Self.Frames[ Self._Frame ].Duration
Self._Frame = (Self._Frame +1) Mod Self.Frames.Length
Wend
End Method

' Aktuellen Frame zurückgeben
Method Frame:Int()
Return Self.Frames[ Self._Frame ].Frame
End Method
End Type

' Ein paar Beispielanimationen
' 5 Frames a 200ms
Local Animation1:TAnimation = New TAnimation
Animation1.Add( 0, 200 )
Animation1.Add( 2, 200 )
Animation1.Add( 4, 200 )
Animation1.Add( 6, 200 )
Animation1.Add( 8, 200 )
' Noch was wildes
Local Animation2:TAnimation = New TAnimation
Animation2.Add( 1, 150 )
Animation2.Add( 3, 100 )
Animation2.Add( 5, 50 )
Animation2.Add( 7, 30 )
Animation2.Add( 9, 10 )
Animation2.Add( 11, 5 )
Animation2.Add( 13, 5 )
Animation2.Add( 15, 70 )
Animation2.Add( 17, 40 )
Animation2.Add( 19, 40 )


SetGraphicsDriver( GLMax2DDriver() )
Graphics 400,300

Local Graph:TImage = CreateImage(399,300,1,DYNAMICIMAGE)
Local MSec:Int = MilliSecs(), MSecPF:Int, MSecLF:Int
While Not (KeyDown(KEY_ESCAPE) Or AppTerminate())
MSecLF = MSec
MSec = MilliSecs()
MSecPF = MSec -MSecLF

Animation1.Play(MSecPF)
Animation2.Play(MSecPF)

Cls

DrawImage Graph, 0, 0
DrawLine 399,149,399, 149 -Animation1.Frame() *5
DrawLine 399,299,399, 299 -Animation2.Frame() *5
GrabImage( Graph, 1, 0 )

Flip
Wend


So wie ich das sehe, geht dein System 2 jeweils solange durch die Liste der Frames, bis es das richtige gefunden hat. Evtl zieht das bei vielen langen Animationen dann doch zu sehr an der Geschwindigkeit...

mfG
Erledige alles Schritt um Schritt - erledige alles. - Holzchopf
CC BYBinaryBorn - Yogurt ♫ (31.10.2018)
Im Kopf da knackt's und knistert's sturm - 's ist kein Gedanke, nur ein Wurm

Kernle 32DLL

BeitragSo, Dez 06, 2009 19:29
Antworten mit Zitat
Benutzer-Profile anzeigen
Also deine Methode ist schon ganz gut Holzchopf, werde sie mir aber nochmal im detail anschauen müssen.

Ich hatte indess nochmal eine Idee wie man das 2. System verbessern könnte. Statt die Zeit "Bereiche" zu speichern, speichere ich nur wie in den Ursprungsdaten (im Code ganz oben) nur die Dauer des Frames, und gehe davon aus dass das nächste Glied in der Kette (ob nun Array, List oder was auch immer) der nächste Frame ist.

Wie ich jetzt vom einen auf den andere Frame komme stelle ich mir recht simpel vor. Ich habe zwei Variablen, eine die auf den aktuellen Frame zeigt, und eine, die quasi die "überschuss Zeit" angibt. Dazu gleich mehr.

Gehen wir davon aus ich habe 2 Frames mit 100 ms die hintereinander kommen, und gehen wir davon aus das wir zur Zeit 0 starten, und seit dem letzten Update 104 ms vergangen sind. Dann passiert folgendes:

Arrow Prüfung ob vergangene Zeit + Überschuss Zeit > Framezeit (100 ms) sind
Arrow Wenn nein, dann Überschusszeit = Alte ÜberschussZeit + Vergangene Zeit
Arrow Wenn ja, dann gehe zum nächsten Frame, und setze Überschusszeit = AlteÜberschussZeit + Vergangene Zeit - Zeit des vorherigen Frames
Arrow Prüfe erneut (diesmal mit Vergangene Zeit = 0), d.h. gehe zum Anfang der Prüffolge

d.h. die ÜberschussZeit beträge in dem Beispiel dann 4ms, und wir wären beim 2. Frame.

Ich denke das System ist recht praktikabel - auch bei vielen Frames. Die einzige schwäche sehe ich wenn es sehr viele kurze Frames gibt, im worstcase sogar 1ms Frames. Dann muss sehr viel gesprungen werden, aber ich glaube das ist leider kaum zu vermeiden.

Was haltet ihr davon? (Ich hoffe meine Erklärung war verständlich irgendwie)
Mein PC: "Bluelight" - Xtreme Gamer PC [Video]
Meine Projekte: Cube-Wars 2010 [Worklog]
Anerkennungen: 1. Platz BCC #7 , 1. Platz BCC #22 , 3. Platz BAC #89
Ich war dabei: NRW Treff III, IV ; Frankfurter BB Treffen 2009

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG Allgemein

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group