pygame

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
Henri.py
User
Beiträge: 20
Registriert: Sonntag 28. März 2021, 15:33

Hi ich habe ein Problem. Ich versuche gerade eine art hintergrund von einer website(https://discordtemplates.me/) nach zu coden.
bis her sieht mein code so aus:

Code: Alles auswählen

import pygame, sys, random

x = 1672
y = 644
white = (255,255,255)
mark = (32,123,123)
bg = (44,47,51)
points_list = list()
aalines = list()

pygame.init()
Clock = pygame.time.Clock()
screen = pygame.display.set_mode((x,y))



def spawn_points():
    global points_list
    amount = int((x*y)/13459)
    for p in range(amount-len(points_list)):
        x_speed = random.randint(-1.0,1.0)
        y_speed = random.randint(-1.0,1.0)
        points_list.append([[random.randint(10,x),random.randint(10,y)],[x_speed,y_speed]])

def update_points():
    global aalines
    for p in range(len(points_list)):
        point = points_list[p][0]
        x_speed = points_list[p][1][0]/4
        y_speed = points_list[p][1][1]/4
        points_list[p][0] = [points_list[p][0][0]+x_speed,points_list[p][0][1]+y_speed]
        pygame.draw.circle(screen,white,point,2)
        if point[0] < 0 or point[0] > x or point[1] < 0 or point[1] > y:
            x_speed = random.randint(-1.0,1.0)
            y_speed = random.randint(-1.0,1.0)
            points_list[p] = [[random.randint(10,x),random.randint(10,y)],[x_speed,y_speed]]
    for line in range(len(aalines)):
        l = aalines[line]
        pygame.draw.aaline(screen,mark,l[0],l[1])
    aalines.clear()



def hitbox():
    global hitboxes, aalines
    hitboxes = []
    for p in range(len(points_list)):
        point = points_list[p][0]
        h = pygame.draw.circle(screen,mark,point,50)
        hitboxes.append(h)
    for h in hitboxes:
        for h2 in hitboxes:
            if h.colliderect(h2):
                aalines.append([[h.x+50,h.y+50],[h2.x+50,h2.y+50]])


spawn_points()

while True:
    hitbox()
    screen.fill(bg)
    mouse = pygame.mouse.get_pos()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
    update_points()
    pygame.display.update()
    Clock.tick(120)
das problem ist jetzt aber das das ganze ziemlich verbuggt sobald die hitbox der punkte den rand berührt.
wenn die hitbox den oberen rand berührt hören die verbindungen auf ihre y variable zu verändern und berührt die hitbox den linken rand hört die x variable auf sich zu verändern.
würde mich über hilfe sehr freuen
LG
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Henri.py: Mein Tipp: Python lernen! Unter anderem Funktionen. Das Programm enthält keine. Das missbraucht ``def`` als Sprungmarken für benannte Code-Abschnitte, die dann auf globalen Variablen operieren.

Die globalen Variablen enthalten dann teilweise verschachtelten Listen wo nicht gleichartige Objekte enthalten sind, sondern magische Indexwerte die Bedeutung des Wertes an diesem Index kodieren. Was dann unter anderem zu solch ”leicht verständlichem” Code führt:

Code: Alles auswählen

        x_speed = points_list[p][1][0]/4
        y_speed = points_list[p][1][1]/4
        points_list[p][0] = [points_list[p][0][0]+x_speed,points_list[p][0][1]+y_speed]
Das ist kein Code den man in Python so schreiben würde. Wobei das `p` natürlich aus einem ``for p in range(len(points_list)):`` kommt. Was auch unpythonisch ist. Man iteriert in Python direkt über die Elemente von `points_list`. Und statt eine Liste über einen Laufindex zu verändern, baut man einfach eine neue Liste mit veränderten Werten auf. Wobei das hier wahrscheinlich auch gar nicht nötig wäre, wenn in der Liste Objekte sind, deren Zustand man ändern könnte.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Grunddatentypen haben nichts in Namen verloren. Den Typen ändert man gar nicht so selten mal während der Programmentwicklung und dann muss man überall im Programm die betroffenen Namen ändern, oder man hat falsche, irreführende Namen im Quelltext.

Warum wird `amount` in `spawn_points()` durch eine Berliner Postleizahl geteilt?

Zur Visualisierung eines Rechtecks eignet sich ein Kreis eher nur bedingt. Also sollte man entweder die Visualisierung ändern, oder die Kollision tatsächlich aufgrund eines Umkreises berechnen.

Das `sys.exit()` hat da nichts zu suchen. Das sieht nach einem Notausgang aus, weil man nicht wusste wie man da raus kommen soll, oder sich keine Gedanken darüber machen wollte.

Das erstellen eines Punktes an zufälliger Stelle mit zufälliger Geschwindigkeit passiert an zwei Stellen durch kopierten Code. Man kopiert keinen Code. Dafür gibt es Funktionen.

Es gibt zu viele magische Zahlwerte im Code die man als Konstanten definieren sollte.

Man kann wahrscheinlich sinnvoll Gebrauch von `pygame.Rect`-Objekten machen und `pygame.Vector2` könnte auch einen Blick Wert sein.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich mochte den Effekt, und habe ihn mal implementiert. Vielleicht haette man die Laengenberechnung des Differenzvectors auch noch anders machen koennen, aber das ist zu simpel, um da was anderes zu bemuehen.

Code: Alles auswählen

import random
import pygame
import math
from dataclasses import dataclass

SIZE = (400, 400)
MAX_LINE_LENGTH = 80

SPEED_MU = 20
MAX_SIZE = 3
WHITE = (255, 255, 255, 255)


@dataclass
class Star:
    x: float
    y: float
    xd: float
    yd: float
    size: int

    def update(self, screen, elapsed):
        pygame.draw.circle(screen, WHITE, self.pos, self.size)
        self.x += elapsed * self.xd
        self.y += elapsed * self.yd
        return int(self.x) in range(SIZE[0]) and int(self.y) in range(SIZE[1])

    @property
    def pos(self):
        return self.x, self.y

    def __sub__(self, other):
        return math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)


def map_distance_to_color(distance):
    if distance < MAX_LINE_LENGTH:
        g = int(255 * (1 - distance / MAX_LINE_LENGTH))
        return (g, g, g, g)
    return None


class StarField:

    def __init__(self, count=100):
        self._count = count
        self._stars = []
        self._respawn()

    def _respawn(self):
        for _ in range(self._count - len(self._stars)):
            star = Star(
                x=random.randint(0, SIZE[0] - 1),
                y=random.randint(0, SIZE[1] - 1),
                xd=random.gauss(0, SPEED_MU),
                yd=random.gauss(0, SPEED_MU),
                size=random.randint(1, MAX_SIZE),
            )
            self._stars.append(star)

    def update(self, screen, elapsed):
        remove = []
        for a in self._stars:
            for b in self._stars:
                if a is not b:
                    distance = a - b
                    color = map_distance_to_color(distance)
                    if color is not None:
                        pygame.draw.line(screen, color, a.pos, b.pos)

        for star in self._stars:
            alive = star.update(screen, elapsed)
            if not alive:
                remove.append(star)
        for star in remove:
            self._stars.remove(star)
        self._respawn()


def main():
    pygame.init()
    pygame.display.set_caption("Star Geometry")

    screen = pygame.display.set_mode(SIZE)
    clock = pygame.time.Clock()
    star_field = StarField()

    running = True
    elapsed = 0

    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
        screen.fill((0, 0, 0))
        star_field.update(screen, elapsed / 1000)
        pygame.display.flip()
        elapsed = clock.tick(60)


if __name__ == "__main__":
    main()
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@__deets__,

ich hab's noch nicht ausprobiert, sieht aber gut aus. Enthält auch ein paar Antworten, auf Fragen, die ich mir in ähnlichen Situationen gestellt hatte.
Ich glaube bei der Abstandsberechnung kann man sich das Wurzelziehen sparen, da es ja nur eine Zahl und keine tatsächliche Länge ist.

Es kommt hier zwar nicht vor, aber wie würdest du das Neuplazieren eines Sterns implementieren? Analog mit einem Setter?

star.pos = (20, 16)
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Der Abstand ist nicht ganz unwichtig, weil davon die Helligkeit der Linie abhängt. Das wäre sonst etwas weniger balanciert. Kann man natürlich drumherum arbeiten, aber bei heutigen FPUs und dann noch in Python ist das glaube ich verschmerzbar. Für das reine Schwellwert-basierte kappen ist das aber natürlich korrekt was du sagst.

Pos setzen… ist so eine Sache. Klar kann man das so machen. Alternativ kann man aber auch einfach pos als Vektor-Typ definieren. Das wäre ja auch an anderen Stellen nett.

Es ist ja nur ein “Confluence Artikel schreiben oder kurz etwas visuell ansprechendes hin hacken”-Projekt 😬
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Apropos Vektor-Typ: `pygame.Vector2`. 😎
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Neumodischer Hexenkram, als ich zum ersten mal vor droelfzig Jahren mit pygame begonnen habe, gab es das noch nicht(tm).
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

So, ich habe das mal gerade ueberarbeitet. Und auf diesen "Vector2" umgestellt. Vor allem aber ist es visuell viel ansprechender: ich sortiere die Linien jetzt nach Helligkeit, so dass dunkle Linien zuerst gemalt werden. Damit durchkreuzen die nicht die hellen Linien.

Code: Alles auswählen

import random
import pygame
import math
from dataclasses import dataclass

SIZE = (400, 400)
MAX_LINE_LENGTH = 80

SPEED_MU = 20
MAX_SIZE = 3
WHITE = (255, 255, 255, 255)


@dataclass
class Star:
    pos: pygame.Vector2
    speed: pygame.Vector2
    size: int

    def update(self, screen, elapsed):
        pygame.draw.circle(screen, WHITE, self.pos, self.size)
        self.pos += elapsed * self.speed
        return int(self.pos.x) in range(SIZE[0]) and int(self.pos.y) in range(SIZE[1])

    def __sub__(self, other):
        return (self.pos - other.pos).length()


def map_distance_to_color(distance):
    if distance < MAX_LINE_LENGTH:
        g = int(255 * (1 - distance / MAX_LINE_LENGTH))
        return (g, g, g, g)
    return None


class StarField:

    def __init__(self, count=100):
        self._count = count
        self._stars = []
        self._respawn()

    def _respawn(self):
        for _ in range(self._count - len(self._stars)):
            star = Star(
                pos=pygame.Vector2(
                    random.randint(0, SIZE[0] - 1),
                    random.randint(0, SIZE[1] - 1),
                ),
                speed=pygame.Vector2(
                    random.gauss(0, SPEED_MU),
                    random.gauss(0, SPEED_MU),
                ),
                size=random.randint(1, MAX_SIZE),
            )
            self._stars.append(star)

    def update(self, screen, elapsed):
        remove = []
        lines = []
        for a in self._stars:
            for b in self._stars:
                if a is not b:
                    distance = a - b
                    color = map_distance_to_color(distance)
                    if color is not None:
                        lines.append((color, a.pos, b.pos))

        for color, a_pos, b_pos in sorted(lines, key=lambda line: line[0]):
            pygame.draw.line(screen, color, a_pos, b_pos)

        for star in self._stars:
            alive = star.update(screen, elapsed)
            if not alive:
                remove.append(star)
        for star in remove:
            self._stars.remove(star)
        self._respawn()


def main():
    pygame.init()
    pygame.display.set_caption("Star Geometry")

    screen = pygame.display.set_mode(SIZE)
    clock = pygame.time.Clock()
    star_field = StarField()

    running = True
    elapsed = 0

    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
        screen.fill((0, 0, 0))
        star_field.update(screen, elapsed / 1000)
        pygame.display.flip()
        elapsed = clock.tick(60)


if __name__ == "__main__":
    main()
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

Warum werden die toten Sterne in einer Liste gespeichert? Kann man die nicht direkt entfernen?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Probiert’s mal ;)
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

Du spielst wohl darauf an, dass man Elemente aus der Liste entfernt während man darüber iteriert?
Also, bei mir funktioniert es auch ohne Zwischenspeichern in eine Liste.

Mir ist gerade auch noch aufgefallen, dass das Muster auf der Discord-Seite durch die Maus beeinflusst werden kann.
Das kann man erstaunlicherweise durch Hinzufügen einer einzigen Zeile erreichen.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn du das machst, dann funktioniert es im Sinne von es kracht nicht. Aber der nächste ⭐️ rückt auf, und dessen Update wird nicht ausgelöst. Das fällt bei dem Effekt nicht auf, aber falsch würde ich sagen ist es trotzdem.
Antworten