eigene Rect Klasse

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Benutzeravatar
Don Polettone
User
Beiträge: 115
Registriert: Dienstag 23. November 2010, 20:26
Wohnort: Schweiz

das mit Numpy ist nett, aber ich möchte glaub ich coden können

ni = MyRect((0, 0), (16, 16))

ich bin's so gewohnt von Pygame und finde das kurz, sauber und lesbar. 2 Fragen aber noch: Wenn ich möchte, dass ich ein Rect mit

ni = Rect(pos, size)

oder

ni = Rect(left, top, width, height)

erstellen kann, wie kann ich das in die Klasse packen, dass beides funktioniert?

Und wie kann ich selbst definieren was passiert, wenn man mein Rect Object printet?
Ich code, also bin ich.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Henry Jones Jr. hat geschrieben:sagen wir mal, ich möchte die Rect Attribute wirklich als ints. Für meine Zwecke - also verpixelte 2D Games - sind die ideal. Immer wenn's ans blitten geht müsste ich sonst umrechnen wenn's floats wären.
Wenn du nur Integer reinsteckst, dann werden auch nur Integer rauskommen ;-) Du musst dir doch nicht unnötigt die Allgemeingültigkeit nehmen.
Henry Jones Jr. hat geschrieben:Jetzt würdest Du x und y (floats) nicht dem rect, sondern dem Char Objekt als Attribute mitgeben, und wenn man diesen einen neuen Wert zuweist würdest Du das char.rect ändern?
Ok, vielleicht gehen wir das Problem mal von der anderen Seite an. Ich habe dich bisher so verstanden, dass du das Rect zum Zeichnen von Objekten benötigst. Bleiben wir einfach mal bei dem von dir ins Spiel gebrachten Character. Dieser Character hat dann eine Position (x,y) und die Größe (width, height), welche du im Rechteck speicherst? Sehe ich das richtig so?

Wenn ja, dann würde ich das Problem anders lösen: der Character bekommt eine Position (cx, cy). Zusätzlich bekommt er ein Rechteck. Dieses erstellst du dann so, dass die Füße (oder was auch immer bei dir der Referenzpunkt ist) sich bei der Position (0, 0) im Rechteck befinden. Und das machst du alles relativ. Mal als Beispiel:

Der Character ist 100 Breit und 200 hoch, dessen Füße befinden sich unten in der Mitte. Dann hat er das Rechteck mit der Position (-50, 0) und die Größe (100, 200). Erst wenn du jetzt den Spieler bei (cx, cy) zeichnen willst, dann erstellst du ein neues Rechteck und addierst darauf (cx, cy).

Du solltest auf keinen Fall die Position und die Ausmaße des Characters irgendwie zusammen in ein Rechteck werfen, das macht später unglaublich viele Dinge kompliziert. Am einfachsten ist es, wenn du alles relativ zur Position der Figur definierst (Ausmaße der Figur, Schatten, Waffen, etc.) und erst ganz am Ende (beim Zeichnen oder bei Kollisionen), das konkrete Rechteck bestimmst. Damit wirst du dir jede Menge unnötige Überlegungen und Berechnungen sparen. Falls meine Überlegungen nicht zutreffen, dann beschreibe doch bitte genauer, was du vor hast.
Henry Jones Jr. hat geschrieben:ni = MyRect((0, 0), (16, 16))
Das könntest du zusätzlich implementieren. NumPy macht viele Dinge halt viele Berechnungen einfacher. Statt zwei Additionen für die Position benötigst du zum Beispiel nur noch eine. Von anderen Transformationen oder Berechnungen von Inversen ganz zu schweigen. Wenn du das alles aber nicht benötigst, dann lasse NumPy einfach weg.
Henry Jones Jr. hat geschrieben:ni = Rect(pos, size)
oder
ni = Rect(left, top, width, height)
Mit ``func(*args)`` kannst du einer Funktion beliebig viele Argumente übergeben (welche dann in args stecken). Wenn es zwei sind, dann könntest du ein Rechteck über die erste Variante erstellen, bei vier über die zweite. Allerdings ist das kein besonder schönes Interface. Üblicherweise bietet sich dafür eine zusätzliche Methode an:

Code: Alles auswählen

class Rect(object):
    def __init__(left, top, width, height):
        ...

    @staticmethod
    def from_tuples(pos, size):
        return Rect(...)

a = Rect(10, 20, 110, 220)
b = Rect.from_tuples((10, 20), (100, 200)
Henry Jones Jr. hat geschrieben:Und wie kann ich selbst definieren was passiert, wenn man mein Rect Object printet?
Implementiere die __str__- und __repr__-Methoden für Rect. Ersteres ist für den Benutzer gedacht, letzteres für den Programmierer. Im Idealfall gibt __repr__ einen String zurück, mit welchem man das Objekt wieder erzeugen könnte.
Das Leben ist wie ein Tennisball.
Benutzeravatar
Don Polettone
User
Beiträge: 115
Registriert: Dienstag 23. November 2010, 20:26
Wohnort: Schweiz

Ich versuche zu erklären, was genau ich machen möchte und weshalb ich x und y als floats und die rect Attribute als ints haben will:

die update Methode wird ja für den Char einmal pro Frame aufgerufen. Sagen wir ein Frame dauert 10ms (das ist mein übliches delta). Der Char soll sich nun bewegen. Hier sind floats gefragt damit ich den Char genau mit der richtigen Geschwindigkeit bewegen kann. Ich möchte hier nicht mit ints arbeiten, also z.Bsp. einen "Counter" setzen, der den Char alle 3 Frames ein Pixel bewegt oder was auch immer. Deshalb die Floats. Smooth Movement. Ich verändere char.x ein klein wenig, und die rect Attribute werden automatisch angepasst (properties). Das rect des Chars brauche ich für Kollisionsabfragen etc, es wäre im aktuellen Fall so gross wie 1 Tile (16 x 16) Pixel. Das Bild des Chars (char.img) ist aber grösser, hat also ein anderes rect.

Wenn ich den char bewege, soll das hit_rect mitbewegt werden, aber nur in absoluten Pixeln (eben ints). Wenn ich den Char aber auch mal z.Bsp. rechtsbündig anhand des hit_rects ausrichten möchte, möchte ich, dass, wenn ich die hit_rect attribute ändere (char.hit_rect.right = ni), auch x und y geändert werden.

Ich will also, dass (x, y) und das hit_rect sich gegenseitig aktiv beeinflussen, dann muss ich doch das so machen innerhalb meiner MyRect - Klasse, wie ich's gemacht habe, nicht?
Ich code, also bin ich.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Du denkst zu verpixelt ... :wink:

Der Grund, dass Du eine eigene Rect-Klasse entwickelst, ist der, dass du intern für deine Spielfigur eine andere Repräsentation benötigst, als auf dem Bildschirm zu sehen ist. Ein Pixel mehr oder weniger ist später ohnehin nicht relevant, weil es nicht wahrgenommen wird.

Das ist für sich genommen schonmal ein guter Ansatz (Trennung von Logik und Darstellung). Aber so wirklich trennen willst Du die beiden dann doch wieder nicht.

Deine Spielelogik, welche auf die floats setzt, muss und sollte nichts von der späteren Darstellung wissen. Sie sollte vielmehr die nötigen Berechnungen durchführen (Bewegungen, Kollisionsprüfungen, Updates, u. v. m.). Das Ergebnis (der Zustand der Logik) wird dann lesend auf den Bildschirm gebracht.

Die mainloop könnte dann vereinfacht wie folgt aussehen.

Code: Alles auswählen

def mainloop(self):
    while self.running:
        delta_time = self.clock.tick(self.FRAME_LIMIT)
        self.process_events()
        self.update_logic(delta_time)
            # Man sollte jedoch zusehen, dass die Logik in FESTEN
            # Schritten arbeitet.
        self.update_graphics(delta_time)
            # Hier wird u. a. der Zustand der Spiellogik in die Grafik
            # überführt.
        self.draw_graphics()
Als Beispiel meine Rect-Klasse. rects.py
und ihre möglich Anwendung bzw. Überführung in ein pygame.Rect. EDIT: Ich verwende hier Python 3.x.

Nochmal zur Erinnerung: Die Kollisionsprüfungen etc. finden in Deiner Rect-Klasse statt. pygame ist in der Hinsicht nun außen vor.
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Da trifft meine Vermutung ja in etwa zu. bwbg ist in seinem Thread noch einmal gut auf das Problem eingegangen. Vergiss die Pixel, arbeite mit Floats. Erst ganz am Ende, beim Zeichnen, brauchst du das Rechteck in absoluten Koordinaten.
Das Leben ist wie ein Tennisball.
Benutzeravatar
Don Polettone
User
Beiträge: 115
Registriert: Dienstag 23. November 2010, 20:26
Wohnort: Schweiz

Hey ich glaube ich mache das doch schon so ähnlich?

Darf ich Euch mal mein bisher umfangreichstes Projekt zeigen? Gibt es einen Weg, mittels dem ich das ganze Game hochladen kann, ohne jede einzelne Datei separat auswählen und betiteln zu müssen? Das Teil ist huge; es sind in etwa mind. 30 .py Files und dann noch Sprites, Sounds und weitere Ordner (levels) etc., das alles in einem Ordner - "New Super Mario Bros py".

Gibt es eine Möglichkeit, Euch das als Ganzes zu zeigen, damit Ihr mal reinzocken und natürlich den Code anschauen könnt? Kann ich irgendwo irgendwie DEN GANZEN ORDNER "New Super Mario Bros py" hochladen, damit ihr ihn kopieren und reinschauen könnt?

Ich würde mich freuen, wenn Ihr da mal reinschauen würdet und mir sagt, was ich denn anders mache als ihr...

Ums zu zocken, braucht ZWINGEND ein Gamepad, das mit DirectInput funktionert (XInput geht NICHT). Wenn das aber gegeben ist, könnt Ihr zocken!
Ich code, also bin ich.
Benutzeravatar
Madmartigan
User
Beiträge: 200
Registriert: Donnerstag 18. Juli 2013, 07:59
Wohnort: Berlin

Du kannst deinen ganzen Ordner in ein ZIP, RAR oder ähnlich verbreitetes Format packen und bei einem Free-Filehoster hochladen.

z.B. http://www.file-upload.net/ (Nein, ich mache keine Werbung für diverse Hoster, ich griff einfach den Ersten, den Google anbot :twisted: )

Das geht ohne Registrierung und du bekommst einen Download-Link, den du hier posten kannst.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Oder du packst das Ding auf Dropbox und machst die Datei öffentlich.

Oder aber, du gehst den sauberen Weg und verwendest eine Versionsverwaltung für dein Projekt. Das bietet sich bei so vielen Dateien so oder so an. Eigentlich schon aber einer... Vielleicht schaust du dir dazu mal GitHub an.
Das Leben ist wie ein Tennisball.
Benutzeravatar
Don Polettone
User
Beiträge: 115
Registriert: Dienstag 23. November 2010, 20:26
Wohnort: Schweiz

OK, schon passiert: New-Super-Mario-Bros-py.7z

Wichtige Hinweise: Am Anfang startet eine "Pseudo-Lade-Szene", diese ladet nicht wirklich Zeugs :D einfach mit ESC skippen. Dann kommt das Intro (liesse sich auch skippen mit ESC).

Im Hauptbildschirm dann einfach auf "Level Editor" klicken und dann auf "Load Level" -> was anderes funktioniert noch nicht!

Dann könnt Ihr mein Test Level zocken. Wenn Ihr am Ziel des Levels seid, macht das Game nix mehr; ich habe die Level-Ziel-Szene noch nicht fertig gecodet. Mit ALT-F4 könnt Ihr das Game JEDERZEIT killen.

Wie gesagt, es braucht mind. 1 (max. 2) DirectInput Controller, sonst startet das Spiel NICHT. (Logitech u ä. sollte funzen). Man kann auch zu zweit zocken.

Den Level Editor könnt Ihr mit der Leertaste aufrufen/schliessen, mit der mittleren Maustaste wird das TileMenu des Editors ein- / ausgeblendet. Mit der Maus könnt Ihr das Level in Echtzeit, während des Zockens, ändern mit dem Tile Editor. Mit ESC gelangt man wieder ins Load/Save Level Menu und von da aus ins Hauptmenu. Das ist eigentlich alles, was man wissen muss.

Happy Testing und ich freue mich auf Euer Feedback (in Bezug auf den Code UND in Bezug aufs Spiel selbst).

Danke für Eure Zeit :D
Zuletzt geändert von Anonymous am Freitag 31. Januar 2014, 17:35, insgesamt 1-mal geändert.
Grund: Link auf Wunsch von Autor entfernt.
Ich code, also bin ich.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Bevor Du auch nur im Ansatz an eine Veröffentlichung denkst, ändere den Namen. Sonst hast Du sehr schnell ein Markenrechtsproblem.

Falls Du Pixelgrafiken aus einem entsprechenden Spiel verwendest und keine Einverständnis zur Nutzung besitzt, tausche diese bitte auch sehr schnell aus. Sonst hast Du ein Urheberrechtsproblem.

Entsprechende Abmahnungen, nebst Forderungen beginnend im mittleren bis oberen vierstelligen Bereich hat man sehr schnell (zzgl. Anwaltskosten).
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Benutzeravatar
Don Polettone
User
Beiträge: 115
Registriert: Dienstag 23. November 2010, 20:26
Wohnort: Schweiz

ich denke nicht im entferntesten daran, dies zu kommerziellen Zwecken zu vertreiben. Ich habe einfach Spass am coden. Ich möcht's Euch ja nur zeigen damit Ihr mir sagen könnt, was in Bezug auf den Code verbessungsfähig wäre.
Ich code, also bin ich.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

IANAL, aber du hast das Problem schon mit der Veroeffentlichung, nicht erst mit dem Vertrieb.
Je nach Rechtssystem gibt es hier fuer die "Kunst" u.a. groessere Freiheiten, aber ich wuerde es nicht darauf ankommen lassen.
Benutzeravatar
Don Polettone
User
Beiträge: 115
Registriert: Dienstag 23. November 2010, 20:26
Wohnort: Schweiz

hat sich's jemand runtergeladen? Dann kann ich ja den Link löschen und die Sache hat sich erledigt.

Oder will niemand reingugg und mir helfen?

danke
Ich code, also bin ich.
Benutzeravatar
Madmartigan
User
Beiträge: 200
Registriert: Donnerstag 18. Juli 2013, 07:59
Wohnort: Berlin

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.

+ Die Limitation auf den Controller grenzt das ansprechbare Auditorium natürlich gewaltig ein, vielleicht ist es überlegenswert, alternativ eine Tastatursteuerung anzubieten.

+ Die Optionen sind nicht anwählbar, zumindest passiert da nichts. Hätte wirklich gern die Auflösung hoch gesetzt, auf einem 27" Monitor ist die Betrachtung schon grenzwertig.

+ Die Font im unteren Bereich ist sehr schlecht bis gar nicht lesbar.

+ Der Level-Editor macht einen soliden Eindruck, allerdings wäre eine Bedienungsanleitung oder zumindest Hinweise, welche Tasten oder ggf. Shortcuts verfügbar sind, wünschenswert.

+ Look & Feel von dem, was ich bisher sehen konnte, sind auf jeden Fall sehr schön retro, hat mich gleich darauf gebracht, mal wieder Gianna Sisters auf meinem Amiga zu zocken. Erinnerungen.... :-)

Zum Code spare ich mir meine Anmerkungen, die diametral verschiedenen Meinungen hier im Forum strapazieren den Thread ja bereits ausreichend. Zudem empfehle ich dir, dein Projekt dann im Forenbereich "Showcase" vorzustellen. Dieser Thread hatte ja deine Rect-Klasse zum Thema, es war aber kein Feedback-Thread. :wink:
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

bwbg hat geschrieben:@pillmuncher: Wo ist die Lila Pille des lambda-Kalküls erhältlich? Ich in meinem mittleren Alter mit (lediglich) FOR muss mich schon schwer anstrengen, um Deinen Beispielen noch folgen zu können ;)
Mir war halt mathematisch zumut ;). Ich wollte eine minimale Sprache finden, mit der ich die Algebra aufbauen konnte. bounded_by() könnte dabei eigentlich noch allgemeiner formuliert werden:

Code: Alles auswählen

def bounded_by(smaller, bigger, dimensions):
    """
    Create a function that takes any number of n-dimensional cuboids
    and applies the limiters smaller and bigger to the corresponding coordinates.
    """
    limiters = [smaller] * dimensions + [bigger] * dimensions
    def bound(*cuboids):
        "Return the upper or lower bound of cuboids, depending on limiters."
        for limit, coordinates in izip(limiters, izip(*cuboids)):
            yield limit(coordinates)
 
    return bound
Der Gedanke dabei ist, dass paraxiale n-dimensionale Körper, also solche, deren Kanten alle parallel zu den Achsen des Koordinatensystems liegen, in algebraischer Hinsicht gleich behandelt werden können. Solche Körper sind:

Code: Alles auswählen

1-dimensional:  Linie
    inflate = bounded_by(min, max, 1)
    deflate = bounded_by(max, min, 1)
2-dimensional:  Rechteck
    inflate = bounded_by(min, max, 2)
    deflate = bounded_by(max, min, 2)
3-dimensional:  Quader
    inflate = bounded_by(min, max, 3)
    deflate = bounded_by(max, min, 3)
4-dimensional:  Tesserakt
    inflate = bounded_by(min, max, 4)
    deflate = bounded_by(max, min, 4)
...
Mit inflate() und deflate() kann man dann bequem die join und meet Funktionen definieren (enclose() und overlap()), und mit diesen alles andere. Die Verbands-Gesetze sind dann in allen Dimensionen erfüllt.

Dazu gibt es in jeder Dimension zwei Konstanten, gewissermaßen alles und nichts, die das infimum und supremum des gesamten Verbands darstellen. Das mengentheoretische Analogon dazu sind die Allmenge und die Leere Menge. nichts ist in allem enthalten und somit kleiner als alles andere, und alles enthält und ist größer alles andere.

Ich hatte in einem Projekt das Problem, dass ich dasselbe sowohl für Linien als auch für Rechtecke brauchte, und dabei sind mir die Gemeinsamkeiten aufgefallen. Außerdem hatte ein Kollege einen Algorithmus gezimmert, der alle Mengen "transitiv" überlappender Rechtecke finden und deren Bounding Box bilden sollte. Das gelang zwar, allerdings mit einem Laufzeitverhalten von O(n^3). Wenn man den vorher von mir geposteten Algorithmus nimmt, dann sind es bloß noch O(n^2), und wenn man den folgenden nimmt, nur noch O(n log n + k). n ist die Anzahl der Rechtecke und k die Anzahl der Überschneidungen.

Code: Alles auswählen

    @classmethod
    def closed_regions(cls, rects):
        """
        Generate the bounding boxes of all closed regions in rects.

        In other words, find all sets of connected rectangles and generate the
        bounding box of each set.  Two rectangles A and B are connected, if
        they either overlap or if there exists a rectangle C such that both A
        and B are connected to C.

        Since cls.EMPTY has no area, rects that equal cls.EMPTY are silently
        ignored.

        Time complexity is O(n log n + k) on average with respect to the
        numbers n of distinct rects and k of overlaps.
        """

        rects = set(rects)
        # EMPTY has no area:
        rects.discard(cls.EMPTY)

        # Sweep Line Algorithm to set up adjacency sets:
        neighbors = defaultdict(set)
        status = SortedDict(updator=OverlappingIntervalsUpdator)
        events = sorted(chain.from_iterable(
                ((r.left, False, r), (r.right, True, r)) for r in rects))
        for _, is_right, rect in events:
            for interval in status.overlap(rect.vertical):
                neighbors[rect].update(status[interval])
            if is_right:
                status.get(rect.vertical, set()).discard(rect)
            else:
                status.setdefault(rect.vertical, set()).add(rect)

        # Implementation of the well known connected components algorithm for
        # graphs. This works because we view overlapping rectangles as
        # connected nodes in a graph. A closed region then is the bounding box
        # of a connected component, i.e. of  a set of transitively connected
        # nodes in the graph, or, IOW, transitively overlapping rectangles.
        #
        # As Alan Kay puts it: point of view is worth 80 IQ points.

        seen = set()

        def component(node, neighbors=neighbors, seen=seen, see=seen.add):
            """
            Generate all nodes of the component to which node belongs.
            """
            todo = set([node])
            next_todo = todo.pop
            while todo:
                node = next_todo()
                see(node)
                todo |= neighbors[node] - seen
                yield node

        boundingbox = cls.enclose
        for node in neighbors:
            if node not in seen:
                yield boundingbox(*component(node))
SortedDict und OverlappingIntervalsUpdator kommen aus Banyan. Die verwendeten Algorithmen sind Sweep Line und Connected Components.
In specifications, Murphy's Law supersedes Ohm's.
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.
Antworten