PandaRunner Reworked - Ein Autoit Game

  • Hallo,

    vielleicht erinnert sich der ein oder andere ja noch an meinen Post zu Pandarunner;
    habe davon ende letztes Jahr eine komplett neue Version erstellt, besser als die Alte,
    jedoch nie veröffentlicht weswegen ich das hier jetzt mache. Bin mittlerweile bei
    der Spieleentwicklung komplett auf C++ umgestiegen (ist ja auch logisch ^^) deswegen
    wird es dafür keine Updates mehr geben.

    Nun zum Spiel:
    [Blockierte Grafik: https://image.prntscr.com/image/2kw-kV2NSCWP4M95MsfC6Q.png]

    Screenshot aus dem Hauptmenü

    Das grundlegende Spielprinzip ist gleich geblieben (versuch so lange zu laufen wie möglich und weiche hindernissen aus), gibt jedoch ein paar Upgrades gegenüber der alten Version:
    - 2 Spieler Modi (WASD und Pfeiltasten)
    - Komplett mit IrrLicht gemacht
    - Bessere grafiken
    - Neue Gameplay Elemente

    Steuerung:
    - WASD zum laufen/springen/ducken
    - Pfeiltasten + Enter fürs Hauptmenü

    Source inkl. Exe sind natürlich im Anhang

    Hier noch ein Virustotal Link für die Exe, obwohl wir wissen, dass Autoit der König der false positives ist :D

    LG,
    FireDiver

  • Moin FireDiver, ich habe mir das Game mal angeschaut.
    An sich nice Idee, aber das Game ist wirklich extrem schwer,
    weil die Physik und Kollisionen nicht gut angepasst sind.

    Die FPS sind abhängig vom Speed. Wenn ich z.B 1000fps habe, ist das Game viel
    schneller als wenn ich 60 hätte (sieht dann wie slow motion aus).

    Code sieht sehr nooblike aus, aber war ja auch schon etwas her. :D

    Danke fürs Releasen, damit können viele etwas anfangen.

  • Moin FireDiver, ich habe mir das Game mal angeschaut.
    An sich nice Idee, aber das Game ist wirklich extrem schwer,
    weil die Physik und Kollisionen nicht gut angepasst sind.

    Die FPS sind abhängig vom Speed. Wenn ich z.B 1000fps habe, ist das Game viel
    schneller als wenn ich 60 hätte (sieht dann wie slow motion aus).

    Code sieht sehr nooblike aus, aber war ja auch schon etwas her. :D

    Danke fürs Releasen, damit können viele etwas anfangen.

    Erstmal danke fürs Feedback ^^
    Das mit den FPS ist komisch. Bei mir ist die Spielgeschwindigkeit unabhängig von den FPS immer gleich schnell (ausser mit vsync an).
    Ja wenn ich mir den Code heute angucke, muss ich selber den Kopf schütteln :D
    Das Spiel war auch eher so ein Proof of Concept, dass es möglich ist, in Autoit Spiele zu machen.

  • Stimmt, hatte ich vergessen zu sagen. Habe den Unterschied gemerkt, als ich vsync angemacht habe. :D
    Bin auch gerade an einem AutoIt Game, mit der Irrlicht Engine ist das noch relativ ressourcensparend.

    Aber auf meinem Arbeits PC funktioniert die irrlicht.dll irgendwie aber nicht - es stürzt immer ab.
    Werde Monatag dein Skript undercover testen und schauen, ob es läuft. :D

  • Stimmt, hatte ich vergessen zu sagen. Habe den Unterschied gemerkt, als ich vsync angemacht habe. :D
    Bin auch gerade an einem AutoIt Game, mit der Irrlicht Engine ist das noch relativ ressourcensparend.

    Aber auf meinem Arbeits PC funktioniert die irrlicht.dll irgendwie aber nicht - es stürzt immer ab.
    Werde Monatag dein Skript undercover testen und schauen, ob es läuft. :D

    Na dann viel Glück.
    Mir ist grad noch eingefallen, warum das bei VSync warscheinlich hängt: Vsync aktualisiert das Bild ja nur, wenn das vorherige vollständig auf dem Bildschirm ist. Das heißt, dass es eine "Wartezeit" gibt in der das Skript pausiert ist, weil es halt darauf wartet dass der Bildschirm fertig wird->Zeitlupe. Eine Lösung dafür wäre das Rendern in einem separaten Thread laufen zu lassen, aber dass kann Autoit ja nicht :(

  • Das Problem hatte ich am Anfang auch. Ich habe das dann so gelöst, dass der Speed aller Aktionen mit den
    aktuellen FPS kalkuliert wird, sodass der Speed jede Sekunde auf die aktuelle FPS-Rate angepasst wird.

    Beispiel:

    Szenario1
    FPS=60
    Speed=1

    Szenario2
    FPS=30
    Speed=2

    Die Speed-Variable wird dann mit allen Speed-Settings multipliziert. :D

  • Das Problem hatte ich am Anfang auch. Ich habe das dann so gelöst, dass der Speed aller Aktionen mit den
    aktuellen FPS kalkuliert wird, sodass der Speed jede Sekunde auf die aktuelle FPS-Rate angepasst wird.

    Ach Herjee, man koppelt doch nicht die Geschwindigkeit eines Games an die FPS. Man koppelt diese immer an die tatsächliche Zeit um auch bei FPS Laggs / Verzögerungen in der Ausführung des Codes noch korrekt berechnen zu können.

  • Ach Herjee, man koppelt doch nicht die Geschwindigkeit eines Games an die FPS. Man koppelt diese immer an die tatsächliche Zeit um auch bei FPS Laggs / Verzögerungen in der Ausführung des Codes noch korrekt berechnen zu können.

    Hast du ein Beispiel, wie du das meinst? Verstehe das nicht wirklich. Wenn ich die Systemzeit theoretisch ändere, wäre das Spiel dann nicht schneller/langsamer als normal?

  • Hast du ein Beispiel, wie du das meinst? Verstehe das nicht wirklich. Wenn ich die Systemzeit theoretisch ändere, wäre das Spiel dann nicht schneller/langsamer als normal?

    Nehmen wir einmal an wir haben eine Spielfigur die wir bewegen können. Die Geschwindigkeit dieser Spielfigur beträgt pro Sekunde 40 Pixel. Sind also bei 0.25 Sekunden 10 Pixel und bei 0.125 Sekunden eben 5 Pixel. Ein Tick in dem Spiel wird alle 0.1 Sekunden ausgeführt. Pro Tick kann zufällig mit einer Wahrscheinlichkeit von 5% pro Tick ein Punkt spawnen die der Spieler einsammeln kann. Das Spiel läuft normalerweise mit einer unbegrenzten FPS. Also so viel, wie der Spieler aus seiner Hardware heraus holen kann. Nehmen wir als Beispiel 3 verschiedene PC. Einen schnellen mit einer FPS von 120, einen normalen mit 60 FPS und einen langsamen der nur 30 FPS schafft.

    Das ist die Ausgangssituation für ein sehr einfaches Beispiel. Betrachten wir einfach mal beide Varianten - Geschwindigkeit des Spielers an die FPS angepasst und einmal an die Zeit.

    FPS als Indikator für die Spieler Geschwindigkeit
    Du hast vorgeschlagen die FPS mit dem Speed zu koppeln. Gehen wir das mal für die einzelnen PCs durch. Da der Spieler pro Sekunde 40 Pixel zurück legen soll muss hier das Ergebnis eben die 40 für die Geschwindigkeit des Spielers heraus kommen. Als zweiten Parameter haben wir dann die FPS und die unbekannte Variable die du oben als Speed-Variable deklariert hast:

    Schneller PC (120 FPS) -> 120 * $iSpeed = 40 -> 40 / 120 = $Speed = 0.333
    Normaler PC (60 FPS) -> 60 * $iSpeed = 40 -> 40 / 60 = $Speed = 0.667
    Langsamer PC (30 FPS) -> 30 * $iSpeed = 40 -> 40 / 30 = $Speed = 1.333

    Diese Werte bedeuten dass die Spielfigur 0.33, 0.67 oder eben 1.33 Pixel pro angezeigten Frame sich bewegt. Pro Frame also den ausgerechneten Wert um diese Anzahl an Pixel um die Geschwindigkeit bei zu behalten. An sich keine schlechte Idee, allerdings hat dies einen Nachteil. Die Framerate eines Games ist niemals konstant da die Anweisungen, die an die GPU oder CPu gesendet werden unterschiedlich lange berechnet werden bzw. das Programm diese unterschiedlich lange reserviert. Da können schon mal gerne 121 FPS oder eben 119 FPS kommen. Auch wenn du die Framerate künstlich limitierst auf 60 FPS kann es vorkommen dass manchmal nur 59 FPS erreicht werden. Der Knackpunkt an der Geschichte ist nun, dass du eine unregelmäßige Bewegung erhälst. Manchmal läuft nämlich alles etwas schneller oder langsamer ab je nachdem wie schnell die Berechnungen von statten gehen.

    Gehen wir mal davon aus unser Normaler PC mit 60 FPS hat gerade ne gute Runde erwischt und schafft so um die 65 FPS gerade weil das Update im Hintergrund fertig ist. Bisher war die Geschwindigkeit des Spielers 1.5 Pixel pro Frame. Da sich die Framerate allerdings nun geändert hat muss der Wert nun angepasst werden: 40 / 65 = 0.615. Nun gibt es ein Problem:

    Du kannst im vorneherein nicht wissen dass plötzlich die Framerate sich erhöht hat da du diese ja nicht jeden Frame neu berechnest, sondern erst nach einer Sekunde und du die Frames bis dahin zählst. Das hat zur Folge dass du 5 Extra Frames hast wo sich die Figur von der vorherigen Berechnung 3.077 Pixel zu viel bewegt da plötzlich statt 60 FPS eben 65 geschafft wurde. Das bedeutet in dieser Sekunde hast du eine Strecke von 43.1 Pixeln zurück gelegt statt den 40 und die Spielfigur war schneller. Nach der Sekunde reguliert sich dass dann durch die Neuberechnung zwar, allerdings verlangsamt sich dann auch die Bewegung der Spielfigur pro Frame. Das fällt als Spieler dann auf wenn die Figur plötzlich quasi kurz "abbremst".

    Andersrum verhält es sich wenn die Framerate dann hinunter geht. Sagen wir von 60 FPS auf 55 FPS. 55 * 0.667 = 36.667 -> Die Figur ist 3.3 Pixel zu langsam. Hat also nicht die 40 Pixel in der Sekunde geschafft...

    Das kann im Spiel dann zu komischen "ruckler" führen. Ich schreibe dir bei Gelegenheit mal eine kleine Simulation um das zu verdeutlichen. :)


    Zeitangabe als Indikator für die Spieler Geschwindigkeit
    Generell nimmt man am Anfang jedes Schleifendurchlaufs (also vor den Berechnungen) den aktuellen Timestamp. Das macht man via TimerInit() in AutoIt. Das Konzept nennt sich Delta Timing und anhand einer gemessenen Zeit die korrekte Position des Spielers im derzeitigen Frame bestimmen. Unsere Figur soll sich pro Sekunde 40 Pixel weit bewegen. Nun wissen wir aber nicht wie viele Frames der PC schafft, kein Problem.

    Wir berechnen einfach die Zeitdifferenz zwischen jedem Frame indem wir schlichtweg einfach den alten Timestamp mit dem neuen vergleichen. Nehmen wir an wir haben folgende beide Timestamps:
    Alter: 0.00 Sekunden (Programmstart / erster Frame überhaupt)
    Neuer: 0.10 Sekunden (2. Frame)

    Wir haben unsere Sekunde und die Zeitdifferenz von 0.10 Sekunden zwischen den beiden Frames. Unsere Figur bewegt sich Pro Sekunde 40 Pixel, die Rechnung sieht folgendermaßen aus: 0.1 / 1 * 40 = 4. [(Neuer Timestamp - Alter Timestamp) / 1 Sekunde * Strecke]

    So können wir unsere Figur pro Frame um die momentan vergangene Zeit bewegen. Ob jetzt in einen Frame nur 2 Pixel oder gar 6 Pixel zurück gelegt werden fällt erst mal nicht auf da der Gesamteindruck für das menschliche Auge mehr stimmt. Zwar sind in der Praxis diese Werte nicht so extrem auseinander (Es sei denn man hat gerade einen sogenannten FPS Lagg - Der fällt immer auf und da kann man nicht viel machen :/) aber die Geschwindigkeit bleibt konstant und wirkt um einiges flüssiger.


    Am besten schreibe ich dazu einfach mal ein Programm was beide Situationen gut verdeutlicht. Dann dürfte gut erkenntlich werden was ich meine. :)


    €dit:
    Habe nun ein Beispielcode. Der Rote Kreis richtet sich nach der Methode die man eher bevorzugen sollte. Der blaue Kreis nach der FPS. Lass das mal 5 Minuten laufen und beobachte das mal genau. Der blaue Kreis wird sich irgendwann von der Position des roten Kreises weg bewegen.

    [autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit][autoit][/autoit][autoit]

    #include <GDIPlus.au3>
    #include <GUIConstants.au3>
    Opt("GUIOnEventMode", 1)

    [/autoit][autoit][/autoit][autoit]

    Global $hGUI, $hGraphics, $hBitmap, $hBuffer, $hBrush, _
    $hTimer, $iCounter, $iXRed, $iXBlue, $iFPS

    [/autoit][autoit][/autoit][autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit][autoit][/autoit][autoit]

    $hGUI = GUICreate("FPS: 0", 500, 200)
    GUISetOnEvent($GUI_EVENT_CLOSE, GUI_EVENT_CLOSE)
    GUISetState()

    [/autoit][autoit][/autoit][autoit]

    ; GDI+ Gedönse
    _GDIPlus_Startup()
    $hGraphics = _GDIPlus_GraphicsCreateFromHWND($hGUI)
    $hBitmap = _GDIPlus_BitmapCreateFromGraphics(500, 200, $hGraphics)
    $hBuffer = _GDIPlus_ImageGetGraphicsContext($hBitmap)
    $hBrushRed = _GDIPlus_BrushCreateSolid(0xffff0000)
    $hBrushBlue = _GDIPlus_BrushCreateSolid(0xff0000ff)

    [/autoit][autoit][/autoit][autoit]

    ; Start FPS ~ So gegen 40 FPS maybe? Mal raten...
    $iFPS = 40

    [/autoit][autoit][/autoit][autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit][autoit][/autoit][autoit]

    $hTimer = TimerInit()

    [/autoit][autoit][/autoit][autoit]

    While _RndLaggs()
    ; FPS Anzeige:
    If TimerDiff($hTimer) >= 1000 Then
    $iFPS = $iCounter
    WinSetTitle($hGUI, "", "FPS: " & $iFPS)
    $iCounter = 0
    $hTimer = TimerInit()
    EndIf

    [/autoit][autoit][/autoit][autoit]

    ; Zeichne die Kreise + Indikator:
    _GDIPlus_GraphicsClear($hBuffer, 0xffffffff)
    _GDIPlus_GraphicsFillEllipse($hBuffer, $iXRed, 40, 40, 40, $hBrushRed)
    _GDIPlus_GraphicsFillEllipse($hBuffer, $iXBlue, 120, 40, 40, $hBrushBlue)
    _GDIPlus_GraphicsDrawLine($hBuffer, $iXRed, 0, $iXRed, 200)
    _GDIPlus_GraphicsDrawLine($hBuffer, $iXRed + 40, 0, $iXRed + 40, 200)
    _GDIPlus_GraphicsDrawImage($hGraphics, $hBitmap, 0, 0)

    [/autoit][autoit][/autoit][autoit]

    ; Berechnet die nächste Position des Kreises:
    _Move()

    [/autoit][autoit][/autoit][autoit]

    ; Für FPS Anzeige
    $iCounter += 1
    WEnd

    [/autoit][autoit][/autoit][autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit][autoit][/autoit][autoit]

    ; Diese Funktion soll lediglich die FPS Zahlen ein wenig manipulieren
    ; Hier könnten auch Spielberechnungen ablaufen etc... ^^
    Func _RndLaggs()
    Return Sleep(Random(10, 20, 1))
    EndFunc

    [/autoit][autoit][/autoit][autoit]

    ; Berechnung der Kreispositionen:
    Func _Move()
    Local Static $hTimestamp = TimerInit()

    [/autoit][autoit][/autoit][autoit]

    ; Berechnung via Delta Timing: (40 Pixel pro Sekunde)
    $iXRed += TimerDiff($hTimestamp) / 1000 * 40
    If $iXRed > 500 Then $iXRed = -40

    [/autoit][autoit][/autoit][autoit]

    ; Berechnung anhand des FPS Wertes: (40 Pixel PI mal Daumen :o)
    $iXBlue += 40 / $iFPS
    If $iXBlue > 500 Then $iXBlue = -40

    [/autoit][autoit][/autoit][autoit]

    $hTimestamp = TimerInit()
    EndFunc

    [/autoit][autoit][/autoit][autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit][autoit][/autoit][autoit]

    ; Cleanup Code
    Func GUI_EVENT_CLOSE()
    _GDIPlus_BrushDispose($hBrushBlue)
    _GDIPlus_BrushDispose($hBrushRed)
    _GDIPlus_GraphicsDispose($hGraphics)
    _GDIPlus_BitmapDispose($hBitmap)
    _GDIPlus_GraphicsDispose($hGraphics)
    _GDIPlus_Shutdown()
    Exit
    EndFunc

    [/autoit][autoit][/autoit][autoit]

    ; ++++++++++ +++++++++ ++++++++ +++++++ ++++++ +++++ ++++ +++ ++ +

    [/autoit]

    Und Nein, wenn du die Systemzeit änderst passiert rein gar nichts. Der Timestamp basiert darauf, wie viele Sekunden bisher seit dem 1. Januar 1970 vergangen ist. Selbst wenn du die Systemzeit änderst, die Hardware im PC hat dafür einen eigenen Zähler den du vom OS aus nicht verändern kannst.

    6 Mal editiert, zuletzt von Yjuq (6. August 2017 um 04:22)

  • Hey Make-Grafik,

    vielen Dank für die ausführliche Erklärung und dem Beispiel. <3 Jetzt verstehe ich was du meinst,
    das Problem mit den leicht asynchronen Bewegungen bei größeren FPS Lags ist mir bei langsamen
    PC's auch schon aufgefallen.

    Die Timestamp Funktion habe ich mal bei mir eingebaut und leider gibt es damit neue Probleme.
    Mit dem Timestamp ist es so, dass bei minimalen FPS Schwankungen die Objekte sich ineinander
    verschieben, da sie bestimmte Routen ablaufen.

    Ein Objekt ist z.B 5 Pixel vor Wegpunkt A und bewegt sich normalerweise 3 Pixel pro Sekunde. Wenn
    es nun kurz laggt, teleportiert sich das Objekt außerhalb der vorgeschriebenen Route, findet sich danach
    aber wieder ein. Und dadurch kommen die Objekte abundzu ineinander. Das passiert immer dann, wenn
    sich der derzeitige Winkel vom Wegpunkt zum nächsten unterscheidet. Das Objekt bewegt bzw. teleportiert
    sich quasi noch anhand des alten Winkels weiter, anstatt den neuen zu nehmen.

    Bei schwachen PC's ist das leider immer Fall.
    Bei der FPS Berechnung kann das nicht passieren, weil da die Objekte sich nicht teleportieren können, sondern
    nur schneller oder langsamer werden. Ich kann dir das gerne mal per Teamviewer oder in Skype zeigen.


    Muss man eigentlich:

    AutoIt
    Local Static $hTimestamp = TimerInit()

    für jedes Objekt neu setzen oder nur einmal pro Schleife?

    3 Mal editiert, zuletzt von xSunLighTx3 (6. August 2017 um 15:16)

  • Ein Objekt ist z.B 5 Pixel vor Wegpunkt A und bewegt sich normalerweise 3 Pixel pro Sekunde. Wenn
    es nun kurz laggt, teleportiert sich das Objekt außerhalb der vorgeschriebenen Route, findet sich danach
    aber wieder ein.

    Eventuell ist ein Rundungsfehler durch die niedrige Zeitdifferenz des Timestamps ist der Unruhestifter.
    Die Zeitdifferenzen pro Schleifendurchlauf sind ja sehr gering und da können kleine Ausreisser schon große Unterschiede bewirken. Das kann man aber mit Hilfefunktionen abfedern.
    In Spielen teleportieren sich ja auch nicht Spieler die laggen direkt zu ihrem neuen Standpunkt sondern gleiten dort hin.

    Eventuell liegt es aber an GDI+ mit AutoIt selber, ich hatte selber schon Probleme, dass ich ein Sprite mit dem Tasten bewegen wollte und es in den ersten paar Sekunden sehr schnell geht und plötzlich abgebremst ist und eine langsemere Geschwindigkeit beibehielt. Scheit also ein bisschen inkonsistent zu sein.

    für jedes Objekt neu setzen oder nur einmal pro Schleife?

    Du willst die Differenz pro Schleifendurchlauf ermitteln und brauchst dafür nur einen Timestamp.
    Da du jedes Objekt pro Frame renderst ist die Zeitdifferenz zum letzten gerenderten Standpunkt genauso lange (zeitlich) entfernt wie deine anderen Objekte.

    Nur das Verhältnis in welchem die Objekte sich bewegen ist anders.
    Sei deine Zeitdifferenz z.B. 2ms dann kann das eine Objekt sich mit 10 Pixeln (bei z.B. 5p/ms) und ein anderes mit 5 Pixeln (bei z.B. 2.5p/ms) weiterbewegt haben.

  • Hey alpines,

    sorry hab das nochmal editiert, wodurch der Effekt zustande kommt.
    Aber ich weiß was du meinst, das hatte ich mit GDI+ auch schon.
    Wegen der Effizienz benutze ich jetzt Irrlicht, da ist das nicht der Fall.

    Okay dann kann man den Timestamp ja für alle Framerate abhängigen
    Berechnungen nehmen. :thumbup:


    Edit: Hab den Fehler behoben, indem ich Geschwindigkeit + Timestamp Delay mit der
    Distanz zum nächsten Wegpunkt subtrahiert habe und den Rest als "MoveExtra" definiert
    habe, welche sich aus Geschwindigkeit + Timestamp Delay + MoveExtra - Distanz zusammensetzt,
    welche bei nächster Möglichkeit mit der normalen Geschwindigkeit + Timestamp addiert wird. =O

    3 Mal editiert, zuletzt von xSunLighTx3 (6. August 2017 um 17:17)