Pygame-Spiel ruckelt/ braucht extrem viel CPU

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Anagkai
User
Beiträge: 8
Registriert: Donnerstag 15. Mai 2014, 09:35

Hallo allerseits,
ich bin neu in diesem Forum, aber nicht bei Python.
Momentan bin ich dabei, ein Videospiel mit Pygame zu programmieren. Im Großen und Ganzen funktioniert das gut, allerdings habe ich ein gewisses Problem mit Ressourcen-Verbrauch. Wenn ich mein Spiel laufen lasse, habe ich eine CPU-Auslastung ven 60 - 80%. Zudem ruckelt das Spiel. Es soll mit 32 fps laufen.
Das ist natürlich nicht so schön.
An meinem Rechner kann es nicht liegen, der hat 2,7 Ghz Dual Core und da laufen auch Sachen wie Skyrim und Supreme Commander II drauf. Daher liegt es wohl an meiner Programmierung.
Durch ein paar hundert Abstandsberechnungen pro Frame kann man so einen Prozessor sicher nicht auslasten, daher vermute ich, dass es am Blitten liegt.
Das bestätigt auch meine bisherige Recherche. Ich habe gelesen, dass man auf keinen Fall Bilder drehen soll, also habe ich dafür gesorgt, dass alle Bilder am Anfang gedreht werden, damit ich meine acht Richtungen habe. Allerdings hat sich bis dahin nur die Spielerfigur gedreht, daher war die Einsparung nicht so der Hammer. Jedoch sicher gut, sobald Monster dazukommen.
Was mich wundert ist, dass ich vorher schon Spiele mit deutlich mehr Bildchen hatte, die problemlos liefen.
Liegt es möglicherweise an teiltransparenten Bildern?
Wäre toll, wenn mir jemand ein paar Tipps geben könnte.

Ach ja, falls das wichtig ist: Es geht um ein 2D-MMORPG mit Perspektive von oben.
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

Gehen wir mal davon aus, dein Python Code ist einigermaßen performant. Ich könnte mir vorstelle GIL ist daran nicht ganz unschuldig, dass die Auslastung so hoch ist.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Du könntest ja auch mal nen Profiler über dein Programm jagen und somit herausfinden, an welchen Stellen im Code die meiste Zeit benötigt wird. Dann hättest du vermutlich eine etwas bessere Grundlage für die Ursachenforschung.
BlackJack

@darktrym: Was soll das GIL damit zu tun haben? Von Threads steht doch da gar nichts? Und bei der CPU-Auslastung wäre das GIL potentiell eher für *weniger* CPU-Auslastung verantwortlich, denn ohne könnte mehr Code parallel laufen.
Anagkai
User
Beiträge: 8
Registriert: Donnerstag 15. Mai 2014, 09:35

Threads habe ich keine. Werde das mit dem Profiling mal versuchen und feststellen, was da so langsam ist.

EDIT: Von 25.9s Ausführung gingen unter anderem 6.1 in pygame.surface.convert, 4.4 in pygame.surface.fill und 2.8 ins Blitten, außerdem 4.7 für Clock und Display-Update.
Letzteres lässt sich wohl kaum sparen, allerdings sollte sich das mit dem Converting vermeiden lassen, oder?
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Surface::convert sollte nur einmal aufgerufen werden, wenn die Resource geladen wird.

Darüber hinaus kann man "Einsparpotentiale" beim Blitten realisieren, in dem lediglich gezeichnet wird, was sich tatsächlich geändert hat. pygame.display.update nimmt eine Liste von Rects entgegen, welche aktualisiert werden sollen.

Grüße ... bwbg
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Anagkai
User
Beiträge: 8
Registriert: Donnerstag 15. Mai 2014, 09:35

Das mit dem Ändern ist so eine Sache. Erstens ändert sich in der Regel ständig etwas. Zweitens wird es sehr kompliziert, wenn man da unterscheidet und drittens soll das Spiel auch dann flüssig laufen, wenn sich viel ändert. Wenn es dann, während sich wenig ändert, etwas mehr CPU braucht als nötig wäre, ist mir das egal. Es geht ja in erster Linie darum, das Ruckeln weg zu bekommen.

Das mit dem convert ist mir vorhin auch schon gekommen, da muss ich mal schauen.

EDIT: Habe das gefixt, am Ruckeln ändert das jedoch nichts.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Ohne Quelltext kann man nicht viel sagen/schreiben. Vielleicht hast Du ja bereits ein Repository online.
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Anagkai
User
Beiträge: 8
Registriert: Donnerstag 15. Mai 2014, 09:35

Code: Alles auswählen

#Worlds
#World
class World(object):
    def __init__(self, game, name, color, xSize, ySize):
        self.game = game
        self.name = name
        self.color = color
        self.xsize = xSize
        self.ysize = ySize
        self.background = pygame.surface.Surface((self.game.screenX, self.game.screenY)).convert()
        self.background.fill(self.color)
        self.entities = {}
        self.border = {}
        self.entity_id = 0
        
    def addEntity(self, entity):
        self.entities[self.entity_id] = entity
        entity.id = self.entity_id
        self.entity_id += 1
        
    def addBorderEntity(self, entity):
        self.border[self.entity_id] = entity
        entity.id = self.entity_id
        self.entity_id += 1
        
    def removeEntity(self, entity):
        del self.entities[entity.id]
        
    def getEntity(self, entity_id):
        if entity_id in self.entities:
            return self.entities[entity_id]
        else:
            return None
            
    def process(self, time_passed):
        time_passed_seconds = time_passed / 1000.0
        for entity in self.entities.values():
            entity.process(time_passed_seconds)
            
    def render(self, surface, x, y, xSize, ySize):
        xblit = xSize / 2 -48
        yblit = ySize / 2 -48   
        
        if self.game.player.location.x < xSize / 2:
            surfaceX = xblit - self.game.player.location.x + 48
            surfaceXS = xSize
        elif self.game.player.location.x > self.xsize - xSize / 2: 
            surfaceX = 0
            surfaceXS = xSize - (xSize / 2 - (self.xsize -self.game.player.location.x))
        else:
            surfaceX = 0                      
            surfaceXS = xSize
        if self.game.player.location.y < ySize / 2:
            surfaceY = yblit - self.game.player.location.y + 48
            surfaceYS = xSize
        elif self.game.player.location.y > self.ysize - ySize / 2: 
            surfaceY = 0
            surfaceYS = ySize - (ySize / 2 - (self.ysize - self.game.player.location.y))
        else:
            surfaceY = 0                       
            surfaceYS = ySize
          
        if self.game.player.location.x > self.xsize - xSize - 100 or self.game.player.location.y > self.ysize - ySize - 100:
            self.background = pygame.surface.Surface((surfaceXS, surfaceYS)).convert()
            self.background.fill(self.color)
        surface.blit(self.background, (surfaceX, surfaceY))

        for entity in self.border.itervalues():
            entity.render(surface, x, y, xSize, ySize)
            
        for entity in self.entities.itervalues():
            if entity.getRenderType() == 0:
                entity.render(surface, x, y, xSize, ySize)
        for entity in self.entities.itervalues():
            if entity.getRenderType() == 1:
                entity.render(surface, x, y, xSize, ySize)
        for entity in self.entities.itervalues():
            if entity.getRenderType() == 2:
                entity.render(surface, x, y, xSize, ySize)
            
    def getCloseEntity(self, name, x, y, searchRange=100):
        for entity in self.entities.itervalues():
            if entity.name == name:
                distance = entity.get_distance_to(x, y)
                if distance < searchRange:
                    return entity
        return None
        
    def getPointEntity(self, x, y, searchRange=24):
        for entity in self.entities.itervalues():
            distance = entity.get_distance_to(x, y)
            if distance < searchRange:
                return entity
        return None        
        
    def getPassability(self, x, y, radius):
        for entity in self.entities.itervalues():
            if entity.isPassable() == False:
                if entity.get_distance_type() == 0:
                    if entity.get_distance_to(x, y) < entity.sizeradius + radius:
                        return False
                else:
                    if entity.get_x_distance(x) < entity.sizeradius + radius and entity.get_y_distance(y) < entity.sizeradius + radius:
                        return False
        return True

#Entities
#GameEntity
class GameEntity(object):
    def __init__(self, name, world, images, x, y, speedModifier, sizeradius, animseq, animn):
        self.world = world
        self.name = name
        self.images = images
        self.location = Vector2(x, y)
        self.destination = Vector2(x, y)
        self.speedModifier = speedModifier
        self.speed = self.speedModifier * 96
        self.entityID = 0
        self.sizeradius = sizeradius
        self.image_to_render1 = 0
        self.image_to_render2 = 0
        self.animn = animn
        self.animseq = animseq
        self.time_passed = 0.0
        self.moving = False
        
    def render(self, surface, x, y, xSize, ySize):
        if self.images[0] != None:
            if self.location.x < x + xSize/2 + 100 and self.location.x > x - xSize/2 - 100 and self.location.y < y + ySize/2 + 100 and self.location.y > y - ySize/2 - 100:
                blit_x = xSize/2 -(x - self.location.x) 
                blit_y = ySize/2 - (y - self.location.y)
                if self.is_animated() == True and self.can_rotate() == True:
                    if self.moving == True: 
                        rendering_image = self.images[self.animseq[self.image_to_render1]]
                    else:
                        rendering_image = self.images[0]
                    rendering_image1 = rendering_image[self.image_to_render2]
                else:
                    rendering_image1 = self.images[0]               
                
                if self.name == "player":
                    surface.blit(rendering_image1,(blit_x-48, blit_y-48)) 
                else: 
                    surface.blit(rendering_image1,(blit_x, blit_y)) 
        
    def process(self, time_passed):
        
        if self.location != self.destination and self.animseq != None:
            self.time_passed += time_passed
            if self.time_passed > 0.25:
                self.time_passed -= 0.25
                self.image_to_render1 += 1
                if self.image_to_render1 > self.animn:
                    self.image_to_render1 = 0
                            
        if self.speedModifier > 0:
            if self.location != self.destination:
                vec_to_destination = self.destination - self.location
                distance_to_destination = vec_to_destination.getMagnitude()
                heading = vec_to_destination
                heading.normalize()
                travelDistance = min(distance_to_destination, time_passed * self.speed)
                processDistance = self.location + heading.__mul__(travelDistance)
                if self.world.getPassability(processDistance.x, processDistance.y, self.sizeradius) == True:
                    self.location = processDistance
                else:
                    self.set_moving(False)
                if distance_to_destination <= 6.0:
                    self.set_moving(False)
                else:
                    rotation = self.setRotation()
                    self.image_to_render2 = rotation
      
    def getAngle(self):
        vs = Vector2(-1.0, 0.0)
        vtd = self.destination - self.location
        if vtd.x != 0 or vtd.y != 0:
            cosvalue = (vs.x * vtd.x + vs.y * vtd.y) / (sqrt(vs.x**2 + vs.y**2) * (sqrt(vtd.x**2 + vtd.y**2)))
            finalvalue = acos(cosvalue) / pi * 180
            if vtd.y > 0:
                finalvalue = 360 - finalvalue
        else:
            finalvalue = 0
        return finalvalue    

    def setRotation(self):
        rotationAngle = self.getAngle()
        hvalue = rotationAngle / 22.5
        if hvalue > 1 and hvalue <= 3:
            image_to_render = 1
        elif hvalue > 3 and hvalue <= 5:
            image_to_render = 2
        elif hvalue > 5 and hvalue <= 7:
            image_to_render = 3
        elif hvalue > 7 and hvalue <= 9:
            image_to_render = 4
        elif hvalue > 9 and hvalue <= 11:
            image_to_render = 5
        elif hvalue > 11 and hvalue <= 13:
            image_to_render = 6
        elif hvalue > 13 and hvalue <= 15:
            image_to_render = 7
        else:
            image_to_render = 0
        return image_to_render
 
#Living Entity           
class LivingEntity(GameEntity):
    def __init__(self, name, world, image, x, y, speedModifier, sizeradius, animseq, animn):
        GameEntity.__init__(self, name, world, image, x, y, speedModifier, sizeradius, animseq, animn)
    
#PLayer        
class Player(LivingEntity):
    def __init__(self, name, world, image, x, y, speedModifier, sizeradius, inv_image, filler, shadow, scx, scy, animseq, animn):
        LivingEntity.__init__(self, name, world, image, x, y, speedModifier, sizeradius, animseq, animn)
        self.inventory = None
        self.scx = scx
        self.scy = scy
        self.add_inventory(inv_image, filler, shadow)

#Obstacle             
class Obstacle(GameEntity):
    def __init__(self, name, world, images, x, y, sizeradius, animseq, animn):
        GameEntity.__init__(self, name, world, images, x, y, 0.0, sizeradius, animseq, animn)
        
#Square Obstacle        
class SquareObstacle(Obstacle):
    def __init__(self, name, world, images, x, y, sizeradius, animseq, animn):
        Obstacle.__init__(self, name, world, images, x, y, sizeradius, animseq, animn)
    
class Collectible(Obstacle):
    def __init__(self, name, world, images, x, y, sizeradius, animseq, animn):
        Obstacle.__init__(self, name, world, images, x, y, sizeradius, animseq, animn) 
        self.drop = None
        
class Flower(Collectible):
    def __init__(self, name, world, images, x, y, sizeradius, animseq, animn):
         Collectible.__init__(self, name, world, images, x, y, sizeradius, animseq, animn) 
        
class Trunk(Obstacle):
    def __init__(self, name, world, x, y, sizeradius, animseq, animn):
         Obstacle.__init__(self, name, world, [None], x, y, sizeradius, animseq, animn)  
         
class Leaves(GameEntity):
    def __init__(self, name, world, images, x, y, animseq, animn):
        GameEntity.__init__(self, name, world, images, x, y, 0.0, 0, animseq, animn)   

#Cursor
class Cursor(object):
    def __init__(self, image, move_image):
        self.image = image
        self.stack = None
        self.move_image = move_image
        
    def render(self, surface, x, y, image = None):
        if self.stack == None:
            if image == None:
                image_to_render = self.image
            else:
                image_to_render = image
        else: 
            image_to_render = self.stack.item.image
        surface.blit(image_to_render,(x, y)) 
        if self.stack != None:
            surface.blit(self.move_image,(x, y)) 

#Main
#Game        
class Game(object):
    def __init__(self):
        self.worlds = []
        self.currentWorld = 0
        self.guis = []
        self.player = None
        self.cursor = None
        self.screenX = 1920
        self.screenY = 1080
        self.run()
        
    def run(self):
        pygame.init()
        screen = pygame.display.set_mode((self.screenX, self.screenY), FULLSCREEN, 32)
        clock = pygame.time.Clock()
        font_inv_index = pygame.font.SysFont("arial", 16)
        self.cursor = Cursor(cursor_normal, cursor_move)
        
        self.addWorld(self, "0", gras, 4800, 4800) 
        self.worlds[self.currentWorld].addBorder(cfelsen, gfelsen1, gfelsen2)
        self.player = Player("player", self.worlds[self.currentWorld], [pnlist, pllist, prlist], 2112, 2112, 1.0, 24, inventory1, invfiller, invshade, self.screenX, self.screenY, [0, 1, 0, 2], 3)
        self.worlds[self.currentWorld].addEntity(self.player)
        self.worlds[self.currentWorld].addEntity(Obstacle("felsen", self.worlds[self.currentWorld], [felsen1], 480, 480, 48, None, 0))
        self.worlds[self.currentWorld].addEntity(Obstacle("felsen", self.worlds[self.currentWorld], [felsen1], 1920, 480, 48, None, 0))
        self.worlds[self.currentWorld].addEntity(Obstacle("felsen", self.worlds[self.currentWorld], [felsen1], 960, 672, 48, None, 0))
        self.worlds[self.currentWorld].addEntity(Obstacle("felsen", self.worlds[self.currentWorld], [felsen1], 2400, 2400, 48, None, 0))
        self.worlds[self.currentWorld].addEntity(Flower("sonnenblume", self.worlds[self.currentWorld], [pygame.transform.rotate(sunflower, 90 * randint(0,3))], 2300, 2300, 24, None, 0))
        self.worlds[self.currentWorld].addEntity(Flower("sonnenblume", self.worlds[self.currentWorld], [pygame.transform.rotate(sunflower, 90 * randint(0,3))], 2200, 2400, 24, None, 0))
        self.worlds[self.currentWorld].addEntity(Flower("sonnenblume", self.worlds[self.currentWorld], [pygame.transform.rotate(sunflower, 90 * randint(0,3))], 2400, 2200, 24, None, 0))
        self.worlds[self.currentWorld].addEntity(Flower("sonnenblume", self.worlds[self.currentWorld], [pygame.transform.rotate(sunflower, 90 * randint(0,3))], 2200, 2300, 24, None, 0))
        self.worlds[self.currentWorld].addEntity(Flower("sonnenblume", self.worlds[self.currentWorld], [pygame.transform.rotate(sunflower, 90 * randint(0,3))], 2300, 2200, 24, None, 0))
        self.worlds[self.currentWorld].addEntity(Flower("sonnenblume", self.worlds[self.currentWorld], [pygame.transform.rotate(sunflower, 90 * randint(0,3))], 2400, 2300, 24, None, 0))
        self.worlds[self.currentWorld].addEntity(Obstacle("felsen", self.worlds[self.currentWorld], [felsen2], 2600, 2600, 48, None, 0))
        self.worlds[self.currentWorld].addTree(self.worlds[self.currentWorld], crown, 1000, 1000)   
        self.worlds[self.currentWorld].addTree(self.worlds[self.currentWorld], crown, 2000, 2900)  
        
        pygame.mouse.set_visible(False)
        
        while True:
            #print(round(self.player.location.x, 0), round(self.player.location.y, 0), round(self.player.destination.x, 0), round(self.player.destination.y, 0))            
            for event in pygame.event.get():
                if event.type==QUIT:
                    exit()
                elif event.type==MOUSEBUTTONDOWN:
                    if event.button == 1:
                        x, y = pygame.mouse.get_pos()
                        gui_in = None
                        for gui in self.guis:
                            if gui.check_inside(self.screenX, self.screenY, x, y) == True:
                                gui_in = gui  
                            
                        if gui_in != None:
                            gui_in.click_at(x, y, self.screenX, self.screenY, self.cursor, 0)
                        else:
                            realX = self.player.location.x - self.screenX / 2 + x
                            if realX < 120:
                                realX = 120
                            elif realX > self.worlds[self.currentWorld].xsize - 120:
                                realX = self.worlds[self.currentWorld].xsize - 120    
                            realY = self.player.location.y - self.screenY / 2 + y
                            if realY < 120:
                                realY = 120
                            elif realY > self.worlds[self.currentWorld].ysize - 120:
                                realY = self.worlds[self.currentWorld].ysize - 120                        
                            self.player.destination = Vector2(realX, realY)
                            self.player.set_moving(True)
                    elif event.button == 3:
                        x, y = pygame.mouse.get_pos()
                        gui_in = None
                        for gui in self.guis:
                            if gui.check_inside(self.screenX, self.screenY, x, y) == True:
                                gui_in = gui  
                                
                        if gui_in != None:
                            gui_in.click_at(x, y, self.screenX, self.screenY, self.cursor, 1)    
                elif event.type==KEYDOWN:
                    if event.key == K_i:
                        self.player.inventory.toggle()
            
            time_passed = clock.tick(32)
            screen.fill(grau)
            self.worlds[self.currentWorld].process(time_passed)
           
            x2, y2 = pygame.mouse.get_pos()
            realX2 = self.player.location.x - self.screenX / 2 + x2
            realY2 = self.player.location.y - self.screenY / 2 + y2
            
            if x2 > 100 and x2 < self.screenX - 100 and y2 > 100 and y2 < self.screenY - 100:
                close_entity = self.worlds[self.currentWorld].getPointEntity(realX2, realY2)  
            else:
                close_entity = None
            cursor_image = None
            
            if close_entity != None:
                if close_entity.isCollectible() == True:
                    cursor_image = cursor_flower
                else:
                    self.cursor_image = cursor_normal
            
            screen.set_clip(0, 0, 1920, 1080)
            self.worlds[self.currentWorld].render(screen, self.player.location.x, self.player.location.y, self.screenX, self.screenY)
            self.player.inventory.render(screen, self.screenX, self.screenY, font_inv_index)
            self.cursor.render(screen, x2, y2, image = cursor_image) 
                
            pygame.display.update()
    
    def addWorld(self, game, name, color, xSize, ySize):
        world = World(self, name, color, xSize, ySize)
        self.worlds.append(world)
        
    def addGUI(self, gui):
        self.guis.append(gui)
        
Game()        
Das ist ein Auszug aus meinem Code, der Teil, der vemutlich problematisch ist.
Der Prozessor braucht jetzt nur noch 30 - 50%, seit ich das mit dem convert gefixt habe, ruckeln tut es aber immer noch.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Auf den ersten Blick sieht die World::render Method ziemlich "gewachsen" aus. Diese ruft für jedes Objekt nochmals eine ziemlich "gewachsene" render-Methode auf. Hier kannst Du eine Menge aufräumen.

Es gibt Sprites und Sprite-Groups. Diese solltest du nutzen.
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
BlackJack

@Anagkai: Ein paar allgemeine Anmerkungen:

Die Namenschreibweisen halten sich nicht an den Style Guide for Python Code.

Was ist das mit der `World.entity_id`? Objekte haben bereits von Haus aus eine ID die man mit der `id()`-Funktion abfragen kann. Und falls das selbe Objekt nicht mehrfach in ein Wörterbuch gesteckt wird, und weder `__hash__()` noch `__eq__()` oder `__cmp__()` überschrieben wurde, könnte man Objekte auch direkt in ein `set()` stecken.

Wird `World.getEntity()` tatsächlich irgendwo benutzt? Und warum übergibt man dort eine ID, also warum wird irgendwo von einem Objekt die ID abgefragt statt das Objekt selbst zu verwenden um dann mit der ID irgendwann wieder das Objekt über diese Methode zu bekommen? Das erscheint mir unnötig umständlich. Die Implementierung selbst von dieser Methode übrigens auch, denn das ist eigentlich ein Einzeiler:

Code: Alles auswählen

    def get_entity(self, entity_id):
        return self.entities.get(entity_id)
Bei `World.render()` wären ein Kommentar nicht schlecht was die ganzen ``if``/``elif``-Zweige am Anfang letztendlich bewirken sollen.

Zumindest die letzten drei Schleifen sind unschön und ineffizient. Das Unschöne ist die selbst bei der Ineffizienz die Quelltextwiederholung — das hätte man in eine Schleife stecken können. Und ineffizient ist es weil dreimal die gleichen Objekte durchlaufen werden und jedes mal der „render type” abgefragt wird. Besser wäre es die Objekte gleich so zu organisieren dass sie *einmal* in der richtigen Reihenfolge durchlaufen werden können. In dem man sie pro Rendertyp in eine eigene Datenstruktur steckt, oder in einer Liste hält die nach dem Typ sortiert ist zum Beispiel.

An der Stelle steckt aber vielleicht noch mehr Beschleunigungspotential über den Vorschlag der ja schon mehrfach gemacht wurde: Nicht immmer *alles* neu zusammenblitten, sondern nur die Felder die sich auch wirklich geändert haben.

Die drei `World.get*()`-Methoden am Ende sind sehr ineffizient. Bei vielen Entity-Objekten wird das fies langsam.

Die vorletzten beiden Methoden sind fast identisch.

Vergleiche mit literalen `True` oder `False` sind überflüssig weil da sowieso nur wieder `True` oder `False` bei heraus kommt. Also kann man auch gleich die andere Seite des Vergleichs direkt verwenden oder mit ``not`` umkehren. Ähnliches gilt für ein ``if`` bei dem der einzige Code im Zweig die Rückgabe eines Wahrheitswertes ist. Da kann man auch gleich das Ergebnis der Bedingung zurückgeben, gegebenenfalls negiert.

Magische Zahlen sind unschön. Bei so etwas wie ``if entity.get_distance_type() == 0:`` weiss man nicht was diese 0 eigentlich bedeutet.

Der Test nach dieser Unterscheidung wäre vielleicht im Entity-Objekt auch besser aufgehoben. Das wäre jedenfalls der sauberere OOP Entwurf.

An der Stelle möchte ich auch noch einmal `pygame.sprite` erwähnen, denn dort gibt es bereits fertige Kollisionstests für Rechtecke und Kreise.

Bei den ganzen Koordinatenberechnungen sollte man prüfen ob man da nicht einiges mit `Rect`-Objekten lösen kann.

Das ein `GameEntity` das nichts zum zeichnen hat durch eine Bilder-Liste mit *einem* `None` drin gekennzeichnet wird, statt mit einer *leeren* Liste finde ich schräg.

`rendering_image` und `rendering_image1` in der `GameEntity` sind verwirrend.

Das in `GameEntity` der Name 'player' beim rendern anders behandelt wird obwohl es auch den Untertyp `Player` gibt, riecht nach einem Entwurfsfehler.

Was soll denn das hier: ``heading.__mul__(travelDistance)``? ”Magische” Methoden sollte man nur direkt aufrufen wenn es nicht anders geht.

Eine Methode die `setRotation()` heisst sollte nichts zurückgeben. Der Name passt ja auch gar nicht, denn diese Methode setzt nichts, sondern fragt den Winkel in Form eines Index in die Bildertabelle ab. Und das könnte deutlich kürzer geschrieben werden, denn `image_to_render` liesse sich aus `hvalue` auch einfach durch einen Ausdruck berechnen, statt dieser ganzen ``if``/``elif``-Zweige.

Bei der Verarbeitung der Mausbuttons wiederholt sich Code. Ausserdem werden alle GUI-Objekte durchgegangen wo man wahrscheinlich die Schleife abbrechen könnte wenn der erste Treffer registriert wurde. Auch hier könnte `pygame.sprite` hilfreich sein.
Anagkai
User
Beiträge: 8
Registriert: Donnerstag 15. Mai 2014, 09:35

Der Grund, warum das Rendern so kompliziert ist, ist folgender:
Das Spielfeld (4000 Pixel Seitenlänge) ist deutlich größer als das Display (1920 x 1080 Pixel, könnte aber noch kleiner sein, mein Bildschirm ist recht groß).
Dabei ist die Spielerfigur stets in der Mitte und alle anderen Objekte werden in Relation dazu geblittet. Das bedeutet, dass alle Objekte bei jeder Spielerfigur neu geblittet werden müssen.
Das mehrfache Durchgehen der Liste ist der Tatsache geschuldet, dass sich Objekte auf unterschiedlichen Höhe befinden können. Zuerst werden Sachen geblittet, die auf dem Boden liegen etc, dann "normale" Objekte und dann noch so Sachen wie Baumkronen, die darüber sind.

Jedenfalls werde ich mir deine Tipps zu Herzen nehmen.
Antworten