Hi,
Vorwort:
Da mittlerweile die GDI+ Projekte/Programme immer zahlreicher werden,
sowie mich die Lust gepackt hat dieses Wissen zu teilen, in der Hoffnung
auf weiterhin gute Skripte.
Ebenfalls zu notieren:
Ich habe dieses Thema, bzw. Technik einfach nur so getauft.
Sie wird weltweit als Render Basis für jedwege Engine verwendet (es gibt auch andere Arten, aber dies ist die Geläufigste).
Hauptteil:
Programmiert man ein simples 2D Spiel, in welchem sich einfach nur eine Figur/Sprite
bewegt.
So würde man sporadisch gesehen folgenden AutoIt-Code schreiben:
Spoiler anzeigen
Global Const $g_fStepCount = 5.0 ;soviele Pixel soll sich die Figur bewegen
Global $g_fPlayerPositionX = 150.0, ;X-Position der Spielfigur
$g_fPlayerPositionY = 150.0 ;Y-Position der Spielfigur
While Game_Running
Render_Game() ;Rendert/Zeichnet das Spiel
Sleep(xyz) ;Lässt das Programm pausieren, um die Framerate zu "drücken", Resourcenverbrauch zu mindern.
WEnd
Func Render_Game()
$g_fPlayerPositionX += $g_fStepCount
$g_fPlayerPositionY += $g_fStepCount
Render_Player()
EndFunc
Obiger Code ist zwar nicht falsch, was mache man allerdings, wenn das Skript nur als decompile-resistente .exe
vorliegt, sprich der Code nicht mehr veränderbar ist, der Sleep()-Wert konstant ist.
Wozu Sorgen machen?
Nun schreibt man ein Programm für einen PC mit 2.0 GHz und alles klappt, stellt sich diese Frage nicht.
Sollte das Programm allerdings nun auf einen überschnellen PC mit 300.0 GHz ausgeführt werden,
so wird der Render_Game()-Code entsprechend schneller ausgeführt.
Aus "lächerlichen" 20 FPS können rasch 200 FPS werden.
Dies bedeutet nun, dass sich die Spielfigur pro Frame um 5 Pixel bewegt, der Code 10x so oft aufgerufen
wird. D.h. der Charakter bewegt sich nun statt 100 Pixel (=5*20) um 1000 Pixel (=5*20*10) pro Sekunde.
Ein Fehler welcher nicht auftreten soll und darf.
Wie umgehe ich dies?
In dem man einen Timer in die Schleife einbaut und nach jedem Render() Aufruf prüfe wieviel Sekunden!!!
vergangen sind.
Diesen Wert übergibt man dann dem nächsten Render() Aufruf.
Spoiler anzeigen
Global Const $g_fStepCount = 5.0 ;soviele Pixel soll sich die Figur bewegen
Global $g_fPlayerPositionX = 150.0, ;X-Position der Spielfigur
$g_fPlayerPositionY = 150.0 ;Y-Position der Spielfigur
Local $timer = 0
Local $fSecsPassed = 0.0
While Game_Running
$timer = TimerInit() ;Initialisiere den Timer
Render_Game($fSecsPassed) ;Rendert/Zeichnet das Spiel
$fSecsPassed = TimerDiff($timer) / 1000.0
WEnd
Func Render_Game($fTime)
$g_fPlayerPositionX += $g_fStepCount * $fTime
$g_fPlayerPositionY += $g_fStepCount * $fTime
Render_Player() ;Zeichnet die Spielfigur
EndFunc
Obiger Code führt dazu, dass die Spielfigur im 1. Frame um 0 Pixel bewegt wird und ab dem 2. Frame um
$g_fStepCount - Pixel pro Sekunde bewegt wird.
Wie nun der Teil mit dem Sleep() gelöst wird bleibt jedem selbst überlassen.
Und nun das Ganze mal mit einem Beispiel:
Spoiler anzeigen
Skript-Aufbau:
Spoiler anzeigen
Das Beispiel zeichnet einfach nur ein Rechteck, welches sich konstant um 35.0 Grad/Sekunde um seinen Mittelpunkt dreht.
1.) Das "Hauptprogramm" wird in einer "main()"-Funktion gehalten.
2.) Zum "Erstellen"/"Zerstören" werden "Create"/"Destroy"-Funktionen genutzt.
3.) Zum "Zeichnen" eine "Draw()"-Funktion.
4.) Zum "Bewegen" der Szene eine "Update()"-Funktion.
5.) Rückgabewerte in obigen Funktionen sind immer: 0 - Fehler. 1 - Erfolg; Bei Fehlschlag enthält @error nähere Informationen.
6.) Erweiterte Rückgabe, wird durch ByRef realisiert.
7.) Berechnung der Rotationsmatrix wird in der "Render"-Funktion erledigt.
8.) Berechnung der Anzahl der Grade, also um wieviel Grad sich das Rechteck beim nächsten "Render"-Aufruf rotieren soll, in der "Update"-Methode.
9.) Handles welche einen Wert von 0 haben, müssen/sollen nicht Dispose'ed() werden.
Skript:
Spoiler anzeigen
#cs ----------------------------------------------------------------------------
AutoIt Version: 3.3.6.1
Author: Dennis a.k.a Ealendil/CentuCore
Script Function:
Example for "flexible Framerate Management".
Copyright:
To nobody and all. At the same moment.
#ce ----------------------------------------------------------------------------
[/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]#include <GDIPlus.au3> ;um überhaupt zeichnen zu können
[/autoit] [autoit][/autoit] [autoit]Opt("MustDeclareVars", 1)
[/autoit] [autoit][/autoit] [autoit]If $CMDLine[0] Then
Local $TmpCMDLine[$CMDLine[0] - 1] ;erstellt ein 1-dimensionales Array mit sovielen "Spalten", wie es Command Line Parameter gibt.
For $i = 1 To $CMDLine[0]
$TmpCMDLine[$i - 1] = $CMDLine[$i] ;kopiere die Parameter von $CMDLine nach $TmpCMDLine
Next
main($CMDLine[0], $TmpCMDLine)
Else
main($CMDLine[0], Int(0))
EndIf
Exit(@error)
Func main($NumCMDParams, $CMDParams)
[/autoit] [autoit][/autoit] [autoit];Daten für das Hauptfenster
Local Const $GUIWidth = 500
Local Const $GUIHeight = 500
;Daten für das zu zeichnende Rechteck
Local Const $fRectWidth = 150.0
Local Const $fRectCenterX = $GUIWidth / 2.0
Local Const $fRectCenterY = $GUIHeight / 2.0
Local $hGUI, $hFrontbufferGraphics, $hBackbufferBitmap, $hBackbufferGraphics
Local $hTranslateMatrix = 0
;Starte GDI+
If Not _GDIPlus_Startup() Then Return SetError(1, 0, 0)
If Not Create("GDI+ Example by Ealendil", $GUIWidth, $GUIHeight, $hGUI, $hFrontbufferGraphics, $hBackbufferBitmap, $hBackbufferGraphics) Then
[/autoit] [autoit][/autoit] [autoit];Fehler beim erstellen der Szene
Destroy($hGUI, $hFrontbufferGraphics, $hBackbufferBitmap, $hBackbufferGraphics)
_GDIPlus_Shutdown()
Return SetError(-1, 0, 0)
EndIf
;berechne Offsets um das Rechteck korrekt zu verschieben
;Der Mittelpunkt des Rechtecks entspricht dem Mittelpunkt des Fensters
Local Const $fTranslateOffsetX = $GUIWidth / 2.0
Local Const $fTranslateOffsetY = $GUIHeight / 2.0
Local Const $fNumDegPerSec = 35.0
Local $fDegToRotate = 0.0
Local $fTime = 0.0
Local $timer = TimerInit()
While GUIGetMsg() <> (-3)
;übermale/lösche Szene mit Weiß
_GDIPlus_GraphicsClear($hBackbufferGraphics, 0xFFffFFff)
;zeichne Szene in den Backbuffer
Render($fTime, $hBackbufferGraphics, $fTranslateOffsetX, $fTranslateOffsetY, $fDegToRotate, $fNumDegPerSec, $fRectWidth)
;präsentiere die gezeichnete Szene
_GDIPlus_GraphicsDrawImage($hFrontbufferGraphics, $hBackbufferBitmap, 0, 0)
;setze vergangene Sekunden und initialisiere den Timer neu
$fTime = TimerDiff($timer) / 1000.0
$timer = TimerInit()
WEnd
Destroy($hGUI, $hFrontbufferGraphics, $hBackbufferBitmap, $hBackbufferGraphics)
Return SetError(0, 0, 1)
EndFunc
Func Render(Const $fSecsPassed, $hGraphics, Const $fTranslateOffsetX, Const $fTranslateOffsetY, ByRef $fDegToRotate, Const $fNumDegPerSec, Const $fRectWidth)
[/autoit] [autoit][/autoit] [autoit];erstelle Rotations/Translations Matrix
Local $l_hMatrix = _GDIPlus_MatrixCreate()
If Not $l_hMatrix Then Return SetError(1, 0, 0)
;berechne Anzahl an Grad, um welche das Rechteck in derzeitigen Frame rotiert werden soll
$fDegToRotate += $fNumDegPerSec * $fSecsPassed
_GDIPlus_MatrixTranslate($l_hMatrix, $fTranslateOffsetX, $fTranslateOffsetY)
_GDIPlus_MatrixRotate($l_hMatrix, $fDegToRotate)
;setze die berechnete Matrix und zeichne das Rechteck
_GDIPlus_GraphicsSetTransform($hGraphics, $l_hMatrix)
_GDIPlus_GraphicsDrawRect($hGraphics, -($fRectWidth / 2.0), -($fRectWidth / 2.0), $fRectWidth, $fRectWidth)
;lösche die Matrix
_GDIPlus_MatrixDispose($l_hMatrix)
Return SetError(0, 0, 1)
EndFunc
; #FUNCTION# ====================================================================================================================
; Name...........: Create
; Description ...: Creates the given resources
; Syntax.........: Create(Const $GUIName, Const $GUIWidth, Const $GUIHeight, ByRef $HGUIOut, ByRef $hFrontbufferOut, ByRef $hGUIBitmapOut, ByRef $hBackbufferOut)
; Parameters ....: $GUIName - The window's name.
; $GUIWidth - The width for the window.
; $GUIHeight - The height for the window.
; $hGUIOut - A handle to the created window.
; $hFrontbufferOut - A handle to a Graphics object.
; $hGUIBitmapOut - A handle to a Bitmap object.
; $hBackbufferOut - A handle to a Graphics object for "$hGUIBitmapOut"
; Return values .: Success - 1
; Failure - 0
; @error - 1 = Couldn't create the window-
; - 2 = Couldn't create the frontbuffer Graphics object.
; - 3 = Couldn't create the backbuffer Bitmap object.
; - 4 = Couldn't create the backbuffer Graphics object.
; Author ........: Dennis (Ealendil/CentuCore)
; ===============================================================================================================================
Func Create(Const $GUIName, Const $GUIWidth, Const $GUIHeight, ByRef $hGUIOut, ByRef $hFrontbufferGraphicsOut, ByRef $hBackbufferBitmapOut, ByRef $hBackbufferGraphicsOut)
$hGUIOut = 0
$hFrontbufferGraphicsOut = 0
$hBackbufferBitmapOut = 0
$hBackbufferGraphicsOut = 0
;erstelle das Hauptfenster
$hGUIOut = GUICreate($GUIName, $GUIWidth, $GUIHeight)
If Not $hGUIOut Then Return SetError(1, 0, 0)
GUISetState(@SW_SHOW, $hGUIOut)
;erstelle Frontbuffer Graphics Objekt
$hFrontbufferGraphicsOut = _GDIPlus_GraphicsCreateFromHWND($hGUIOut)
If Not $hFrontbufferGraphicsOut Then Return SetError(2, 0, 0)
;erstelle Backbuffer Bitmap Objekt
$hBackbufferBitmapOut = _GDIPlus_BitmapCreateFromGraphics($GUIWidth, $GUIHeight, $hFrontbufferGraphicsOut)
If Not $hBackbufferBitmapOut Then Return SetError(3, 0, 0)
;erstelle Backbuffer Graphics Objekt
$hBackbufferGraphicsOut = _GDIPlus_ImageGetGraphicsContext($hBackbufferBitmapOut)
If Not $hBackbufferGraphicsOut Then Return SetError(4, 0, 0)
Return SetError(0, 0, 1)
EndFunc
; #FUNCTION# ====================================================================================================================
; Name...........: Destroy
; Description ...: Destroys the given resources.
; Syntax.........: Destroy(ByRef $hGUI, ByRef $hFrontbufferGraphics, ByRef $hGUIBitmap, ByRef $hBackbufferGraphics)
; Parameters ....: $hGUI - Handle to a window
; $hFrontbufferGraphics - A handle to a Graphics object.
; $hGUIBitmap - A handle to a Bitmap object.
; $hBackbufferGraphics - A handle to a Graphics object.
; Return values .: Success - 1
; Failure - None!
; Author ........: Dennis (Ealendil/CentuCore)
; Remarks .......: Each parameter will be set to zero after deleting.
; ===============================================================================================================================
Func Destroy(ByRef $hGUI, ByRef $hFrontbufferGraphics, ByRef $hBackbufferBitmap, ByRef $hBackbufferGraphics)
;Zerstöre Backbuffer's Graphics Objekt
If $hBackbufferGraphics Then
_GDIPlus_GraphicsDispose($hBackbufferGraphics)
$hBackbufferGraphics = 0
EndIf
;Zerstöre Backbuffer Bitmap Objekt - enthält die Daten des Backbuffers
If $hBackbufferBitmap Then
_GDIPlus_BitmapDispose($hBackbufferBitmap)
$hBackbufferBitmap = 0
EndIf
;Zerstöre das Frontbuffer Graphics Objekt
If $hFrontbufferGraphics Then
_GDIPlus_GraphicsDispose($hFrontbufferGraphics)
$hFrontbufferGraphics = 0
EndIf
;Zerstöre das Fenster
If $hGUI Then
GUIDelete($hGUI)
$hGUI = 0
EndIf
Return 1
EndFunc
Nachwort:
Diese Technik soll helfen, dass Skripte auf jedem PC gleich "flüssig" laufen,
also jede Figur sich gleich weit pro Tastendruck oder ähnliches bewegt.
Ich hoffe ich habe es verständlich erklärt.
Sollten Fragen auftreten, scheut euch nicht diese zu stellen.
LG,
Dennis a.k.a Ealendil
EDIT: Beispiel hinzugefügt!