Pygame:与墙壁碰撞时,玩家精灵无法正确制作动画

时间:2020-07-16 15:34:54

标签: python animation pygame

我正在pygame中基于图块的rpg工作,但是我的播放器动画设置遇到了一个异常错误。

当玩家行走时,他们的精灵会循环浏览列表中存储的4张图像(在此示例中,我使用了基本的彩色表面),并且变量self.walking设置为True。仅当self.walking为true时,该精灵才应设置动画。当玩家与墙壁碰撞时,即使玩家继续沿该方向行走,其速度也被设置为零。

当玩家在x和y方向上的速度均为零时,应将self.walking设置为False。 (这必须是双向的,以允许玩家沿着墙壁滑动。)当self.walking为False时,玩家的sprite不应动画,它应保留在列表中的第一个sprite上。

self.walking在Player类中初始化,并在Player类中的self.animate()函数中设置为False和True。

但是,我遇到了两个问题:

  1. 当玩家与墙壁碰撞并继续向该墙壁移动时,精灵会从黄色快速闪烁为红色,表明玩家仍在为一帧制作动画,但随后立即在下一帧中将其设置回精灵1。我通过程序在程序循环时打印播放器的速度进行了检查,发现一帧的速度设置为零,下一帧的速度不为零。

  2. 当播放器移到某个角落时,有时即使与两堵墙碰撞,播放器仍会继续在动画帧中循环播放。

我试图通过在collide_with_walls函数中将速度设置为零来阻止这些错误的发生,但这似乎无济于事。

播放器和墙对象使用一个单独的矩形,称为hit_rect来处理碰撞。对于此示例,hit_rect与两个对象的基本rect相同。

import pygame as pg
import sys
vec = pg.math.Vector2

WHITE =     ( 255, 255, 255)
BLACK =     (   0,   0,   0)
RED =       ( 255,   0,   0)
YELLOW =    ( 255, 255,   0)
BLUE =      (   0,   0, 255)

WIDTH = 512 # 32 by 24 tiles
HEIGHT = 384
FPS = 60
TILESIZE = 32
PLAYER_SPEED = 3 * TILESIZE

MAP = ["1111111111111",
       "1...........1",
       "1.P.........1",
       "1...11111...1",
       "1...1...1...1",
       "1...1...1...1",
       "1...11111...1",
       "1...........1",
       "1...........1",
       "1111111111111"]



def collide_with_walls(sprite, group, dir):
    if dir == "x":
        hits = pg.sprite.spritecollide(sprite, group, False, collide_hit_rect)
        if hits:
            sprite.vel.x = 0
            if hits[0].rect.centerx > sprite.hit_rect.centerx:
                sprite.pos.x = hits[0].rect.left - sprite.hit_rect.width / 2
            if hits[0].rect.centerx < sprite.hit_rect.centerx:
                sprite.pos.x = hits[0].rect.right + sprite.hit_rect.width / 2
            sprite.hit_rect.centerx = sprite.pos.x
    if dir == "y":
        hits = pg.sprite.spritecollide(sprite, group, False, collide_hit_rect)
        if hits:
            sprite.vel.y = 0
            if hits[0].rect.centery > sprite.hit_rect.centery:
                sprite.pos.y = hits[0].rect.top - sprite.hit_rect.height / 2
            if hits[0].rect.centery < sprite.hit_rect.centery:
                sprite.pos.y = hits[0].rect.bottom + sprite.hit_rect.height / 2
            sprite.hit_rect.centery = sprite.pos.y

def collide_hit_rect(one, two):
    return one.hit_rect.colliderect(two.rect)



############### PLAYER CLASS ####################

class Player(pg.sprite.Sprite):
    def __init__(self, game, x, y):
        self.groups = game.all_sprites
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.vel = vec(0, 0)
        self.pos = vec(x, y) *TILESIZE
        self.current_frame = 0
        self.last_update = 0
        self.walking = False
        
        self.walking_sprites = [pg.Surface((TILESIZE, TILESIZE)),
                                pg.Surface((TILESIZE, TILESIZE)),
                                pg.Surface((TILESIZE, TILESIZE)),
                                pg.Surface((TILESIZE, TILESIZE))]
        self.walking_sprites[0].fill(YELLOW)
        self.walking_sprites[1].fill(RED)
        self.walking_sprites[2].fill(YELLOW)
        self.walking_sprites[3].fill(BLUE)
        
        self.image = self.walking_sprites[0]
        self.rect = self.image.get_rect()
        self.hit_rect = self.rect
        self.hit_rect.bottom = self.rect.bottom


    def update(self):
        self.get_keys()
        self.rect = self.image.get_rect()
        self.rect.center = self.pos
        
        self.hit_rect.centerx = self.pos.x
        collide_with_walls(self, self.game.walls, "x")
        self.hit_rect.centery = self.pos.y
        collide_with_walls(self, self.game.walls, "y")

        self.pos += self.vel * self.game.dt
        self.rect.midbottom = self.hit_rect.midbottom
        self.animate()


    def get_keys(self):        
        self.vel = vec(0,0)
        keys = pg.key.get_pressed()
        
        if keys[pg.K_LEFT] or keys[pg.K_a]:
            self.vel.x = -PLAYER_SPEED
        if keys[pg.K_RIGHT] or keys[pg.K_d]:
            self.vel.x = PLAYER_SPEED
        if keys[pg.K_UP] or keys[pg.K_w]:
            self.vel.y = -PLAYER_SPEED
        if keys[pg.K_DOWN] or keys[pg.K_s]:
            self.vel.y = PLAYER_SPEED


    def animate(self):
        now = pg.time.get_ticks()
        if self.vel == vec(0,0):    # If the player's velocity is zero in both directions...
            self.walking = False
        else:                       # If it is not...
            self.walking = True
        # show walk animation
        if self.walking:
            if now - self.last_update > 200:
                self.last_update = now
                self.current_frame = (self.current_frame + 1) % len(self.walking_sprites)
                self.image = self.walking_sprites[self.current_frame]
                self.rect = self.image.get_rect()
                self.rect.midbottom = self.hit_rect.midbottom

        # idle sprite
        if not self.walking:
            self.current_frame = 0
            self.image = self.walking_sprites[self.current_frame]
            self.rect = self.image.get_rect()
            self.rect.midbottom = self.hit_rect.midbottom



############### OBSTACLE CLASS ####################

class Obstacle(pg.sprite.Sprite):
    def __init__(self, game, x, y):
        self.groups = game.walls
        pg.sprite.Sprite.__init__(self, self.groups)
        self.x = x * TILESIZE
        self.y = y * TILESIZE
        self.w = TILESIZE
        self.h = TILESIZE
        self.game = game
        self.image = pg.Surface((self.w,self.h))
        self.image.fill(BLACK)
        self.rect = self.image.get_rect()
        self.hit_rect = self.rect
        self.rect.x = self.x
        self.rect.y = self.y



############### GAME CLASS ####################

class Game:
    def __init__(self):
        pg.init()
        self.screen = pg.display.set_mode((WIDTH, HEIGHT))
        pg.display.set_caption("Hello Stack Overflow")
        self.clock = pg.time.Clock()
        pg.key.set_repeat(500, 100)

    def new(self):
        self.all_sprites = pg.sprite.Group()
        self.walls = pg.sprite.Group()
        for row, tiles in enumerate(MAP):
            for col, tile in enumerate(tiles):
                if tile == "1":
                    Obstacle(self, col, row)
                elif tile == "P":
                    print("banana!")
                    self.player = Player(self, col, row)

    def quit(self):
        pg.quit()
        sys.exit()

    def run(self):
        # game loop - set self.playing = False to end the game
        self.playing = True
        while self.playing:
            self.dt = self.clock.tick(FPS) / 1000
            self.events()
            self.update()
            self.draw()


    def events(self):
        # catch all events here
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.quit()


    def update(self):
        self.player.update()


    def draw(self):
        self.screen.fill(WHITE)
        for wall in self.walls:
            self.screen.blit(wall.image, wall.rect)
        for sprite in self.all_sprites:
            self.screen.blit(sprite.image, sprite.rect)
    

        pg.display.flip()



# create the game object
g = Game()
while True:
    g.new()
    g.run()
    
pg.quit()

在宏伟的方案中,这是一个相对较小的图形错误,但令人沮丧的是,将其保留下来。

TL; DR-当我的播放器精灵与墙碰撞时,它会继续动画一帧,从而使其闪烁烦人。我希望游戏者不在行走时,子画面保持静止。

1 个答案:

答案 0 :(得分:3)

这是顺序问题。首先移动物体,然后进行碰撞测试。如果物体与墙壁碰撞,则将速度设置为零,并校正物体的位置。最后,您可以绘制对象并为其设置动画:

class Player(pg.sprite.Sprite):
    # [...]

        def update(self):
        self.get_keys()
        self.rect = self.image.get_rect()
        self.rect.center = self.pos
        
        self.pos += self.vel * self.game.dt
        self.hit_rect.center = [self.pos.x, self.pos.y]
        
        collide_with_walls(self, self.game.walls, "x")
        self.hit_rect.center = [self.pos.x, self.pos.y]
        collide_with_walls(self, self.game.walls, "y")
        self.hit_rect.center = [self.pos.x, self.pos.y]
        
        self.rect.midbottom = self.hit_rect.midbottom
        self.animate()
相关问题