Pygame 当 fps 超过 15 时蛇速度太高

Pygame snake velocity too high when the fps above 15

在我使用 pygame 制作的贪吃蛇游戏中,我很难理解速度的物理原理。 问题是一旦我将 fps 设置为 15 以上,蛇的速度也会增加。 我知道这与我发现有效的毫秒等有关,高 fps 速度慢。但是在那个时候,我无法让 X 和 Y 正确,这样我就可以吃苹果了。 在这一点上我几乎迷路了。 这是我的整个贪吃蛇游戏。我不介意分享它,因为我想一旦完成就开源它。

import pygame
import random
from pygame.locals import (
    K_UP,
    K_DOWN,
    K_LEFT,
    K_RIGHT,
    K_ESCAPE,
    KEYDOWN,
    K_n,
    K_o,
    K_w,
    K_a,
    K_s,
    K_d,
    K_RETURN,
    QUIT,
)

pygame.init()
pygame.display.set_caption("Le jeu snake !")


class Game:
    def __init__(self):
        self.white = (255, 255, 255)
        self.black = (0, 0, 0)
        self.red = (255, 0, 0)
        self.blue = (0, 0, 255)
        self.green = (0, 255, 0)

        self.background = pygame.image.load("snake_bg.png")

        self.SCREEN_WIDTH = 600
        self.SCREEN_HEIGHT = 400

        self.screen = pygame.display.set_mode([self.SCREEN_WIDTH, self. SCREEN_HEIGHT])

        self.running = True
        self.paused = False
        self.agreed = False

        self.snake_block = 25
        self.apple_block = 25

        self.snake_x = 0
        self.snake_y = 0
        self.apple_x = 0
        self.apple_y = 0

        self.center = (self.SCREEN_WIDTH // 2, self.SCREEN_HEIGHT // 2)

        self.width_grid = 0
        self.height_grid = 0

        self.snake_x_change = 0
        self.snake_y_change = 0

        self.snake_list = list()
        self.snake_head = list()
        self.snake_length = 1

        self.score = 0

        self.clock = pygame.time.Clock()

        self.velocity = 25

    def draw_grid(self):
        self.width_grid = [x * 25 for x in range(0, self.SCREEN_WIDTH)]
        self.height_grid = [y * 25 for y in range(0, self.SCREEN_WIDTH)]

        """for grid_x in self.width_grid:
            pygame.draw.line(self.screen, self.white, [0, grid_x], [self.SCREEN_WIDTH, grid_x], 2)
            if grid_x >= 600:
                break
        for grid_y in self.height_grid:
            pygame.draw.line(self.screen, self.white, [grid_y, 0], [grid_y, self.SCREEN_WIDTH], 2)
            if grid_y >= 600:
                break"""

    def set_position(self, thing):
        if thing == "snake":
            self.snake_x = self.SCREEN_WIDTH / 2
            self.snake_y = self.SCREEN_HEIGHT / 2
        if thing == "apple":
            self.apple_x = random.choice(self.width_grid[0:24])
            self.apple_y = random.choice(self.height_grid[0:16])

    def draw(self, obj):
        if obj == "snake":
            for XnY in self.snake_list:
                pygame.draw.rect(self.screen, self.green, (XnY[0], XnY[1], self.snake_block, self.snake_block), 2)
        elif obj == "apple":
            pygame.draw.rect(self.screen, self.red, (self.apple_x, self.apple_y, self.apple_block, self.apple_block))

    def set_keys_direction(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.running = False
            if event.type == KEYDOWN:
                if event.key == K_DOWN:
                    self.snake_y_change = self.velocity
                    self.snake_x_change = 0
                if event.key == K_RIGHT:
                    self.snake_x_change = self.velocity
                    self.snake_y_change = 0
                if event.key == K_UP:
                    self.snake_y_change = -self.velocity
                    self.snake_x_change = 0
                if event.key == K_LEFT:
                    self.snake_x_change = -self.velocity
                    self.snake_y_change = 0
                if event.key == K_s:
                    self.snake_y_change = self.velocity
                    self.snake_x_change = 0
                if event.key == K_w:
                    self.snake_y_change = -self.velocity
                    self.snake_x_change = 0
                if event.key == K_d:
                    self.snake_x_change = self.velocity
                    self.snake_y_change = 0
                if event.key == K_a:
                    print("Q works")
                    self.snake_x_change = -self.velocity
                    self.snake_y_change = 0
                if not self.agreed:
                    if event.key == K_ESCAPE:
                        pygame.quit()
                    if event.key == K_RETURN:
                        self.agreed = True
                if self.agreed:
                    if event.key == K_n:
                        pygame.quit()
                    if event.key == K_o:
                        self.paused = False
                        self.score = 0
                if event.key == K_ESCAPE:
                    self.snake_length += 1

        self.elapsed = self.clock.tick(30)

    def build_snake(self):
        snake_head = list()
        snake_head.append(self.snake_x)
        snake_head.append(self.snake_y)
        self.snake_list.append(snake_head)

        if len(self.snake_list) > self.snake_length:
            del self.snake_list[0]

        for snake in self.snake_list[:-1]:
            if snake == snake_head:
                self.snake_reset()

        self.draw("snake")

    def check_apple_eaten(self):
        if self.snake_x == self.apple_x and self.snake_y == self.apple_y:
            self.set_position("apple")
            self.snake_length += 1
            self.score += 1

    def snake_borders_check(self):
        if self.snake_x < 0 or self.snake_x > self.SCREEN_WIDTH - 25:
            self.snake_reset()
        if self.snake_y < 0 or self.snake_y > self.SCREEN_HEIGHT - 25:
            self.snake_reset()

    def snake_reset(self):
        self.paused = True
        self.set_position("snake")
        self.set_position("apple")
        del self.snake_list[1:]
        self.snake_length = 1

    def snake_bit_check(self):
        pass

        """if len(self.snake_list) >= 6:
            for snake in self.snake_list[2:]:
                if self.snake_list[0][0] == snake[0] and self.snake_list[0][1] == snake[1]:
                    print("SnakeList[0][0]: {0} || SnakeList[0][1]: {0}".format(self.snake_list[0][0],
                                                                                self.snake_list[0][1]))
                    print("snake: {0}".format(snake))

                    self.snake_reset()"""

    def show_text(self, message, position, font_name="fonts/arial_narrow_7.ttf", font_size=32):
        font = pygame.font.Font(font_name, font_size)
        text = font.render(message, True, self.white, self.black)
        text_rect = text.get_rect(center=position)

        self.screen.blit(text, text_rect)

    def game_over(self):
        self.screen.blit(self.background, (0, 0))
        self.show_text("Ton score: {0}".format(self.score), (100, 20))
        self.show_text("Tu as perdu ! Veux-tu recommencer ?",
                      (self.SCREEN_WIDTH // 2, self.SCREEN_HEIGHT // 2 - 100))
        self.show_text("Appuie sur O (oui) ou sur N (non)",
                      (self.SCREEN_WIDTH // 2, self.SCREEN_HEIGHT // 2))
        self.show_text("Sinon, Echap pour quitter",
                      (self.SCREEN_WIDTH // 2, self.SCREEN_HEIGHT // 2 + 100))

    def check_agreement(self):
        self.show_text("Ceci est une réplique du jeu snake faite",
                      (self.SCREEN_WIDTH // 2, self.SCREEN_HEIGHT // 2 - 100))
        self.show_text("par Kevin pour un simple projet personnel.",
                      (self.SCREEN_WIDTH // 2 + 1, self.SCREEN_HEIGHT // 2 - 70))
        self.show_text("Si vous voulez jouer au jeu, appuyez sur la",
                      (self.SCREEN_WIDTH // 2 + 5, self.SCREEN_HEIGHT // 2 - 20))
        self.show_text("touche Entrée, sinon Echap pour quitter !",
                      (self.SCREEN_WIDTH // 2 - 11, self.SCREEN_HEIGHT // 2 + 10))
        self.show_text("- Utilise les touches flèches pour changer de direction",
                      (self.SCREEN_WIDTH // 2 - 10, self.SCREEN_HEIGHT // 2 + 55), font_size=24)
        self.show_text("- Ne te mord pas toi-même",
                      (self.SCREEN_WIDTH // 2 - 144, self.SCREEN_HEIGHT // 2 + 75), font_size=24)
        self.show_text("- Ne rentre pas dans les bords",
                      (self.SCREEN_WIDTH // 2 - 128, self.SCREEN_HEIGHT // 2 + 95), font_size=24)

    def game(self):
        self.draw_grid()
        self.set_position("snake")
        self.set_position("apple")

        while self.running:
            self.screen.blit(self.background, (0, 0))
            self.set_keys_direction()

            if self.agreed:
                self.draw_grid()

                self.draw("apple")

                self.build_snake()

                self.check_apple_eaten()

                self.snake_bit_check()

                self.snake_borders_check()
            else:
                self.check_agreement()

            if not self.paused:
                self.snake_x += self.snake_x_change
                self.snake_y += self.snake_y_change
            else:
                self.game_over()

            self.clock.tick(30)

            pygame.display.flip()


game = Game()

game.game()

pygame.quit()

self.clock.tick() 的 return 值是自上次调用以来经过的时间。 使用 return 值来控制速度。定义蛇每秒移动的距离(例如 self.velocity = 400 表示每秒 400 像素)。获取帧之间的时间 (delta_t) 并根据经过的时间 (delta_t / 1000) 缩放蛇的移动:

class Game:
    def __init__(self):
        # [...]

        # distance per second 
        self.velocity = 400

    # [...]

    def game(self):

        # [...]

        while self.running:
            delta_t = self.clock.tick(30)

            # [...]

            if not self.paused:
                step = delta_t / 1000 # / 1000 because unit of velocity is seconds
                self.snake_x += self.snake_x_change * step
                self.snake_y += self.snake_y_change * step
            else:
                self.game_over()

            pygame.display.flip()

使用此设置可以轻松控制蛇的速度。例如,当蛇长大时,可以提高速度(例如self.velocity += 50)。

当然画蛇的时候要把蛇的位置(self.snake_x,self.snake_y)四舍五入到网格大小的倍数(25的倍数)碰撞测试。使用 round 这样做:

x, y = round(self.snake_x / 25) * 25, round(self.snake_y / 25) * 25

确保存储在snake_list中的位置是25的倍数。如果蛇的头已经到达新的字段,只需将新的头添加到列表中:

if len(self.snake_list) <= 0 or snake_head != self.snake_list[-1]:
   self.snake_list.append(snake_head)

将其应用于方法 build_snake drawcheck_apple_eaten

class Game:
    # [...]

    def build_snake(self):
        snake_head = list()
        x, y = round(self.snake_x / 25) * 25, round(self.snake_y / 25) * 25
        snake_head.append(x)
        snake_head.append(y)
        if len(self.snake_list) <= 0 or snake_head != self.snake_list[-1]:
            self.snake_list.append(snake_head)

        if len(self.snake_list) > self.snake_length:
            del self.snake_list[0]

        for snake in self.snake_list[:-1]:
            if snake == snake_head:
                self.snake_reset()

        self.draw("snake")

    def check_apple_eaten(self):
        x, y = round(self.snake_x / 25) * 25, round(self.snake_y / 25) * 25
        if x == self.apple_x and y == self.apple_y:
            self.set_position("apple")
            self.snake_length += 1
            self.score += 1

    def snake_borders_check(self):
        x, y = round(self.snake_x / 25) * 25, round(self.snake_y / 25) * 25
        if x < 0 or x > self.SCREEN_WIDTH - 25:
            self.snake_reset()
        if y < 0 or y > self.SCREEN_HEIGHT - 25:
            self.snake_reset()