Related
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.
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
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()
I am rewriting a simple game. Before I just used e.g. pygame.draw.circle() to create and interact with objects.
However, there was a need to use masks for more complex tasks (like collision with polygons).
This is how I let the ball bounce on walls before:
def bounce(self):
# Right boundary
if self.x > width - self.radius:
self.x = 2 * (width - self.radius) - self.x
self.angle = - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Left boundary
elif self.x < self.radius:
self.x = 2 * self.radius - self.x
self.angle = - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Top boundary
if self.y > height - self.radius:
self.y = 2 * (height - self.radius) - self.y
self.angle = math.pi - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Bottom boundary
elif self.y < self.radius:
self.y = 2 * self.radius - self.y
self.angle = math.pi - self.angle
self.velocity = Vector2(self.velocity) * elasticity
But now with masks implemented, it only stops when it hits the wall but doesn't bounce off anymore.
Full code:
import pygame
from pygame.math import Vector2
import math
width = 1150
height = 800
# Colors
GOLD = (255, 215, 0)
drag = 0.999 # Between 0 and 1
elasticity = 0.75 # Between 0 and 1
pygame.init()
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
# Define game object class
class Circle:
def __init__(self, coordinates, velocity, angle, radius, objectColor):
self.x = coordinates[0]
self.y = coordinates[1]
self.velocity = velocity
self.angle = angle
self.radius = radius
self.objectColor = objectColor
self.initSurface = pygame.Surface((self.radius*2, self.radius*2), pygame.SRCALPHA)
self.rectangle = self.initSurface.get_rect(center=Vector2(self.x, self.y))
self.surface = self.initSurface
self.mask = None
self.draw()
def draw(self):
pygame.draw.circle(self.initSurface, self.objectColor, [self.radius, self.radius], self.radius)
self.mask = pygame.mask.from_surface(self.initSurface)
def move(self):
self.x += self.velocity[0]
self.y += self.velocity[1]
self.velocity = Vector2(self.velocity) * drag
self.rectangle.center = Vector2(self.x, self.y)
def bounce(self):
# Right boundary
if self.x > width - self.radius:
self.x = 2 * (width - self.radius) - self.x
self.angle = - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Left boundary
elif self.x < self.radius:
self.x = 2 * self.radius - self.x
self.angle = - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Top boundary
if self.y > height - self.radius:
self.y = 2 * (height - self.radius) - self.y
self.angle = math.pi - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Bottom boundary
elif self.y < self.radius:
self.y = 2 * self.radius - self.y
self.angle = math.pi - self.angle
self.velocity = Vector2(self.velocity) * elasticity
class Polygon:
def __init__(self, coordinates, velocity, angle, pointList, objectColor):
self.x = coordinates[0]
self.y = coordinates[1]
self.velocity = velocity
self.angle = angle
self.pointList = pointList
self.objectColor = objectColor
self.initSurface = pygame.Surface((max(self.pointList, key=lambda item: item[0])[0],
max(self.pointList, key=lambda item: item[1])[1]), pygame.SRCALPHA)
self.rectangle = self.initSurface.get_rect(center=Vector2(self.x, self.y))
self.surface = self.initSurface
self.mask = None
self.draw()
def draw(self):
pygame.draw.polygon(self.initSurface, self.objectColor, self.pointList)
self.mask = pygame.mask.from_surface(self.initSurface)
def move(self):
self.x += self.velocity[0]
self.y += self.velocity[1]
self.rectangle.center = Vector2(self.x, self.y)
def rotate(self, angle):
self.angle += angle
self.velocity.rotate_ip(-angle)
surface = pygame.transform.rotate(self.initSurface, self.angle)
self.rectangle = surface.get_rect(center=self.rectangle.center)
# We need a new mask after the rotation.
self.mask = pygame.mask.from_surface(surface)
self.surface = surface
# Colliding game object particles
def collide(p1, p2):
offset = p1.rectangle[0] - p2.rectangle[0], p1.rectangle[1] - p2.rectangle[1]
overlap = myBall.mask.overlap(p1.mask, offset)
if overlap:
p2.velocity = Vector2(p1.velocity) * 1.4
# Images
BG_IMG = pygame.Surface((1150, 800))
BG_IMG.fill((30, 120, 30))
# Init Ball and car (input)
myBall = Circle(Vector2(575, 400), Vector2(0, 0), 0, 60, GOLD)
myInput = Polygon(Vector2(470, 370), Vector2(3, 0), 0, [(0, 0), (50, 10), (50, 20), (0, 30)], GOLD)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
myInput.rotate(5)
elif keys[pygame.K_RIGHT]:
myInput.rotate(-5)
# Move the car
myInput.move()
# Move the ball
myBall.velocity *= .99 # Friction
myBall.move()
myBall.bounce()
# Car collision.
collide(myInput, myBall)
# Drawing
screen.blit(BG_IMG, (0, 0))
screen.blit(myBall.surface, myBall.rectangle)
screen.blit(myInput.surface, myInput.rectangle)
pygame.display.flip()
clock.tick(60)
pygame.quit()
What am I missing here? I can't see through anymore.
In the bounce method you make the the velocity smaller, you never flip it so, just add a minus sign and it should bounce back instead of moving slower and slower into the boundry
self.velocity = Vector2(-self.velocity) * elasticity
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.