eigene Rect Klasse

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
BlackJack

@Henry Jones Jr.: Ich stand erst einmal vor der Herausforderung unter den Dateien das Modul zum starten zu finden. Da wäre eine README nicht schlecht.

Ein anderer Weg das anzugehen ist eine bessere Organisation der Module. Zum Beispiel nur *ein* Skript auf oberster Verzeichnisebene und den Rest in ein Package stecken. In dem Skript muss nicht viel stehen. Importieren des Startmoduls aus dem Package und ausführen der Hauptfunktion würde schon reichen. Dieses Programm wäre für mich auch die einzige Stelle wo ein ändern von `sys.path` okay ist um das Package im Pfad zu haben egal von welchem Arbeitsverzeichnis man das Skript startet. Das Ändern damit die Verzeichnisse ``core`` und ``levels`` im Pfad sind ist IMHO unsauber. Warum sind das nicht einfach Packages? Es macht doch wenig Sinn den Quelltext auf Verzeichnisse aufzuteilen, nur um diese Struktur dann für Importe wieder „flachzuklopfen” in dem alle Verzeichnisse zu den Modulsuchpfaden hinzugefügt werden.

Ein ähnliches „vermanschen” machst Du Durch die Sternchenimporte. `constants` importiert aus *fünf* Modulen alles mit Sternchen und das Modul wird dann selbst wieder in *21* anderen Modulen mit Sternchenimport verwendet. Dort dann nachzuvollziehen wo einzelne Namen aus den fünf ursprünglichen Modulen her kommen ist unnötig schwer.

Beim Code habe ich hier und da mal reingeschaut und da wird einiges komplizierter gemacht als es müsste. Bei 6K+ Zeilen Code möchte ich da aber jetzt nicht alles durchgehen. :-) Es könnten aber wohl weniger sein, wenn man Quelltext der nach kopieren und einfügen und geringfügig anpassen aussieht umschreibt, zum Beispiel mit Schleifen und/oder Funktionen.

Ein Blick in die Datei mit der gestartet wird zeigt mir auch dass ich da gar nicht ins Detail gehen möchte solange das Modul nur aus *einer* Klasse mit über *80* Methoden in cirka 1800 Zeilen Code besteht. Das ist im Grunde bloss ein Modul mit lauter ``global``-Anweisungen in eine Klasse verschoben so das ``global`` dann durch ``self`` ersetzt ist. Semantisch macht es das aber nicht besser.
Benutzeravatar
Don Polettone
User
Beiträge: 115
Registriert: Dienstag 23. November 2010, 20:26
Wohnort: Schweiz

@ Madmartigan:

erst mal vielen Dank fürs reinschauen und die konstruktive Kritik, das ist toll!
Madmartigan hat geschrieben:Ich habe es zwar runtergeladen, komme aber nur bis zum Hauptmenü, starten kann ich es nicht. Habe leider nur einen XBox360- und einen PS3-Controller angeschlossen, kann daher also zum Gameplay nichts sagen.
im Hauptmenü ist die Steuerung per Maus :mrgreen: Alles "übergeordnete" steuert sich per Maus (also Level Editor und Hauptmenü). Nur den Char steuert man mit dem Controller. Die eigentliche Idee wäre es, dass es den Level Editor im fertigen Spiel gar nicht mehr gibt - dieser ist "nur" dazu da, die Levels des Spiels zu designen. Schlussendlich möchte ich ein Spiel das nur noch per Controller gespielt wird (eben: Retro Feel :wink:). Ach ja, und XBox Controller müsste eigentlich funzen. Ne PS3 hab ich auch aber ich habe es soweit noch nicht geschafft den Controller sauber anzusprechen mit pygame (SDL). Noch zur alternativen Steuerung per Keybi: So ein Spiel lässt sich nicht wirklich "testen" per Keyvoard. Man hat keine Chance, wenn das Level auch nur ansatzweise fordernd ist. Aber zu Development Zwecken hast Du schon recht, warum auch nicht. Dann können mehr Leute reinschauen, die mehr Ahnung haben vom proggen als ich :wink: .

Und noch zum Hauptmenü: Hab ja geschrieben: alles, was im Moment funzt, ist auf "Level Editor" klicken und dann "Load Level", dann wird der Test Level geladen und man kann zocken.

Dann noch zur Auflösung: Dein Gerät ist schon 16:9 nehme ich an? Dafür ist mein Game ausgelegt. Wenn Dein Gerät 16:9 ist, sollte der ganze Bildschirm ausgefüllt sein (und dementsprechend krass verpixelt). GENAU SO will ich das :mrgreen: Die Schrift unten ist mein game-internes "Std Out". Dieses benötige ich in erster Linie um anzuzeigen, wie viel vom Zeit delta mir per Frame noch übrig bleibt; also, ob das Game noch performant ist, etc. Diese Font wird auf den Buffer geblittet und dann hochgerechnet, deshalb ist sie genau so pixelig wie das Spiel selbst. Für mich so in Ordnung.

Der Tipp mit dem "Showcase" ist auch toll; ich kannte das noch nicht. Ist auch gut für mich um zu schauen, was andere so machen.

Es ginge mir nun in erster Linie schon darum, den eigentlichen Code, eben gerade in Bezug auf imports etc, so zu überarbeiten, dass es a) "sauberer" ist, und b) dass ich trotzdem (wie bis anhin) relativ schnell ein neues Projekt anfangen kann und viele Sachen schon bereit habe (quasi meine "Game Engine", eben der Inhalt des Core Ordners). BlackJack hat diesbezüglich interessante Inputs gebracht. Ich gehe gleich darauf ein.

Probier's doch nochmals aus mit Game starten wenn Dü möchtest; evtl. kannst Du dann noch 3 Minuten mehr in alten Zeiten schwelgen... (ich verstehe Dich total :mrgreen: :mrgreen: )
Ich code, also bin ich.
Benutzeravatar
Don Polettone
User
Beiträge: 115
Registriert: Dienstag 23. November 2010, 20:26
Wohnort: Schweiz

@ BlackJack: auch Dir vielen Dank fürs reinschauem und für die interessanten Inputs!
BlackJack hat geschrieben: Ein anderer Weg das anzugehen ist eine bessere Organisation der Module. Zum Beispiel nur *ein* Skript auf oberster Verzeichnisebene und den Rest in ein Package stecken. In dem Skript muss nicht viel stehen. Importieren des Startmoduls aus dem Package und ausführen der Hauptfunktion würde schon reichen. Dieses Programm wäre für mich auch die einzige Stelle wo ein ändern von `sys.path` okay ist um das Package im Pfad zu haben egal von welchem Arbeitsverzeichnis man das Skript startet. Das Ändern damit die Verzeichnisse ``core`` und ``levels`` im Pfad sind ist IMHO unsauber. Warum sind das nicht einfach Packages? Es macht doch wenig Sinn den Quelltext auf Verzeichnisse aufzuteilen, nur um diese Struktur dann für Importe wieder „flachzuklopfen” in dem alle Verzeichnisse zu den Modulsuchpfaden hinzugefügt werden.
.

Das mit den Packages klingt einlauchtend und Du hast Recht; ich habe noch nie Packages benutzt und bin allgemein bzgl. Struktur von Programmen noch nicht der King... die Packages muss ich mir anschauen. Dann könnte ich z.Bsp. meine "Engine" (eben den "Core" Ordner), die ich für jedes Game brauche und immer wieder extende, als package importieren (wie pygame selbst)? Das klingt gut!
BlackJack hat geschrieben: Ein ähnliches „vermanschen” machst Du Durch die Sternchenimporte. `constants` importiert aus *fünf* Modulen alles mit Sternchen und das Modul wird dann selbst wieder in *21* anderen Modulen mit Sternchenimport verwendet. Dort dann nachzuvollziehen wo einzelne Namen aus den fünf ursprünglichen Modulen her kommen ist unnötig schwer.
Das mit dem constants Modul hab ich eben gemacht, dass ich, wenn ich ein neues Skript anfange, dieses Modul mit * importieren kann und so all die wichtigen Konstanten des jeweiligen Games (RES, directions, Farben, load_image() etc) immer überall parat habe. Als ich dieses constant File noch nicht hatte, war es übler; ich glaube im Grundsatz ist das nicht so schlecht, dieses constants File, das die Engine und wichtige Konstanten des Spiels einliest und zur Verfügung stellt?
BlackJack hat geschrieben: Beim Code habe ich hier und da mal reingeschaut und da wird einiges komplizierter gemacht als es müsste. Bei 6K+ Zeilen Code möchte ich da aber jetzt nicht alles durchgehen. :-) Es könnten aber wohl weniger sein, wenn man Quelltext der nach kopieren und einfügen und geringfügig anpassen aussieht umschreibt, zum Beispiel mit Schleifen und/oder Funktionen.

Ein Blick in die Datei mit der gestartet wird zeigt mir auch dass ich da gar nicht ins Detail gehen möchte solange das Modul nur aus *einer* Klasse mit über *80* Methoden in cirka 1800 Zeilen Code besteht. Das ist im Grunde bloss ein Modul mit lauter ``global``-Anweisungen in eine Klasse verschoben so das ``global`` dann durch ``self`` ersetzt ist. Semantisch macht es das aber nicht besser.
Mal vorab: Wenn Du mit "kopieren und einfügen und geringfügig anpassen" meinst, dass ich Teile von der letzten Game Klasse, die ich geschrieben habe, wiederverwende (und gegebenenfalls anpasse), dann hast Du... wohl Recht :D . Aber wenn Du meinst, ich kopiere mir irgendwas zusammen von woher-auch-immer: Nö. ALLES "from scratch" (abgesehen von Pygame natürlich)! Ich schreibe mein Game selbst und es ist eher mein "Problem", dass ich sogar low level Zeugs wie eben die Rect Klasse lieber selbst schreibe (und dabei was lerne), als dass ich bestehenden Code verwende... wenn ICH es geschrieben habe, weiss ich, was es macht und was ich tun muss, wenn's es nicht mehr macht :wink: . Das ist mir wohler als Zeugs zu kopieren.

Dann sagst Du, "Es könnten aber wohl weniger sein..." "...zum Beispiel mit Schleifen und/oder Funktionen."
Meine Game Instanz HAT ja nur Schleifen und Funktionen (Methoden)? Siehst Du da "losen" Code rumhängen? Da verstehe ich nun wirklich nicht was Du meinst... Die Methoden meines Games, auch wenn es viele sind - und es braucht deren viele -, sind doch allesamt schön verpackt?

Du scheinst ganz grundsätzlich mit der Game Klasse unzufrieden zu sein, hab ich das richtig verstanden? Diese sei gross / kann zu viel (hat zu viele Methoden) oder was genau meinst Du? Ist es nicht sinnvoll, dass es eine Game Klasse gibt, von welcher ich dann eine Game Instanz erzeuge, und dieser sage: run()? Dann sollte ich dem Game doch auch sagen können: blit_chars()? Und wait(frames)? und check_quit()? Ich weiss nicht genau, was Du findest, ist daran nicht gut? Kannst Du mir das noch etwas näher bringen?

Dann hat das Game ja verschiedene "scenes". Sprich: Im Titelmenü wird etwas immer wieder (gleich) gemacht, im Editor, im Spiel selbst, im Intro... dies sind meine verschiedenen Scenes (Methoden der Instanz "game") und das Game, wenn ich sage game.run(), macht dann einfach immer dasselbe: es führt game.scene() aus. Und wenn game.scene None ist, bricht das Spiel ab. Das ist doch eine gute Grundstruktur für ein Programm/Game?

Verstehe mich nicht falsch, ich möcht meinen Code besser machen, aber was findest Du an dieser Struktur mit der Game-Instanz, welche das eigentliche Spiel selbst darstellt, nicht gut?
Ich code, also bin ich.
BlackJack

@Henry Jones Jr.: Doch im Grundsatz ist das mit den `constants` schlecht das Du überall auf magische Weise Namen in den Modulnamensraum abkippst. Das mag beim schreiben einfacher erscheinen, aber beim Lesen ist es halt schwerer nachzuvollziehen wo die Werte eigentlich herkommen und welche es überhaupt gibt im Modul. Und man liest Quelltext deutlich öfter als das man ihn schreibt, darum sollte man das schreiben nicht auf Kosten des lesens vereinfachen wollen.

Mit „kopieren und einfügen und geringfügig anpassen” meine ich den Quelltext selbst, innerhalb Deines eigenen Quelltextes machst Du das. Kann sein dass Du nicht die Kopieren und Einfügen Funktionen des Texteditors verwendet hast sondern tatsächlich fast den gleichen Quelltext mehrfach getippt hast, aber das ändert am Problem ja nichts. Und die Bemerkung bezog sich nicht speziell auf die `Game`-Klasse, die hatte ich ja gar nicht weiter angeschaut. Das `levels`-Modul ist beispielsweise voll von solchem Code. Haufenweise Methoden und Schleifen mit der selben Codestruktur nur mit unterschiedlichen Werten. Aber auch zwischen Modulen wurden teilweise ganze Methoden 1:1 kopiert. Zum Beispiel `get_*_tiles()`- und diverse `check_*()`-Methoden in `creeps` und `powerups` oder Methoden in `sprite` und `tiles` die identisch sind. Und das sind keine kurzen Methoden sondern welche die jeweils so um die 10 Zeilen Code enthalten.

Ob Dein Code oder die `Game`-Klasse alles ”schön” in Methoden verpackt hat, ändert ja nichts daran das dort trotzdem viel Code sein kann, den man durch Schleifen und/oder weitere Methoden kürzer und weniger redundant machen kann. Die folgenden 12 Zeilen Quelltext findet man in der `Game`-Klasse wenn ich mich jetzt nicht verzählt habe an *sechs* verschiedenen Stellen, also sechs Kopien davon:

Code: Alles auswählen

            if self.editor_mode:

                self.check_editor_active()
                
                if self.editor.active:
                    self.editor.update()
                    self.blit_editor()
                    self.blit_dimension_startpos_tile()
                    self.blit_editor_creeps()
                    self.blit_cursor()
                else:
                    self.update_player_anzeige()
                    self.blit_player_anzeige()

            self.clock.tick()
            frame_counter += 1
In `get_creeps_to_activate()` sind die verschachtelten ``for``-Schleifen 8 kopierte Zeilen, die man durch eine geschicktere Erstellung von `x_list` und `y_list` einsparen kann. Und dann noch einiges an Code der mit `char.rect` arbeitet und fast identisch in verschiedenen anderen Modulen vorkommt. Dort ist in der Regel statt `char` der Name `self` eingesetzt, aber die Berechnungen sind immer gleich. Und von dem Code mit `self` existieren dann in verschiedenen Modulen manchmal identische Kopien.

An einer 1800 Zeilen-Klasse mit 80+ Methoden ist nicht gut das die total unüberschaubar ist und zu viel macht. Ich schrieb es ja schon mal, das ist im Grunde ein Riesenmodul mit vielen Funktionen und globalen Variablen. Nur syntaktisch anders aufgeschrieben, aber ebensowenig durchschaubar oder sinnvoll strukturiert. Da steckt ja letztendlich *alles* drin, Intro, Editor, Level, verschiedene „Szenen”, und alles *direkt* in dieser Klasse statt sinnvoll auf andere Datentypen verteilt. Das ist eindeutig ein Gottobjekt.
Benutzeravatar
Don Polettone
User
Beiträge: 115
Registriert: Dienstag 23. November 2010, 20:26
Wohnort: Schweiz

huiuiui....

wo soll ich anfangen? Ich denke, mal einen Kreuzzug gegen die Redundanz zu starten wäre ein sinnvoller Anfang?

Dann würde ich bei der get_creeps_to_activate() Methode anfangen. Immer wenn der aktuelle screen-Auschnitt verschoben wird (um 1 Tile), muss das Game checken, ob's neue Creeps zu aktivieren gibt und mir diese sammeln und zurückgeben, das wäre der Sinn.

Nun habe ich mir überlegt, es wäre eh besser und auch schneller, wenn ich nicht immer die ganzen Positionen am Rand des Screens durchgehe, sondern nur die NEUEN; sprich: Wenn der Bildschirm nach rechts gemoved ist, nur die Tiles am rechten Rand prüfen (and vice versa); und dasselbe mit der y Achse. Ich müsste also dies zuerst abfangen - die Bewegungsrichtung des screens....

na dann...

...Auf auf zum Atem!!

Danke noch, BlackJack! :)
Ich code, also bin ich.
Benutzeravatar
Don Polettone
User
Beiträge: 115
Registriert: Dienstag 23. November 2010, 20:26
Wohnort: Schweiz

...hab nun angefangen, das zu "fixen" und es geht schon los...:

Ich habe rausgefunden, dass gerade die move_screen - Methode sehr viele kleine Teiloperationen durchgehen muss, etwa wie folgt:

- ich muss ermitteln, welcher Char am weitesten links, rechts, unten und oben im Bild ist (es können bis zu 4 Player sein)

- wenn einer dieser Chars zu nah am jeweiligen Bildrand ist, muss der screen sich in diese Richtung bewegen.

- die Hauptrichtung des Spiels ist rechts - wenn einer zu weit rechts steht, moved der screen nach rechts, komme was wolle, und "zieht" eventuelle Spieler am linken Bildrand mit / zerquetscht sie (wenn sie an einem Tile hängenbleiben, kann der linke Bildrand sie "erdrücken").

das wäre ein guter Anfang, aber wie soll ich das sinnvoll aufteilen? Gibt das nun eine weitere Methode meiner Game Instanz oder eine / mehrere Funktion/en innerhalb der move_screen() - Methode oder eine andere Struktur?

Im Moment sieht die Methode so aus - ich kann mich noch erinnern, dass es ziemlich was gebraucht hat, bis es mit mehreren Playern so funktioniert hatte, wie es sollte... Aber es ist eine ziemliche Killerfunktion; ich hab's nicht geschafft, sie kürzer zu halten...:

Code: Alles auswählen

    def move_screen(self):
        
        buffer_pos_before = self.buffer_rect.center
        buffer_tilepos_before = self.buffer_tilepos
        moved = False # wenn self.buffer_tilepos sich veraendert -> moved = True

        # und wenn moved, werden danach self.tilepos_on_screen sowie
        # alle tile_lists upgedated

        scroll_speed = 3

        if self.editor.active:

            if self.mouse.pos[0] >= RES[0]:
                self.buffer_rect.x += scroll_speed
            elif self.mouse.pos[0] <= 0:
                self.buffer_rect.x -= scroll_speed

            if self.mouse.pos[1] >= RES[1]:
                self.buffer_rect.y += scroll_speed
            elif self.mouse.pos[1] <= 0:
                self.buffer_rect.y -= scroll_speed

        else:

            TOL_RIGHT = TOL_LEFT = RES[0] / 5
            TOL_TOP = TOL_BOTTOM = RES[1] / 5

            limit_rechts = self.buffer_rect.right - TOL_RIGHT
            limit_links = self.buffer_rect.left + TOL_LEFT
            limit_oben = self.buffer_rect.top + TOL_TOP
            limit_unten = self.buffer_rect.bottom - TOL_BOTTOM

            rightmost_x = 0
            leftmost_x = 1000000

            move_right = move_left = move_up = move_down = False

            for char in self.chars:
                if not char.state in (KILLED, BUBBLED) and not char.dead and not char.game_over:
                    if char.hit_sprite.rect.right > limit_rechts and char.hit_sprite.rect.right > rightmost_x:
                        move_right = True
                        rightmost_x = char.hit_sprite.rect.right   
            if move_right:
                self.buffer_rect.right = rightmost_x + TOL_RIGHT
            else:
                for char in self.chars:
                    if not char.state in (KILLED, BUBBLED) and not char.dead and not char.game_over:
                        if char.hit_sprite.rect.left < limit_links and char.hit_sprite.rect.left < leftmost_x:
                            move_left = True
                            leftmost_x = char.hit_sprite.rect.left
                if move_left:
                    self.buffer_rect.left = leftmost_x - TOL_LEFT
                    limit_rechts = self.buffer_rect.right - TOL_RIGHT
                    for char in self.chars:
                        if not char.state in (KILLED, BUBBLED) and not char.dead and not char.game_over:
                            if char.hit_sprite.rect.right > limit_rechts and char.hit_sprite.rect.right > rightmost_x:
                                move_right = True
                                rightmost_x = char.hit_sprite.rect.right
                    if move_right:
                        self.buffer_rect.right = rightmost_x + TOL_RIGHT

            top_y = 1000000
            bottom_y = 0

            for char in self.chars:
                if not char.state in (KILLED, BUBBLED) and not char.dead and not char.game_over:
                    if char.rect.top < limit_oben and char.rect.top < top_y:
                        move_up = True
                        top_y = char.rect.top
            if move_up:
                self.buffer_rect.top = top_y - TOL_TOP
            else:
                for char in self.chars:
                    if not char.state in (KILLED, BUBBLED) and not char.dead and not char.game_over:
                        if char.rect.bottom > limit_unten and char.rect.bottom > bottom_y:
                            move_down = True
                            bottom_y = char.rect.bottom
                if move_down:
                    self.buffer_rect.bottom = bottom_y + TOL_BOTTOM
                    limit_oben = self.buffer_rect.top + TOL_TOP
                    for char in self.chars:
                        if not char.state in (KILLED, BUBBLED) and not char.dead and not char.game_over:
                            if char.rect.top < limit_oben and char.rect.top < top_y:
                                move_up = True
                                top_y = char.rect.top
                    if move_up:
                        self.buffer_rect.top = top_y - TOL_TOP
                
        if self.buffer_rect.left < 0:
            self.buffer_rect.left = 0
        elif self.buffer_rect.right > self.dimension.rect.right:
            self.buffer_rect.right = self.dimension.rect.right
        if self.buffer_rect.top < 0:
            self.buffer_rect.top = 0
        elif self.buffer_rect.bottom > self.dimension.rect.bottom:
            self.buffer_rect.bottom = self.dimension.rect.bottom

        buffer_pos_new = self.buffer_rect.center
        dist = get_distance(buffer_pos_before, buffer_pos_new)
        if dist > self.chars[0].x_speed_max_high:
            buffer_pos_new = approach(buffer_pos_before, buffer_pos_new, self.chars[0].x_speed_max_high)
            self.buffer_rect.center = (round(buffer_pos_new[0]),
                                       round(buffer_pos_new[1]))

        x = self.buffer_rect.left / TILESIZE * TILESIZE
        y = self.buffer_rect.top / TILESIZE * TILESIZE
        self.buffer_tilepos = (x, y)
        if self.buffer_tilepos != buffer_tilepos_before:
            moved = True
        return moved
wie soll ich das anständig verpacken? Hat jemand vielleicht eine Idee?

vielen Dank und Gruss,
Ich code, also bin ich.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Indem du erstmal den ganzen doppelten und dreifachen Code zusammenfasst und daraus Funktionen machst ;-) Viele Teile sind nahezu identisch und unterscheiden sich nur in winzigen Details. So als Faustregel: Ist eine Funktion länger als eine Bildschirmseite, dann ist sie zu lang.
Das Leben ist wie ein Tennisball.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Lasst uns "Bildschirmseite" wie folgt definieren: 79 x 24 Zeichen. Nicht, dass noch jemand mit drei Bildschirmen im Portrait-Stack daher kommt ;)
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Antworten