meine Spiele-Engine: Interesse?

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
Teilu
User
Beiträge: 3
Registriert: Freitag 4. Oktober 2013, 20:54

Hallo zusammen,

seit einiger Zeit arbeite ich an einer Art Spiele-Engine. Diese basiert auf Pygame und macht das Programmieren von Spielen für mich wegen einiger netter Funktionen einfacher.
Die wichtigste Funktion ist vielleicht, dass die Darstellung von Gegenständen übernommen wird.
Meine Frage ist jetzt, ob sich noch jemand anderes für die Benutzung interessieren könnte.
Oder gibt es überhaupt jemanden, der im Bereich Spiele-Programmierung mit Python tätig ist?

Danke im voraus,
Teilu
Tobs
User
Beiträge: 65
Registriert: Sonntag 29. September 2013, 11:11

Ich würde mich schon dafür interessieren, aber ich habe gerade erst mit Python angefangen habe in Pygame bisher nicht mehr als das folgende geschrieben.

Code: Alles auswählen

# -*- coding: cp1252 -*-


import pygame
import easygui


def Entfernung(a, b):
    Abstand = ((a[0]-b[0])**2+(a[1]-b[1])**2)**0.5
    Abstand = str(int(Abstand))
    return Abstand

class Ball:
    
    def __init__(self,pos):
        self.pos = pos
        self.int_pos = pos
        
    def bewegen(self,target,distance):
        Ueberschritten = False
        vektor = [target[0] - self.pos[0] , target[1] - self.pos[1]]
        vektor_distance = (vektor[0]**2+vektor[1]**2)**0.5
        if not vektor_distance == 0:
            vektor[0] = vektor[0] / vektor_distance
            vektor[1] = vektor[1] / vektor_distance
        if distance > vektor_distance:
            Ueberschritten = True
        self.pos[0] = self.pos[0] + vektor[0] * distance
        self.pos[1] = self.pos[1] + vektor[1] * distance
        
        self.int_pos = [int(self.pos[0]) , int(self.pos[1])]
        
        if Ueberschritten:
            self.int_pos = target


pygame.init()
screen = pygame.display.set_mode([640,480])
clock = pygame.time.Clock()
this_ball = Ball([320,240])
step_distance = float(easygui.enterbox("Geschwindigkeit in 100Pixel/Sekunde"))
message1_str = "Klick mit der Maus irgendwo hin!"
message2_str = "Du kannst die Maus auch gedrückt halten!"
message3_str = "WARNUNG: FPS EINBRUCH!"
messagex_str = "x: 320"
messagey_str = "y: 240"
messageabstand_str = "Abstand: ?"
ballx_str = "x: 320"
bally_str = "y: 240"
gedrueckt = False
running = True

while running:
    clock.tick(60)
    try:
        messagefps_str = str(int(clock.get_fps())) + " FPS"
        messagex_str = str("x: " + str(finish[0]))
        messagey_str = str("y: " + str(finish[1]))
    except:
        pass
    try:
        Abstand = int(((finish[0]-this_ball.int_pos[0])**2+(finish[1]-this_ball.int_pos[1])**2)**0.5)
        messageabstand_str = "Abstand: " + str(Abstand)
    except:
        pass
    ballx_str = "x: " + str(this_ball.int_pos[0])
    bally_str = "y: " + str(this_ball.int_pos[1])
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.MOUSEBUTTONDOWN:
            gedrueckt = True
            finish = event.pos
        if event.type == pygame.MOUSEBUTTONUP:
            gedrueckt = False
        if event.type == pygame.MOUSEMOTION:
            if gedrueckt:
                finish = event.pos
    try:
        this_ball.bewegen(finish,step_distance)
    except:
        pass
    message1_font = pygame.font.Font(None, 18)
    message1_surf = message1_font.render(message1_str, 1, (255, 255, 255))
    message2_font = pygame.font.Font(None, 18)
    message2_surf = message2_font.render(message2_str, 1, (255, 255, 255))
    messagefps_font = pygame.font.Font(None, 18)
    messagefps_surf = messagefps_font.render(messagefps_str, 1, (255, 0, 0))
    messagex_font = pygame.font.Font(None, 18)
    messagex_surf = messagex_font.render(messagex_str, 1, (255, 255, 255))
    messagey_font = pygame.font.Font(None, 18)
    messagey_surf = messagey_font.render(messagey_str, 1, (255, 255, 255))
    messageabstand_font = pygame.font.Font(None, 18)
    messageabstand_surf = messageabstand_font.render(messageabstand_str, 1, (255, 255, 255))
    ballx_font = pygame.font.Font(None, 18)
    ballx_surf = ballx_font.render(ballx_str, 1, (255, 255, 255))
    bally_font = pygame.font.Font(None, 18)
    bally_surf = bally_font.render(bally_str, 1, (255, 255 ,255))
    screen.fill([0,0,0])
    pygame.draw.circle(screen,[128,255,128],this_ball.int_pos, 25, 0)
    screen.blit(message1_surf, [10, 10])
    screen.blit(message2_surf, [10, 30])
    screen.blit(messagefps_surf, [560, 10])
    screen.blit(messagex_surf, [500, 30])
    screen.blit(messagey_surf, [500, 50])
    screen.blit(messageabstand_surf, [10, 90])
    screen.blit(ballx_surf, [400, 30])
    screen.blit(bally_surf, [400 ,50])
    if clock.get_fps < 100:
        message3_font = pygame.font.Font(None, 26)
        message3_surf = message3_font.render(message3_str, 1, (255,0,0))
        screen.blit(message3_surf, [10,90])
    pygame.display.flip()
Das ist ein Programm, mit dem ich für zukünftige Spiele testen wollte, wie man NPC bewegen kann.
Zuletzt geändert von Anonymous am Samstag 5. Oktober 2013, 10:49, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
Teilu
User
Beiträge: 3
Registriert: Freitag 4. Oktober 2013, 20:54

Bisher gibt es noch keine Dokumentation und die Benutzung von den eingebauten Funktionalitäten wird bis jetzt nur durch wenige Beispiele erklärt.
Naja, das grafik-system funktioniert bei mir jedenfalls so, dass Gegenstände angelegt werden können.
Die wichtigsten Attribute sind Position, Abmessung, Ausrichtung, Farbe und Textur.
Mögliche Figuren sind bisher Kreis, Rechteck und Linie.
Mit dem Aufruf von einer Funktion wird die gesamte Szene gezeichnet.
Daneben gibt es noch Funktionen für kollision, sound, Benutzereingaben und Speicher-Zugriff nach bestimmten "Datenstrukturen".
Unklar ist für mich, ob sich das Schreiben einer Dokumentation, da diese doch sehr lang wird, lohnt.

MFG,
Teilu

@Tobs: Dein Code würde auf jeden Fall deutlich kürzer werden!
BlackJack

@Tobs: Ein paar Anmerkungen zum Quelltext:

Die `Entfernung()`-Funktion wird nirgends verwendet. Der Name hat auch zwei Probleme: 1. er fängt mit einem Grossbuchstaben an, was per Konvention nur Klassennamen sollten (oder Konstanten die komplett in Grossbuchstaben benannt werden), und 2. beschreibt er keine Tätigkeit. Letzteres ist üblich um Funktionen leichter von Werten unterscheiden zu können. Denn `entfernung` könnte ja auch eine Zahl sein die eine Entfernung angibt. Für die Funktion drei Zeilen zu verwenden in denen der gleiche Name an zwei sehr unterschiedliche Datentypen gebunden wird, ist auch keine so gute Idee. Vom Typ her ist eine Entfernung oder ein Abstand ja auch keine Zeichenkette. Diese Umwandlung gehört deshalb auch nicht in so eine Funktion.

`Ball.int_pos()` als unabhängiges Attribut zu führen ist fehleranfällig. So muss man überall wo man das `pos`-Attribut verändert immer darauf achten, dass auch `int_pos` entsprechend gesetzt wird. Dafür würde sich ein `property()`, also ein berechnetes Attribut anbieten. Dazu muss `Ball` von `object` erben, was man generell machen sollte (in Python 2.x, und in Python 3.x schadet es zumindest nicht).

Etwas später werden die Werte dann auch sehr komisch ungleich behandelt. Wenn das Ziel überschritten wurde, wird nur `int_pos` auf das Ziel gesetzt, `pos` dagegen normal berechnet, also über das Ziel hinaus. Dazu wird auch noch ein Flag `Ueberschritten` über die gesamte Funktion verteilt. Wenn man die Bedingungen und Aktionen sinvoll umstellt, braucht man weder das `Ueberschritten`-Flag, noch den Test ob die Länge des Vektors grösser als Null ist bevor man teilt. Damit kann die Methode ein cirka ein Drittel kürzer und damit auch leichter verständlich werden. `vektor_distance` sollte man dann noch in `vector_length` umbenennen, damit es die richtige Bezeichnung für den Wert ist und nicht Deutsch und Englisch in einem Bezeichner vermischt werden.

Der Hauptprogramm-Quelltext sollte nicht auf Modulebene stehen, sondern in einer Funktion. Beziehungsweise in mehr, denn das ist eine ganz schöne Menge an Code der unterschiedliche Sachen tut und Wiederholungen enthält, die man sinnvoll auf Funktionen verteilen kann.

Ausserdem enthät das Hauptprogramm eine Menge „magischer” Zahlen die man entweder als Konstanten definieren, oder aus diesen Konstanten berechnen kann. Noch schlimmer ist, dass ein paar Zeichenketten diese magischen Zahlen auch noch mal die gleichen magischen Zahlen enthalten. Überleg mal wo Du überall Änderungen von Hand vornehmen musst, wenn Du die Anzeigegrösse ändern willst. Im Idealfall gibt es dafür *eine* Konstante im Programm die man ändern muss, und das war es dann.

Der `this_`-Präfix bei `this_ball` macht keinen Sinn. Und `step_distance` als Name für die Antwort auf die Frage nach der Geschwindigkeit hiesse besser `speed`. In der Frage steckt auch wieder eine magische Zahl, die man als Konstante festlegen sollte, damit man sie einfach ändern kann. Dann hätte man auch nicht das Problem dass der ganze Code von 100 FPS ausgeht *ausser* die Stelle an der mit der `clock` dann tatsächlich auf 60 FPS hingearbeitet wird. Das ist Dir wahrscheinlich nicht aufgefallen weil die FPS-Einbruchswarnung entweder *nie* oder *immer* ausgegeben wird, weil Du dort nicht den FPS-Wert mit einer Zahl vergleichst, sondern die *Methode*. Du hättest sie an der Stelle auch *aufrufen* müssen.

Warum hast Du Zeichenketten am Anfang an an so nichtssagende, durchnummerierte Namen wie `message1_str` gebunden die dann später benutzt werden und man an der Stelle dann nicht mehr sieht was da eigentlich ausgegeben wird. Also nicht mal mehr die Bedeutung am Namen, denn so etwas wie `message2_str` sagt nicht wirklich etwas aus, zumindest nicht dass was der Leser an der Stelle gerne wissen möchte.

Auch die anderen `message*`-Variablen werden relativ weit von der Verwendung entfernt definiert. Und unnötig vor der Schleife wegen diesem gruseligen Ausnahmedurcheinander was Du Dir mit `finish` eingehandelt hast. Wenn der Name vor der ersten Verwendung nicht definiert ist, dann „behandelt” man das nicht mit ``except``, schon gar nicht ohne eine konkrete Ausnahme dort anzugeben, sondern in dem man doch bitte vorher einfach mal `finish` definiert. Damit spart man sich dann eine ganze Menge von dem Code der nur existiert um diesen einen Spezialfall abzufangen.

Die ganzen ``if``-Bedingungen in der Ereignisverarbeitung schliessen sich gegenseitig aus. Hier sollte man also mit ``elif``\s arbeiten.

Wenn man bei den `blit()`-Aufrufen für die Texte mal die Zeilen die dafür benötigt werden direkt darüber gruppiert und das nicht alles über die gesamte Schleife verteilt, dann sieht man deutlich das immer die gleichen drei bis vier Schritte nur mit anderen Werten durchgeführt werden. Das lässt sich also prima in eine Funktion auslagern. Damit lassen sich einige Zeilen einsparen.

Statt mit `int()`, `str()`, und ``+`` zu hantieren um Werte und Zeichenketten zusammenzusetzen wie in BASIC, bietet Python Zeichenkettenformatierung mittels der `format()`-Methode auf Zeichenketten und Platzhaltern und Formatierungsangaben in der Zeichenkette.

Mal angefangen die Punkte umzusetzen:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
import easygui
import pygame

DISPLAY_WIDTH, DISPLAY_HEIGHT = DISPLAY_SIZE = (640, 480)
PIXEL_PER_SECOND = 100
DEFAULT_FONT_SIZE = 18
BLACK, WHITE, RED = (0, 0, 0), (255, 255, 255), (255, 0, 0)
BALL_COLOR = (128, 255, 128)
BALL_RADIUS = DISPLAY_HEIGHT // 20


def blit_text(
    surface, text, position, color=WHITE, font_size=DEFAULT_FONT_SIZE
):
    surface.blit(
        pygame.font.Font(None, font_size).render(text, 1, color), position
    )


class Ball(object):
    
    def __init__(self, pos):
        self.pos = pos
    
    @property
    def int_pos(self):
        return (int(self.pos[0]), int(self.pos[1]))

    def move(self, target, distance):
        vector = (target[0] - self.pos[0] , target[1] - self.pos[1])
        vector_length = (vector[0]**2 + vector[1]**2)**0.5
        if distance >= vector_length:
            self.pos = target
        else:
            self.pos = (
                self.pos[0] + vector[0] / vector_length * distance,
                self.pos[1] + vector[1] / vector_length * distance
            )


def main():
    speed = float(
        easygui.enterbox(
            'Geschwindigkeit in {} Pixel/Sekunde'.format(PIXEL_PER_SECOND)
        )
    )
    pygame.init()
    screen = pygame.display.set_mode(DISPLAY_SIZE)
    clock = pygame.time.Clock()
    ball = Ball(screen.get_rect().center)
    target = ball.int_pos
    any_mouse_button_down = False
    running = True
    while running:
        clock.tick(PIXEL_PER_SECOND)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.MOUSEBUTTONDOWN:
                any_mouse_button_down = True
                target = event.pos
            elif event.type == pygame.MOUSEBUTTONUP:
                any_mouse_button_down = False
            elif event.type == pygame.MOUSEMOTION:
                if any_mouse_button_down:
                    target = event.pos
        
        ball.move(target, speed)

        screen.fill(BLACK)
        pygame.draw.circle(screen, BALL_COLOR, ball.int_pos, BALL_RADIUS)

        blit_text(screen, 'Klick mit der Maus irgendwo hin!', (10, 10))
        blit_text(screen, u'Du kannst die Maus auch gedrückt halten!', (10, 30))
        blit_text(screen, '{0:.0f} FPS'.format(clock.get_fps()), (560, 10), RED)
        blit_text(screen, 'x: {0}'.format(target[0]), (500, 30))
        blit_text(screen, 'y: {0}'.format(target[1]), (500, 50))
        distance = int(
            (
                (target[0] - ball.int_pos[0])**2
                + (target[1] - ball.int_pos[1])**2
            )**0.5
        )
        blit_text(screen, 'Abstand: {0}'.format(distance), (10, 90))
        blit_text(screen, 'x: {0}'.format(ball.int_pos[0]), (400, 30))
        blit_text(screen, 'y: {0}'.format(ball.int_pos[1]), (400, 50))

        if clock.get_fps() < PIXEL_PER_SECOND * 0.95:
            blit_text(screen, 'WARNUNG: FPS EINBRUCH!', (10, 110), RED, 28)

        pygame.display.flip()


if __name__ == '__main__':
    main()
Teilu
User
Beiträge: 3
Registriert: Freitag 4. Oktober 2013, 20:54

Naja,
wie ich sehe insgesamt sehr wenige Rückmeldungen.
Falls doch noch jemand interesse zeigen sollte findet man mein projekt unter:
http://sourceforge.net/projects/pygengine/
oder bei der Eingabe von pyGEngine in die Suchleiste der Suchmaschiene.
Wie gesagt, es gibt keine Dokumentation.
Gruß,
Teilu
BlackJack

@Teilu: Nach einem Blick in den Quelltext habe ich definitiv kein Interesse.

Mal ein Beispiel:

Code: Alles auswählen

class new_controller():
   __slots__ = ("stick","button_ID","axis_ID","name","ID","axis","pressed","button","deadzone","sign")
   def __init__(obj,ID):
      obj.stick = None; obj.name = "(kein Name)"; obj.ID = ID
      obj.axis_ID = []; obj.button_ID = []
      obj.axis = []; obj.sign = []; obj.button = []
      obj.deadzone = 0
      try:
         obj.stick = joystick.Joystick(ID)
         obj.stick.init()
         obj.name = obj.stick.get_name().replace(" ","")
         obj.axis = [0]*obj.stick.get_numaxes()
         obj.button = [False]*obj.stick.get_numbuttons()
      except py_error: pass
      obj.pressed = False#pressed for user
      obj.load()
   def get_state(obj):
      if obj.stick and obj.stick.get_init():
         #get all axis states with deadzone and sign
         for i in range(len(obj.axis)):
            obj.axis[obj.axis_ID[i]] = round(obj.stick.get_axis(i)*100)*obj.sign[i]
            #set axis to 0 if its value is in deadzone
            if abs(obj.axis[obj.axis_ID[i]]) <= obj.deadzone:
               obj.axis[obj.axis_ID[i]] = 0
         #get all buttons states
         for i in range(len(obj.button)):
            obj.button[obj.button_ID[i]] = obj.stick.get_button(i)
   #load IDs of axis, button and sign of axis
   def load(obj):
      #load data from file named like joystick
      data = memory.open_file(path+"\\Data"+obj.name+".txt","r")
      if data:
         res = True
         values = memory.load_values(data)
         memory.close_file(data)
      else:
         #standart joystick if file does not exist
         res = False
         values = [i for i in range(len(obj.button))] + [i for i in range(len(obj.axis))] + [0]
      #set ID,sign for axis
      for i in range(0,len(obj.axis)):
         obj.axis_ID.append(int(abs(values[i])))
         obj.sign.append(pos_neg(values[i]))
      length = len(obj.axis)
      #set IDs for buttons
      for i in range(len(obj.button)):
         obj.button_ID.append(int(values[i+length]))
      length += len(obj.button)
      obj.deadzone = values[length]
      return res
Und das sieht alles so aus. Die Klassen haben Funktionsnamen, Attribute sind alles `__slots__`, `self` heisst `obj`, Schleifen über Sequenzen mit ``for i in range(0, len(sequenze)):``, Einrücktiefe von 3 pro Ebene. :shock: Also wenn man nicht in Python programmieren möchte, dann soll man es doch einfach lassen wenn einen niemand dazu zwingt…
Benutzeravatar
/me
User
Beiträge: 3561
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

BlackJack hat geschrieben:Also wenn man nicht in Python programmieren möchte, dann soll man es doch einfach lassen wenn einen niemand dazu zwingt…
Für den erfahrenen Python-Entwickler sieht das in der Tat nicht schön aus.

Ich möchte hier dringend auf den Style Guide for Python Code verweisen.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Teilu hat geschrieben:Unklar ist für mich, ob sich das Schreiben einer Dokumentation, da diese doch sehr lang wird, lohnt.
Ich habe es mir angewöhnt, parallel zum Coden die Doku mitzuschreiben. Die Vorteile überwiegen klar den Umstand, dass Programmieren mehr Spaß macht als Dokumentationen zu formulieren:
  • Umständlicher Code und dadurch oftmals umständliche Bedienung werden schneller erkannt
  • Eine durchgängig gleich strukturierte API bzw. UI sind beim Schreiben der Doku leichter zu definieren
  • Ideen neuer Features bzw. Verbesserung bestehender entwickeln sich oft während der Notwendigkeit, die Verwendung des eigenen Programmes zu erklären
  • Nach Zeiten, in denen man sich gedanklich in ein bestimmtes Problem hineingeschraubt hat, tut es gut, über die Doku wieder einen Blick auf das "große Gesamte" zu werfen
  • Last but not least: Fängt man zu spät zu dokumentieren an, macht man es mangels Motivation doch meistens eh nicht mehr... ;-) Und ein schlecht dokumentiertes Programm oder Framework wird später kaum jemand verwenden.
Meine Erfahrung jedenfalls.

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Antworten