Как закодировать движение отскока в понге с помощью pygame

Я новичок в питоне, и я пытаюсь воссоздать игру Pong, и я пытаюсь сделать это самостоятельно, насколько это возможно.

Вот проблемы, которые у меня сейчас есть с моим кодом:

  1. По сути, я закодировал каждое возможное движение мяча, когда он отскакивает от края. Я потратил часы, работая над этим, и я заставил его работать, мне просто интересно, есть ли более эффективный способ создать аналогичный вывод с моим кодом (потому что я знаю, что это должен быть проект для начинающих)?
  2. Каждый раз, когда мяч отскакивает от края, счет увеличивается на долю секунды и возвращается к 0 каждый раз, когда мяч возрождается.
  3. Как я могу заставить мяч появляться в случайном направлении? в начале (и перезапуске) каждого раунда?

Вот мой код для класса и основной функции программы:

import pygame
import random
from rect_button import Button

pygame.init()

WIDTH = 800
HEIGHT = 500
window = pygame.display.set_mode((WIDTH, HEIGHT))

TITLE = "Pong"
pygame.display.set_caption(TITLE)

# COLORS
black = (0, 0, 0)
white = (255, 255, 255)
red = (255, 0, 0)
green = (0, 255, 0)

# FONTS
small_font = pygame.font.SysFont("courier", 20)
large_font = pygame.font.SysFont("courier", 60, True)


class Paddle:
    def __init__(self, x, y, width, height, vel, color):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.vel = vel
        self.color = color

    def draw(self, window):
        pygame.draw.rect(window, self.color, (self.x, self.y, self.width, self.height), 0)


class Ball:
    def __init__(self, x, y, side, vel, color):
        self.x = x
        self.y = y
        self.side = side
        self.vel = vel
        self.color = color
        self.lower_right = False
        self.lower_left = True
        self.upper_right = False
        self.upper_left = False
        self.ball_bag = []
        self.last_movement = 'ball.lower_right'

    def draw(self, window):
        pygame.draw.rect(window, self.color, (self.x, self.y, self.side, self.side), 0)

    def move_lower_right(self):
        self.x += self.vel
        self.y += self.vel

    def move_upper_right(self):
        self.x += self.vel
        self.y -= self.vel

    def move_upper_left(self):
        self.x -= self.vel
        self.y -= self.vel

    def move_lower_left(self):
        self.x -= self.vel
        self.y += self.vel

    def start(self):
        self.lower_right = True
        self.lower_left = False
        self.upper_right = False
        self.upper_left = False

        self.last_movement = 'ball.lower_left'

        # return random.choice([self.lower_right, self.lower_left, self.upper_left, self.upper_right]) is True

def main():
    run = True
    fps = 60
    clock = pygame.time.Clock()

    # Initializing Paddles

    left_paddle = Paddle(20, 100, 10, 50, 5, white)
    right_paddle = Paddle(770, 350, 10, 50, 5, white)

    balls = []

    def redraw_window():
        window.fill(black)

        left_paddle.draw(window)
        right_paddle.draw(window)
        for ball in balls:
            ball.draw(window)

        player_A_text = small_font.render("Player A: " + str(score_A), 1, white)
        player_B_text = small_font.render("Player B: " + str(score_B), 1, white)
        window.blit(player_A_text, (320 - int(player_A_text.get_width() / 2), 10))
        window.blit(player_B_text, (480 - int(player_B_text.get_width() / 2), 10))

        pygame.draw.rect(window, white, (20, 450, 760, 1), 0)
        pygame.draw.rect(window, white, (20, 49, 760, 1), 0)
        pygame.draw.rect(window, white, (19, 50, 1, 400), 0)
        pygame.draw.rect(window, white, (780, 50, 1, 400), 0)

        pygame.display.update()

    while run:
        score_A = 0
        score_B = 0
        clock.tick(fps)

        if len(balls) == 0:
            ball = Ball(random.randrange(320, 465), random.randrange(200, 285), 15, 3, white)
            balls.append(ball)
        if ball.lower_left:
            ball.move_lower_left()

            if ball.last_movement == 'ball.lower_right':
                if ball.y + ball.side > HEIGHT - 50:
                    ball.lower_left = False
                    ball.last_movement = 'ball.lower_left'
                    ball.upper_left = True

            if ball.last_movement == 'ball.upper_left':
                if ball.x < 30:
                    if left_paddle.x < ball.x < left_paddle.x + left_paddle.width:
                        if left_paddle.y < ball.y + ball.side < left_paddle.y + left_paddle.height:
                            ball.lower_left = False
                            ball.last_movement = 'ball.lower_left'
                            ball.lower_right = True
                    else:
                        score_B += 1
                        balls.remove(ball)
                        #ball.start()

                if ball.y + ball.side > HEIGHT - 50:
                    ball.lower_left = False
                    ball.last_movement = 'ball.lower_left'
                    ball.upper_left = True

        if ball.upper_left:
            ball.move_upper_left()

            if ball.last_movement == 'ball.lower_left':
                if ball.x < 30:
                    if left_paddle.x < ball.x < left_paddle.x + left_paddle.width:
                        if left_paddle.y < ball.y + ball.side < left_paddle.y + left_paddle.height:
                            ball.upper_left = False
                            ball.last_movement = 'ball.upper_left'
                            ball.upper_right = True
                    else:
                        score_B += 1
                        balls.remove(ball)
                        #ball.start()

                if ball.y < 50:
                    ball.upper_left = False
                    ball.last_movement = 'ball.upper_left'
                    ball.lower_left = True

            if ball.last_movement == 'ball.upper_right':
                if ball.y < 50:
                    ball.upper_left = False
                    ball.last_movement = 'ball.upper_left'
                    ball.lower_left = True

        if ball.upper_right:
            ball.move_upper_right()

            if ball.last_movement == 'ball.upper_left':
                if ball.y < 50:
                    ball.upper_right = False
                    ball.last_movement = 'ball.upper_right'
                    ball.lower_right = True

            if ball.last_movement == 'ball.lower_right':
                if ball.x + ball.side > WIDTH - 30:
                    if right_paddle.x + right_paddle.width > ball.x + ball.side > right_paddle.x:
                        if right_paddle.y < ball.y + ball.side < right_paddle.y + right_paddle.height:
                            ball.upper_right = False
                            ball.last_movement = 'ball.upper_right'
                            ball.upper_left = True
                    else:
                        score_A += 1
                        balls.remove(ball)
                        #ball.start()

                if ball.y < 50:
                    ball.upper_right = False
                    ball.last_movement = 'ball.upper_right'
                    ball.lower_right = True

        if ball.lower_right:
            ball.move_lower_right()

            if ball.last_movement == 'ball.upper_right':
                if ball.y + ball.side > HEIGHT - 50:
                    ball.lower_right = False
                    ball.last_movement = 'ball.lower_right'
                    ball.upper_right = True

                if ball.x + ball.side > WIDTH - 30:
                    if right_paddle.x + right_paddle.width > ball.x + ball.side > right_paddle.x:
                        if right_paddle.y < ball.y + ball.side < right_paddle.y + right_paddle.height:
                            ball.lower_right = False
                            ball.last_movement = 'ball.lower_right'
                            ball.lower_left = True
                    else:
                        score_A += 1
                        balls.remove(ball)
                        #ball.start()

            if ball.last_movement == 'ball.lower_left':
                if ball.y + ball.side > HEIGHT - 50:
                    ball.lower_right = False
                    ball.last_movement = 'ball.lower_right'
                    ball.upper_right = True

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
                quit()

        keys = pygame.key.get_pressed()

        if keys[pygame.K_UP] and right_paddle.y > 50:
            right_paddle.y -= right_paddle.vel
        if keys[pygame.K_w] and left_paddle.y > 50:
            left_paddle.y -= left_paddle.vel
        if keys[pygame.K_DOWN] and right_paddle.y + right_paddle.height < HEIGHT - 50:
            right_paddle.y += right_paddle.vel
        if keys[pygame.K_s] and left_paddle.y + left_paddle.height < HEIGHT - 50:
            left_paddle.y += left_paddle.vel
        if keys[pygame.K_SPACE]:
            pass

        redraw_window()

    quit()


def main_menu():
    run = True

    play_button = Button(green, 100, 350, 150, 75, "Play Pong")
    quit_button = Button(red, 550, 350, 150, 75, "Quit")

    pong_text = large_font.render("Let's Play Pong!!!", 1, black)

    while run:
        window.fill(white)

        play_button.draw(window, black)
        quit_button.draw(window, black)

        window.blit(pong_text, (int(WIDTH / 2 - pong_text.get_width() / 2), 100))

        pygame.display.update()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
                quit()

            if event.type == pygame.MOUSEMOTION:
                if play_button.hover(pygame.mouse.get_pos()):
                    play_button.color = (0, 200, 0)
                else:
                    play_button.color = green
                if quit_button.hover(pygame.mouse.get_pos()):
                    quit_button.color = (200, 0, 0)
                else:
                    quit_button.color = red

            if event.type == pygame.MOUSEBUTTONDOWN:
                if play_button.hover(pygame.mouse.get_pos()):
                    main()
                if quit_button.hover(pygame.mouse.get_pos()):
                    run = False
                    quit()


main_menu()

Спасибо!!!


person Kyle Angelo Gonzales    schedule 28.09.2020    source источник
comment
вы можете попробовать это репо на github: github.com/skar91/pong- python/blob/master/pong.py   -  person Peyman Majidi    schedule 28.09.2020
comment
или этот: github.com/WillBaumbach/Pong-Python   -  person Peyman Majidi    schedule 28.09.2020
comment
Есть ли шанс, что вы можете поделиться классом Paddle / всем проектом?   -  person Paul M.    schedule 28.09.2020
comment
Я загрузил весь код: @PaulM.   -  person Kyle Angelo Gonzales    schedule 28.09.2020
comment
Вас может заинтересовать иногда мяч не нельзя отскакивать от ракетки в игре в понг   -  person Rabbid76    schedule 28.09.2020


Ответы (4)


отвечать на

  1. Каждый раз, когда мяч отскакивает от края, счет увеличивается на долю секунды и возвращается к 0 каждый раз, когда мяч возрождается.

Оценка непрерывно инициализируется в основном цикле. Вы должны инициализировать счет перед циклом:

def main():
    # [...]

    score_A = 0 # <--- INSERT
    score_B = 0
    
    while run:
        # score_A = 0 <--- DELETE
        # score_B = 0
person Rabbid76    schedule 28.09.2020

Ответ на вопрос 1: Да. Определенно есть гораздо более эффективный способ сделать что-то. Нет необходимости делать так много переменных для направления. Просто скажите direction = [True, False], где direction[0] представляет лево, а not direction[0] представляет право по оси x. Точно так же direction[1] представляет ось Y. Это также решает вашу проблему рандомизации направления при запуске. Вы можете просто сделать direction = [random.randint(0, 1), random.randint(0, 1)] в своем методе инициализации, чтобы рандомизировать направление. Точно так же составьте список и для скорости. self.speed = [0.5, random.uniform(0.1, 1)]. Таким образом, скорость влево и вправо всегда будет одинаковой, но y будет варьироваться в зависимости от выбранного случайного числа, поэтому будет надлежащая случайность, и вам также не придется жестко задавать случайные направления. Благодаря этому движение становится очень простым.

class Ball:
def __init__(self, x, y, color, size):
    self.x = x
    self.y = y
    self.color = color
    self.size = size
    self.direction = [random.randint(0, 1), random.randint(0, 1)]
    self.speed = [0.5, random.uniform(0.1, 1)]
    
def draw(self, display):
    pygame.draw.rect(display, self.color, (self.x, self.y, self.size, self.size))

def move(self):
    if self.direction[0]:
        self.x += self.speed[0]
    else:
        self.x -= self.speed[0]
    if self.direction[1]:
        self.y += self.speed[0]
    else:
        self.y -= self.speed[0]

Поскольку мы определили направления, направления являются логическими, изменение состояния также становится очень простым. Если мяч попадает в ракетку, вы можете просто переключить логическое значение direction[0] = not direction[0] в x и выбрать новое случайное число для y вместо того, чтобы назначать логические значения вручную.

def switchDirection(self):
    self.direction = not self.direction
    self.speed[1] = random.uniform(0.1, 1)

Paddle также можно немного улучшить, предоставив классу Paddle функцию move вместо перемещения в основном цикле. Это просто означает, что вам нужно писать меньше кода.

def move(self, vel, up=pygame.K_UP, down=pygame.K_DOWN):
    keys = pygame.key.get_perssed()
    if keys[up]:
        self.y -= vel
    if keys[down]:
        self.y += vel

Для коллизий я рекомендую использовать pygame.Rect() и colliderect, так как они намного надежнее и, возможно, более эффективны.

Пример:

import random
import pygame

WIN = pygame.display
D = WIN.set_mode((800, 500))

class Paddle:
    def __init__(self, x, y, width, height, vel, color):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.vel = vel
        self.color = color

    def move(self, vel, up=pygame.K_UP, down=pygame.K_DOWN):
        keys = pygame.key.get_pressed()
        if keys[up]:
            self.y -= vel
        if keys[down]:
            self.y += vel
        
    def draw(self, window):
        pygame.draw.rect(window, self.color, (self.x, self.y, self.width, self.height), 0)

    def getRect(self):
        return pygame.Rect(self.x, self.y, self.width, self.height)

left_paddle = Paddle(20, 100, 10, 50, 5, (0, 0, 0))
right_paddle = Paddle(770, 350, 10, 50, 5, (0, 0, 0))

class Ball:
    def __init__(self, x, y, color, size):
        self.x = x
        self.y = y
        self.color = color
        self.size = size
        self.direction = [random.randint(0, 1), random.randint(0, 1)]
        self.speed = [0.3, random.uniform(0.2, 0.2)]
        
    def draw(self, window):
        pygame.draw.rect(window, self.color, (self.x, self.y, self.size, self.size))

    def switchDirection(self):
        self.direction[0] = not self.direction[0]
        self.direction[1] = not self.direction[1]
        self.speed = [0.2, random.uniform(0.1, 0.5)]

    def bounce(self):
        self.direction[1] = not self.direction[1]
        self.speed = [0.2, random.uniform(0.01, 0.2)]
        
    def move(self):
        if self.direction[0]:
            self.x += self.speed[0]
        else:
            self.x -= self.speed[0]
        if self.direction[1]:
            self.y += self.speed[1]
        else:
            self.y -= self.speed[1]

    def getRect(self):
        return pygame.Rect(self.x, self.y, self.size, self.size)

    def boundaries(self):
        if ball.x <= 10:
            self.switchDirection()
        if ball.x + self.size >= 800:
            self.switchDirection()
        if ball.y + self.size >= 490:
            self.bounce()
        if ball.y <= 0:
            self.bounce()


ball = Ball(400, 250, (255, 0, 0), 20)
while True:
    pygame.event.get()
    D.fill((255, 255, 255))

    ball.draw(D)
    ball.boundaries()
    ball.move()
    #print(ball.x, ball.y)
    
    left_paddle.draw(D)
    right_paddle.draw(D)

    right_paddle.move(0.4)
    left_paddle.move(0.4, down=pygame.K_s, up=pygame.K_w)

    if left_paddle.getRect().colliderect(ball.getRect()):
        ball.switchDirection()
    if right_paddle.getRect().colliderect(ball.getRect()):
        ball.switchDirection()
    
    WIN.flip()
person Community    schedule 28.09.2020

По сути, я закодировал каждое возможное движение мяча, когда он отскакивает от края. Я потратил часы, работая над этим, и я заставил его работать, мне просто интересно, есть ли более эффективный способ создать аналогичный вывод с моим кодом (потому что я знаю, что это должен быть проект для начинающих)?

а также

Как я могу заставить мяч появляться в случайном направлении? в начале (и перезапуске) каждого раунда?

Вместо того, чтобы реализовывать каждое направление мяча, вы можете использовать координаты с плавающей запятой. эти переменные обычно называются dx и dy. Таким образом, получить случайное или обратное направление для вашего мяча очень просто, просто используйте случайные или обратные значения для dx и dy.

Обновление для вашего мяча должно выглядеть так:

def update(self. dt):
    self.x += self.dx * self.speed * time_elapsed
    self.y += self.dy * self.speed * time_elapsed # Time elasped is often called dt.

Каждый раз, когда мяч отскакивает от края, счет увеличивается на долю секунды и возвращается к 0 каждый раз, когда мяч возрождается.

См. ответ Rabid76. В идеале у вас должен быть объект GameState с очками, жизнями и другими вещами в качестве атрибутов.

person Taek    schedule 28.09.2020

Немного пищи для размышлений о том, как уменьшить весь этот код движения/столкновения мяча и сделать его более многоразовым:

import pygame


class Ball:

    def __init__(self, bounds, color):
        from random import randint, choice
        self.bounds = bounds
        self.position = pygame.math.Vector2(
            randint(self.bounds.left, self.bounds.left+self.bounds.width),
            randint(self.bounds.top, self.bounds.top+self.bounds.height)
        )
        self.velocity = pygame.math.Vector2(choice((-1, 1)), choice((-1, 1)))
        self.color = color
        self.size = 8

    def draw(self, window):
        pygame.draw.rect(
            window,
            self.color,
            (
                self.position.x-self.size,
                self.position.y-self.size,
                self.size*2,
                self.size*2
            ),
            0
        )

    def update(self):
        self.position.x += self.velocity.x
        self.position.y += self.velocity.y
        if not self.bounds.left+self.size < self.position.x < self.bounds.left+self.bounds.width-self.size:
            self.velocity.x *= -1
        if not self.bounds.top+self.size < self.position.y < self.bounds.top+self.bounds.height-self.size:
            self.velocity.y *= -1


def main():

    from random import randint

    window_width, window_height = 800, 500

    window = pygame.display.set_mode((window_width, window_height))
    pygame.display.set_caption("Pong")

    clock = pygame.time.Clock()

    black = (0, 0, 0)
    white = (255, 255, 255)

    padding = 20

    bounds = pygame.Rect(padding, padding, window_width-(padding*2), window_height-(padding*2))

    ball = Ball(bounds, white)

    def redraw_window():
        window.fill(black)
        pygame.draw.rect(
            window,
            white,
            (
                padding,
                padding,
                window_width-(padding*2),
                window_height-(padding*2)
            ),
            1
        )

        ball.draw(window)

        pygame.display.update()

    while True:
        clock.tick(60)
        ball.update()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                break
        else:
            redraw_window()
            continue
        break
    pygame.quit()
    return 0
        

if __name__ == "__main__":
    import sys
    sys.exit(main())

Это не полная замена, я просто повторно реализовал класс Ball. Весла нет. По сути, при создании экземпляра мяча вы передаете pygame.Rect, который описывает границы, в которых мяч может прыгать. Вы также передаете кортеж цвета. Затем мяч выбирает случайную позицию в пределах границ (позиция представляет собой pygame.math.Vector2, в отличие от сохранения x и y в качестве отдельных переменных экземпляра). Мяч также имеет скорость, которая также равна pygame.math.Vector2, так что у вас могут быть независимые компоненты скорости — одна для x (горизонтальная скорость) и одна для y (вертикальная скорость). size мяча просто описывает размеры мяча. Например, если size установить на 8, то размер шара будет 16x16 пикселей.

Класс Ball также имеет метод update, который вызывается один раз за итерацию игрового цикла. Он перемещает мяч в следующую позицию, определяемую скоростью, и проверяет, не сталкивается ли мяч с границами. Если это так, измените соответствующую составляющую скорости.

person Paul M.    schedule 28.09.2020