Неустойчивая анимация в Pygame

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

Я был в состоянии оживить коробку; Однако анимация довольно изменчива. Вот два видео, которые иллюстрируют то, о чем я говорю:

30 FPS: https://www.youtube.com/watch?v=0de8ENxn7GQ

60 FPS: https://www.youtube.com/watch?v=b5sXgeOlgHU

И вот мой код:

import sys
import random

import pygame


class Box:

    def __init__(self, x, y):
        self.width = 38
        self.height = 38
        self.color = (255, 0, 0)
        self.x = x
        self.y = y
        self.old_x = x
        self.old_y = y
        self.d_x = 1
        self.d_y = -1
        self.px_per_second = 200

    def move(self):
        self.old_x = self.x
        self.old_y = self.y
        if self.x <= 0:
            self.d_x *= -1
        if self.x + self.width >= task.width:
            self.d_x *= -1
        if self.y <= 0:
            self.d_y *= -1
        if self.y + self.height >= task.height:
            self.d_y *= -1
        self.x += ((self.px_per_second*self.d_x)*
                  (task.ms_from_last_frame/1000.0))
        self.y += ((self.px_per_second*self.d_y)*
                  (task.ms_from_last_frame/1000.0))

    def draw(self):
        self.x_i = int(self.x)
        self.y_i = int(self.y)
        self.old_x_i = int(self.old_x)
        self.old_y_i = int(self.old_y)
        _old_rect = (pygame.Rect(self.old_x_i, self.old_y_i, 
                                 self.width, self.height))
        _new_rect = (pygame.Rect(self.x_i, self.y_i, self.width, self.height))
        if _old_rect.colliderect(_new_rect):
            task.dirty_rects.append(_old_rect.union(_new_rect))
        else:
            task.dirty_rects.append(_old_rect)
            task.dirty_rects.append(_new_rect)
        pygame.draw.rect(task.screen, task.bg_color, _old_rect)
        pygame.draw.rect(task.screen, (self.color), _new_rect)


class ObjectTask:

    def __init__(self, width, height):
        pygame.init()
        self.max_fps = 60
        self.clock = pygame.time.Clock()
        self.width = width
        self.height = height
        self.bg_color = (255, 255, 255)
        self.dirty_rects = []
        self.screen = pygame.display.set_mode((self.width, self.height),
                                               pygame.FULLSCREEN)
        self.screen.fill(self.bg_color)
        pygame.display.update()

    def animation_loop(self):
        self.box1 = Box(self.width/2, self.height/2)
        while 1:
            self.ms_from_last_frame = self.clock.tick(self.max_fps)
            self.box1.move()
            self.box1.draw()
            pygame.display.update(self.dirty_rects)
            self.dirty_rects = []
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()


if __name__ == "__main__":
    task = ObjectTask(726, 546)
    task.animation_loop()

Могу ли я что-нибудь сделать, чтобы уменьшить хаотичность? Кроме того, я новичок в Pygame, поэтому, если вы видите что-то, что я делаю неправильно / неэффективно, пожалуйста, дайте мне знать.

Я запускаю анимацию на 64-битной машине Windows 7, i5-6300u с 12 ГБ ОЗУ. Я использую Python 2.7.12 и Pygame 1.9.2.

Заранее спасибо!

1 ответ

У меня было немного времени, и я подумал, что смогу показать, как напишу эту программу. Я пытался объяснить, что все делают в комментариях. Я удалил твой ObjectTask класс, потому что все может поместиться в одну функцию, а функции аккуратны (на мой взгляд, они быстрее и легче читаются / отлаживаются).

Я также изменил его, чтобы вы передавали переменные в методы вместо чтения из глобальной переменной (task объект), потому что это делает программу более модульной и более легкой для изменения / рефакторинга.

Наконец, я положил некоторые атрибуты в один. x а также y связаны, поэтому я положил их в один общий атрибут position,

import pygame
pygame.init()


# It's convenient if all your visible objects inherit 'pygame.sprite.Sprite'. It allows you to use sprite groups and
# some useful collision detection functions.
class Box(pygame.sprite.Sprite):
    def __init__(self, x, y, screen_width, screen_height):
        super(Box, self).__init__()  # Initialize the 'pygame.sprite.Sprite' (the super class).
        self.size = (38, 38)
        self.color = (255, 0, 0)
        self.position = pygame.math.Vector2(x, y)  # Vector is a 2-element tuple with some neat methods shown later.
        self.velocity = pygame.math.Vector2(200, -200)
        self.screen_size = (screen_width, screen_height)

        # Instead of drawing the rectangle onto the screen we're creating an image which we 'blit' (put) onto the screen
        self.image = pygame.Surface(self.size)
        self.image.fill(self.color)

        # We create a rectangle same size as our image and where the top left of the rectangle is positioned at our
        # 'position'.
        self.rect = self.image.get_rect(topleft=self.position)

    # Changing name to update. Game objects usually have two methods 'update' and 'draw', where 'update' is the function
    # that will be called every game loop and update the object, and 'draw' draws the game object every game loop.
    def update(self, dt):  # 'dt' (delta time) is what you formerly called task.ms_from_last_frame.
        # unpack the variables so it's easier to read. Not necessary, but nice.
        width, height = self.size
        x, y = self.position
        screen_width, screen_height = self.screen_size

        if x <= 0 or x + width >= screen_width:  # Only one if-statement needed.
            self.velocity[0] *= -1  # First element is the x-velocity.
        if y <= 0 or y + height >= screen_height:
            self.velocity[1] *= -1  # Second element is y-velocity.

        # Addition and subtraction works element-wise with vectors. Adding our two vectors 'position' and 'velocity':
        #    position += velocity  <=>  position = position + velocity
        # is the same as this:
        #    position_x, position_y = position_x + velocity_x, position_y + position_y
        #
        # Multiplication and division works on all elements. So 'velocity * dt / 1000' is the same as:
        #    velocity_x * dt / 1000, velocity_y * dt / 1000
        self.position += self.velocity * dt / 1000

        # Instead of creating a new rect we just move the top left corner of our rect to where we calculated our
        # position to be.
        self.rect.topleft = self.position

    # 'surface' is the Surface object you want to draw on (in this case the screen).
    def draw(self, surface):
        # This puts our image unto the screen at the given position.
        surface.blit(self.image, self.position)


def main():
    max_fps = 60
    clock = pygame.time.Clock()
    bg_color = (255, 255, 255)
    width, height = 726, 546
    screen = pygame.display.set_mode((width, height))
    screen.fill(bg_color)

    box1 = Box(width / 2, height / 2, width, height)
    sprite_group = pygame.sprite.Group(box1)
    while 1:
        # First handle time.
        dt = clock.tick(max_fps)

        # Then handle events.
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                quit()  # 'pygame.quit' does not quit the program! It only uninitialize the pygame modules.
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    quit()

        # Update your objects.
        sprite_group.update(dt)  # Calls the update method on all sprites in the group.

        # Draw your objects.
        screen.fill(bg_color)  # Clear the screen.
        sprite_group.draw(screen)  # Draws all sprites on the screen using the 'image' and 'rect' attributes.

        # Display everything you've drawn.
        pygame.display.update()


if __name__ == "__main__":
    main()

В этом примере я не беспокоился об отслеживании грязных участков. Я испытал это, что вызывает дрожание. Этот пример, который я привел, должен работать прилично, но время от времени всегда есть некоторое дрожание. Если вы хотите попробовать этот пример с отслеживанием грязных каналов, просто измените:

sprite_group = pygame.sprite.Group(box1)

в

sprite_group = pygame.sprite.RenderUpdates(box1)

а также

sprite_group.draw(screen)  # Draws all sprites on the screen using the 'image' and 'rect' attributes.

# Display everything you've drawn.
pygame.display.update() 

в

dirty_rects = sprite_group.draw(screen)  # Draws all sprites on the screen using the 'image' and 'rect' attributes.

# Display everything you've drawn.
pygame.display.update(dirty_rects)  # Update only the areas that have changes
Другие вопросы по тегам