[英]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.