0
\$\begingroup\$

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()
\$\endgroup\$
5
  • \$\begingroup\$ I'm not sure I see any blur. Did you check the gif you posted on a different device to confirm the issue? \$\endgroup\$ – Vaillancourt Jan 26 at 1:33
  • \$\begingroup\$ Looks the same on my android phone, it's not terribly blurry, but I assumed I was doing something wrong in my code that it seems out of focus a bit unless the speed is like 10 pixels or lower (I think position += 10 would be pixels anyway) \$\endgroup\$ – justthom8 Jan 26 at 1:35
  • \$\begingroup\$ I don't notice a blur here either. Can you capture a still of the image on one of the blurred frames so we can see the effect? \$\endgroup\$ – DMGregory Jan 26 at 4:26
  • \$\begingroup\$ I'll see if I can, it's kinda hard to get a screen capture when it's burry. All the screen captures I just did had no blur as a still. Maybe my eyes are just getting bad, I don't know. \$\endgroup\$ – justthom8 Jan 26 at 4:28
  • \$\begingroup\$ Maybe I should rephrase, it's not blurry per-se, as in the image itself is blurry, but it's harder to follow with my eyes when the speed is faster, appearing like there image is no longer clear in the rendering. I assumed it was a problem with my code. Could this just be an optical illusion, or a specific issue with my video as some internet posts have suggested? I saw something that mentioned frame ghosting as an issue, and it's got something to do with high contrast between the background and the image moving around on screen. Could it be that? \$\endgroup\$ – justthom8 Jan 26 at 16:08

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.