player dissapear when the window is held

Viewed 50

I'm trying to do frame based movement. However I noticed when the window is held the fps increase momentarily and the player dissapeares. The value of dt, in my system, is between 1.06 and 0.96. Everything seems to work fine except when the window is held.
This video shows the problem. Lines 124 and 125 solves the problem, but as the commentary says i don't know if this is the correct way to fix it.
The problem seems to appear only on windows 10 systems.

import sys
import pygame
from pygame.locals import *
vec = pygame.math.Vector2

pygame.init()

fps = 60
fpsClock = pygame.time.Clock()

width, height = 1024, 768
TILESIZE = 32
screen = pygame.display.set_mode((width, height))

class Player(pygame.sprite.Sprite):
    def __init__(self,x,y):
        super().__init__()
        self.image = pygame.Surface((TILESIZE, TILESIZE))
        self.image.fill('green')
        self.rect = self.image.get_rect(topleft=(x,y))
        self.pos = vec(self.rect.topleft)
        self.speed = .8
        self.direction = vec(0,0)
        self.gravity = .8
        self.jump_height = -15
        self.friction = -.12
        self.velocity = vec(0,0)
        self.acceleration = vec(0,self.gravity)

    def update(self):
        keys = pygame.key.get_pressed()
        self.acceleration.x = 0
        if keys[K_RIGHT]:
            self.acceleration.x = self.speed 
        elif keys[K_LEFT]:
            self.acceleration.x = -self.speed
        if keys[K_UP]:
            self.velocity.y = self.jump_height        
    
class Obstacle(pygame.sprite.Sprite):
    def __init__(self,pos,width,height,color):
        super().__init__()
        self.image = pygame.Surface((width,height))
        self.image.fill(color)
        self.rect = self.image.get_rect(topleft=pos)


tiles_map = [
    "................................",
    "................................",
    "................................",
    "................................",
    "................................",
    "................................",
    "................................",
    "................................",
    "................................",
    "................................",
    "................................",
    "................................",
    "................................",
    "................................",
    "................................",
    "................................",
    "................................",
    "................................",
    "................................",
    "..P.............................",
    "................................",
    "................................",
    "11111111111111111111111111111111",
    "11111111111111111111111111111111"
]

class Game:
    def __init__(self):
        self.player_sprite = pygame.sprite.GroupSingle()
        self.obstacles =  pygame.sprite.Group()
        for row,line in enumerate(tiles_map):
            for col, char in enumerate(line):
                if char == "1":
                    self.obstacles.add(Obstacle((col*TILESIZE,row*TILESIZE),TILESIZE,TILESIZE,'red'))
                elif char == "P":
                    self.player_sprite.add(Player(col*TILESIZE,row*TILESIZE))
    
    def horizontal_movement(self, dt):
        player = self.player_sprite.sprite
        # equations of motion
        player.acceleration.x += player.velocity.x * player.friction
        player.velocity.x += player.acceleration.x * dt
        player.pos.x += player.velocity.x * dt + 0.5 * player.acceleration.x * dt **2
        player.rect.x = round(player.pos.x)
    
    def vertical_movement(self, dt):
        player = self.player_sprite.sprite
        # equations of motion
        player.velocity.y += player.acceleration.y * dt
        player.pos.y += player.velocity.y * dt + (player.acceleration.y * .5) * dt**2
        player.rect.y = round(player.pos.y)

        for obstacle in self.obstacles.sprites():
            if obstacle.rect.colliderect(player.rect):
                if player.velocity.y > 0: # falling
                    player.rect.bottom = obstacle.rect.top
                    player.pos.y = player.rect.y
                    player.velocity.y = 0
        

    def update(self, dt):
        self.player_sprite.update()
        self.player_sprite.draw(screen)
        self.obstacles.draw(screen)
        self.horizontal_movement(dt)
        self.vertical_movement(dt)

game = Game()
while True:
    dt = fpsClock.tick(60) /1000 * fps # normalize
    print(dt)
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()    
    # if dt > 1.2: # it solves the problem but i don't know if this is the correct way to fix it
    #     dt = 1.2
    screen.fill('white')    
    game.update(dt)
    pygame.display.update()
    
    
1 Answers

pygame.time.Clock.tick returns how many milliseconds have passed since the previous call. The problem with your code is that the first time returned is very large. This is the time from the creation of the pygame.time.Clock object to the first call of tick. game = Game() takes a long time.

Create the pygame.time.Clock right before the application loop to solve the problem:

game = Game()
fpsClock = pygame.time.Clock()
while True:
    dt = fpsClock.tick(fps) / 1000 * fps
   
    # [...]

If there is to much time between 2 frames dt is too big, the accelerations are too big and your algorithm breaks down. If there is only one "slow" frame, gravity gets too big and the player goes down to nowhere. The only way to solve this is to limit dt:

while True:
    dt = fpsClock.tick(60) / 1000 * fps
    dt = min(dt, 1.5)

    # [...]
Related