zufälliges imageset klappt nicht - Python, Pygame

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

Abend allerseits,

habe voll das Problem gerade und ich verstehe es einfach nicht... weiss jemand eine Lösung?

Es geht darum, dass ich in meinem Ballerspiel den Soldaten(Soldat Klasse) diverse Imagesets verpassen möchte in Form eines dicts. In diesem dict sind die Keys die Namen der Imagesets (z.Bsp. "splatter") und diese verweisen auf eine Liste mit Bildern, also etwa so: imageset = {"splatter" : [Bild, Bild, Bild], "walk" : [Bild, Bild, Bild], etc etc}

Nun ist die Sache, dass ich beim "dead" imageset verschiedene Imagesets habe, und jeder Soldat soll sich quasi beim initialisieren ein zufälliges solches "dead" imageset zuweisen, also self.images["dead"] = #zufälliges imageset#.

Und genau DA liegt das Problem! Eine Armee erzeugt jeweils 6 Soldaten, und diese haben immer dasselbe "dead" imageset - egal, wie ich das code. Es kommen schon beide Imagesets zum Zuge; es wäre also alles da, aber die Soldaten derselben Armee haben immer dasselbe dead imageset.

Evtl. hilft dies hier, mir zu helfen:

Code: Alles auswählen

import pygame
import random

from sprite import My_Sprite
from grafik import bild_laden, bild_spiegeln
from sound import sound_laden
from zufall import fifty_fifty

from soldat_shot import Soldat_Shot

#----------------------------------------------------------------------------------------------------#
#------------------------------    Death Animation Imagesets laden     ------------------------------#
#----------------------------------------------------------------------------------------------------#

bildordner = """Characters\The Good\Soldaten"""

player_numbers = (1, 2)
death_animation_imagesets = {}
anzahl_death_sets = 2

for player_nr in player_numbers:
    death_animation_imagesets[player_nr] = {}

    for n in range(1, anzahl_death_sets + 1):
        death_animation_imagesets[player_nr][n] = []

        for buchstabe in ("a", "b", "c", "d"):
            bildname = "soldat_" + str(player_nr) + "_dead_" + str(n) + "_" + buchstabe + ".png"
            bild = bild_laden(bildordner, bildname, True)
            death_animation_imagesets[player_nr][n].append(bild)

#----------------------------------------------------------------------------------------------------#

class Soldat(My_Sprite):

    splatter_sound = sound_laden("Sound FX", "splatter.wav")
    splatter_sound.set_volume(0.3)

    imagesets = {}

    imageset_types = ("stand", "walk", "splatter")

    for player_nr in player_numbers:
        imagesets[player_nr] = {}

        for imageset_type in imageset_types:
            imagesets[player_nr][imageset_type] = []

            if imageset_type == "stand":
                
                bildname = "soldat_" + str(player_nr) + ".png"
                bild = bild_laden(bildordner, bildname, True)
                imagesets[player_nr][imageset_type].append(bild)

            else:

                image_types = ("a", "b")

                for image_type in image_types:
                    bildname = "soldat_" + str(player_nr) + "_" + imageset_type + "_" + image_type + ".png"
                    bild = bild_laden(bildordner, bildname, True)
                    imagesets[player_nr][imageset_type].append(bild)


    def __init__(self, army, pos):

        self.army = army
        self.speed = army.speed
        self.number = self.army.player.number

        self.shot = False
        self.corpse = False

        self.images = Soldat.imagesets[self.number]
        
        self.images["dead"] = death_animation_imagesets[self.number][random.randint(1, anzahl_death_sets)]
Hat jemand eine Idee..?

Danke (auch für's Lesen :mrgreen: )
Ich code, also bin ich.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo.

Das liegt daran, dass alle ``self.images`` auf das selbe Objekt zeigen, nämlich auf ``Soldat.imagesets``. Insgesamt hat dein Code aber viele Probleme:

- Der ganze Code in der Soldat-Klasse gehört dort nicht hin. Dort entsteht auch dein Problem, da der gesamte Code nur einmal ausgeführt wird. Offensichtlich hast du hier noch große Schwierigkeiten mit der Klassensemantik von Python. Da solltest du noch einmal genauer nachlesen.
- Dein zufall-Modul sieht unnötig aus
- Schaue dir PEP 8 an
- Du hast zu viel Code auf Modulebene und solltest mehr Funktionen einsetzen
- Entscheide dich für deutsche oder (vorzugsweise) englische Bezeichner, aber mische nicht beide Sprachen
- Indizes fangen normalerweise bei 0 an und nicht bei 1. Dann fallen auch einige Sonderbehandlungen bei dir weg.
- Um ein Tupel zu erzeugen brauchst du keine Klammern
- ``for buchstabe in ("a", ..., "d")`` kannst du viel kürzer als ``for buchstabe in "abcd"`` schreiben.
- Strings solltest du nicht mit + zusammensetzen, sondern mittels String Formatting
- Für Ressourcen solltest du einen eigenen Manager einführen, damit diese nur einmal geladen werden. Ein Soldat weiß im besten fall nichts davon, wie er zu zeichnen ist, sondern nur was für Eigenschaften er hat (Geschwindigkeit, Healt, etc.)
- Benutze Konstanten
- Nahezu identischer Code sollte in Funktionen ausgelagert werden
- ``number`` ist total nichtssagend.

Sebastian
Das Leben ist wie ein Tennisball.
Benutzeravatar
Don Polettone
User
Beiträge: 115
Registriert: Dienstag 23. November 2010, 20:26
Wohnort: Schweiz

Hi Sebastian,

vielen Dank für die Ratschläge!

Demnach wäre es sicher mal besser, im soldat Modul z.Bsp. am Anfang eine Funktion zu machen wie get_imagesets() anstelle das Zeugs "lose" einzulesen?

Und dann ist es so, dass, wenn ich im Army Modul eine Armee zusammenstelle aus Soldat - Objekten, die __init__Methode von Soldat nur einmal ausgeführt wird, obwohl ich 10 Soldatobjekte mache (for range(0, 10): new_soldat = Soldat()? Das ist mir in der Tat neu... und ja - ich hab tatsächlich mit Klassen resp. Klassen- und Instanzvariablen noch etwas Mühe - zum einen beim Geltungsbereich und zum anderen wo eines dem anderen vorzuziehen wäre. Gibt es dazu ein gutes Tutorial von wegen Geltungsbereich von Klassenvariablen und Instanzvariablen?

Und sorry aber... ...wie machst Du Listen ohne zuerst die Liste zu erstellen und dann in einer Schleife Objekte hinzuzufügen..? Dies übersteigt meinen momentanen Horizont

von wegen Sprachmix: Ich weiss und ich möchte und es geht immer mehr Richtung Englisch; IMMER ist dies aber schwer durchzusetzen - ich habe gerne Variablennamen, die mir das sagen, was ich willen will, und im Deutschen geht dass eben manchmal besser. Aber Du hast zweifelsohne Recht und ich versuche, dem weiter näherzukommen.

Das mit "abcde" hatte ich sogar schon mal so gemacht... habe es aber wieder vergessen, dass man ja über Strings iterieren kann... na ja.

Du sagst, der Soldat solle im Idealfall nicht wissen, "wie er zu zeichnen ist". Ist aber doch schon sinnvoll, wenn er sich unter self.images seine Imagesets einliest und dann einfach das imageset ändert und den imageindex, damit das ganze viriabel ist unanbhängig vom jeweiligen imageset..? Oder bin ich da bereits auf dem falschen Weg..? (Zur Erklärung; das ganze unten ist natütterlich ein Sniplet innerhalb eines ganzen Games; es hat eine Game Klasse, eine Player Klasse etc etc etc; die Game Klasse führt das ganze zusammen und hat Methoden wie blit_soldiers() etc.

Bzgl. PEP: Habs mir schon mal etwas angeschaut; macht schon Sinn, danke.

vielen Dank und viele Grüsse,


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

Hallo.

Ich dein Problem einfach mal in zwei Teile zu zerteilen. Als Grundlage ist es hier wichtig zu wissen, dass es in Python veränderliche (mutable) und nicht-veränderliche (immutable) Objekte gibt. Zu ersterem gehören zum Beispiel Dictionaries und Listen, zu letzterem Tupel, Strings, Integer, Floats und Komplexe Zahlen. Wenn du eine Operation auf einem unveränderlichen Typ durchführst, dann wird immer ein neues Objekt erzeugt:

Code: Alles auswählen

>>> a = 42
>>> b = a
>>> id(a), id(b)
(36805544, 36805544)
>>> a += 1
>>> a, b
(43, 42)
>>> id(a), id(b)
(36805520, 36805544)
Bei veränderlichen Typen wird das im allgemeinen nicht gemacht:

Code: Alles auswählen

>>> a = [42, 23]
>>> b = a
>>> id(a), id(b)
(140122475860696, 140122475860696)
>>> a[1] = 0
>>> a, b
([42, 0], [42, 0])
>>> id(a), id(b)
(140122475860696, 140122475860696)
Hier liegt auch dein Problem. Du hast eine Liste von Bildern und änderst das letzte Element. Dies wird sich dann auf alle Soldatenobjekte aus und nicht auf das aktuell erzeugte.

Das zweite Problem ist, dass die Bilder auf der Ebene der Klasse geladen werden. Alles was an eine Klasse gebunden ist, existiert nur einmal und nicht als Kopie zu jedem Objekt.

Code: Alles auswählen

>>> class Spam(object):
...     eggs = []
... 
>>> s, p = Spam(), Spam()
>>> s.eggs, p.eggs
([], [])
>>> id(s.eggs), id(p.eggs)
(140122476011176, 140122476011176)
>>> s.eggs.append(42)
>>> s.eggs, p.eggs
([42], [42])
>>> id(s.eggs), id(p.eggs)
(140122476011176, 140122476011176)
Ganz grob gesagt wird alles was im class-Block steht genau einmal ausgeführt. Im Beispiel oben wird zum Beispiel genau einmal das Attribut ``eggs`` der Klasse hinzugefügt. Die Methoden verhalten sich nicht anders: Die Definition der Methode wird genau einmal durchgeführt, einen Aufruf kannst du beliebig oft schreiben. Daher wird die __init__-Methode auch bei jedem Aufruft von Soldat() wieder ausgeführt. Wenn du dann allerdings an die Soldatinstanz ein Attribut der Klasse bindest, dann ist dies für alle Instanzen das selbe. Am einfachsten ist es also, wenn du in der __init__-Methode eine Kopie von ``imagesets`` erzeugst:

Code: Alles auswählen

>>> class Eggs(object):
...     default = [23, 42]
...     def __init__(self, x):
...         self.values = Eggs.default[:]                                                                                  
...         self.values.append(x)                                                                                          
...                                                                                                                        
>>> e, f = Eggs(1), Eggs(2)                                                                                                
>>> e.values, f.values                                                                                                     
([23, 42, 1], [23, 42, 2])                                                                                                 
>>> id(e.values), id(f.values)
(140122476044368, 140122476011104)
Da Python immer mit Referenzen arbeitet und niemals Kopien von Objekten erstellt, außer mal fordert dies explizit an, musst du aber auch hier vorsichtig sein, da nur flache Kopien erstellt werden:

Code: Alles auswählen

>>> l = [[42]]                                                                                                             
>>> m = l[:]                                                                                                               
>>> l, m                                                                                                                   
([[42]], [[42]])                                                                                                           
>>> l[0].append(23)
>>> l, m                                                                                                                   
([[42, 23]], [[42, 23]])
Zum Zeichnen: Der Soldat weiß im Idealfall nichts davon wie er zu zeichnen ist, da dies eine Vermischung von verschiedenen Konzepten mit sich bringt und keine Trennung mehr im Code gegeben ist. Daher werden auch bei Spielen, analog zum MVC-Muster, die Daten, die Ansicht und der Controller geteilt. Gerade bei Spielen bietet es sich an, da dann Modelle, Texturen und Sounds nicht doppelt und dreifach geladen werden müssen. Dazu wird dann, der von mir bereits erwähnte Ressourcenmanager, oder auch mehrere davon, eingesetzt.

Sebastian
Das Leben ist wie ein Tennisball.
Benutzeravatar
Don Polettone
User
Beiträge: 115
Registriert: Dienstag 23. November 2010, 20:26
Wohnort: Schweiz

Hey ey Du :mrgreen:

Danke! Hilfreich! Sehr sogar.

Nur noch etwas bzgl. dem erwähnten "Ressourcenmanager": Wie wärs mit einem separaten imageset Modul in meinem Systemordner? dieses hätte dann sicher eine Methode wie get_imageset, welche den erzeugten Spriteobjekten deren Bilder "liefert" beim Initialisieren? Das wäre doch schon mal "pythonischer", oder? Aber ist es auch das, was Du mit "Ressourcenmanager" meinst..?

Es grüsst,


Henry

bzgl. string formatting: Habe gerade gesehen dass es das erst ab Version 2.6 gibt..? Auf der Pygameseite heisst es noch immer 2.5 sei im Moment die stabilste Version... Muss ich das glauben oder wird die Seite einfach nicht mehr "bewirtschaftet"..? Weiss das jemand?
Ich code, also bin ich.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo.

Du brauchst im Prinzip mehrere Ebenen von Managern. Als Basis würde zum Beispiel ein Manager für Texturen dienen:

Code: Alles auswählen

>>> class Manager(object):
...     def __init__(self):
...         self.ressources = {}
...     def get_ressource(self, name):
...         try:
...             return self.ressources[name]
...         except KeyError:
...             #load ressource here
...             res = name
...             self.ressources[name] = res
...             return res
...                                                                                                                        
>>> m = Manager()
>>> m.get_ressource(1)
1                                                                                                                          
>>> m.get_ressource(2)                                                                                                    
2
Mittels ``get_ressource`` könnten Texturen geladen werden, was einfach mal als Kommentar angedeutet ist. Ist die Textur bereits geladen, so wird einfach eine Referenz zurückgeliefert. Damit ist sichergestellt, dass du dich nicht unnötig zumüllst. Damit hast du nun ein Mapping von Texturname - im einfachsten Fall ein Dateiname - auf eine konkrete Textur.

Da zu zu jedem Soldaten eine andere Menge an Texturen haben willst, brauchen deine Soldaten erstmal eine Id. Ich habe das mal beispielhaft über einen Namen gelöst:

Code: Alles auswählen

>>> class Soldier(object):
...     def __init__(self, name):
...         self.name = name
...     def __hash__(self):
...         return hash(self.name)
Nun brauchst du nur noch einen Manager um die Texturen mit den Soldaten zu verbinden:

Code: Alles auswählen

>>> class TextureSets(object):
...     def __init__(self, manager):
...         self.manager = manager
...         self.sets = {}
...     def add(self, soldier, textures):
...         textures = map(self.manager.get_ressource, textures)
...         self.sets[soldier] = textures
... 
>>> p = Soldier("spam")
>>> q = Soldier("eggs")
>>> sets = TextureSets(m)
>>> sets.add(p, (1,2,3))
>>> sets.add(q, (1,2,4))
>>> sets.sets[p]
[1, 2, 3]
>>> sets.sets[q]
[1, 2, 4]
Die Tupel, welche an ``add`` übergeben werden, können nun aus Dateinamen bestehen, diese werden dann automatisch geladen und den Soldaten zugewiesen. Beim Zeichnen kannst du nun bei den TextureSets für jeden Soldaten nachfragen, welche Menge an Texturen verwendet werden soll.

Insgesamt könnte man an den Klassen noch etwas verfeinern, so dass die get- und add-Methoden mittels überladenem Indexoperator realisiert werden, das fand ich als Beispiel aber nicht besonders übersichtlich. Das kannst du bei deiner Implementierung selber umsetzen. Beim Manager bietet sich noch ein defaultdict an.

Vielleicht noch etwas zu deiner Modulaufteilung: Nicht jede Klasse sollte in eine eigene Datei gepackt werden. Wenn zwei Klassen zusammen gehören, dann sollten diese auch in einem Modul stehen.
Das Leben ist wie ein Tennisball.
BlackJack

@Henry Jones Jr.: Die `format()`-Methode auf Zeichenketten gibt es noch nicht so lange, aber Formatierung mittels ``%`` gab es auch in 2.5 schon und war auch damals schon ``+`` vorzuziehen.
Antworten