I made this in order to teach myself pygame. This displays an images loop at the same speed on any computer (It will lag on a slow computer).

Is my time management good or do my datetime call could be optimized to loose less time while doing computation?

Any other remarks are welcolmed.

#!/usr/bin/env python
#-*- coding: utf-8 -*-

import datetime
import os
import pygame

from os import listdir
from os.path import isfile, join
from pygame.locals import *

class ImageSequenceDisplayer():
    Display an image sequence independantly of the processing speed running this program

    def __init__(self, directory, time_between_frame_ms):
        self.directory = directory
        self.time_between_frame_ms = time_between_frame_ms
        self._surfaces_to_display = self._load_images_as_surfaces(directory)
        self._current_index = 0
        self._last_time_a_surface_was_displayed = None

    def _load_images_as_surfaces(self, directory):
        print "ImageSequenceDisplayer : Loading images from : '{}'".format(directory)
        surfaces_to_display = load_all_images_from(directory)
        if len(surfaces_to_display) == 0:
            raise Exception("No images found in '{}'".format(directory))
        print "ImageSequenceDisplayer : Loading completed, {} images loaded".format(len(surfaces_to_display))
        return surfaces_to_display

    def _compute_ms_since_last_display(self, current_date):
        timedelta_since_last_display = current_date - self._last_time_a_surface_was_displayed
        # .microseconds does not contain the seconds, so we have to add them
        return timedelta_since_last_display.seconds * 1000 + (timedelta_since_last_display.microseconds / 1000)

    def display_next_image(self, surface_to_display_to, pos_to_display_to):
        Measure how much time has passed since the last image display and will choose the next image to display (for
        example if we set the displayer to 50ms and 172ms have passed then we will skip 2 frames.

        :param surface_to_display_to: the surface we want to display the image
        :param pos_to_display_to:  the position in "surface_to_display_to" where the image will be blitted

        if self._last_time_a_surface_was_displayed is None:
            self._last_time_a_surface_was_displayed = datetime.datetime.now()
            self._current_index = 0
            current_date = datetime.datetime.now()
            ms_since_last_display = self._compute_ms_since_last_display(current_date)

            if ms_since_last_display > self.time_between_frame_ms:
                n_frame_to_advance = self.time_between_frame_ms/ms_since_last_display + 1
                new_index = self._current_index + n_frame_to_advance
                if new_index >= len(self._surfaces_to_display):
                    # int division, yes I want to discard the decimal part
                    n_to_remove = new_index/len(self._surfaces_to_display)
                    new_index = new_index - (n_to_remove * len(self._surfaces_to_display))
                self._last_time_a_surface_was_displayed = current_date
                #not enought time has passed, we display the same old frame, without updating self._last_time_a_surface_was_displayed
                new_index = self._current_index

            self._current_index = new_index

        surface_to_display = self._surfaces_to_display[self._current_index]
        surface_to_display_to.blit(surface_to_display, pos_to_display_to)

def get_all_files_from(my_path):
    file_list_without_path = [ f for f in listdir(my_path) if isfile(join(my_path,f)) and 'desktop.ini' not in f ]
    return file_list_without_path

def load_all_images_from(my_path, resize_hw=None):
    res = []
    for im_name in get_all_files_from(my_path):
        im_surface = pygame.image.load(os.path.abspath(join(my_path, im_name)))
        if resize_hw is not None:
            im_surface = pygame.transform.scale(im_surface, resize_hw)
    return res

ball_position = 200, 200
main_window_size = 900, 750
background_color = (50 , 50, 50)
origin_position = (0, 0)

window = pygame.display.set_mode(main_window_size)
background_surface = pygame.Surface(main_window_size)
window.blit(background_surface, origin_position)

time_between_frames_ms = 40
directory_of_images_loop = "ball"
ball_displayer = ImageSequenceDisplayer(directory_of_images_loop, time_between_frames_ms)

doIt = True
while doIt:
    for event in pygame.event.get():
        if event.type == QUIT:
            print "doIt is false"
            doIt = False

    ball_displayer.display_next_image(window, ball_position)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.