Pygame中的蹩脚动画

时间:2017-01-13 14:44:30

标签: python animation pygame dirtyrectangle

我试图创建一个动画,显示一个框从屏幕边缘反弹。并且,我试图使用基于时间的动画和脏矩形来完成此任务。

我能够为盒子制作动画;然而,动画非常不稳定。这是两个视频,说明了我在谈论的内容:

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 GB。我使用的是Python 2.7.12和Pygame 1.9.2。

提前致谢!

1 个答案:

答案 0 :(得分:1)

我有一段时间,并认为我可能会展示我将如何编写此程序。我试图解释评论中的所有内容。我删除了你的ObjectTask课程,因为一切都可以放在一个函数中,而且函数很整齐(在我看来,它们更快,更容易阅读/调试)。

我也更改了它,因此您将变量传递给方法而不是从全局变量(task对象)中读取它,因为它使程序更加模块化,更容易更改/重构。

最后,我将一些属性合并为一个。 xy已关联,因此我将它们放入一个公共属性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()

在这个例子中,我并没有因为跟踪脏的行为而烦恼。我已经体验到导致抖动的原因。我提供的这个例子应该可以正常工作,但总会有一些抖动。如果您想尝试跟踪脏的rects,请更改:

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