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.
Pygame-Spiel ruckelt/ braucht extrem viel CPU
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.
@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.
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?
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?
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
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!"
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.
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.
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()
Der Prozessor braucht jetzt nur noch 30 - 50%, seit ich das mit dem convert gefixt habe, ruckeln tut es aber immer noch.
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.
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!"
@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:
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.
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)
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.
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.
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.