简体   繁体   English

如何在基于图块的游戏中平稳地移动玩家?

[英]How do I move the player smoothly in a tile based game?

In a tilebased rpg I am creating, I am trying to implement a function that moves the player between tiles smoothly.在我正在创建的基于图块的角色扮演游戏中,我正在尝试实现一个 function,它可以在图块之间平滑地移动玩家。 I have applied this in the player update and getkeys functions.我已经在播放器更新和getkeys函数中应用了它。 When the player moves in any of the four directions, the program should calculate the next tile the player should land on, and until they land on that tile, the player should be moved smoothly between the two tiles.当玩家向四个方向中的任何一个方向移动时,程序应计算玩家应落在的下一个方块,并且在他们落在该方块上之前,玩家应在两个方块之间平稳移动。

However, the function I have created is not positioning the player correctly.但是,我创建的 function 没有正确定位播放器。 The function is undershooting where the next tile should be, causing the player to move off the grid, which causes errors with collision. function 低于下一个瓷砖的位置,导致玩家离开网格,从而导致碰撞错误。

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 = ["1111111111111111",
       "1..............1",
       "1...........P..1",
       "1..1111........1",
       "1..1..1........1",
       "1..1111........1",
       "1..............1",
       "1........11111.1",
       "1........1...1.1",
       "1........11111.1",
       "1..............1",
       "1111111111111111"]


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

def player_collisions(sprite, group):
    hits_walls = pg.sprite.spritecollide(sprite, group, False, collide_hit_rect)
    if hits_walls:
        sprite.pos -= sprite.vel * TILESIZE




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.walk_buffer = 200
        self.vel = vec(0, 0)
        self.pos = vec(x, y) *TILESIZE
        self.dirvec = vec(0, 0)
        self.last_pos = self.pos
        self.next_pos = vec(0, 0)
        
        self.current_frame = 0
        self.last_update = pg.time.get_ticks()
        self.walking = True
        self.between_tiles = False
        
        self.walking_sprites = [pg.Surface((TILESIZE, TILESIZE))]
        self.walking_sprites[0].fill(YELLOW)
        
        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.topleft = self.pos
        
        if self.pos == self.next_pos:
            self.between_tiles = False
            
        if self.between_tiles:
            self.pos += self.vel * self.game.dt
        
        self.hit_rect.topleft = self.pos
        player_collisions(self, self.game.walls)  # may change postion
        self.hit_rect.topleft = self.pos  # reset rectangle

        self.rect.midbottom = self.hit_rect.midbottom


    def get_keys(self):        
        self.dirvec = vec(0,0)
        now = pg.time.get_ticks()
        keys = pg.key.get_pressed()
        
        if now - self.last_update > self.walk_buffer:
            self.vel = vec(0,0)
            self.last_update = now
            if keys[pg.K_LEFT] or keys[pg.K_a]:
                self.dirvec.x = -1
                self.vel.x = -PLAYER_SPEED
            elif keys[pg.K_RIGHT] or keys[pg.K_d]:
                self.dirvec.x = 1
                self.vel.x = PLAYER_SPEED
            elif keys[pg.K_UP] or keys[pg.K_w]:
                self.dirvec.y = -1
                self.vel.y = -PLAYER_SPEED
            elif keys[pg.K_DOWN] or keys[pg.K_s]:
                self.dirvec.y = 1
                self.vel.y = PLAYER_SPEED
                

            if self.dirvec != vec(0,0):
                self.between_tiles = True
                self.walking = True

##                self.offset = self.vel * self.game.dt
                self.last_pos = self.pos
                self.next_pos = self.pos + self.dirvec * TILESIZE
                
            else:
                self.between_tiles = False
                self.walking = False





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


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 update and getkeys functions are incorrectly calculating the position of the next tile the player should move too, causing them to fall off the tile grid and creating collsion errors TL;DR update 和 getkeys 函数错误地计算了玩家也应该移动的下一个图块的 position,导致它们从图块网格上掉下来并产生碰撞错误

There are some issues.有一些问题。

Make sure that the motion status attributes are only changed when a key is pressed.确保仅在按下某个键时才更改运动状态属性。 Set a variable new_dir_vec when a key is pressed.按下某个键时设置变量new_dir_vec Change the direction of movement and the status variables depending on the new direction of movement.根据新的运动方向更改运动方向和状态变量。

new_dir_vec = vec(0, 0)
if keys[pg.K_LEFT] or keys[pg.K_a]:
    new_dir_vec = vec(-1, 0)
# [...]

if new_dir_vec != vec(0,0):
    self.dirvec = new_dir_vec
    # [...]

The target position ( next_pos ) must be aligned with the grid.目标 position ( next_pos ) 必须与网格对齐。 Calculate the index of the current cell and the target position:计算当前单元格和目标position的索引:

current_index = self.rect.centerx // TILESIZE, self.rect.centery // TILESIZE
self.last_pos = vec(current_index) * TILESIZE
self.next_pos = self.last_pos + self.dirvec * TILESIZE

Complete method get_keys :完成方法get_keys

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

    def get_keys(self):        
        now = pg.time.get_ticks()
        keys = pg.key.get_pressed()
        
        if now - self.last_update > self.walk_buffer:
            self.last_update = now
            
            new_dir_vec = vec(0, 0)
            if self.dirvec.y == 0:
                if keys[pg.K_LEFT] or keys[pg.K_a]:
                    new_dir_vec = vec(-1, 0)
                elif keys[pg.K_RIGHT] or keys[pg.K_d]:
                    new_dir_vec = vec(1, 0)
            if self.dirvec.x == 0:
                if keys[pg.K_UP] or keys[pg.K_w]:
                    new_dir_vec = vec(0, -1)
                elif keys[pg.K_DOWN] or keys[pg.K_s]:
                    new_dir_vec = vec(0, 1)
                
            if new_dir_vec != vec(0,0):
                self.dirvec = new_dir_vec
                self.vel = self.dirvec * PLAYER_SPEED
                self.between_tiles = True
                self.walking = True
                current_index = self.rect.centerx // TILESIZE, self.rect.centery // TILESIZE
                self.last_pos = vec(current_index) * TILESIZE
                self.next_pos = self.last_pos + self.dirvec * TILESIZE

Make sure the player doesn't step over the target.确保玩家不会越过目标。 Compute the distance to the target ( delta = self.next_pos - self.pos ).计算到目标的距离 ( delta = self.next_pos - self.pos )。 If the next step is greater than the distance to the target, use the target position to determine the position ( self.pos = self.next_pos ):如果下一步大于目标的距离,则使用目标position来确定position( self.pos = self.next_pos ):

delta = self.next_pos - self.pos
if delta.length() > (self.vel * self.game.dt).length():
    self.pos += self.vel * self.game.dt
else:
    self.pos = self.next_pos
    self.vel = vec(0, 0)
    # [...]

Complete method update :完整的方法update

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

    def update(self):
        self.get_keys()
        self.rect = self.image.get_rect()
        self.rect.topleft = self.pos
        
        if self.pos != self.next_pos:
            
            delta = self.next_pos - self.pos
            if delta.length() > (self.vel * self.game.dt).length():
                self.pos += self.vel * self.game.dt
            else:
                self.pos = self.next_pos
                self.vel = vec(0, 0)
                self.dirvec = vec(0, 0)
                self.walking = False
                self.between_tiles = False
                    
        self.hit_rect.topleft = self.pos
        player_collisions(self, self.game.walls)  # may change postion
        self.hit_rect.topleft = self.pos  # reset rectangle

        self.rect.midbottom = self.hit_rect.midbottom

See also Move in grid .另请参阅在网格中移动


Minimal example:最小的例子:

import pygame

TILESIZE = 32
WIDTH = TILESIZE * 16
HEIGHT = TILESIZE * 12
PLAYER_SPEED = 3 * TILESIZE

MAP = ["1111111111111111",
       "1..............1",
       "1...........P..1",
       "1..1111........1",
       "1..1..1........1",
       "1..1111........1",
       "1..............1",
       "1........11111.1",
       "1........1...1.1",
       "1........11111.1",
       "1..............1",
       "1111111111111111"]

class Player(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.walk_buffer = 50
        self.pos = pygame.math.Vector2(x, y) * TILESIZE
        self.dirvec = pygame.math.Vector2(0, 0)
        self.last_pos = self.pos
        self.next_pos = self.pos
        
        self.current_frame = 0
        self.last_update = pygame.time.get_ticks()
        self.between_tiles = False
        
        self.image = pygame.Surface((TILESIZE, TILESIZE))
        self.image.fill((255, 0, 0))
        self.rect = self.image.get_rect(topleft = (self.pos.x, self.pos.y))

    def update(self, dt, walls):
        self.get_keys()
        self.rect = self.image.get_rect(topleft = (self.pos.x, self.pos.y))
        
        if self.pos != self.next_pos:
            
            delta = self.next_pos - self.pos
            if delta.length() > (self.dirvec * PLAYER_SPEED * dt).length():
                self.pos += self.dirvec * PLAYER_SPEED * dt
            else:
                self.pos = self.next_pos
                self.dirvec = pygame.math.Vector2(0, 0)
                self.between_tiles = False
                    
        self.rect.topleft = self.pos
        if pygame.sprite.spritecollide(self, walls, False):
            self.pos = self.last_pos
            self.next_pos = self.last_pos
            self.dirvec = pygame.math.Vector2(0, 0)
            self.between_tiles = False
        self.rect.topleft = self.pos

    def get_keys(self):        
        now = pygame.time.get_ticks()
        keys = pygame.key.get_pressed()
        
        if now - self.last_update > self.walk_buffer:
            self.last_update = now
            
            new_dir_vec = pygame.math.Vector2(0, 0)
            if self.dirvec.y == 0:
                if keys[pygame.K_LEFT] or keys[pygame.K_a]:
                    new_dir_vec = pygame.math.Vector2(-1, 0)
                elif keys[pygame.K_RIGHT] or keys[pygame.K_d]:
                    new_dir_vec = pygame.math.Vector2(1, 0)
            if self.dirvec.x == 0:
                if keys[pygame.K_UP] or keys[pygame.K_w]:
                    new_dir_vec = pygame.math.Vector2(0, -1)
                elif keys[pygame.K_DOWN] or keys[pygame.K_s]:
                    new_dir_vec = pygame.math.Vector2(0, 1)
                
            if new_dir_vec != pygame.math.Vector2(0,0):
                self.dirvec = new_dir_vec
                self.between_tiles = True
                current_index = self.rect.centerx // TILESIZE, self.rect.centery // TILESIZE
                self.last_pos = pygame.math.Vector2(current_index) * TILESIZE
                self.next_pos = self.last_pos + self.dirvec * TILESIZE
                
class Obstacle(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.image = pygame.Surface((TILESIZE, TILESIZE))
        self.image.fill((0, 0, 0))
        self.rect = self.image.get_rect(topleft = (x * TILESIZE, y * TILESIZE))

pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()

all_sprites = pygame.sprite.Group()
walls = pygame.sprite.Group()
for row, tiles in enumerate(MAP):
    for col, tile in enumerate(tiles):
        if tile == "1":
            obstacle = Obstacle(col, row)
            walls.add(obstacle)
            all_sprites.add(obstacle)
        elif tile == "P":
            player = Player(col, row)
            all_sprites.add(player)

run = True
while run:
    dt = clock.tick(60) / 1000
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
    
    player.update(dt, walls)
    
    window.fill((255, 255, 255))

    for x in range (0, window.get_width(), TILESIZE):
        pygame.draw.line(window, (127, 127, 127), (x, 0), (x, window.get_height()))
    for y in range (0, window.get_height(), TILESIZE):
        pygame.draw.line(window, (127, 127, 127), (0, y), (window.get_width(), y))

    walls.draw(window)
    for sprite in all_sprites:
        window.blit(sprite.image, sprite.rect)

    pygame.display.flip()
    
pygame.quit()
exit()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM