[Pygame] Isometrische Engine

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
glocke
User
Beiträge: 66
Registriert: Mittwoch 23. Februar 2011, 21:18

Hi, ich habe einen Versuch gestartet mittels Pygame eine isometrische Karte zu erzeugen und die "Kamera"-Position zu verändern. Allerdings habe ich schon bei kleinen Auflösungen (z.B. 640x480, auf dem Netbook) ziemliche Performanceprobleme.

In Sachen Optimierung bin ich wahrlich kein Experte aber vielleicht kann mir hier jemand helfen. Den kompletten Source habe ich auf meinem Webspace stehen:
http://glocke89.gl.funpic.de/isoengine/ ... 312.tar.gz

Wie in main.py (Zeile 61) zu lesen ist, wird für das Setzen der Kamera die Methode set_camera der Klasse IsometricMap (lib/core/iso.py) verwendet. Aus meiner Sicht mache ich in der Methode irgendetwas falsch.

LG Glocke
lunar

@glocke: Du erreichst mehr Leute, wenn Du den Quelltext bei GitHub oder BitBucket hochlädst :)
glocke
User
Beiträge: 66
Registriert: Mittwoch 23. Februar 2011, 21:18

Irgendwie habe ich eher zu SourceForge tendiert ... https://sourceforge.net/projects/pire/files/
lunar

@glocke: Ich meinte eher, dass Du den Quelltext in der Versionskontrolle comittest, so dass man ihn online anschauen kann :)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Also bist Du vom Suchen jetzt zum Erstellen übergegangen? ;-) An sich interessant - aber meinst Du nicht auch, dass die FIFE-Leute da nicht ggf. schon etwas robusteres haben? Du schriebst zwar, dass Du damit Performanceprobleme hattest, aber Deine eigene Engine kränkelt ja auch noch...

Imho ist das immer die Gretchenfrage: Will ich mich auf die Logik und das Spiel konzentrieren, oder macht mir die Entwicklung einer Engine mehr Spaß? Viele "Spiele" scheitern imho an dem Versuch, beides umzusetzen.

Ansonsten stimme ich lunar zu. Das wäre wirklich ein lohnender Schritt, da ich im Moment auch keine Lust habe, mir ein Archiv herunter zu laden.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
deets

Ich hab's mal runtergeladen. Rendert, aber gibt nen "Beachball" auf meinem Mac.

Ansonsten faellt auf, das du schon Java-Programmierer bist. Ein bisschen weniger __irgendwas tut gut. Dann solltest du filenamen nicht per + zusammenbasteln - os.path.join ist dein Freund.

Dann benutzt du multithreading. Ich habe noch nicht versucht rauszufinden wozu - daher die Frage: wozu? In dem Moment wo ich das rausgeschmissen habe, funktioniert's.

Jetzt werde ich mal den profiler anschmeissen... stay tuned.
deets

So, hier mal ein erstes Ergebnis:

Code: Alles auswählen

  ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   24.605   24.605 <string>:1(<module>)
        1    0.000    0.000   24.605   24.605 main.py:70(foo)
        1    0.014    0.014   24.605   24.605 /private/tmp/isoengine/lib/core/engine.py:127(run)
      177    0.004    0.000   21.088    0.119 /private/tmp/isoengine/lib/core/engine.py:63(__handle_input)
      127    0.039    0.000   20.938    0.165 /private/tmp/isoengine/lib/core/engine.py:57(broadcast)
    48821    0.075    0.000   20.867    0.000 /private/tmp/isoengine/lib/core/utilities.py:72(notify)
      126    0.001    0.000   20.744    0.165 main.py:49(onKeyDown)
      125    0.941    0.008   20.741    0.166 /private/tmp/isoengine/lib/core/iso.py:275(set_camera)
    76375    0.515    0.000   14.984    0.000 /private/tmp/isoengine/lib/core/iso.py:340(hide_vector)
    66838    1.070    0.000   13.829    0.000 /private/tmp/isoengine/lib/core/engine.py:51(unregister)
    66965   10.085    0.000   12.786    0.000 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/weakref.py:350(keys)
      529    0.085    0.000    2.660    0.005 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pygame/sprite.py:401(draw)
    63435    2.570    0.000    2.570    0.000 {method 'blit' of 'pygame.Surface' objects}
 27057848    2.157    0.000    2.157    0.000 {method 'append' of 'list' objects}
   304567    1.217    0.000    2.029    0.000 /private/tmp/isoengine/lib/core/iso.py:369(vector2offset)
    51625    0.352    0.000    1.599    0.000 /private/tmp/isoengine/lib/core/iso.py:320(show_vector)
Und wie man sieht, ist ein Problem das der Javaprogrammierer in dir durchgegangen ist. Du verliest 9!!! Sekunden in weakref - weil du so einen komischen Event-register/unregister-Kram machst, der voellig ueberkanditelt ist. Kaum schmeisst man den mal raus (iso.py:330), laeuft die ganze Sache deutlich smoother - Faktor 3 schneller.
glocke
User
Beiträge: 66
Registriert: Mittwoch 23. Februar 2011, 21:18

Hi, erstmal danke für die Mühe.

@Hyperion:
So sieht's aus ^^ Irgendwie reizt mich das "selber schreiben" dann doch :D

@deets:
Ja, da ist der Java-Programmierer in mir mit dir durchgegangen .. nach 3 Semestern Java aber glaube kein Wunder O_o xD
Die weak-ref Sache hatte ich beim Googeln mehrfach gefunden und auf dem ersten Blick für Gut befunden ... würdest du die gesamte Event-Register-Unregister-Sache anders machen oder nur die weakrefs durch dicts ersetzen?

LG
deets

Ich wuerde das komplette register entfernen - das ist viel zu kompliziert. Aus Bildschirm + Kamerakoordinaten rechnest du in Weltkoordinaten, und von denen in Tilekoordinaten, und dann hast du eine kleine, feine Liste von Kandidaten die das Event kommunizieren. Und nicht diesen hunderte-Kacheln-registrieren-Wahnsinn :)
glocke
User
Beiträge: 66
Registriert: Mittwoch 23. Februar 2011, 21:18

@Hyperion:
Was die Gretchenfrage angeht: Ich habe nun vor erst die Engine zu schreiben und mit der Zeit ein Demo-Spiel aufzusetzen (um die Engine beim Entwickeln zu testen). Um das eigentliche Spiel kümmere ich mich erst, wenn die Engine zu meiner Zufriedenheit funktioniert - ich denke so werde ich micht nicht so verrennen, wie du andeutest. :)

Ich habs gerade nochmal komplett umgeschrieben (ohne Threading, ohne Register/Unregister-Wahnsinn). Sobald ich mir GitHub genau angesehen habe, lade ich's da hoch.

LG

/EDIT: here we go https://github.com/christiangloeckner/P ... ing-Engine aber as mache ich jetzt damit? xD

Hab mal ein paar Zeiten mir ausgeben lassen (Samsung N220 Marvel) bei 30x30 Kacheln:

Leerlauf:

Code: Alles auswählen

total: 0.0381729602814s
	render:	0.037878036499s
	logic:	0.000294923782349s
Kamerabewegung:

Code: Alles auswählen

total: 0.113308906555s
	render:	0.0363929271698s
	logic:	0.0769159793854s
Bei 50x50 Kacheln sind es Ruckler mit 0.3s pro Frame
deets

1) keine pyc/pyo Dateien einchecken, die haben im Source nix zu suchen.
2) so habe ich geprofiled

Code: Alles auswählen

if __name__ == '__main__':
    app = Application('Demo', (720, 480), False, 'demo', 50)
    app.controller = MyController
    from cProfile import run
    def foo():
        app.run()
    run("foo()", "/tmp/profile.log")
Bei mir war das meiste im Blit verbraten. Ich vermute mal, dass du wenn du in der Anzahl der Kacheln nicht skalierst dich um entsprechende Filterung bemuehen musst. Also ausegehend vom Kamerapunkt mueglichst schnell die darzustellenden Kacheln rausfinden, und nur die blitten - zb durch BSPs oder QuadTrees oder aehnliches.
glocke
User
Beiträge: 66
Registriert: Mittwoch 23. Februar 2011, 21:18

Wie entferne ich die pyc und pyo Files dort wieder?

Die Blits habe ich vorhin entfernt indem ich die Sprites in eine Group adde und die in der Engine mittels group.draw(screen) rendere. Das brachte imho ein bisschen was.

Was sind BSPs? Quadtrees schau ich mir mal an.

Nur warum bekomm ich für die tick methode so viel Zeit, wenn es am Blitting liegt? Ich hab die draw-Aufrufe der SpriteGroups mal auskommentiert, so dass nix gezeichnet wird. Drücke ich eine der Bewegungstasten seh ich in der Konsole den steigenden Zeitbedarf der tick Methode. Ist der Ansatz mit dem verschieben der Tiles also zu ineffizient?

LG :)
Zuletzt geändert von glocke am Dienstag 27. März 2012, 18:16, insgesamt 1-mal geändert.
nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

Ein Bash-Einzeiler: (natürlich aus dem Quellcodeverzeichnis heraus)

Code: Alles auswählen

echo -e '*.pyc\n*.pyo' > .gitignore && git add .gitignore && git rm lib/*.py{c,o} && git commit -m 'remove pyc and pyo files and create gitignore' && git push origin master
Durch die Datei .gitignore werden in Zukunft die Pyc-/Pyo-Dateien ignoriert, jedoch müssen die Dateien einmal gelöscht werden. Dann werden die Änderungen gespeichert und an Github gesendet.

Edit: Ich habe an dem Einzeiler noch herumgewerkelt – du hast jetzt übrigens das Problem, dass du keine .gitignore-Datei hast.
Zuletzt geändert von nomnom am Dienstag 27. März 2012, 18:31, insgesamt 1-mal geändert.
glocke
User
Beiträge: 66
Registriert: Mittwoch 23. Februar 2011, 21:18

/EDIT: jetzt sind sie raus ^^ hatte irgendwie nen Moment gedauert.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

BSP = Binary Space Partitioning. Verwende einfach Quadtrees, so wie ich das sehe, ist das momentan fast überall Standard.
glocke hat geschrieben:Nur warum bekomm ich für die tick methode so viel Zeit, wenn es am Blitting liegt?
Vielleicht weil du nicht genau weißt was die tick-Methode macht ;-) Schau dir doch mal die Dokumentation dazu an und überlege dir, warum du länger in der Methode bist, wenn nichts berechnet wird und warum du kaum in der Methode bist, wenn viel gearbeitet wird.
Das Leben ist wie ein Tennisball.
glocke
User
Beiträge: 66
Registriert: Mittwoch 23. Februar 2011, 21:18

EyDu hat geschrieben:Vielleicht weil du nicht genau weißt was die tick-Methode macht ;-) Schau dir doch mal die Dokumentation dazu an
Die tick Methode verwende ich auf eine von object abgeleitete Klasse. In welcher Dokumentation sollte ich da denn nachschauen?
Soweit ich das sehe macht die Methode nix anderes als die Eingabe zu prüfen und entsprechend die Offsets der Kacheln zu verschieben.
EyDu hat geschrieben:... und überlege dir, warum du länger in der Methode bist, wenn nichts berechnet wird und warum du kaum in der Methode bist, wenn viel gearbeitet wird.
Ich bin aber weniger in der Methode wenn nichts berechnet wird, und länger wenn viel gearbeitet wird. Oder verdrehe ich hier was O_o

LG

/EDIT: wenn ich bei der Bewegung der Kamera keine Vector-Objekte sondern 2 Integer Zahlen verwende (und die Tiles durch 2 geschachtelte Dicts realisiere). Läuft es mehr als nur flüssiger O_o (Code gleich @GitHub)

Hat noch jemand Ideen die Performance zu verbessern?

Performance (100x100 Kacheln)
Netbook: 8-10 FPS
PC Freundin: 18-27 FPS
PC Kumpel: >50 FPS
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Ich würde ja vermuten, dass es sich um pygame.time.Clock.tick handelt.
Das Leben ist wie ein Tennisball.
glocke
User
Beiträge: 66
Registriert: Mittwoch 23. Februar 2011, 21:18

Ich meinte aber controller.tick() ein paar Zeilen drüber. Der Clock-Tick hat mit der Performance in dem Sinne hier nix am Hut, als dass er nachteilig wirken würde (bei 50 fps etc.)

/EDIT: In der Doku finde ich nix verdächtiges.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

glocke hat geschrieben:Ich meinte aber controller.tick() ein paar Zeilen drüber. Der Clock-Tick hat mit der Performance in dem Sinne hier nix am Hut, als dass er nachteilig wirken würde (bei 50 fps etc.)
Der Clock-Tick hat gerade lange Laufzeiten bei hohen Frameraten, bzw. bei der Zielframerate, da die tick-Methode lediglich Zeit verbrennt um die Framerate auf das gewünschte Niveau zu senken. Wenn dein Programm nichts zu tun hat, dann wird die tick-Methode irgendwo auf die 100% Zeit zulaufen.

Vielleicht schaffe ich es morgen einen Blick in deinen Code zu werfen, heute abend ruft das Bett. Ich würde aber auch stark vermuten, dass zu viele unnötige Kacheln gezeichnet werden, welche nicht im Sichtbereich liegen. Um das zu testen, brauchst du noch nicht einmal einen Quadtree zu verwenden. Da du den Bildschirmausschnitt kennst, sollten sich leicht die zu sehenden Kacheln bestimmen lassen.

Falls du es noch nicht tust, solltest du vielleicht auch numpy in Betracht ziehen. Damit lassen sich viele Rechnungen vereinfachen und und beschleunigen.
Das Leben ist wie ein Tennisball.
glocke
User
Beiträge: 66
Registriert: Mittwoch 23. Februar 2011, 21:18

Ja es werden nicht sichtbare Kacheln gezeichnet. Da alle Kacheln in der SpriteGroup bottomlayer (Engine.py) liegen und ich mit zusätzlichem Rein- und Rausnehmen nicht noch mehr Rechenaufwand erzeugen will.

Btw habe ich jetzt eine Spielfigur (mit der sich die Kamera zusammen bewegt) und eine stehende Spielfigur (die sich mit den Kacheln bewegt) hinzugefügt. Bis auf einige Probleme mit diagonalen Bewegungen (dann ist irgendwann die WorldPosition "kaputt") läufts ganz gut. Das Problem schau ich mir morgen an; vielleicht können wir dann gemeinsam noch etwas an der Performance drehen.

Den Code werde ich morgen mal "aufräumen" und bisschen besser kommentieren ... ^^

Liebe Grüße und gute Nacht.
Antworten