How do I move the player smoothly in a tile based game? - python
In a tilebased rpg I am creating, I am trying to implement a function that moves the player between tiles smoothly. I have applied this in the player update and getkeys functions. 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. The function is undershooting where the next tile should be, causing the player to move off the grid, which causes errors with collision.
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
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. 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. Calculate the index of the current cell and the target 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:
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). 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):
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:
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()
Related
Why am I getting a second image of sprite that won't rotate and shouldn't be there?
I am trying to create a TractorBeam (or Thrust) coming out of the back of my rocket ship and of course the sprite is to rotate with rocket ship. It seems to be working fine but I am getting a second image of the same TractorBeam image that stays in the same vertical position as the rocket ship rotates and the TractorBeam image rotates. I have it programmed the same as the laser coming out the front (except for different speed, lifetime, etc.) and the laser doesn't seem to have this unknown second image. Any suggestions of where I am going wrong or how to eliminate this second unwanted image? import pygame as pg import os from random import uniform vec = pg.math.Vector2 TITLE = "GRAVITAR" WIDTH = 800 HEIGHT = 600 FPS = 60 GREY = (211, 211, 211) # Player properties ROCKET_SHIP = 'Images/Rocket_Ship.png' PLAYER_ACC = 0.5 PLAYER_FRICTION = -0.00 PLAYER_GRAV = 0.1 PLAYER_ROT_SPEED = 200 LASER = 'Images/Laser_1.png' LASER_SPEED = 400 LASER_LIFETIME = 1500 LASER_RATE = 150 LASER_COUNT = 1 LASER_SPREAD = 3 LASER_OFFSET = vec(20, -1) TRACTOR_BEAM = 'Images/TractorBeam_1.png' TRACTOR_BEAM_SPEED = 400 TRACTOR_BEAM_LIFETIME = 25 TRACTOR_BEAM_RATE = 25 TRACTOR_BEAM_COUNT = 1 TRACTOR_BEAM_SPREAD = 1 TRACTOR_BEAM_OFFSET = vec(-12, 0) class Player(pg.sprite.Sprite): def __init__(self, game, x, y): pg.sprite.Sprite.__init__(self) self.game = game self.image = game.rocket_ship self.rect = self.image.get_rect() self.rect.center = (x, y) self.vel = vec(0, 0) self.pos = vec(x, y) self.rot = 90 self.last_shot = 0 self.thrust = False self.frame = 0 self.last_update = 0 def get_keys(self): self.rot_speed = 0 self.acc = vec(0, PLAYER_GRAV) keys = pg.key.get_pressed() if keys[pg.K_LEFT]: self.rot_speed = PLAYER_ROT_SPEED if keys[pg.K_RIGHT]: self.rot_speed = -PLAYER_ROT_SPEED if keys[pg.K_UP]: self.vel += vec(PLAYER_ACC, 0).rotate(-self.rot) if keys[pg.K_SPACE]: self.shoot() if keys[pg.K_t]: self.tractor_beam() self.vel += self.acc + self.vel * PLAYER_FRICTION max_vel = 1.75 self.vel[0] = max(-max_vel, min(max_vel, self.vel[0])) self.vel[1] = max(-max_vel, min(max_vel, self.vel[1])) self.pos += self.vel def shoot(self): now = pg.time.get_ticks() if now - self.last_shot > LASER_RATE: self.last_shot = now dir = vec(1, 0).rotate(-self.rot) pos = self.pos + LASER_OFFSET.rotate(-self.rot) for i in range(LASER_COUNT): spread = uniform(-LASER_SPREAD, LASER_SPREAD) Laser(self.game, pos, dir.rotate(spread), self) def tractor_beam(self): now = pg.time.get_ticks() if now - self.last_shot > TRACTOR_BEAM_RATE: self.last_shot = now dir = vec(1, 0).rotate(-self.rot + 180) pos = self.pos + TRACTOR_BEAM_OFFSET.rotate(-self.rot) spread = uniform(-TRACTOR_BEAM_SPREAD, TRACTOR_BEAM_SPREAD) TractorBeam(self.game, pos, dir.rotate(spread), self) def update(self): self.get_keys() self.rot = (self.rot + self.rot_speed * self.game.dt) % 360 self.image = pg.transform.rotate(self.game.rocket_ship, self.rot) self.rect = self.image.get_rect() self.pos += self.vel * self.game.dt self.rect.center = self.pos if self.pos.x > WIDTH: self.pos.x = 0 if self.pos.x < 0: self.pos.x = WIDTH if self.pos.y > HEIGHT: self.pos.y = 0 if self.pos.y < 0: self.pos.y = HEIGHT class Laser(pg.sprite.Sprite): def __init__(self, game, pos, dir, player): self.groups = game.all_sprites # , game.bullet pg.sprite.Sprite.__init__(self, self.groups) self.game = game self.player = player self.image = game.laser self.rect = self.image.get_rect() self.pos = vec(pos) self.rect.center = pos self.vec = dir * LASER_SPEED * uniform(0.9, 1.1) self.spawn_time = pg.time.get_ticks() self.rot = 90 def update(self): self.image = pg.transform.rotate(self.game.laser, self.player.rot - 90) self.rect = self.image.get_rect() self.pos += self.vec * self.game.dt self.rect.center = self.pos if pg.time.get_ticks() - self.spawn_time > LASER_LIFETIME: self.kill() class TractorBeam(pg.sprite.Sprite): def __init__(self, game, pos, dir, player): self.groups = game.all_sprites pg.sprite.Sprite.__init__(self, self.groups) self.game = game self.player = player self.image = game.tractor_beam self.rect = self.image.get_rect() self.pos = vec(pos) self.rect.center = pos self.pos = vec(pos) self.vec = dir * TRACTOR_BEAM_SPEED * uniform(0.9, 1.1) self.spawn_time = pg.time.get_ticks() self.rot = 0 def update(self): self.image = pg.transform.rotate(self.game.tractor_beam, self.player.rot - 90) self.rect = self.image.get_rect() self.pos += self.vec * self.game.dt self.rect.center = self.pos if pg.time.get_ticks() - self.spawn_time > TRACTOR_BEAM_LIFETIME: self.kill() class Game: def __init__(self): # Initialize pygame and create window pg.init() pg.mixer.init() pg.key.set_repeat(10, 50) os.environ['SDL_VIDEO_WINDOW_POS'] = '568, 101' self.screen = pg.display.set_mode((WIDTH, HEIGHT)) pg.display.set_caption(TITLE) self.clock = pg.time.Clock() self.running = True self.load_data() def load_data(self): self.rocket_ship = pg.image.load(ROCKET_SHIP).convert_alpha() self.rocket_ship = pg.transform.scale(self.rocket_ship, (36, 18)) self.laser = pg.image.load(LASER).convert_alpha() self.laser = pg.transform.scale(self.laser, (3, 8)) self.tractor_beam = pg.image.load(TRACTOR_BEAM).convert_alpha() self.tractor_beam = pg.transform.scale(self.tractor_beam, (30, 30)) def new(self): # Start a new game self.all_sprites = pg.sprite.Group() self.lasers = pg.sprite.Group() self.tractorBeams = pg.sprite.Group() self.player = Player(self, WIDTH / 2, HEIGHT / 4) self.all_sprites.add(self.player) self.run() def run(self): # Game loop self.playing = True while self.playing: self.dt = self.clock.tick(FPS) / 1000.0 self.events() self.update() self.draw() def update(self): # Game loop update self.all_sprites.update() def events(self): # Game loop events for event in pg.event.get(): if event.type == pg.QUIT: if self.playing: self.playing = False self.running = False def draw(self): # Game loop draw pg.display.set_caption("{:.2f}".format(self.clock.get_fps())) self.screen.fill(GREY) self.all_sprites.draw(self.screen) # After drawing everything, flip display pg.display.flip() def show_start_screen(self): pass def show_go_screen(self): pass g = Game() g.show_start_screen() while g.running: g.new() g.show_go_screen() pg.quit()
There appears to be 2 tractor beams, but there is just 1. However, the update method of the TractorBeam instance is not called in the first frame in which it is drawn. It is constructed in the update loop of the the Group from the player. Sprites that are add to the Group during update are not updated in this frame, but in the next frame. You have to invoke update manually once: class Player(pg.sprite.Sprite): # [...] def tractor_beam(self): now = pg.time.get_ticks() if now - self.last_shot > TRACTOR_BEAM_RATE: self.last_shot = now dir = vec(1, 0).rotate(-self.rot + 180) pos = self.pos + TRACTOR_BEAM_OFFSET.rotate(-self.rot) spread = uniform(-TRACTOR_BEAM_SPREAD, TRACTOR_BEAM_SPREAD) beam = TractorBeam(self.game, pos, dir.rotate(spread), self) beam.update() There is the same problem with the laser, but it is barely noticeable.
Pygame: player sprite colliding incorrectly and clipping into wall
I am creating a 4-way movement, tile based rpg. The code I have written for collision detection is relatively simple but I have a minor graphical error. The player moves directly between evenly spaced tiles. To control the speed the player moves at there is a walk buffer of 200 seconds. When the player collides with a wall, they should be pushed back in the same direction they hit the wall. This works, however, very briefly the player sprite will flicker in the wall. I suspect it's to do with the player update function and how that's ordered but I've messed around with it to no avail. 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", "1P.............1", "1..............1", "1..1111........1", "1..1..1........1", "1..1111.111111.1", "1............1.1", "1........111.1.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.current_frame = 0 self.last_update = 0 self.walking = False self.walking_sprite = pg.Surface((TILESIZE, TILESIZE)) self.walking_sprite.fill(YELLOW) self.image = self.walking_sprite 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 self.pos += self.vel * TILESIZE self.hit_rect.topleft = self.pos player_collisions(self, self.game.walls) self.rect.midbottom = self.hit_rect.midbottom def get_keys(self): self.vel = 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.vel.x = -1 elif keys[pg.K_RIGHT] or keys[pg.K_d]: self.vel.x = 1 elif keys[pg.K_UP] or keys[pg.K_w]: self.vel.y = -1 elif keys[pg.K_DOWN] or keys[pg.K_s]: self.vel.y = 1 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()```
You're setting the rect before checking for collision but not resetting it after the collision check. Here is the updated code (in Player class, update method): self.hit_rect.topleft = self.pos player_collisions(self, self.game.walls) # may change postion self.hit_rect.topleft = self.pos # reset rectangle
pygame spritesheet sprite animation
I am trying to make a walking animation for my sprite (4 directional) by using 4 different sprite sheets (Movement up, down, left and right). As well as this, I would like to replace the coloured square with the image/s but am unsure as how I am supposed to do this. Below is the main.py game file: import pygame as pg import sys from settings import * from os import path from sprites import * from tilemap import * class Spritesheet: def __init__(self, filename, cols, rows): self.sheet = pg.image.load(filename) self.cols = cols #no. of columns in spritesheet self.rows = rows #no. of rows self.totalCellCount = cols*rows self.rect=self.sheet.get_rect() w = self.cellWidth = self.rect.width / cols h = self.cellHeight = self.rect.height / rows hw, hh = self.cellCenter = (w / 2, h / 2) self.cells = list([(index % cols * w, index // cols * h, w, h) for index in range(self.totalCellCount)]) self.handle = list([ (0, 0), (-hw, 0), (-w, 0), (0, -hh), (-hw, -hh), (-w, -hh), (0, -h), (-hw, -h), (-w, -h),]) def draw(self, surface, cellIndex, x, y, handle = 0): surface.blit(self.sheet, (x + self.handle[handle][0], y + self.handle[handle][1]), self.cells[cellIndex]) CENTER_HANDLE = 4 index = 0 class Game: def __init__(self): pg.init() self.screen=pg.display.set_mode((WIDTH, HEIGHT)) pg.display.set_caption(TITLE) self.clock = pg.time.Clock() pg.key.set_repeat(500, 100) self.load_data() def load_data(self): game_folder = path.dirname(__file__) img_folder = path.join(game_folder, 'img') self.map= Map(path.join(game_folder, 'map.txt')) def new(self): # initialize all variables and do all the setup for a new game self.all_sprites = pg.sprite.Group() self.walls = pg.sprite.Group() self.player1group = pg.sprite.Group() self.player2group = pg.sprite.Group() for row, tiles in enumerate(self.map.data): for col, tile in enumerate(tiles): if tile == '1': Wall(self, col, row) if tile =='P': self.player = Civilian(self, col, row) if tile =='T': self.player2 = Thief(self, col, row) 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 quit(self): pg.quit() #Calls the quit function, game window closes sys.exit() def update(self): # update portion of the game loop self.all_sprites.update() #All sprite attributes, position etc are updated def draw_grid(self): for x in range(0, WIDTH, TILESIZE): pg.draw.line(self.screen, LIGHTGREY, (x, 0), (x, HEIGHT)) for y in range(0, HEIGHT, TILESIZE): pg.draw.line(self.screen, LIGHTGREY, (0, y), (WIDTH, y)) def draw(self): self.screen.fill(BGCOLOR) self.draw_grid() self.all_sprites.draw(self.screen) pg.display.flip() def events(self): # catch all events here for event in pg.event.get(): if event.type == pg.QUIT: self.quit() if event.type == pg.KEYDOWN: if event.key == pg.K_ESCAPE: self.quit() def show_start_screen(self): pass def show_go_screen(self): pass # create the game object g = Game() g.show_start_screen() while True: g.new() g.run() g.show_go_screen() Here is the sprites.py file, cont. sprite classes import pygame as pg from settings import * vec = pg.math.Vector2 class Civilian(pg.sprite.Sprite): def __init__(self, game, x, y): self.groups = game.all_sprites, game.player1group pg.sprite.Sprite.__init__(self, self.groups) self.game = game self.image = pg.Surface((TILESIZE, TILESIZE)) self.image.fill(YELLOW) self.rect = self.image.get_rect() self.vel = vec(0, 0) self.pos = vec(x, y) * TILESIZE def get_keys(self): self.vel= vec(0, 0) keys = pg.key.get_pressed() if keys[pg.K_a]: # Const. subtracts player speed from velocity (E.g. Moves sprite to the left) self.vel.x= -PLAYER_SPEED if keys[pg.K_d]: # Const. adds player speed value to velocity (E.g. Moves sprite to the right) self.vel.x= PLAYER_SPEED if keys[pg.K_w]: # Const. subtracts player speed value from y velocity (Moves player upwards; opposite) self.vel.y= -PLAYER_SPEED if keys[pg.K_s]: # Const. adds player speed value to y velocity (Moves player downwards; opposite) self.vel.y= PLAYER_SPEED if self.vel.x != 0 and self.vel.y != 0: # Offsetting increased vecocity when moving diagonally (Has both x and y velocity) self.vel *= 0.7071 def collide_with_player2(self, dir, ifColliding): if dir == 'x': collides = pg.sprite.spritecollide(self, self.game.player2group, False) if collides: if self.vel.x > 0: self.pos.x = collides[0].rect.left - self.rect.width if self.vel.x < 0: self.pos.x = collides[0].rect.right self.vel.x = 0 self.rect.x = self.pos.x print("collide x") self.ifColliding = True if dir == 'y': collides = pg.sprite.spritecollide(self, self.game.player2group, False) if collides: if self.vel.y > 0: self.pos.y = collides[0].rect.top - self.rect.height if self.vel.y < 0: self.pos.y = collides[0].rect.bottom self.vel.y = 0 self.rect.y = self.pos.y print("collide y") self.ifColliding = True def collide_with_walls(self, dir): if dir == 'x': collides = pg.sprite.spritecollide(self, self.game.walls, False) if collides: if self.vel.x > 0: self.pos.x = collides[0].rect.left - self.rect.width if self.vel.x < 0: self.pos.x = collides[0].rect.right self.vel.x = 0 self.rect.x = self.pos.x if dir == 'y': collides = pg.sprite.spritecollide(self, self.game.walls, False) if collides: if self.vel.y > 0: self.pos.y = collides[0].rect.top - self.rect.height if self.vel.y < 0: self.pos.y = collides[0].rect.bottom self.vel.y = 0 self.rect.y = self.pos.y def update(self): self.ifColliding = False self.get_keys() self.pos += self.vel * self.game.dt self.rect.x = self.pos.x self.collide_with_walls('x'), self.collide_with_player2('x', self.ifColliding) self.rect.y = self.pos.y self.collide_with_walls('y'), self.collide_with_player2('y', self.ifColliding) if self.ifColliding == True: Thief.health -= COL_DAMAGE print(Thief.health) class Thief(pg.sprite.Sprite): health = 100 def __init__(self, game, x, y): self.groups = game.all_sprites, game.player2group pg.sprite.Sprite.__init__(self, self.groups) self.game = game self.image = pg.Surface((TILESIZE, TILESIZE)) self.image.fill(RED) self.rect = self.image.get_rect() self.vel = vec(0, 0) self.pos = vec(x, y) * TILESIZE s = Spritesheet("spritesheet_thief.png", 9, 4) def get_keys(self): self.vel = vec(0, 0) keys = pg.key.get_pressed() if keys[pg.K_LEFT]: # Const. subtracts player speed from velocity (E.g. Moves sprite to the left) self.vel.x= -PLAYER_SPEED elif keys[pg.K_RIGHT]: # Const. adds player speed value to velocity (E.g. Moves sprite to the right) self.vel.x= PLAYER_SPEED elif keys[pg.K_UP]: # Const. subtracts player speed value from y velocity (Moves player upwards; opposite) self.vel.y= -PLAYER_SPEED elif keys[pg.K_DOWN]: # Const. adds player speed value to y velocity (Moves player downwards; opposite) self.vel.y= PLAYER_SPEED elif self.vel.x != 0 and self.vel.y != 0: # Offsetting increased vecocity when moving diagonally (Has both x and y velocity) self.vel *= 0.7071 def collide_with_player1(self, dir, ifColliding): if dir == 'x': collides = pg.sprite.spritecollide(self, self.game.player1group, False) if collides: if self.vel.x > 0: self.pos.x = collides[0].rect.left - self.rect.width if self.vel.x < 0: self.pos.x = collides[0].rect.right self.vel.x = 0 self.rect.x = self.pos.x print("collide x") self.ifColliding = True if dir == 'y': collides = pg.sprite.spritecollide(self, self.game.player1group, False) if collides: if self.vel.y > 0: self.pos.y = collides[0].rect.top - self.rect.height if self.vel.y < 0: self.pos.y = collides[0].rect.bottom self.vel.y = 0 self.rect.y = self.pos.y print("collide y") self.ifColliding = True def collide_with_walls(self, dir): if dir == 'x': collides = pg.sprite.spritecollide(self, self.game.walls, False) if collides: if self.vel.x > 0: self.pos.x = collides[0].rect.left - self.rect.width if self.vel.x < 0: self.pos.x = collides[0].rect.right self.vel.x = 0 self.rect.x = self.pos.x if dir == 'y': collides = pg.sprite.spritecollide(self, self.game.walls, False) if collides: if self.vel.y > 0: self.pos.y = collides[0].rect.top - self.rect.height if self.vel.y < 0: self.pos.y = collides[0].rect.bottom self.vel.y = 0 self.rect.y = self.pos.y def update(self): s.draw(self.game.screen, index % s.totalCellCount, HW, HH, CENTER_HANDLE) index += 1 self.ifColliding = False self.get_keys() self.pos += self.vel * self.game.dt self.rect.x = self.pos.x self.collide_with_walls('x'), self.collide_with_player1('x', self.ifColliding) self.rect.y = self.pos.y self.collide_with_walls('y'), self.collide_with_player1('y', self.ifColliding) if Thief.health <= 0: self.kill() class Wall(pg.sprite.Sprite): def __init__(self, game, x, y): self.groups = game.all_sprites, game.walls pg.sprite.Sprite.__init__(self, self.groups) self.game = game self.image = pg.Surface((TILESIZE, TILESIZE)) self.image.fill(GREEN) self.rect = self.image.get_rect() self.x = x self.y = y self.rect.x = x * TILESIZE self.rect.y = y * TILESIZE Hoping to run through a row of cells within the sprite sheet depending on the direction the sprite is moving in and set that as the sprite image (Giving the illusion that the player sprite is animated) Any help would be appreciated, sorry if the Question is confusing, first time using the site and not too good at coding at the current stage.
Load four images as self.image_up = pygame.image.load(...) self.image_down = pygame.image.load(...) self.image_left = pygame.image.load(...) self.image_right = pygame.image.load(...) and later when you need it replace self.image = self.image_up or self.image = self.image_down etc. If you have all positions in one file then you can use pygame.Surface.subsurface to cut off part of image and create new one spritesheet = pygame.image.load(...) self.image_up = spritesheet.subsurface( Rect(0,0,10,10) ) self.image_down = spritesheet.subsurface( Rect(...) ) self.image_left = spritesheet.subsurface( Rect(...) ) self.image_right = spritesheet.subsurface( Rect(...) ) My simple example (with all positions in one file) on GitHub: pygame-spritesheet Use left/right arrows to move object and it will use different image.
Bullet not colliding with enemy Pygame (Python 3)
I want to make a mega man similar game where you jump around and shooting stuff. But I've noticed there's something wrong with the collision, I have a video below: https://youtu.be/p2VCtbBkefo I'm planning to make this project open source, so anybody can customize it. Please don't steal this code, but you may use chunks of it to help you with something. because I haven't put it on GitHub publicly yet. main.py: import pygame as pg from player import * from settings import * from levels import * from block import * from enemy import * class Game: def __init__(self): pg.init() pg.mixer.init() self.screen = pg.display.set_mode((width, height)) pg.display.set_caption("wait until realesed.") self.clock = pg.time.Clock() self.enemiesList = [] self.running = True self.shootRight = True def loadLevel(self, level, enemies, group, group2, group3): for y in range(0, len(level)): for x in range(0, len(level[y])): if (level[y][x] == 1): blockList.append(Block(x*32, y*32)) group.add(Block(x*32, y*32)) group2.add(Block(x*32, y*32)) for amount in range(0, enemies): group2.add(FlyingEnemy(self)) group3.add(FlyingEnemy(self)) self.enemies.add(FlyingEnemy(self)) self.enemiesList.append(FlyingEnemy(self)) def new(self): self.platforms = pg.sprite.Group() self.all_sprites = pg.sprite.Group() self.enemies = pg.sprite.Group() self.bullets = pg.sprite.Group() self.player = Player() self.loadLevel(level1["platform"], level1["enemies"], self.platforms, self.all_sprites, self.enemies) self.all_sprites.add(self.player) self.run() def shoot(self): if self.shootRight: self.bullet = Bullet(self.player.rect.centerx, self.player.rect.centery) self.bullet.speed = 10 self.all_sprites.add(self.bullet) self.bullets.add(self.bullet) print(self.bullet) elif self.shootRight == False: self.bullet = Bullet(self.player.rect.centerx, self.player.rect.centery) self.bullet.speed = -10 self.all_sprites.add(self.bullet) self.bullets.add(self.bullet) print(self.bullet) def run(self): self.playing = True while self.playing: self.clock.tick(FPS) self.events() self.update() self.draw() def update(self): self.all_sprites.update() self.enemy_hits = pg.sprite.spritecollide(self.player, self.enemies, False) #print(enemy_hits) if self.enemy_hits: pass #print("hit") self.bullet_hits = pg.sprite.groupcollide(self.enemies, self.bullets, True, True) if self.bullet_hits: print(self.bullet_hits) pygame.quit() hits = pg.sprite.spritecollide(self.player, self.platforms, False) if hits: self.player.pos.y = hits[0].rect.top + 1 self.player.vel.y = 0 def events(self): for event in pg.event.get(): if event.type == pg.QUIT: if self.playing: self.playing = False self.running = false if event.type == pg.KEYDOWN: if event.key == pg.K_UP: self.player.jump() if event.key == pg.K_SPACE: self.shoot() if event.key == pg.K_RIGHT: self.shootRight = True if event.key == pg.K_LEFT: self.shootRight = False def draw(self): self.screen.fill((255, 255, 255)) self.all_sprites.draw(self.screen) pg.display.flip() def show_start_screen(self): pass def show_go_screen(self): pass g = Game() g.show_start_screen() while g.running: g.new() g.show_go_screen() pg.quit() """width = 800 height = 600 FPS = 60 pg.init() pg.mixer.init() screen = pg.display.set_mode((width, height)) pg.display.set_caption("doom room") clock = pg.time.Clock() running = True while running: for event in pg.event.get(): clock.tick(FPS) if event.type == pg.QUIT: running = false screen.fill((255, 255, 255)) pg.display.flip() pg.quit()""" import pygame class Bullet(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.image = pygame.Surface((20, 10)) self.image.fill((240, 43, 12)) self.rect = self.image.get_rect() self.rect.bottom = y self.rect.centerx = x self.speed = -10 def update(self): self.rect.x += self.speed if self.rect.bottom < 0: self.kill() player.py import pygame as pg from settings import * from laser import * vec = pg.math.Vector2 class Player(pg.sprite.Sprite): def __init__(self): pg.sprite.Sprite.__init__(self) self.image = pg.Surface((40, 40)) self.image.fill((80, 123, 255)) self.rect = self.image.get_rect() self.rect.center = (width / 2, height / 2) self.pos = vec(width / 2, height / 2) self.vel = vec(0, 0) self.acc = vec(0, 0) #self.vx = 0 #self.vy = 0 def jump(self): self.vel.y = -15 def update(self): self.acc = vec(0, player_gravity) keys = pg.key.get_pressed() if keys[pg.K_LEFT]: self.acc.x = -player_acc if keys[pg.K_RIGHT]: self.acc.x = player_acc self.acc.x += self.vel.x * player_friction self.vel += self.acc self.pos += self.vel + 0.5 * self.acc if self.pos.x > width: self.pos.x = 0 if self.pos.x < 0: self.pos.x = width if self.pos.y <= 0: self.pos.y += 15 self.rect.midbottom = self.pos enemy.py import pygame as pg from random import * from settings import * class FlyingEnemy(pg.sprite.Sprite): def __init__(self, game): pg.sprite.Sprite.__init__(self) self.game = game self.image = pg.Surface((45, 45)) self.image.fill((20, 203, 50)) self.rect = self.image.get_rect() self.rect.centerx = choice([-100, width + 100]) self.vx = randrange(4, 7) if self.rect.centerx > width: self.vx *= -1 self.rect.y = height / 4 self.rect.x = 0 self.vy = 0 self.dy = 0.5 def update(self): if self.rect.x > width: self.rect.x = 0 if self.rect.x < 0: self.rect.x = width self.rect.x += self.vx self.vy += self.dy if self.vy > 3 or self.vy < -3: self.dy *= -1 center = self.rect.center if self.dy < 0: pass #print("bobbed up") else: pass #print("bobbed down") settings.py import pygame blockList = [] player_acc = 1.0 player_friction = -0.12 player_gravity = 0.5 bullets = pygame.sprite.Group() true = True false = False width = 800 height = 600 FPS = 60 levels.py level1 = { "platform": [ [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1], [1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0], [0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0], [0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0], [0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0], [0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0], [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], ], "enemies": 5 } block.py import pygame as pg class Block(pg.sprite.Sprite): def __init__(self, x, y): pg.sprite.Sprite.__init__(self) self.image = pg.Surface((32, 32)) self.image.fill((0, 0, 0)) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y Thanks all help is appreciated.
This part of the loadLevel method causes the problem: for amount in range(0, enemies): group2.add(FlyingEnemy(self)) group3.add(FlyingEnemy(self)) self.enemies.add(FlyingEnemy(self)) self.enemiesList.append(FlyingEnemy(self)) You're adding 4 different FlyingEnemy objects to these groups and the list (btw, the list is useless), so the sprites in the self.all_sprites group and in the self.enemies group are not the same. Since you're only updating the all_sprites and not the enemies, the sprites in the enemies group, which are used for the collision detection, stay at the left screen edge all the time and are also invisible, because you don't draw this group. To solve the problem, create one instance and add this instance to the two groups: for amount in range(0, enemies): enemy = FlyingEnemy(self) self.enemies.add(enemy) self.all_sprites.add(enemy) I found the bug by printing the rect of one enemy sprite in the self.enemies group. Then I checked the update method of this sprite, but it looked correct, so I went to the instantiation part in loadLevel and noticed the mistake.
How to jump only when standing on the ground? (Python)
As of now, I jump whenever I press space. How can I make it so I only jump when standing on something? Would I create some variables such as STANDING and JUMPING? And if I do, how to I reference to them in my class Player? Here is my code, all help is appreciated. Thanks everyone. import pygame as pg import os # create a variable for pg.math.Vector2 vec = pg.math.Vector2 TITLE = "Jumping 1" WIDTH = 800 HEIGHT = 600 clock = pg.time.Clock() FPS = 60 GREEN = (0, 255, 0) LIGHTBLUE = (50, 200, 250) RED = (255, 0, 0) # Player properties PLAYER_ACC = 0.5 PLAYER_FRICTION = -0.12 PLAYER_GRAVITY = 0.8 game_folder = os.path.dirname(__file__) img_folder = os.path.join(game_folder, "img") class Player(pg.sprite.Sprite): def __init__(self): pg.sprite.Sprite.__init__(self) self.image = pg.Surface((50, 50)) self.image.fill(GREEN) self.rect = self.image.get_rect() self.rect.center = ((WIDTH / 2, HEIGHT / 2)) # position, velocity, acceleration self.pos = vec(WIDTH / 2, HEIGHT / 2) self.vel = vec(0, 0) self.acc = vec(0, 0) def update(self): self.acc = vec(0, PLAYER_GRAVITY) keystate = pg.key.get_pressed() if keystate[pg.K_LEFT]: self.acc.x = -PLAYER_ACC if keystate[pg.K_RIGHT]: self.acc.x = PLAYER_ACC # apply friction self.acc.x += self.vel.x * PLAYER_FRICTION # equations of motion self.vel += self.acc self.pos += self.vel + 0.5 * self.acc # wrap around the sides of the screen if self.pos.x > WIDTH: self.pos.x = 0 if self.pos.x < 0: self.pos.x = WIDTH self.rect.midbottom = self.pos def jump(self): self.vel.y = -20 player = Player() class Platform(pg.sprite.Sprite): def __init__(self): pg.sprite.Sprite.__init__(self) self.image = pg.Surface((WIDTH, 50)) self.image.fill(RED) self.rect = self.image.get_rect() self.rect.center = ((WIDTH / 2, HEIGHT - 25)) platform = Platform() def game_loop(): pg.init() screen = pg.display.set_mode((WIDTH, HEIGHT)) pg.display.set_caption(TITLE) all_sprites = pg.sprite.Group() all_sprites.add(player, platform) ground_sprite = pg.sprite.Group() ground_sprite.add(platform) running = True while running: for event in pg.event.get(): if event.type == pg.QUIT: running = False if event.type == pg.KEYDOWN: if event.key == pg.K_SPACE: player.jump() all_sprites.update() hits = pg.sprite.spritecollide(player, ground_sprite, False) if hits: player.pos.y = hits[0].rect.top or platform.rect.top player.vel.y = 0 screen.fill(LIGHTBLUE) all_sprites.draw(screen) pg.display.flip() clock.tick(FPS) pg.quit() game_loop()
In your Player class add a standing flag. Then when space is pressed before performing the jump check if standing is true. If standing is false then do not allow the jump to happen, essentially: onSpaceBar() { if(standing) { \\ perform jump standing = false; } } Then also make sure after the jump is completed (i.e. you land on something) set the standing flag back to true.