Pygame / Python-Programmstruktur

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

Hallo allerseits,

habe nun an meinem 8-Bit Panzergeballere - Klon gebastelt und habe einige Ratschläge umgesetzt, welche ich hier erhielt.

Macht eigentlich jetzt schon Spass das ganze finde ich :mrgreen:, und läuft jetzt auch flüssig

Bild

Nun habe ich aber mal fragen wollen, wie ihr das so seht bzgl. Programmstruktur. Wer mag kann sich mal mein Prog anschauen - ist eigentlich noch ganz übersichltich soweit:

Code: Alles auswählen

import sys, pygame, os, random, time
sys.path.append("""C:\Python25\Game Development\System""")
pygame.mixer.pre_init(44100,-16,2, 1024)
pygame.init()
from pygame.locals import *

NES_RES = (512, 480)

RES = (640, 480)

SCREEN = pygame.display.set_mode(RES, FULLSCREEN, 32)

from sprite import My_Sprite
from farben import *
from grafik import *
from sound import sound_laden, play_music
from gamepads import Gamepad, get_gamepad_ids
from tanks import Tank
from tank_levels import levels
from tank_map import Map

class Game():

    def __init__(self):

        self.tick = 10
        
        self.SCREEN = SCREEN
        self.RES = RES
        self.u = 24

        self.intro_on = False

        self.black_screen = pygame.Surface(self.RES)
        self.black_screen.fill(schwarz)

        self.titelbild = bild_laden("Title Screens", "pixel_tanks.png", True)

        self.levels = levels
        self.level = 1

        self.map = Map(self.RES, self.u)
        self.map.init_tiles(self.levels[self.level])

        self.gamepads = []
        self.tanks = []

        self.gamepad_ids = get_gamepad_ids()

        self.nr_of_players = len(self.gamepad_ids)

        for pad_id in self.gamepad_ids:
            gamepad = Gamepad(pad_id)
            self.gamepads.append(gamepad)
            tank = Tank(gamepad, self.u)
            tank.init_map(self.map)
            self.tanks.append(tank)

        for tank in self.tanks:
            for gegner_tank in self.tanks:
                if gegner_tank != tank:
                    tank.gegnerliste.append(gegner_tank)
        
        self.hintergrund = self.black_screen

    def check_quit(self):

        self.events = pygame.event.get()

        for event in self.events:
            if event.type == QUIT or event.type == KEYDOWN and event.key == K_ESCAPE:
                pygame.quit()
                sys.exit()

    def get_standbild(self):

        self.standbild = pygame.Surface(self.RES)
        self.standbild.blit(self.SCREEN, (0, 0))

    def blit_hintergrund(self):

        self.SCREEN.blit(self.hintergrund, (0, 0))

    def einblenden(self, speed = -2.5):
        """Szene, die das Bild einblendet. speed = 2.5 (langsam)"""

        self.get_standbild()

        alpha = 255
        alpha_min = 0

        for a in range(alpha, alpha_min, speed):
            self.check_input()
            self.black_screen.set_alpha(a)
            self.SCREEN.blit(self.standbild, (0, 0))
            self.SCREEN.blit(self.black_screen, (0, 0))
            self.slow_motion()

        self.black_screen.set_alpha(255)

    def ausblenden(self, speed = 2.5, wait_time = 0):
        """Szene, die das Bild ausblendet. speed = 2.5 (langsam), wait_time = Wartezeit nach Ausblende"""

        self.get_standbild()
        
        alpha = 0
        alpha_max = 255

        for a in range(alpha, alpha_max, speed):
            self.check_input()
            self.black_screen.set_alpha(a)
            self.SCREEN.blit(self.standbild, (0, 0))
            self.SCREEN.blit(self.black_screen, (0, 0))
            self.slow_motion()

        if wait_time > 0:
            self.wait(wait_time)

    def intro(self):

        if self.intro_on:

            self.SCREEN.blit(self.titelbild, (0, 0))
            self.einblenden()
            self.wait(50)
            self.ausblenden(5)
            self.wait(50)

    def run(self):

        pygame.mouse.set_visible(0)

        while True:

            t_before = pygame.time.get_ticks()

            self.blit_hintergrund()

            self.check_quit()

            for pad in self.gamepads:
                pad.check_input()

            for tank_x in self.tanks[:]:
                tank_x.update()
                if tank_x.remove_me:
                    self.tanks.remove(tank_x)
                for shot in tank_x.shots:
                    self.SCREEN.blit(shot.bild, shot.rect)
                self.SCREEN.blit(tank_x.bild, tank_x.rect)

            for tile in self.map.tiles.values():
                self.SCREEN.blit(tile.bild, tile.rect)

            pygame.display.flip()

            t_after = pygame.time.get_ticks()

            t_passed = t_after - t_before

            wait_time = self.tick - t_passed
            
            pygame.time.wait(wait_time)

new_game = Game()
new_game.run()

pygame.quit()
sys.exit()
hier noch die Module:

Code: Alles auswählen

levels = {}

levels[1] = {

    "stahl" : [(4, 4), (5, 4), (6, 4), (7, 4),
               (4, 5), (5, 5), (6, 5), (7, 5),
               (4, 6), (5, 6), (4, 7), (5, 7),

               (17, 4), (18, 4), (19, 4), (20, 4),
               (17, 5), (18, 5), (19, 5), (20, 5),
               (19, 6), (20, 6), (19, 7), (20, 7),

               (4, 11), (5, 11), (4, 12), (5, 12),
               (4, 13), (5, 13), (6, 13), (7, 13),
               (4, 14), (5, 14), (6, 14), (7, 14),

               (19, 11), (20, 11), (19, 12), (20, 12),
               (17, 13), (18, 13), (19, 13), (20, 13),
               (17, 14), (18, 14), (19, 14), (20, 14)],

    "stein" : [(8, 4), (9, 4), (10, 4), (11, 4), (12, 4), (13, 4), (14, 4), (15, 4), (16, 4),
               (8, 5), (9, 5), (10, 5), (11, 5), (12, 5), (13, 5), (14, 5), (15, 5), (16, 5),

               (8, 13), (9, 13), (10, 13), (11, 13), (12, 13), (13, 13), (14, 13), (15, 13), (16, 13),
               (8, 14), (9, 14), (10, 14), (11, 14), (12, 14), (13, 14), (14, 14), (15, 14), (16, 14),

               (4, 8), (5, 8), (4, 9), (5, 9), (4, 10), (5, 10),
               (19, 8), (20, 8), (19, 9), (20, 9), (19, 10), (20, 10)],

    "tarntile" : [(6, 6), (7, 6), (6, 7), (7, 7),
                 (6, 11), (7, 11), (6, 12), (7, 12),
                 (17, 6), (18, 6), (17, 7), (18, 7),
                 (17, 11), (18, 11), (17, 12), (18, 12)]}

Code: Alles auswählen

from map_tiles import Tile

class Map():

    def __init__(self, res, u):

        self.RES = res
        self.u = u

        self.x_tiles = res[0] / u - 1 # maximale x_tiles
        self.y_tiles = res[1] / u - 1 # maximale y_tiles

        self.alle_tilepos = [] # Blitpositionen aller Tiles

        for y in range(0, self.y_tiles + 1):

            for x in range(0, self.x_tiles + 1):

                blit_x = x * u
                blit_y = y * u

                self.alle_tilepos.append((blit_x, blit_y))

        self.tiles = {} # noch keine Tiles vorhanden

    def init_tiles(self, level):

        for tiletyp in level.keys():

            for pos in level[tiletyp]:

                topleft = (pos[0] * self.u, pos[1] * self.u)
                
                tile = Tile(tiletyp, topleft)
                self.tiles[topleft] = tile

Code: Alles auswählen

from sprite import My_Sprite
from grafik import bild_laden

ordner = "Tiles"

tile_typen = ("stahl", "stein", "tarntile")

alle_tilebilder = {}

for tile_typ in tile_typen:
    if tile_typ != "tarntile":
        alle_tilebilder[tile_typ] = bild_laden(ordner, tile_typ + ".png")
    else:
        alle_tilebilder[tile_typ] = bild_laden(ordner, tile_typ + ".png", True)

class Tile(My_Sprite):

    def __init__(self, typ, topleft):

        self.typ = typ

        tilebild = alle_tilebilder[typ]

        centerpos = (topleft[0] + tilebild.get_width() / 2, topleft[1] + tilebild.get_height() / 2)

        My_Sprite.__init__(self, tilebild, centerpos)

        self.remove_me = False

        if typ == "stahl":
            self.zerstoerbar = False
            self.passierbar = False
        elif typ == "stein":
            self.zerstoerbar = True
            self.passierbar = False
        elif typ == "tarntile":
            self.zerstoerbar = False
            self.passierbar = True

Code: Alles auswählen

from sprite import My_Sprite
from grafik import bild_laden, bild_spiegeln, bild_drehen
from sound import sound_laden
from farben import *
from tank_shots import Panzer_Shot

class Tank(My_Sprite):

    max_level = 3
    max_players = 4
    bildordner = "Characters\\The Good\\Panzer"

    images = {}
            
    image_types = ("a", "b")
    levels = range(1, max_level + 1)
    players = range(1, max_players + 1)
    directions = (8, 6, 2, 4)

    for player in players:
        images[player] = {}

        for lvl in levels:
            images[player][lvl] = {}
            
            for direction in directions:
                
                images[player][lvl][direction] = []

                if direction == 8:
                    for image_type in image_types:
                        bild = bild_laden(bildordner, "tank_" + str(player) + "_lvl" + str(lvl) + "_" + image_type + ".png", True)
                        images[player][lvl][direction].append(bild)
                elif direction == 6:
                    for bild_n in images[player][lvl][8]:
                        bild = bild_drehen(bild_n, 90)
                        images[player][lvl][direction].append(bild)
                elif direction == 2:
                    for bild_n in images[player][lvl][6]:
                        bild = bild_drehen(bild_n, 90)
                        images[player][lvl][direction].append(bild)
                else:
                    for bild_n in images[player][lvl][2]:
                        bild = bild_drehen(bild_n, 90)
                        images[player][lvl][direction].append(bild)

        images[player]["explosion"] = bild_laden("Shots", "panzer_explode.png", True)

    def __init__(self, gamepad, u):

        self.u = u

        self.gamepad = gamepad
        self.shoot_button = self.gamepad.buttons["button_4"]

        self.id = self.gamepad.id
        self.player_id = self.id + 1

        self.images = Tank.images[self.player_id]
        self.bild_index = 0

        self.level = 1
        self.max_level = Tank.max_level

        self.speed = 1
        self.max_shots = 1

        if self.player_id == 1:
            self.dir = 6
            self.startpos = (80, 200)
            
        elif self.player_id == 2:    
            self.dir = 4
            self.startpos = (560, 280)

        elif self.player_id == 3:    
            self.dir = 2
            self.startpos = (280, 80)       

        else:
            self.dir = 8
            self.startpos = (360, 400)

        My_Sprite.__init__(self, self.images[self.level][self.dir][self.bild_index], self.startpos)

        self.animation_counter = 0
        self.animation_trigger = 3

        self.shots = []
        self.gegnerliste = []

        self.hit = False
        self.remove_me = False

        self.explosion_sound = sound_laden("Defender Sounds", "photon_cannon_impact.wav")
        self.explosion_sound.set_volume(0.5)
        self.shot_sound = sound_laden("Defender Sounds", "blobfighter_attack.wav")
        self.brumm_sound = sound_laden("Sound FX", "mario_level_done.wav")

    def init_map(self, map_object):
        self.map = map_object

    def move(self):

        if not self.hit:

            digital_pos = self.gamepad.digital_pos
            dig_x = digital_pos[0]
            dig_y = digital_pos[1]

            if dig_x == 1:
                self.rect.centerx += self.speed
                self.dir = 6
            
            elif dig_x == -1:
                self.rect.centerx -= self.speed
                self.dir = 4
                
            elif dig_y == 1:
                self.rect.centery -= self.speed
                self.dir = 8
            
            elif dig_y == -1:
                self.rect.centery += self.speed
                self.dir = 2

            self.bildrand_check()

    def bildrand_check(self):

            if self.rect.left < 0:
                self.rect.left = 0
            elif self.rect.right > 640:
                self.rect.right = 640

            if self.rect.top < 0:
                self.rect.top = 0
            elif self.rect.bottom > 480:
                self.rect.bottom = 480

    def bild_update(self):

        if not self.hit:

            digital_pos = self.gamepad.digital_pos
            dig_x = digital_pos[0]
            dig_y = digital_pos[1]

            if dig_x or dig_y:

                self.animation_counter += 1

                if self.animation_counter > self.animation_trigger:
                    self.animation_counter = 0
                    if self.bild_index < 1:
                        self.bild_index += 1
                    else:
                        self.bild_index = 0

            self.set_bild(self.images[self.level][self.dir][self.bild_index])

        else:

            if self.bild != self.images["explosion"]:
                self.set_bild(self.images["explosion"])
                self.animation_counter = -1

            self.animation_counter += 1

            if self.animation_counter >= self.animation_trigger:
                self.remove_me = True

    def shot_update(self):

        for shot in self.shots[:]:
            shot.update()
            if shot.remove_me:
                self.shots.remove(shot)
                del shot

    def shoot(self):

        if not self.hit:

            self.gamepad.get_ready("button_4")

            if self.shoot_button.ready:

                if self.shoot_button.pressed:

                    if len(self.shots) < self.max_shots:
                        
                        self.shoot_button.ready = False
                        shot = Panzer_Shot(self)
                        self.shots.append(shot)

    def collision_checkpos_update(self):
        """merkt sich die 3 relevanten Punkte des Panzer-Rects,
        mit welchen die relevanten Tiles für die Rect-Kollision
        berechnet und in self.ground_tiles aufgenommen werden können"""

        if self.dir == 8:
            self.collision_checkpos = [self.rect.topleft, self.rect.midtop, self.rect.topright]

        elif self.dir == 6:
            self.collision_checkpos = [self.rect.topright, self.rect.midright, self.rect.bottomright]

        elif self.dir == 2:
            self.collision_checkpos = [self.rect.bottomleft, self.rect.midbottom, self.rect.bottomright]

        else:
            self.collision_checkpos = [self.rect.topleft, self.rect.midleft, self.rect.bottomleft]

    def get_ground_tiles(self):

        if not self.hit:

            self.collision_checkpos_update()

            self.ground_tiles = []

            for pos in self.collision_checkpos:

                tile_topleft = (pos[0] / self.u * self.u, pos[1] / self.u * self.u)

                if self.map.tiles.has_key(tile_topleft):
                    tile = self.map.tiles[tile_topleft]
                    if not tile in self.ground_tiles:
                        if not tile.passierbar:
                            self.ground_tiles.append(tile)

    def tile_collision_check(self):

        if not self.hit:

            for tile in self.ground_tiles:
                if self.front_collision(tile):
                    if self.dir == 8:
                        self.rect.top = tile.rect.bottom
                    elif self.dir == 6:
                        self.rect.right = tile.rect.left
                    elif self.dir == 2:
                        self.rect.bottom = tile.rect.top
                    else:
                        self.rect.left = tile.rect.right

    def tank_collision_check(self):

        if not self.hit:

            for gegner in self.gegnerliste:
                if self.rect_collision(gegner):
                    self.hit = gegner.hit = True

    def gegnerlisten_update(self):

        for gegner in self.gegnerliste[:]:
            if gegner.hit:
                self.gegnerliste.remove(gegner)
    
    def update(self):
        
        self.gegnerlisten_update()                            
        self.move()
        self.shot_update()
        self.shoot()
        self.bild_update()
        self.get_ground_tiles()
        self.tile_collision_check()
        self.tank_collision_check()

Code: Alles auswählen

from sprite import My_Sprite
from grafik import bild_laden


bild = bild_laden("Shots", "panzer_shot_1.png", True)

class Panzer_Shot(My_Sprite):

    def __init__(self, panzer):

        self.panzer = panzer
        self.map = panzer.map
        self.u = self.map.u

        self.dir = panzer.dir

        self.bild = bild

        self.speed = 5

        centerpos = self.panzer.rect.center

        My_Sprite.__init__(self, self.bild, centerpos)

        self.remove_me = False

    def move(self):

        if self.dir == 6:
            self.rect.centerx += self.speed

        elif self.dir == 4:
            self.rect.centerx -= self.speed
            
        elif self.dir == 8:
            self.rect.centery -= self.speed

        else:
            self.rect.centery += self.speed

        if self.rect.left > 640 or self.rect.right < 0 or self.rect.top > 480 or self.rect.bottom < 0:
            self.remove_me = True

    def tank_collision_check(self):

        for gegner in self.panzer.gegnerliste:

            if self.rect_collision(gegner):

                gegner.hit = True
                self.remove_me = True

    def tile_collision_check(self):

        tile = None

        tilepos = (self.rect.centerx / self.u * self.u, self.rect.centery / self.u * self.u)

        if self.map.tiles.has_key(tilepos):
            tile = self.map.tiles[tilepos]

        if tile:
            if self.rect_collision(tile):
                if not tile.passierbar:
                    self.remove_me = True
                if tile.zerstoerbar:
                    del self.map.tiles[tile.rect.topleft]

    def update(self):

        self.move()
        self.tank_collision_check()
        self.tile_collision_check()

Code: Alles auswählen

# -*- coding: cp1252 -*-

class My_Sprite():

    def __init__(self, bild, centerpos):

        self.bild = bild
        self.rect = bild.get_rect()
        self.rect.center = centerpos

    def set_bild(self, bild):

        self.bild = bild
        centerpos = self.rect.center
        self.rect = self.bild.get_rect()
        self.rect.center = centerpos

    def pos_in_rect(self, pos, sprite_object):

        rect = sprite_object.rect

        if pos[0] > rect.left and pos[0] < rect.right:
            if pos[1] > rect.top and pos[1] < rect.bottom:
                return True

    def front_collision(self, sprite_object):
        """Gibt True zurück, wenn eine der pos in self.collision_checkpos
        innerhalb von sprite_object.rect liegt"""

        for pos in self.collision_checkpos:
            if self.pos_in_rect(pos, sprite_object):
                return True

    def rect_collision(self, sprite_object):

        x_dist = self.rect.centerx - sprite_object.rect.centerx
        y_dist = self.rect.centery - sprite_object.rect.centery

        if x_dist < 0:
            x_dist *= -1
        if y_dist < 0:
            y_dist *= -1

        x_dist -= (self.rect.width / 2 + sprite_object.rect.width / 2)
        y_dist -= (self.rect.height / 2 + sprite_object.rect.height / 2)

        if x_dist <= 0 and y_dist <= 0:
            return True
Zum Beispiel habe ich manchmal Mühe, wie ich meinen Sprites gewisse Variablen mitgeben soll. z.Bsp. ist game.u (Tilebreite in Pixel) immer gleich, aber ich gebe das immer jedem Sprite mit beim Initialisieren mit. Auch die Auflösung ist so eine Variable, die ich immer mitgebe. Die Struktur der Karte mit dem tiles - Dict finde ich jetzt besser, vorhin war meine Map einfach eine Liste von Tiles, kein Dict.

Im Allgemeinen, was fällt Euch auf, was ich überdenken sollte aus Eurer Sicht? Dies würde mir helfen denke ich, damit ich lerne, schönen objektorientierten Code zu schreiben.

Danke im Voraus - nur schon für's Lesen.

keep on codin',

Henry
Ich code, also bin ich.
BlackJack

@Henry Jones Jr.: Eine Datei pro Klasse ist in Python nicht unbedingt üblich. Damit übergeht man das Modul als Organisationseinheit.

Das was Du mit `t_before`, `t_after`, und `t_passed` machst, kann man einfacher mit `pygame.time.Clock` haben.

In `Map.init_tiles()` hätte ich ``level.iteritems()`` statt ``level.keys()`` verwendet.

Du könntest etwas souveräner mit Wahrheitswerten umgehen. Das Ergebnis von Vergleichsoperationen ist ein Wahrheitswert, mit dem man direkt arbeiten kann. Da muss man nicht noch extra ein ``if`` bemühen. Beispiele sind das laden der Bilder für die Kacheln:

Code: Alles auswählen

alle_tilebilder = dict(
    (tile_typ, bild_laden(ordner, tile_typ + '.png', tile_typ == 'tarntile'))
    for tile_typ in tile_typen
)
In `PanzerShot.move()`:

Code: Alles auswählen

        self.remove_me = (
            self.rect.left > 640
            or self.rect.right < 0
            or self.rect.top > 480
            or self.rect.bottom < 0
        )
In `MySprite` könnte man nicht nur unnötige ``if``\s einsparen, sondern die Testmethoden in dieser Klasse geben gar nicht `True`/`False` zurück, sondern `True` oder ein implizites `None`. Das ist unsauber.

Die Eigenschaften der Kacheltypen sollten Daten sein, und nicht in ``if``/``elif``-Zweigen in Code gegossen werden.

Ein Panzer weiss auf Klassenebene wieviele Level das Spiel hat!? Und auf Klassenebene hat man in der Regel nicht so viel Code stehen, der beim erstellen des Klassenobjekts ausgeführt wird.

Die Zahlen für die Richtungen sind zu magisch.

Der Panzer weiss — hart kodiert — wie gross das Bild auf dem er fährt in Pixeln ist. Und einige andere Objekte anscheinend auch.

Das hier ist unter der Annahme das der Bild-Index nur positiv sein kann, eine ziemlich umständliche Art die Werte zwischen 0 und 1 tauschen.

Code: Alles auswählen

                    if self.bild_index < 1:
                        self.bild_index += 1
                    else:
                        self.bild_index = 0
Statt über eine Kopie einer Liste zu iterieren, in deren Verlauf möglicherweise Elemente mit `list.remove()` entfernt werden, solltest Du besser eine neue Liste erstellen, in die ungewünschte Elemente nicht eingefügt werden. Und das ``del shot`` in `Tank.shot_update()` ist unsinnig — entweder wird der Name im nächsten Schleifendurchlauf sofort an ein neues Objekt gebunden, oder die Methode ist zuende und der Name verschwindet auch ohne das ``del``.

Mehrfach verschachtelte ``if``\s, die einfach immer nur noch eine Bedingung hinzufügen zu dem Code der im innersten Block ausgeführt wird, kann man auch in *einem* ``if`` mit logischen Operatoren zusammen fassen.

Die Aufgaben sind teilweise nicht richtig auf die Objekte verteilt. Ein Objekt sollte nicht zu tief in den „Innereien” eines anderen Objekts herum wühlen. `Tank.get_ground_tiles()` weiss und macht Sachen mit der Karte und den Kacheln, die eigentlich eher in die Karte gehören. Ausserdem sind in dem Umfeld Methoden, die irgendwie zu prozedural aussehen und über Attribute kommunizieren die nur diese Funktionen etwas angehen, aber nicht wirklich zum Zustand eines Panzers gehören. Es gibt Argumente und Rückgabewerte für so etwas. Man sollte einem Objekt nur Attribute verpassen, die den Gesamtzustand des Objekts beschreiben und die auch möglichst in der `__init__()` mit sinnvollen Werten belegen. Attribute in anderen Methoden einzuführen ist ein „code smell”.

`dict.has_key()` ist veraltet und sollte mit dem ``in``-Operator ersetzt werden. In Python 3.x gibt es die Methode nicht mehr. Man könnte auch `get()` verwenden und testen ob man etwas bekommen hat.

IMHO ist das mit den Kachelpositionen in der Karte „falsch herum”. Das Wörterbuch dort solle eher Kartenkoordinaten auf Kacheln abbilden und nicht Bildschirmkoordinaten auf Kacheln.

Vielleicht, aber das ist auch Geschmacksfrage, macht der Panzer zu viel. Einiges davon würde ich eher in die übergeordnete `Game`-Klasse verlagern.

In `Tile.__init__()` würde ich noch mal schauen ob man `centerpos` nicht mit hilfe von `Rect`-Objekten einfacher formulieren kann.

`u` ist ein schlechter Name für `tilesize`.

`PanzerShot.tile_collision_check()` ist zu umständlich formuliert.

Wenn eine Klasse in der `__init__()` ein Attribut setzt, macht es wenig Sinn dieses Attribut vor dem Aufruf dieser Methode schon zu setzen. Das betrifft `bild` auf `MySprite`-Exemplaren.

Wenn ich `MySprite.rect_collision` lesen, frage ich mich natürlich warum zur Hölle muss man das selber schreiben. Und Du solltest Dir dringend mal die `abs()`-Funktion ansehen.
webspider
User
Beiträge: 485
Registriert: Sonntag 19. Juni 2011, 13:41

Ich fragte mich kurz wieso wir keine Spoiler-Tags zum Darstellen von Inhalten in zusammengeklappter Form haben. Dann fiel mir ein, dass längere Stücke Code üblicherweise auf Pastebins ausgelagert werden (von denen es hier auch einen gibt).

Von daher, tu den Leuten hier den Gefallen und betreib etwas "Outsourcing" :mrgreen:
Benutzeravatar
Don Polettone
User
Beiträge: 115
Registriert: Dienstag 23. November 2010, 20:26
Wohnort: Schweiz

Geilo, vielen Besten für die vielen guten Ratschläge! Habe denn sogleich einige davon umgesetzt.

Und das mit dem Pastebinzeugs geht in Ordnung, werde das berücksichtigen :shock:

Ein paar Rückfragen hab ich noch:
Die Eigenschaften der Kacheltypen sollten Daten sein, und nicht in ``if``/``elif``-Zweigen in Code gegossen werden.
Demnach Tile-Superklasse und Subklassen für alle Tiletypen? Ja, das wäre sexy. Kommt noch.
Ein Panzer weiss auf Klassenebene wieviele Level das Spiel hat!?
Nönönö, die Panzerklasse weiss das max_level eines Panzerobjekts, damit die Panzertilesets dynamisch geladen werden beim Einlesen der Klasse und danach nicht mehr.
Die Zahlen für die Richtungen sind zu magisch.
Das versteh ich nun gar nicht... "magisch"? Ich finds eigentlich ganz gut, weil kurz und einprägsam weil am Numblock abgeschaut vom Keyboard. Was genau heisst "magisch"..?
Der Panzer weiss — hart kodiert — wie gross das Bild auf dem er fährt in Pixeln ist. Und einige andere Objekte anscheinend auch.
Meinst Du game.map, das beim Initialisieren an einige Sprites als eigene Instanzvariable referenziert wird? Dann sollte ich das besser jeweils als Argument mitgeben bei der update - Funktion des Panzers und den anderen Objekten?
Statt über eine Kopie einer Liste zu iterieren, in deren Verlauf möglicherweise Elemente mit `list.remove()` entfernt werden, solltest Du besser eine neue Liste erstellen, in die ungewünschte Elemente nicht eingefügt werden. Und das ``del shot`` in `Tank.shot_update()` ist unsinnig — entweder wird der Name im nächsten Schleifendurchlauf sofort an ein neues Objekt gebunden, oder die Methode ist zuende und der Name verschwindet auch ohne das ``del``.
Das mit der Kopie der Liste und remove im Original erschien mir am leserlichsten, habs aber nun doch an einigen Orten geändert. Die Sache mit den 'del's - klarer Fall, hab ich weggemacht, danke.

Auch sonst habe eigentlich das meiste umgesetzt, besten Dank.
Ich code, also bin ich.
nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

Henry Jones Jr. hat geschrieben:Und das mit dem Pastebinzeugs geht in Ordnung, werde das berücksichtigen :shock:
Also wirklich jetzt? Ich sehe hier noch keine Links zu Pastes … Außerdem muss man erst mal selber herausfinden was das für Module sind, also solltest du noch angeben, welche Dateien du da verlinkst.
BlackJack

@Henry Jones Jr.: Man kann auch Unterklassen für Tile-Typen machen. Eventuell auch ohne Basisklasse wenn da nicht wirklich etwas drinstehen würde. Das meinte ich aber nicht zwingend. Es sollte halt nur keine Entscheidung im Code sein, die für jeden Typ unterschiedliche Werte an die zwei gleichen Namen bindet. Das sollten *Daten* sein und kein Code.

Wenn die Panzerklasse das `max_level` kennt, dann weiss sie doch auch wie viele Level es gibt. Und damit weiss das auch jedes Panzerexemplar. Das sollte den Panzer aber eigentlich gar nichts angehen.

Magisch heisst, dass das willkürliche Zahlen sind. Mag sein dass Dir das mit dem Numpad logisch vorkam, ich bin da nicht drauf gekommen und habe mich gefragt warum überhaupt Zahlen und dann warum so komische. Konstanten sind da besser lesbar, denn bei ``UP`` oder ``HOCH`` kann man viel eher darauf schliessen worum es geht als bei ``8``.

Ich weiss nicht wie Du auf `game.map` kommst wenn ich die hart kodierte Bildgrösse im `Panzer`-Code anspreche!? Da stehen die Dimensionen des angezeigten Bildes hart kodiert als literale Zahlen im Code. Ändere mal die Ausmasse von der Anzeige, zum Beispiel auf ein Tile mehr in beide Richtungen und schau dann wo Du plötzlich überall im Quelltext Zahlen ändern musst, und probier mal aus ob Du da auf Anhieb wirklich alle erwischst.
Antworten