python – Sprite walk animation blurring when sprite image moves too quickly

I didn’t see any other questions like this that had an answer so I thought I’d ask in hopes this helps someone else too. I have a game I’m in the beginning stages of working on, and I’ve gotten to the point where I have a sprite on the screen that I am animating to make it look like it’s walking.

The issue is when I move the images too quickly across the screen, the images start to get blurry. I’ve done a little googling, but I’m still not quite sure what’s going on. See here for a video of it.

When I turn the SPEED variable up, the walking gets blurry, but when it’s turned down to 10 or below, it’s no longer blurry. From what I could tell this is maybe frame ghosting, but there are other phenomena it could be as well, and I’m too new to game development to know how to solve it. I have added and taken away clock.tick() to see if my sprite frame limiting function may be the cause, but it seems to be the same.

You can take a look at my repo here or take a look at the code below. p.s. I know I need to move the sprites into a sheet, and load them using list comprehension or something, just haven’t gotten around to it yet.

# Import the pygame module

import pygame
from common_utilities import *
from time import time
# Import pygame.locals for easier access to key coordinates

# Updated to conform to flake8 and black standards

from pygame.locals import (
    K_w,
    K_s,
    K_a,
    K_d,
    K_UP,
    K_DOWN,
    K_ESCAPE,
    KEYDOWN,
    QUIT,

)

BLUE = (0,0,255)
PURPLE = (153,50,204)
GREEN = (0,128,0)

MAP = ((BLUE, GREEN, BLUE),
       (GREEN, PURPLE, GREEN),
       (PURPLE, GREEN, BLUE))

DEBUG = False
        
# Define constants for the screen width and height
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

SPEED = 15
# Setup the clock for a decent framerate
clock = pygame.time.Clock()


#Constants
SCREENS = 3

SPRITE_SIZE = (150,198)
SCREEN_SIZE = (SCREEN_WIDTH,SCREEN_HEIGHT)

worldMaxX = SCREEN_SIZE(0)*SCREENS
worldMaxY = SCREEN_SIZE(1)*SCREENS



class FrameRate():
    def __init__(self,dT):
        self.previousTime = 0
        self.currentTime  = 0
        self.elapsed_time = 0
        self.dT           = dT

    def integrate_state(self,callback,*args):
        '''
        
        '''
        self.callback = callback
        self.currentTime = int(time()*1000)

        self.elapsed_time += self.currentTime - self.previousTime
        if (DEBUG):
            print('self.elapsed_time:',self.elapsed_time)
        self.previousTime = self.currentTime
        if (self.elapsed_time >= int(self.dT)): 
            if (DEBUG):
                print('self.elapsed_time:',self.elapsed_time)
            #Call function after elapsed time    
            self.callback(*args)
            #Reset elapsed time
            self.elapsed_time = 0

class Camera():
    def __init__(self):
        self.worldX = 1000
        self.worldY = 1000
        self.cameraX = SCREEN_SIZE(0)
        self.cameraY = SCREEN_SIZE(1)
        self.screen = (2, 2)
    def worldCoordinates(self,screenX, screenY):
        worldX = self.screen(0)*SCREEN_SIZE(0) + screenX
        worldY = self.screen(1)*SCREEN_SIZE(1) + screenY
        return (worldX, worldY)

    def screenCoordinates(self,worldX, worldY):
        screenX = worldX - self.screen(0)*SCREEN_SIZE(0)
        screenY = worldY - self.screen(1)*SCREEN_SIZE(1)
        return (screenX,screenY)

    def updateCamera(self,position):
        #Move player to other side of screen when crossing screens
        if (position.x < -SPRITE_SIZE(0) and self.screen(0) != 0):
            position.x = SCREEN_SIZE(0)
            if (self.screen(0) >= 1):
                self.screen(0) -= 1

        elif (position.x > SCREEN_SIZE(0) and self.screen(0) != 2):
            position.x = 0
            if (self.screen(0) < SCREENS-1):
                self.screen(0) += 1

        if (position.y < -25 and self.screen(1) != 0):
            position.y += SCREEN_SIZE(1)
            if (self.screen(1) >= 1):
                self.screen(1) -= 1
        elif (position.y > SCREEN_SIZE(1) and self.screen(1) != 2):
            position.y = 0
            if (self.screen(1) < SCREENS-1):
                self.screen(1) += 1

        #Prevent player from leaving edge of map
        if (position.x < 0 and self.screen(0) == 0):
            position.x = 0
        elif (position.x > SCREEN_SIZE(0)-SPRITE_SIZE(0) and self.screen(0) == 2):
            position.x = SCREEN_SIZE(0)-SPRITE_SIZE(0)

        if (position.y < 0 and self.screen(1) == 0):
            position.y = 0
            
        if (position.y > SCREEN_SIZE(1)-SPRITE_SIZE(1) and self.screen(1) == 2):
            position.y = SCREEN_SIZE(1)-SPRITE_SIZE(1)

        if (DEBUG):
            print('self.screen:',self.screen)
            print('self.worldX:',self.worldX)
            print('self.worldY:',self.worldY)

            print('self.rect.x:',position.x)
            print('self.rect.y:',position.y)

        return self.screen

class Player(Camera):
    def __init__(self):
        super(Player,self).__init__()
        self.orientation = 'Right'
        self.images = ()
        self.images.append(pygame.image.load('C:/Games/Random_World/Assets/Sprites/pumpking_walk/walk1.png'))
        self.images.append(pygame.image.load('C:/Games/Random_World/Assets/Sprites/pumpking_walk/walk2.png'))
        self.images.append(pygame.image.load('C:/Games/Random_World/Assets/Sprites/pumpking_walk/walk3.png'))
        self.images.append(pygame.image.load('C:/Games/Random_World/Assets/Sprites/pumpking_walk/walk4.png'))
        self.images.append(pygame.image.load('C:/Games/Random_World/Assets/Sprites/pumpking_walk/walk5.png'))
        self.images.append(pygame.image.load('C:/Games/Random_World/Assets/Sprites/pumpking_walk/walk6.png'))
        self.images.append(pygame.image.load('C:/Games/Random_World/Assets/Sprites/pumpking_walk/walk7.png'))
        self.images.append(pygame.image.load('C:/Games/Random_World/Assets/Sprites/pumpking_walk/walk8.png'))
        self.images.append(pygame.image.load('C:/Games/Random_World/Assets/Sprites/pumpking_walk/walk9.png'))
        self.images.append(pygame.image.load('C:/Games/Random_World/Assets/Sprites/pumpking_walk/walk10.png'))
 
        self.index = 0
 
        self.image = self.images(self.index)

        self.position = struct(x=0,y=0)
    def update(self, pressed_keys):
        global SPEED
        if (pressed_keys(K_UP)):
            SPEED += 1
            print('SPEED:',SPEED,flush=True)
        elif (pressed_keys(K_DOWN)):
            SPEED -= 1
            print('SPEED:',SPEED,flush=True)
            if (SPEED <= 0):
                SPEED = 0
                
        if self.index > len(self.images)-1:
            self.index = 0
        # Move the SPRITE_SIZE based on user keypresses
        if pressed_keys(K_w):
            self.worldX,self.worldY = self.worldCoordinates(self.position.x,self.position.y)
            self.worldY -= SPEED
            self.orientation = 'Right'
            self.image = self.images(self.index)
            self.index += 1
            self.position.x,self.position.y =  self.screenCoordinates(self.worldX,self.worldY)
        
        if self.index > len(self.images)-1:
            self.index = 0

        if pressed_keys(K_s):
            self.worldX,self.worldY = self.worldCoordinates(self.position.x,self.position.y)
            self.worldY += SPEED
            self.orientation = 'Left'
            self.image = self.images(self.index)
            self.index += 1
            self.position.x,self.position.y =  self.screenCoordinates(self.worldX,self.worldY)

        if self.index > len(self.images)-1:
            self.index = 0

        if pressed_keys(K_a):
            self.worldX,self.worldY = self.worldCoordinates(self.position.x,self.position.y)
            self.worldX -= SPEED
            self.orientation = 'Left'
            self.image = self.images(self.index)
            self.index += 1
            self.position.x,self.position.y =  self.screenCoordinates(self.worldX,self.worldY)

        if self.index > len(self.images)-1:
            self.index = 0

        if pressed_keys(K_d):
            self.worldX,self.worldY = self.worldCoordinates(self.position.x,self.position.y)
            self.worldX += SPEED
            self.orientation = 'Right'
            self.image = self.images(self.index)
            self.index += 1
            self.position.x,self.position.y =  self.screenCoordinates(self.worldX,self.worldY)
        
        if ((not pressed_keys(K_a)) and (not pressed_keys(K_s)) 
             and (not pressed_keys(K_w)) and (not pressed_keys(K_d))):
            self.image = self.images(0)

    def render(self,surface,position):
        if self.orientation == "Right":
            screen.blit(surface, (position.x,position.y))
        elif self.orientation == "Left":
            screen.blit(pygame.transform.flip(surface, True, False), (position.x,position.y))

class Map():
    def __init__(self,world):
        self.map = world
        self.map_view = PURPLE
    def update(self,coordinates):
        self.map_view = self.map(coordinates(0))(coordinates(1))
        if (DEBUG):
            print('self.map_view:',self.map_view)
    def render(self,screen):
        screen.fill(self.map_view)

# Initialize pygame
pygame.init()


# Create the screen object
# The size is determined by the constant SCREEN_SIZE(0) and SCREEN_SIZE(1)
screen = pygame.display.set_mode((SCREEN_SIZE(0), SCREEN_SIZE(1)))


# Instantiate player. Right now, this is just a rectangle.
player = Player()
#Instantiate map, takes list of lists map to be rendered
world = Map(MAP)

# Variable to keep the main loop running
running = True

frame = FrameRate(100)

# Main loop
while running:

    # for loop through the event queue

    for event in pygame.event.get():
        # Check for KEYDOWN event
        if event.type == KEYDOWN:
            # If the Esc key is pressed, then exit the main loop
            if event.key == K_ESCAPE:
                running = False
        # Check for QUIT event. If QUIT, then set running to false.
        elif event.type == QUIT:
            running = False

    # Get all the keys currently pressed
    pressed_keys = pygame.key.get_pressed()



    frame.integrate_state(player.update,pressed_keys)
    worldScreen = player.updateCamera(player.position)
    world.update(worldScreen)
    world.render(screen)
    # Draw the player on the screen
    player.render(player.image,player.position)



    # Update the display
    pygame.display.update()