Bewegender kreis

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
Tobi234
User
Beiträge: 6
Registriert: Dienstag 27. Februar 2024, 11:49

Hi, ich befasse mich gerade mit pygame und habe auch schon einfache Dinge realisiert. Hier komme ich aber nicht weiter. Ich möchte ein ganz einfaches Spiel erstellen im Vollbildmodus. Es soll sich ein kleiner weißer Ball zufällig über den schwarzen Bildschirm bewegen. Wenn der Ball über den touchscreen berührt wird soll er verschwinden und ein neuer Ball soll an zufälliger Stelle entstehen mit der gleichen Funktion. Wer nach 1 Minute die meisten Treffer hat wird im Highscore festgehalten. Je länger der ball nicht getroffen wird je schneller soll er werden.

Meine Versuche sind bisher alle fehlgeschlagen. Entweder der Ball zittert nur oder er bewegt sich nur in einem Radius von wenigen cm. Der Touchscreen wird auch nicht erkannt, es funktioniert nur mit der Maus. Den touchscreen habe ich aber schon mit einem anderen Spiel zum laufen bekommen.

Wer kann mir helfen auf die Sprünge zu kommen? Wo kann ich mich einlesen um das Problem zu lösen oder wer hat hilfreiche Hinweise für mich?
Tausend Dank :wink:
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Bitte den Code posten, in den dazugehörigen Code-Tags, damit er gut lesbar bleibt.
Tobi234
User
Beiträge: 6
Registriert: Dienstag 27. Februar 2024, 11:49

Ich habe es tatsächlich zum laufen bekommen und der touchscreen funktioniert auch. Nur bewegt sich der Kreis immer gerade durch den Monitor und nicht zufällig wie eine Schlange oder Biene. Der Ball sollte die Geschwindigkeit langsam steigern wenn er nicht getroffen wird. Wie könnte ich das lösen?

import pygame
import sys
import random
from pygame.locals import *
# Initialize Pygame
pygame.init()
# Set up the screen
WIDTH, HEIGHT = 1920, 1080
SCREEN = pygame.display.set_mode((WIDTH, HEIGHT), FULLSCREEN)
pygame.display.set_caption("Ball Explosion Game")
# Load the sound
punch_sound = pygame.mixer.Sound("/home/nala/Downloads/punch-2-123106.mp3")
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
# Ball properties
BALL_RADIUS = 18
BALL_SPEED = 2
# Game variables
hits = 0
start_time = pygame.time.get_ticks()
game_duration = 60000 # 60 seconds
# Function to draw a ball
def draw_ball(x, y):
pygame.draw.circle(SCREEN, WHITE, (x, y), BALL_RADIUS)
# Function to create a new ball at a random position
def create_new_ball():
return random.randint(BALL_RADIUS, WIDTH - BALL_RADIUS), random.randint(BALL_RADIUS, HEIGHT - BALL_RADIUS)
# Main game loop
running = True
ball_x, ball_y = create_new_ball()
direction_x = random.choice([-1, 1])
direction_y = random.choice([-1, 1])
ball_clicked = False
new_ball = False
while running:
# Check for events
for event in pygame.event.get():
if event.type == QUIT:
running = False
elif event.type == MOUSEBUTTONDOWN:
if event.button == 1 and not ball_clicked: # Left mouse button clicked and no ball clicked
mouse_x, mouse_y = pygame.mouse.get_pos()
if ((mouse_x - ball_x) ** 2 + (mouse_y - ball_y) ** 2) <= BALL_RADIUS ** 2: # Check if click is inside the ball
hits += 1
ball_clicked = True
# Play the punch sound
punch_sound.play()
# Clear the screen
SCREEN.fill(BLACK)
if not ball_clicked:
# Draw the ball
draw_ball(ball_x, ball_y)
# Update ball position
ball_x += BALL_SPEED * direction_x
ball_y += BALL_SPEED * direction_y
# Check if ball hits the wall and change direction
if ball_x <= BALL_RADIUS or ball_x >= WIDTH - BALL_RADIUS:
direction_x *= -1
if ball_y <= BALL_RADIUS or ball_y >= HEIGHT - BALL_RADIUS:
direction_y *= -1
# Check if the ball is clicked
mouse_click = pygame.mouse.get_pressed()
if mouse_click[0]: # Left mouse button clicked
if ((mouse_x - ball_x) ** 2 + (mouse_y - ball_y) ** 2) <= BALL_RADIUS ** 2: # Check if click is inside the ball
hits += 1
ball_clicked = True
# Play the punch sound
punch_sound.play()
# Check game duration
current_time = pygame.time.get_ticks()
if current_time - start_time >= game_duration:
running = False
else:
# Create a new ball
ball_x, ball_y = create_new_ball()
ball_clicked = False
# Display hits and time
pygame.font.init()
font = pygame.font.SysFont('Arial', 30)
hits_text = font.render("Hits: " + str(hits), True, RED)
time_text = font.render("Time: " + str((game_duration - (current_time - start_time)) // 1000), True, RED)
SCREEN.blit(hits_text, (50, 50))
SCREEN.blit(time_text, (WIDTH - 200, 50))
# Update the display
pygame.display.update()
# Quit Pygame
pygame.quit()
sys.exit()
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Und hier derselbe Code mit Code-Tags:

Code: Alles auswählen

import pygame
import sys
import random
from pygame.locals import *
# Initialize Pygame
pygame.init()
# Set up the screen
WIDTH, HEIGHT = 1920, 1080
SCREEN = pygame.display.set_mode((WIDTH, HEIGHT), FULLSCREEN)
pygame.display.set_caption("Ball Explosion Game")
# Load the sound
punch_sound = pygame.mixer.Sound("/home/nala/Downloads/punch-2-123106.mp3")
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
# Ball properties
BALL_RADIUS = 18
BALL_SPEED = 2
# Game variables
hits = 0
start_time = pygame.time.get_ticks()
game_duration = 60000  # 60 seconds
# Function to draw a ball
def draw_ball(x, y):
    pygame.draw.circle(SCREEN, WHITE, (x, y), BALL_RADIUS)
# Function to create a new ball at a random position
def create_new_ball():
    return random.randint(BALL_RADIUS, WIDTH - BALL_RADIUS), random.randint(BALL_RADIUS, HEIGHT - BALL_RADIUS)
# Main game loop
running = True
ball_x, ball_y = create_new_ball()
direction_x = random.choice([-1, 1])
direction_y = random.choice([-1, 1])
ball_clicked = False
new_ball = False
while running:
    # Check for events
    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
        elif event.type == MOUSEBUTTONDOWN:
            if event.button == 1 and not ball_clicked:  # Left mouse button clicked and no ball clicked
                mouse_x, mouse_y = pygame.mouse.get_pos()
                if ((mouse_x - ball_x) ** 2 + (mouse_y - ball_y) ** 2) <= BALL_RADIUS ** 2:  # Check if click is inside the ball
                    hits += 1
                    ball_clicked = True
                    # Play the punch sound
                    punch_sound.play()
    # Clear the screen
    SCREEN.fill(BLACK)
    if not ball_clicked:
        # Draw the ball
        draw_ball(ball_x, ball_y)
        # Update ball position
        ball_x += BALL_SPEED * direction_x
        ball_y += BALL_SPEED * direction_y
        # Check if ball hits the wall and change direction
        if ball_x <= BALL_RADIUS or ball_x >= WIDTH - BALL_RADIUS:
            direction_x *= -1
        if ball_y <= BALL_RADIUS or ball_y >= HEIGHT - BALL_RADIUS:
            direction_y *= -1
        # Check if the ball is clicked
        mouse_click = pygame.mouse.get_pressed()
        if mouse_click[0]:  # Left mouse button clicked
            if ((mouse_x - ball_x) ** 2 + (mouse_y - ball_y) ** 2) <= BALL_RADIUS ** 2:  # Check if click is inside the ball
                hits += 1
                ball_clicked = True
                # Play the punch sound
                punch_sound.play()
        # Check game duration
        current_time = pygame.time.get_ticks()
        if current_time - start_time >= game_duration:
            running = False
    else:
        # Create a new ball
        ball_x, ball_y = create_new_ball()
        ball_clicked = False
    # Display hits and time
    pygame.font.init()
    font = pygame.font.SysFont('Arial', 30)
    hits_text = font.render("Hits: " + str(hits), True, RED)
    time_text = font.render("Time: " + str((game_duration - (current_time - start_time)) // 1000), True, RED)
    SCREEN.blit(hits_text, (50, 50))
    SCREEN.blit(time_text, (WIDTH - 200, 50))
    # Update the display
    pygame.display.update()
# Quit Pygame
pygame.quit()
sys.exit()
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nunja, du berechnest eben nur einmal eine Richtung, und dann titschst du einfach an den Raendern ab. Woher soll da eine Schlangenbewegung herkommen? Oder Biene oder so?

Wenn du sowas bauen willst, musst du etwas tiefer in die Trickkiste greifen, und zB den Richtungsvektor abhaengig vom Zufall regelmaessig um ein paar Grad in eine oder andere Richtung drehen. Oder sogar noch weiter gehen, und Zb einen Kreisradius auswuerfeln, und sich fuer ein paar Frames auf dem Bewegen (womit der Vektor also jeden Frame etwas anders ist). Etc. Da sind der Phantasie keine Grenzen gesetzt.
Tobi234
User
Beiträge: 6
Registriert: Dienstag 27. Februar 2024, 11:49

Danke für den Hinweis, alles was ich bisher versucht habe endete in einem schwarzen Monitor. Kannst du mir eine Seite empfehlen wo ich mich speziell darin einlesen kann? Danke dir
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

Hi Tobi,

schau dir mal diesen Youtube Channel und diese Playlist hier an: https://www.youtube.com/watch?v=70MQ-Fu ... gGXKUUsPOM
Hier ist das Online Buch dazu: https://natureofcode.com/

Der Code ist zwar in Processing, das ist Java ähnlich, ist aber ohne Probleme in andere Sprachen umsetzbar.
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Tobi234: 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).

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.

Kommentare die beschreiben was eine Funktion macht, wären besser Docstrings.

Alles was eine Funktion oder Methode ausser Konstanten benötigt, wird als Argument(e) übergeben. `draw_ball()` braucht also `screen` als Argument, denn der veränderliche Bildschirm(inhalt) ist keine Konstante.

`new_ball` wird definiert, aber nirgends verwendet.

Der Test ob der Ball angeklickt wurde, steht unnötigerweise zweimal im Code.

Das zusammenstückeln von Zeichenketten und Werten mittels ``+`` und `str()` ist eher BASIC als Python. Dafür gibt es die `format()`-Methode auf Zeichenketten und f-Zeichenkettenliterale.

Für jeden Frame das `Font`-Objekt immer wieder neu erstellen macht keinen Sinn.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import random
import sys

import pygame
from pygame.locals import *

WIDTH, HEIGHT = 1920, 1080

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)

BALL_RADIUS = 18
BALL_SPEED = 2
GAME_DURATION = 60_000  # in milliseconds.


def draw_ball(screen, x, y):
    pygame.draw.circle(screen, WHITE, (x, y), BALL_RADIUS)


def create_new_ball():
    return (
        random.randint(BALL_RADIUS, WIDTH - BALL_RADIUS),
        random.randint(BALL_RADIUS, HEIGHT - BALL_RADIUS),
    )


def main():
    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT), FULLSCREEN)
    pygame.display.set_caption("Ball Explosion Game")
    font = pygame.font.SysFont("Arial", 30)
    punch_sound = pygame.mixer.Sound("/home/nala/Downloads/punch-2-123106.mp3")

    hits = 0
    start_time = pygame.time.get_ticks()
    ball_x, ball_y = create_new_ball()
    direction_x = random.choice([-1, 1])
    direction_y = random.choice([-1, 1])
    ball_clicked = False
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == QUIT:
                running = False
            elif (
                event.type == MOUSEBUTTONDOWN
                and event.button == 1
                and not ball_clicked
            ):
                mouse_x, mouse_y = pygame.mouse.get_pos()
                #
                # Check if click is inside the ball
                #
                if (
                    (mouse_x - ball_x) ** 2 + (mouse_y - ball_y) ** 2
                ) <= BALL_RADIUS**2:
                    hits += 1
                    ball_clicked = True
                    punch_sound.play()

        screen.fill(BLACK)
        if not ball_clicked:
            draw_ball(screen, ball_x, ball_y)
            ball_x += BALL_SPEED * direction_x
            ball_y += BALL_SPEED * direction_y
            #
            # Check if ball hits the wall and change direction
            #
            if ball_x <= BALL_RADIUS or ball_x >= WIDTH - BALL_RADIUS:
                direction_x *= -1
            if ball_y <= BALL_RADIUS or ball_y >= HEIGHT - BALL_RADIUS:
                direction_y *= -1

            current_time = pygame.time.get_ticks()
            if current_time - start_time >= GAME_DURATION:
                running = False
        else:
            ball_x, ball_y = create_new_ball()
            ball_clicked = False

        screen.blit(font.render(f"Hits: {hits}", True, RED), (50, 50))
        screen.blit(
            font.render(
                f"Time: {(GAME_DURATION - (current_time - start_time)) // 1000}",
                True,
                RED,
            ),
            (WIDTH - 200, 50),
        )
        pygame.display.update()

    pygame.quit()
    sys.exit()


if __name__ == "__main__":
    main()
Es würde Sinn machen die Geschwindigkeit zu begrenzen in der die Hauptschleife läuft. Pygame hat da ein `Clock`-Objekt für.

Und ein sinnvoller nächster Schritt wäre es den Ball in einem Objekt zusammenzufassen, und mal zu schauen welche Werkzeuge Pygame für Kollisionstests bereits stellt.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Tobi234
User
Beiträge: 6
Registriert: Dienstag 27. Februar 2024, 11:49

Hi, ich bin ein bisschen weiter gekommen und habe eine Bewegung hinbekommen, die ist aber nicht wirklich flüssig und bewegt sich auch nicht über den ganzen Bildschirm. Kann mir jemand helfen und sagen was man ändern könnte?

Wie kann man den Code darstellen dass er gut lesbar ist?

import pygame
import sys
import random

# Initialisierung von Pygame
pygame.init()

# Bildschirmgröße
screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)

# Hintergrund laden
background = pygame.image.load("/home/nala/Ballspiel/hintergrund.png")
background = pygame.transform.scale(background, (screen.get_width(), screen.get_height()))

# Biene laden und verkleinern
biene_original = pygame.image.load("/home/nala/Ballspiel/biene3.png").convert_alpha()
biene = pygame.transform.scale(biene_original, (int(biene_original.get_width() * 0.5), int(biene_original.get_height() * 0.5)))
bee_rect = biene.get_rect(center=(screen.get_width() // 2, screen.get_height() // 2)) # Startposition in der Mitte des Bildschirms

# Countdown-Bilder laden
countdown_images = [
pygame.image.load("/home/nala/Ballspiel/3.png").convert_alpha(),
pygame.image.load("/home/nala/Ballspiel/2.png").convert_alpha(),
pygame.image.load("/home/nala/Ballspiel/1.png").convert_alpha()
]

# Vektor für die Bewegung der Biene
bee_velocity = pygame.math.Vector2(0, 0)

# Sound laden
hit_sound = pygame.mixer.Sound("/home/nala/Ballspiel/hit.mp3")

# Font initialisieren
font = pygame.font.Font(None, 36)

# Spielzeit
game_time = 60 # 1 Minute in Sekunden
game_start_time = pygame.time.get_ticks()

# Klicks
clicks = 0

# Countdown Variable initialisieren
countdown_index = 0
countdown_duration = 1000 # 1 Sekunde pro Countdown

# Countdown zeigen
while countdown_index < len(countdown_images):
screen.blit(countdown_images[countdown_index], (screen.get_width() // 2 - countdown_images[countdown_index].get_width() // 2,
screen.get_height() // 2 - countdown_images[countdown_index].get_height() // 2))
pygame.display.flip()
pygame.time.wait(countdown_duration) # Wartezeit für jedes Bild im Countdown
countdown_index += 1

# Spiel-Schleife nach Countdown
running = True
clock = pygame.time.Clock()

while running:
# Ereignisse überprüfen
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
# Wenn Mausklick innerhalb der Biene
if bee_rect.collidepoint(event.pos):
# Sound abspielen
hit_sound.play()
# Biene ausblenden
screen.blit(background, bee_rect, bee_rect)
pygame.display.flip()
# Neue zufällige Position setzen
bee_rect.center = (random.randint(0, screen.get_width()), random.randint(0, screen.get_height()))
# Klicks erhöhen
clicks += 1

# Spielzeit aktualisieren
current_time = pygame.time.get_ticks()
time_elapsed = (current_time - game_start_time) // 1000 # Umwandlung von Millisekunden in Sekunden
time_remaining = max(0, game_time - time_elapsed)

# Wenn die Spielzeit abgelaufen ist, Spiel beenden
if time_remaining <= 0:
running = False

# Zufälligen Vektor für die Bewegung der Biene wählen
random_direction = pygame.math.Vector2(random.uniform(-5, 5), random.uniform(-5, 5))

# Geschwindigkeit auf eine maximale Länge von 10 begrenzen
random_direction.scale_to_length(random.uniform(1, 10)) # Zufällige Geschwindigkeit zwischen 1 und 10

# Neue Geschwindigkeit setzen
bee_velocity = random_direction

# Biene bewegen
bee_rect.move_ip(bee_velocity)

# Wenn die Biene den Bildschirmrand erreicht, bringen Sie sie zurück
if bee_rect.left < 0 or bee_rect.right > screen.get_width() or bee_rect.top < 0 or bee_rect.bottom > screen.get_height():
bee_rect.center = (random.randint(0, screen.get_width()), random.randint(0, screen.get_height())) # Zufällige Startposition

# Hintergrund und Biene zeichnen
screen.blit(background, (0, 0))
screen.blit(biene, bee_rect)

# Spielzeit und Klicks anzeigen
time_text = font.render(f"Time: {time_remaining} seconds", True, (255, 255, 255))
click_text = font.render(f"Clicks: {clicks}", True, (255, 255, 255))
screen.blit(time_text, (screen.get_width() - time_text.get_width(), 0))
screen.blit(click_text, (0, 0))

# Bildschirm aktualisieren
pygame.display.flip()

# Begrenze die Bildrate auf 60 Frames pro Sekunde
clock.tick(60)

# Spielzeit und Klicks am Ende des Spiels anzeigen
end_time_text = font.render(f"Time: {time_remaining} seconds", True, (255, 255, 255))
end_click_text = font.render(f"Clicks: {clicks}", True, (255, 255, 255))
screen.blit(end_time_text, (screen.get_width() - end_time_text.get_width(), 0))
screen.blit(end_click_text, (0, 0))

# Biene Retry Bild anzeigen
screen.blit(biene_retry, biene_retry_rect)
pygame.display.flip()

# Warten auf Klick auf Biene Retry Bild
retry_clicked = False
while not retry_clicked:
for event in pygame.event.get():
if event.type == pygame.QUIT:
retry_clicked = True
running = False
elif event.type == pygame.MOUSEBUTTONDOWN and bee_retry_rect.collidepoint(event.pos):
retry_clicked = True

pygame.quit()
sys.exit()
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Für realistische Bewegungen muß man die Physik berücksichtigen. Ein Objekt bewegt sich gleichförmig in eine Richtung, so lange keine Kraft auf es einwirkt.
Konkret, Dein Objekt hat eine Position und eine Geschwindigkeit, die änderst die Geschwindigkeit abrupt, was aber nicht unrealistisch ist, daher die komischen Bewegungen.
Das einfachste Beispiel ist ein Ball, der nach unten gezogen wird.
Wenn er einen Anfangsort und eine Anfangsgeschwindigkeit hat, bewegt er sich auf einer Parabelbahn. Da es dafür genug Beispiele im Netz gibt, würde ich empfehlen, dass Du damit mal anfängst, um ein Gefühl für Physik zu bekommen.
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich halte Physik hier für möglich, aber nicht nötig. Am Ende wirkt eine Trägheit + Kraft nahezu identisch zu einem Tiefpassfilter. Oder einfach einer trägeren Steuerung. Zb indem man die gewählte Richtung nur im gewissen Rahmen nach links/rechts verändert. Spannender ist da schon die Frage nach der Abdeckung des Bildschirms. Ich würde da mit einer groben Heatmap arbeiten. Also eine Kachel-Aufteilung des Bildschirms, bei der man für jede Kachel einfach vermerkt, wie lange/of die Biene da drin war. Man wählt als Ziel eine der Kacheln mit der niedrigsten Belegung aus, und mischt immer einen Vektor in deren Richtung anteilig dazu. Sobald man da angekommen ist, kann man die Kachel zb “bestrafen”, also extra teuer machen. Und dann sucht man die nächste. Das ganze lässt sich viel variieren, zb kann man den Cursor des Users ebenfalls die Belegung erhöhen passen, und erreicht dadurch, das die Biene die Richtung eher vermeidet.

Zur Frage der Code Formatierung: das geht mit den Code-Tags, ist der </>-Knopf im vollständigen Editor.
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich habe mal eine Variante gecodet, die mir ganz gut gefaellt. Und die eine Mischung aus zufaelliger Bewegung mit einem gezielten Anflug eines Punktes zeigt. Wenn man den letzteren basierend auf der genannten Heatmap macht, dann sollte der gesamte Bereich des Bildschirmes abgedeckt werden.

Code: Alles auswählen

import pygame
import pathlib
import math
import numpy as np
import time
import random

BEE_PATH = pathlib.Path(__file__).parent / "atari-st-busy-bee.png"
assert BEE_PATH.exists()


class Bee:

    # turn rate per second in degrees
    MAXIMUM_TURN_RATE = 700
    # pixels per second
    MAX_SPEED = 200
    PATTERN_LENGTH_BOUNDS = (.3, 1.5)
    TARGET_POS_WEIGHT = 5.0

    def __init__(self, pos=(200, 200)):
        self._bee = pygame.image.load(str(BEE_PATH))
        self.pos = np.array(pos, dtype=np.float_)
        self._previous_pos = None
        self.orientation = 0.0  # rad
        # rotation change per second in rad
        self._dw = 1.0
        # orientation of the bee is upwards, so that's the
        # directional speed component
        self._speed = np.array((0.0, -self.MAX_SPEED))
        self._until = time.monotonic()
        self._target_pos = None

    def update_target_pos(self, pos):
        if pos is not None:
            self._target_pos = np.array(pos, dtype=np.float_)
        else:
            self._target_pos = None

    def update(self, dt):
        if time.monotonic() >= self._until:
            self._randomize_flight()

        self.orientation += dt * self._dw
        # the negation is needed to orientate leftwards,
        # in accordance with the pygame.transform.rotate interpretation
        # of that quantity.
        c, s = np.cos(-self.orientation), np.sin(-self.orientation)
        R = np.array(((c, -s), (s, c)))
        dp = np.matmul(R, self._speed) * dt

        if self._target_pos is not None:
            tv = (self._target_pos - self.pos)
            tv = tv / np.linalg.norm(tv) * self.TARGET_POS_WEIGHT
        else:
            tv = np.array((0.0, 0.0))
        self._previous_pos = np.array(self.pos)
        self.pos += dp + tv

    @property
    def flight_orientation(self):
        if self._previous_pos is not None:
            d = self.pos - self._previous_pos
            # Negation and rotated coordinates account
            # for the different coordinate systems
            # in pixels vs atan2 being mathy
            return -math.atan2(d[0], -d[1])
        return 0.0

    def _randomize_flight(self):
        # for a few seconds
        l, h = self.PATTERN_LENGTH_BOUNDS
        self._until = time.monotonic() + random.random() * (h - l) + l
        degrees = (random.random() - 0.5) * self.MAXIMUM_TURN_RATE
        rad = degrees / 180.0 * math.pi
        self._dw = rad

    def blit(self, dest):
        rotation = 180 * (-math.pi / 4 + self.flight_orientation) / math.pi
        bee = pygame.transform.rotate(self._bee, rotation)
        # Correct for the transform's resizing
        # and place the center of the bee over
        # the coordinate
        w, h = bee.get_size()
        ow, oh = self._bee.get_size()
        diff = np.array((ow - w, oh - h))
        pos = tuple(self.pos + diff / 2 - np.array((ow, oh)) / 2)
        sw, sh = dest.get_size()
        dest.blit(bee, (pos[0] % sw, pos[1] % sh))


def main():
    pygame.init()
    screen = pygame.display.set_mode([500, 500])
    bee = Bee()
    clock = pygame.time.Clock()

    running = True
    while running:
        elapsed = clock.tick(30) * .001
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
        screen.fill((255, 255, 255))
        left, _, right = pygame.mouse.get_pressed(num_buttons=3)
        if left:
            bee.update_target_pos(pygame.mouse.get_pos())
        elif right:
            bee.update_target_pos(None)

        bee.update(elapsed)
        bee.blit(screen)
        pygame.display.flip()

    pygame.quit()


if __name__ == '__main__':
    main()
Bild
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Könnte sein, dass Du ein bisschen was manuell nachprogrammiert hast, was es schon als `pygame.Vector2` gibt.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Bestimmt. Alter Hund, neue Tricks. “Früher gabs das nicht, und wir sind auch klargekommen!”
Antworten