pygame spritesheet sprite animation - python

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.

Related

How do I add a camera with a sprite grid in pygame?

I have seen this answer, and am still trying to add this answer. This IS NOT a duplicate.
I have a 20x20 grid, and the screen size is 800x640. The screen cannot see the full grid, and I want it to be able to via a scrolling camera.
However, I am not sure how I would even implement this.
The way my code works is that, in the main loop, player.update() is called to update the player's position and collision, and then an update() function is called, which redraws all sprites in the grid (to show any changes).
Code:
import pygame
from pygame.locals import *
import os
import random
import time
pygame.init()
W, Height = 800, 640
screen = pygame.display.set_mode((W,Height))
SCREEN_SIZE = pygame.Rect((0,0,W,Height))
TILE_SIZE = 48
t0 = time.time()
font = pygame.font.SysFont(None, 24)
print("Time needed to create fonts: " + str(time.time() - t0))
vec = pygame.math.Vector2
# USER CAN MODIFY
ACC = 0.5
FRIC = -0.12
FPS = 60
clock = pygame.time.Clock()
class BlueBlock(pygame.sprite.Sprite):
def __init__(self, x, y):
super(BlueBlock, self).__init__()
self.surf = pygame.Surface((48,48))
self.surf.fill((0,191,255))
self.rect = self.surf.get_rect()
self.original_surface = self.surf
self.hover_surface = self.original_surface.copy()
pygame.draw.rect(self.hover_surface, (255, 255, 0), self.hover_surface.get_rect(), 6)
self.surf = self.original_surface
self.rect = self.surf.get_rect(center = (x, y))
self.hover = False
self.mouse_pos = None
self.count = 0
def update(self):
if player.mode == "build":
mouse_pos = pygame.mouse.get_pos()
self.hover = self.rect.collidepoint(mouse_pos)
self.surf = self.hover_surface if self.hover else self.original_surface
if self.hover and mouse_pos == self.mouse_pos:
self.count += 1
if self.count > 10:
self.image = pygame.Surface((48,48))
self.image.fill((255,255,255))
class textureblock(pygame.sprite.Sprite):
def __init__(self, imagefile, x, y, type):
super(textureblock, self).__init__()
self.imagefile = imagefile
self.original_image = pygame.image.load(imagefile).convert_alpha()
self.original_image = pygame.transform.scale(self.original_image, (48,48))
self.hover_image = self.original_image.copy()
pygame.draw.rect(self.hover_image, (255, 255, 0), self.hover_image.get_rect(), 6)
self.image = self.original_image
self.rect = self.image.get_rect(center = (x, y))
self.hover = False
self.mouse_pos = None
self.count = 0
self.type = type
def update(self):
if player.mode == "build":
pygame.draw.rect(self.hover_image, (255,0,0), self.hover_image.get_rect(), 6)
elif player.mode == "destroy":
pygame.draw.rect(self.hover_image, (255, 255,0), self.hover_image.get_rect(), 6)
mouse_pos = pygame.mouse.get_pos()
self.hover = self.rect.collidepoint(mouse_pos)
self.image = self.hover_image if self.hover else self.original_image
if self.hover and mouse_pos == self.mouse_pos and player.mode == "destroy":
self.count += 1
if self.count > 10:
self.image = pygame.Surface((48,48))
self.image.fill((0,191,255))
item = Item(self.imagefile, self.rect.x, self.rect.y, self.type)
items.add(item)
self.remove(blocks)
else:
self.count = 0
self.mouse_pos = mouse_pos
class Player(pygame.sprite.Sprite):
def __init__(self):
super(Player, self).__init__()
self.surf = pygame.Surface((40,40))
self.surf.fill((255,0,0))
self.rect = self.surf.get_rect()
self.pos = vec(0,144) # Position
self.vel = vec(0,0) # Velocity
self.acc = vec(0,0) # Acceleration
self.inventory = {} # Items can be added
self.mode = "destroy" # Mode. Probably will be changed later
self.selected_item = None # Selected item
def move(self, pressed_keys):
self.acc = vec(0,0.5)
if pressed_keys[K_LEFT]:
self.acc.x = -ACC
if pressed_keys[K_RIGHT]:
self.acc.x = ACC
self.acc.x += self.vel.x * FRIC
self.vel += self.acc
self.pos.x += self.vel.x + 0.5 * self.acc.x
self.rect.midbottom = self.pos
hit_side = False
for entity in blocks:
if self.rect.colliderect(entity.rect):
# move left and hit the block on the right
if self.vel.x < 0 and self.rect.right > entity.rect.right:
self.rect.left = entity.rect.right
self.pos.x = self.rect.centerx
hit_side = True
# move right and hit the block on the left
if self.vel.x > 0 and self.rect.left < entity.rect.left:
self.rect.right = entity.rect.left
self.pos.x = self.rect.centerx
hit_side = True
if hit_side:
self.vel.x = 0
self.acc.x = 0
def update(self):
self.acc = vec(0,0.5)
if pressed_keys[K_LEFT]:
self.acc.x = -ACC
if pressed_keys[K_RIGHT]:
self.acc.x = ACC
self.acc.x += self.vel.x * FRIC
self.vel += self.acc
self.pos.x += self.vel.x + 0.5 * self.acc.x
self.rect.midbottom = self.pos
hit_side = False
for entity in blocks:
if self.rect.colliderect(entity.rect):
# move left and hit the block on the right
if self.vel.x < 0 and self.rect.right > entity.rect.right:
self.rect.left = entity.rect.right
self.pos.x = self.rect.centerx
hit_side = True
# move right and hit the block on the left
if self.vel.x > 0 and self.rect.left < entity.rect.left:
self.rect.right = entity.rect.left
self.pos.x = self.rect.centerx
hit_side = True
if hit_side:
self.vel.x = 0
self.acc.x = 0
hits = pygame.sprite.spritecollide(self, blocks, False)
self.pos.y += self.vel.y + 0.5 * self.acc.y
self.rect.midbottom = self.pos
for entity in blocks:
if self.rect.colliderect(entity.rect):
if self.vel.y > 0:
self.rect.bottom = entity.rect.top
self.pos.y = self.rect.bottom
self.vel.y = 0
if self.vel.y > 0:
if hits:
self.pos.y = hits[0].rect.top + 1
self.vel.y = 0
def jump(self):
self.vel.y = -15
# Item class - Spawned when a player breaks a block.
class Item(pygame.sprite.Sprite):
def __init__(self, image, x, y, type):
super(Item, self).__init__()
self.image = pygame.image.load(image).convert_alpha()
self.image = pygame.transform.scale(self.image, (24,24))
self.rect = self.image.get_rect()
self.pos = vec(x, y)
self.vel = vec(0,0)
self.acc = vec(0,0)
self.type = type
def move(self):
self.acc = vec(0,0.5)
self.acc.x += self.vel.x * FRIC
self.vel += self.acc
self.pos.x += self.vel.x + 0.5 * self.acc.x
self.rect.midbottom = self.pos
hit_side = False
for entity in blocks:
if self.rect.colliderect(entity.rect):
# move left and hit the block on the right
if self.vel.x < 0 and self.rect.right > entity.rect.right:
self.rect.left = entity.rect.right
self.pos.x = self.rect.centerx
hit_side = True
# move right and hit the block on the left
if self.vel.x > 0 and self.rect.left < entity.rect.left:
self.rect.right = entity.rect.left
self.pos.x = self.rect.centerx
hit_side = True
if hit_side:
self.vel.x = 0
self.acc.x = 0
def update(self):
hits = pygame.sprite.spritecollide(self, blocks, False)
self.pos.y += self.vel.y + 0.5 * self.acc.y
self.rect.midbottom = self.pos
for entity in blocks:
if self.rect.colliderect(entity.rect):
if self.vel.y > 0:
self.rect.bottom = entity.rect.top
self.pos.y = self.rect.bottom
self.vel.y = 0
if self.vel.y > 0:
if hits:
self.pos.y = hits[0].rect.top + 1
self.vel.y = 0
# Now we check if we hit the Player
if self.rect.colliderect(player.rect):
# Attempt to check the player inventory
if self.type not in player.inventory.keys():
player.inventory[self.type] = 1
self.kill()
else:
player.inventory.update({self.type: player.inventory.get(self.type) + 1})
self.kill()
player = Player()
blueblock = BlueBlock(0,0)
running = True
all_sprites = pygame.sprite.Group()
blocks = pygame.sprite.Group()
items = pygame.sprite.Group()
miscBlocks = pygame.sprite.Group()
def drawGraph(graph, start):
y = start
x = 0
for item in graph:
if item == "O":
spr = BlueBlock(x, y)
spr.rect.x = x
spr.rect.y = y
all_sprites.add(spr)
miscBlocks.add(spr)
screen.blit(spr.surf, spr.rect)
x += 48
elif item == "G":
spr = textureblock("media/Grass.jpeg", x, y, "Grass")
spr.rect.x = x
spr.rect.y = y
all_sprites.add(spr)
blocks.add(spr)
screen.blit(spr.image, spr.rect)
x += 48
elif item == "S":
spr = textureblock("media/Stone.png",x,y, "Stone")
spr.rect.x = x
spr.rect.y = y
all_sprites.add(spr)
blocks.add(spr)
screen.blit(spr.image, spr.rect)
x += 48
elif item == "C":
spr = textureblock("media/Coal.jpeg",x,y, "Coal")
spr.rect.x = x
spr.rect.y = y
all_sprites.add(spr)
blocks.add(spr)
screen.blit(spr.image, spr.rect)
x += 48
elif item == "NL":
y += 48
x = 0
else:
print("Item not found: " + item)
print("[Debug] Generation finished. Amount of blocks: " + str(len(blocks.sprites())))
def randomGen(graph):
print("[Debug] Begin randomGen...")
# We assume the user has not done anything with the graph, so we add the sky and grass
for i in range(20):
newgraph.append('O')
newgraph.append('NL')
for i in range(20):
newgraph.append('O')
newgraph.append('NL')
for i in range(20):
newgraph.append('O')
newgraph.append('NL')
for i in range(20):
newgraph.append('O')
newgraph.append('NL')
for i in range(20):
newgraph.append('O')
newgraph.append('NL')
for i in range(20):
newgraph.append('G')
newgraph.append('NL')
# Next begins the random ore gen
for i in range(20):
x = 0
for i in range(20):
# Chance of coal - 1 in 15
iscoal = random.randint(1,15)
if iscoal == 6:
graph.append("C")
else:
graph.append("S")
x += 48
graph.append('NL')
print("[Debug] randomGen finished. Block Stats: %s air blocks, %s grass blocks, %s stone blocks, %s coal blocks, and %s newlines." % (str(graph.count('O')), str(graph.count('G')), str(graph.count('S')), str(graph.count('C')), str(graph.count('NL'))))
newgraph = []
randomGen(newgraph)
all_sprites.add(player)
def update():
for entity in all_sprites:
try:
screen.blit(entity.surf, entity.rect)
except:
screen.blit(entity.image, entity.rect)
screen.blit(player.surf, player.rect)
for entity in blocks:
screen.blit(entity.image, entity.rect)
for entity in items:
screen.blit(entity.image, entity.rect)
pygame.display.update()
drawGraph(newgraph, 0)
# Calculate the size of the level
level_width = 0
for i in newgraph:
if i != "NL":
level_width += 1
elif i == "NL":
break
level_width = level_width * TILE_SIZE
level_height = (newgraph.count("NL")-1)*TILE_SIZE
print("[Debug] Calculated level width and height: %s and %s" % (str(level_width), str(level_height)))
while running:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
player.jump()
if event.key == pygame.K_ESCAPE:
running = False
pygame.quit()
if event.key == pygame.K_e:
if len(player.inventory) == 0:
print("Player Inventory: Empty")
else:
print("Player Inventory: " + str(player.inventory).replace("{", "").replace("}","").replace(":", " x").replace("'",""))
if event.key == pygame.K_b:
if player.mode == "build":
player.mode = "destroy"
elif player.mode == "destroy":
player.mode = "build"
print("Changed player mode to " + player.mode)
if event.key == pygame.K_1:
player.selected_item = list(player.inventory)[0]
print("Item: " + list(player.inventory)[0])
if event.type == pygame.MOUSEBUTTONDOWN:
x,y = event.pos
if event.type == pygame.QUIT:
running = False
pygame.quit()
miscBlocks.update()
blocks.update()
blocks.draw(screen)
pressed_keys = pygame.key.get_pressed()
for entity in items:
entity.move()
# Commented out because it lagged everything.
items.update()
player.update()
update()
img1 = font.render("FPS: " + str(clock.get_fps()), True, (255,255,0))
img2 = font.render("Mode: " + player.mode, True, (255,0,0))
try:
img3 = font.render("Selected Item: " + player.selected_item, True, (0,255,0))
except Exception:
img3 = font.render("Selected Item: None", True, (0,255,0))
screen.blit(img1, (0,0))
screen.blit(img2, (0,24))
screen.blit(img3, (0,48))
pygame.display.update()
clock.tick(60)
Adding scrolling level is very easy. You just need from x take away a camera x and from y take away a camera y, for example: self.rect = self.image.get_rect(center = (x - camerax, y - cameray)).
How this is working:
Its little bit hard to answer how this works. Normaly if camerapos is always (0, 0) the map will not be scroling but if you move camera with player it will scroll. If you have a square at x 500 and you gona make camerax every second bigger by one it will move, for example: x = 500 and cameray = 100 screen will blit an image at x 400 so the image is moved, the same with the y cordinate.
How to add a camera:
So the most simple way is to create two ints or floats: cx and cy (camerax, cameray). But if you want something more modern you can do something like this:
def camera(pos):
return pos[0] - cx, pos[1] - cy
Modified code:
import pygame
from pygame.locals import *
import os
import random
import time
pygame.init()
W, Height = 800, 640
screen = pygame.display.set_mode((W,Height))
SCREEN_SIZE = pygame.Rect((0,0,W,Height))
TILE_SIZE = 48
t0 = time.time()
font = pygame.font.SysFont(None, 24)
print("Time needed to create fonts: " + str(time.time() - t0))
vec = pygame.math.Vector2
# USER CAN MODIFY
ACC = 0.5
FRIC = -0.12
FPS = 60
cx = 0
cy = 0
def camera(pos):
return pos[0] - cx, pos[1] - cy
clock = pygame.time.Clock()
class BlueBlock(pygame.sprite.Sprite):
def __init__(self, x, y):
super(BlueBlock, self).__init__()
self.surf = pygame.Surface((48,48))
self.surf.fill((0,191,255))
self.rect = self.surf.get_rect()
self.original_surface = self.surf
self.hover_surface = self.original_surface.copy()
pygame.draw.rect(self.hover_surface, (255, 255, 0), self.hover_surface.get_rect(), 6)
self.surf = self.original_surface
self.rect = self.surf.get_rect(center = (x, y))
self.hover = False
self.mouse_pos = None
self.count = 0
def update(self):
if player.mode == "build":
mouse_pos = pygame.mouse.get_pos()
self.hover = self.rect.collidepoint(mouse_pos)
self.surf = self.hover_surface if self.hover else self.original_surface
if self.hover and mouse_pos == self.mouse_pos:
self.count += 1
if self.count > 10:
self.image = pygame.Surface((48,48))
self.image.fill((255,255,255))
class textureblock(pygame.sprite.Sprite):
def __init__(self, imagefile, x, y, type):
super(textureblock, self).__init__()
self.imagefile = imagefile
self.original_image = pygame.image.load('img.png').convert_alpha()
self.original_image = pygame.transform.scale(self.original_image, (48,48))
self.hover_image = self.original_image.copy()
pygame.draw.rect(self.hover_image, (255, 255, 0), self.hover_image.get_rect(), 6)
self.image = self.original_image
self.rect = self.image.get_rect(center = (x, y))
self.hover = False
self.mouse_pos = None
self.count = 0
self.type = type
def update(self):
if player.mode == "build":
pygame.draw.rect(self.hover_image, (255,0,0), self.hover_image.get_rect(), 6)
elif player.mode == "destroy":
pygame.draw.rect(self.hover_image, (255, 255,0), self.hover_image.get_rect(), 6)
mouse_pos = pygame.mouse.get_pos()
self.hover = self.rect.collidepoint(mouse_pos)
self.image = self.hover_image if self.hover else self.original_image
if self.hover and mouse_pos == self.mouse_pos and player.mode == "destroy":
self.count += 1
if self.count > 10:
self.image = pygame.Surface((48,48))
self.image.fill((0,191,255))
item = Item(self.imagefile, self.rect.x, self.rect.y, self.type)
items.add(item)
self.remove(blocks)
else:
self.count = 0
self.mouse_pos = mouse_pos
class Player(pygame.sprite.Sprite):
def __init__(self):
super(Player, self).__init__()
self.surf = pygame.Surface((40,40))
self.surf.fill((255,0,0))
self.rect = self.surf.get_rect()
self.pos = vec(0,144) # Position
self.vel = vec(0,0) # Velocity
self.acc = vec(0,0) # Acceleration
self.inventory = {} # Items can be added
self.mode = "destroy" # Mode. Probably will be changed later
self.selected_item = None # Selected item
def move(self, pressed_keys):
self.acc = vec(0,0.5)
if pressed_keys[K_LEFT]:
self.acc.x = -ACC
if pressed_keys[K_RIGHT]:
self.acc.x = ACC
self.acc.x += self.vel.x * FRIC
self.vel += self.acc
self.pos.x += self.vel.x + 0.5 * self.acc.x
self.rect.midbottom = self.pos
global cx, cy
cx = self.pos.x
cy = self.pos.y
hit_side = False
for entity in blocks:
if self.rect.colliderect(entity.rect):
# move left and hit the block on the right
if self.vel.x < 0 and self.rect.right > entity.rect.right:
self.rect.left = entity.rect.right
self.pos.x = self.rect.centerx
hit_side = True
# move right and hit the block on the left
if self.vel.x > 0 and self.rect.left < entity.rect.left:
self.rect.right = entity.rect.left
self.pos.x = self.rect.centerx
hit_side = True
if hit_side:
self.vel.x = 0
self.acc.x = 0
def update(self):
self.acc = vec(0,0.5)
if pressed_keys[K_LEFT]:
self.acc.x = -ACC
if pressed_keys[K_RIGHT]:
self.acc.x = ACC
self.acc.x += self.vel.x * FRIC
self.vel += self.acc
self.pos.x += self.vel.x + 0.5 * self.acc.x
self.rect.midbottom = self.pos
global cx, cy
cx = self.pos.x - 400
cy = self.pos.y - 400
hit_side = False
for entity in blocks:
if self.rect.colliderect(entity.rect):
# move left and hit the block on the right
if self.vel.x < 0 and self.rect.right > entity.rect.right:
self.rect.left = entity.rect.right
self.pos.x = self.rect.centerx
hit_side = True
# move right and hit the block on the left
if self.vel.x > 0 and self.rect.left < entity.rect.left:
self.rect.right = entity.rect.left
self.pos.x = self.rect.centerx
hit_side = True
if hit_side:
self.vel.x = 0
self.acc.x = 0
hits = pygame.sprite.spritecollide(self, blocks, False)
self.pos.y += self.vel.y + 0.5 * self.acc.y
self.rect.midbottom = self.pos
for entity in blocks:
if self.rect.colliderect(entity.rect):
if self.vel.y > 0:
self.rect.bottom = entity.rect.top
self.pos.y = self.rect.bottom
self.vel.y = 0
if self.vel.y > 0:
if hits:
self.pos.y = hits[0].rect.top + 1
self.vel.y = 0
def jump(self):
self.vel.y = -15
# Item class - Spawned when a player breaks a block.
class Item(pygame.sprite.Sprite):
def __init__(self, image, x, y, type):
super(Item, self).__init__()
self.image = pygame.image.load('img2.png').convert_alpha()
self.image = pygame.transform.scale(self.image, (24,24))
self.rect = self.image.get_rect()
self.pos = vec(x, y)
self.vel = vec(0,0)
self.acc = vec(0,0)
self.type = type
def move(self):
self.acc = vec(0,0.5)
self.acc.x += self.vel.x * FRIC
self.vel += self.acc
self.pos.x += self.vel.x + 0.5 * self.acc.x
self.rect.midbottom = self.pos
hit_side = False
for entity in blocks:
if self.rect.colliderect(entity.rect):
# move left and hit the block on the right
if self.vel.x < 0 and self.rect.right > entity.rect.right:
self.rect.left = entity.rect.right
self.pos.x = self.rect.centerx
hit_side = True
# move right and hit the block on the left
if self.vel.x > 0 and self.rect.left < entity.rect.left:
self.rect.right = entity.rect.left
self.pos.x = self.rect.centerx
hit_side = True
if hit_side:
self.vel.x = 0
self.acc.x = 0
def update(self):
hits = pygame.sprite.spritecollide(self, blocks, False)
self.pos.y += self.vel.y + 0.5 * self.acc.y
self.rect.midbottom = self.pos
for entity in blocks:
if self.rect.colliderect(entity.rect):
if self.vel.y > 0:
self.rect.bottom = entity.rect.top
self.pos.y = self.rect.bottom
self.vel.y = 0
if self.vel.y > 0:
if hits:
self.pos.y = hits[0].rect.top + 1
self.vel.y = 0
# Now we check if we hit the Player
if self.rect.colliderect(player.rect):
# Attempt to check the player inventory
if self.type not in player.inventory.keys():
player.inventory[self.type] = 1
self.kill()
else:
player.inventory.update({self.type: player.inventory.get(self.type) + 1})
self.kill()
player = Player()
blueblock = BlueBlock(0,0)
running = True
all_sprites = pygame.sprite.Group()
blocks = pygame.sprite.Group()
items = pygame.sprite.Group()
miscBlocks = pygame.sprite.Group()
def drawGraph(graph, start):
y = start
x = 0
for item in graph:
if item == "O":
spr = BlueBlock(x, y)
spr.rect.x = x
spr.rect.y = y
all_sprites.add(spr)
miscBlocks.add(spr)
screen.blit(spr.surf, spr.rect)
x += 48
elif item == "G":
global cx
spr = textureblock("media/Grass.jpeg", x, y, "Grass")
spr.rect.x = x
spr.rect.y = y
all_sprites.add(spr)
blocks.add(spr)
screen.blit(spr.image, spr.rect)
x += 48
elif item == "S":
spr = textureblock("media/Stone.png",x,y, "Stone")
spr.rect.x = x
spr.rect.y = y
all_sprites.add(spr)
blocks.add(spr)
screen.blit(spr.image, spr.rect)
x += 48
elif item == "C":
spr = textureblock("media/Coal.jpeg",x,y, "Coal")
spr.rect.x = x
spr.rect.y = y
all_sprites.add(spr)
blocks.add(spr)
screen.blit(spr.image, spr.rect)
x += 48
elif item == "NL":
y += 48
x = 0
else:
print("Item not found: " + item)
print("[Debug] Generation finished. Amount of blocks: " + str(len(blocks.sprites())))
def randomGen(graph):
print("[Debug] Begin randomGen...")
# We assume the user has not done anything with the graph, so we add the sky and grass
for i in range(20):
newgraph.append('O')
newgraph.append('NL')
for i in range(20):
newgraph.append('O')
newgraph.append('NL')
for i in range(20):
newgraph.append('O')
newgraph.append('NL')
for i in range(20):
newgraph.append('O')
newgraph.append('NL')
for i in range(20):
newgraph.append('O')
newgraph.append('NL')
for i in range(20):
newgraph.append('G')
newgraph.append('NL')
# Next begins the random ore gen
for i in range(20):
x = 0
for i in range(20):
# Chance of coal - 1 in 15
iscoal = random.randint(1,15)
if iscoal == 6:
graph.append("C")
else:
graph.append("S")
x += 48
graph.append('NL')
print("[Debug] randomGen finished. Block Stats: %s air blocks, %s grass blocks, %s stone blocks, %s coal blocks, and %s newlines." % (str(graph.count('O')), str(graph.count('G')), str(graph.count('S')), str(graph.count('C')), str(graph.count('NL'))))
newgraph = []
randomGen(newgraph)
all_sprites.add(player)
def update():
screen.blit(player.surf, camera(player.rect))
for entity in blocks:
screen.blit(entity.image, camera(entity.rect))
for entity in items:
screen.blit(entity.image, camera(entity.rect))
pygame.display.update()
drawGraph(newgraph, 0)
# Calculate the size of the level
level_width = 0
for i in newgraph:
if i != "NL":
level_width += 1
elif i == "NL":
break
level_width = level_width * TILE_SIZE
level_height = (newgraph.count("NL")-1)*TILE_SIZE
print("[Debug] Calculated level width and height: %s and %s" % (str(level_width), str(level_height)))
while running:
screen.fill((0, 0, 255))
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
player.jump()
if event.key == pygame.K_ESCAPE:
running = False
pygame.quit()
if event.key == pygame.K_e:
if len(player.inventory) == 0:
print("Player Inventory: Empty")
else:
print("Player Inventory: " + str(player.inventory).replace("{", "").replace("}","").replace(":", " x").replace("'",""))
if event.key == pygame.K_b:
if player.mode == "build":
player.mode = "destroy"
elif player.mode == "destroy":
player.mode = "build"
print("Changed player mode to " + player.mode)
if event.key == pygame.K_1:
player.selected_item = list(player.inventory)[0]
print("Item: " + list(player.inventory)[0])
if event.type == pygame.MOUSEBUTTONDOWN:
x,y = event.pos
if event.type == pygame.QUIT:
running = False
pygame.quit()
miscBlocks.update()
blocks.update()
# blocks.draw(screen)
pressed_keys = pygame.key.get_pressed()
for entity in items:
entity.move()
# Commented out because it lagged everything.
items.update()
player.update()
update()
img1 = font.render("FPS: " + str(clock.get_fps()), True, (255,255,0))
img2 = font.render("Mode: " + player.mode, True, (255,0,0))
try:
img3 = font.render("Selected Item: " + player.selected_item, True, (0,255,0))
except Exception:
img3 = font.render("Selected Item: None", True, (0,255,0))
screen.blit(img1, (0,0))
screen.blit(img2, (0,24))
screen.blit(img3, (0,48))
pygame.display.update()
!IMPORTANT!
Don't try do cx = playerx it will make your player go to the x 0, for example: if x = 500 and cx = 500 then the screen will blit at x 0. But you can do something like this cx = playerx - 400.
I hope it helped.

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. 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()

Make bullets fire off in the direction the player is facing

I was just getting some help to figure out how to get my player fire bullets when I realized that they only go (kinda expected this but however as only had y value for movement). I don't know how I'll make the bullets fire off in the direction the player is facing.
I have some idea of what to but I just don't know how to do it... I thought I could somehow use the cursor and player tracking that's in this game for the visuals but I don't know how to make that a one-time thing instead of a constant. For diagonal movement of the bullet, I have no clue.
Code below (split into two parts/file Main.py and PlayerSprite.py):
Main:
py.init()
py.mixer.init()
screen = py.display.set_mode((WIDTH, HEIGHT))
py.display.set_caption("Dimensional Drifter")
clock = py.time.Clock()
all_sprites = py.sprite.Group()
NPCs = py.sprite.Group()
bullets = py.sprite.Group()
player = Player()
all_sprites.add(player)
for i in range(14):
n = NPC(player)
all_sprites.add(n)
NPCs.add(n)
# Game loop
running = True
while running:
# keep loop running at the right speed
clock.tick(FPS)
for event in py.event.get():
# check for closing window
if event.type == py.QUIT:
running = False
elif event.type == py.KEYDOWN:
if event.key == py.K_SPACE:
New_bullet = player.Shoot()
all_sprites.add(New_bullet)
bullets.add(New_bullet)
# Update
all_sprites.update()
# # check if there a collision between the bullet and NPC
hits = py.sprite.groupcollide(NPCs, bullets, True, True)
# check if there a collision between the player and NPC
hits = py.sprite.spritecollide(player, NPCs, True)
if hits:
running = False
# updates the position of of mouse and rotates it towards the mouse position
mouse_x, mouse_y = py.mouse.get_pos()
player.rotate(mouse_x, mouse_y)
# render
screen.fill(BLACK)
all_sprites.draw(screen)
# flip the display
py.display.flip()
py.quit()
PlayerSprite
import pygame as py
import math
import random
WIDTH = 800
HEIGHT = 600
FPS = 60
# define colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
class Player(py.sprite.Sprite):
def __init__(self):
py.sprite.Sprite.__init__(self)
self.image = py.Surface((40, 40), py.SRCALPHA)
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.centerx = WIDTH / 2
self.rect.bottom = HEIGHT / 2
self.Yspeed = 0
self.rotatableimage = self.image
def update(self):
self.Xspeed = 0
self.Yspeed = 0
# line below allow for key press to equate to move of sprite
keypreesed = py.key.get_pressed()
if keypreesed[py.K_a]:
self.Xspeed = - 11
if keypreesed[py.K_d]:
self.Xspeed = 11
if keypreesed[py.K_w]:
self.Yspeed = - 11
if keypreesed[py.K_s]:
self.Yspeed = 11
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
# line below allow the sprite to wrap around the screen
if self.rect.left > WIDTH:
self.rect.right = 0
if self.rect.right < 0:
self.rect.left = WIDTH
if self.rect.top > HEIGHT:
self.rect.top = 0
if self.rect.bottom < 0:
self.rect.bottom = HEIGHT
def rotate(self, mouse_x, mouse_y):
rel_x = mouse_x - self.rect.x
rel_y = mouse_y - self.rect.y
angle = (180 / math.pi) * -math.atan2(rel_y, rel_x)
self.image = py.transform.rotate(self.rotatableimage, int(angle))
self.rect = self.image.get_rect(center=(self.rect.centerx, self.rect.centery))
return
def Shoot(self):
return Bullet(self.rect.centerx, self.rect.top)
class NPC(py.sprite.Sprite):
def __init__(self, player):
py.sprite.Sprite.__init__(self)
self.player = player
self.image = py.Surface((30, 30)).convert_alpha()
self.image.fill(RED)
self.originalimage = self.image
self.rect = self.image.get_rect()
self.spawn()
# allows of spawning from all four side of the screen and set the x, y speed and spawn position
def spawn(self):
self.direction = random.randrange(4)
if self.direction == 0:
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.Xspeed = random.randrange(-2, 2)
self.Yspeed = random.randrange(4, 8)
elif self.direction == 1:
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(HEIGHT, HEIGHT + 60)
self.Xspeed = random.randrange(-2, 2)
self.Yspeed = -random.randrange(4, 8)
elif self.direction == 2:
self.rect.x = random.randrange(-100, -40)
self.rect.y = random.randrange(HEIGHT - self.rect.height)
self.Xspeed = random.randrange(4, 8)
self.Yspeed = random.randrange(-2, 2)
elif self.direction == 3:
self.rect.x = random.randrange(WIDTH, WIDTH + 60)
self.rect.y = random.randrange(HEIGHT - self.rect.height)
self.Xspeed = -random.randrange(4, 8)
self.Yspeed = random.randrange(-2, 2)
def update(self):
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
# makes it so that NPC point to wards the player as it passes from side to side
dir_x, dir_y = self.player.rect.x - self.rect.x, self.player.rect.y - self.rect.y
self.rot = (180 / math.pi) * math.atan2(-dir_x, -dir_y)
self.image = py.transform.rotate(self.originalimage, self.rot)
# Respawns the NPC when they hit an side
if self.direction == 0:
if self.rect.top > HEIGHT + 10:
self.spawn()
elif self.direction == 1:
if self.rect.bottom < -10:
self.spawn()
elif self.direction == 2:
if self.rect.left > WIDTH + 10:
self.spawn()
elif self.direction == 3:
if self.rect.right < -10:
self.spawn()
class Bullet(py.sprite.Sprite):
def __init__(self, x, y):
py.sprite.Sprite.__init__(self)
self.image = py.Surface((5, 5))
self.image.fill(YELLOW)
self.rect = self.image.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.Yspeed = -10
def update(self):
self.rect.y += self.Yspeed
# kill if moved of screen
if self.rect.bottom > HEIGHT or self.rect.top < 0:
self.kill()
if self.rect.right > WIDTH or self.rect.left < 0:
self.kill()
Add 2 attributes self.lastX and self.lastY to the class Player and change the attributes when the player changes the direction:
class Player(py.sprite.Sprite):
def __init__(self):
# [...]
self.lastX = 0
self.lastY = -10
def update(self):
# [...]
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
if self.Xspeed != 0 or self.Yspeed != 0:
self.lastX = self.Xspeed
self.lastY = self.Yspeed
Add an argument Xspeed ans Yspeed to the class Bullet
class Bullet(py.sprite.Sprite):
def __init__(self, x, y, Xspeed, Yspeed):
py.sprite.Sprite.__init__(self)
self.image = py.Surface((5, 5))
self.image.fill(YELLOW)
self.rect = self.image.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.Xspeed = Xspeed
self.Yspeed = Yspeed
def update(self):
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
# [...]
Set the attributes when the bullet spawns
class Player(py.sprite.Sprite):
# [...]
def Shoot(self):
return Bullet(self.rect.centerx, self.rect.centery, self.lastX, self.lastY)
Alternatively it is also possible to set the speed dependent on the direction to the mouse cursor.
Get the position of the player and the mouse cursor and compute the x and y distance (Vector ):
pos = self.rect.center
mpos = py.mouse.get_pos()
vx = mpos[0] - pos[0]
vy = mpos[1] - pos[1]
If the mouse position and the bullet position are equal, that does not make any sense, thus the bullet is skipped
if vx == 0 and vy == 0:
return None
Of course this vector is far to long, if you would use it for the direction (Xspeed, Yspeed) directly, then the bullet would step to the mouse cursor in one turn.
In the following I use pygame.math.Vector2, because it provides the handy method scale_to_length, that scales a vector to a specified Euclidean length:
direction = py.math.Vector2(vx, vy)
direction.scale_to_length(10)
Now the x and y component of the vector contain the x and y component of the speed. Since the components are floating point values, they are round to integral values:
return Bullet(pos[0], pos[1], round(direction.x), round(direction.y))
Method Shoot:
class Player(py.sprite.Sprite):
# [...]
def Shoot(self):
pos = self.rect.center
mpos = py.mouse.get_pos()
vx, vy = mpos[0] - pos[0], mpos[1] - pos[1]
if vx == 0 and vy == 0:
return None
direction = py.math.Vector2(vx, vy)
direction.scale_to_length(10)
return Bullet(pos[0], pos[1], round(direction.x), round(direction.y))
Note, if you set the bullet dependent on the direction to the mouse cursor, then it may be useful to spawn the bullet by a mouse click:
while running:
# [...]
for event in py.event.get():
if event.type == py.QUIT:
# [...]
elif event.type == py.MOUSEBUTTONDOWN:
if event.button == 1:
New_bullet = player.Shoot()
if New_bullet:
all_sprites.add(New_bullet)
bullets.add(New_bullet)
You can use pygames Vector2 to move in any direction. You calculate the angle of the player you can use that.
class Player(py.sprite.Sprite):
def __init__(self):
...
self.angle = 0
def rotate(self, mouse_x, mouse_y):
...
self.angle = -angle #make negative otherwise it will be going away from mouse
def Shoot(self):
return Bullet(self.rect.centerx, self.rect.top, py.math.Vector2(1,0).rotate(self.angle))
then in your bullet class, get the direction and add to its position
class Bullet(py.sprite.Sprite):
def __init__(self, x, y, Dir):
...
self.Dir = Dir
def update(self):
self.rect.y += self.Dir[1] * self.speed
self.rect.x += self.Dir[0] * self.speed
...

How do I center a surface (subsurface) around a rectangle? (scaled sprite hitbox / collision rect)

Currently I have working code which will cycle through a spritesheet, adding each cell/image (9 in total) as a subsurface into a list. As the game updates, I am setting the Player image as the current cell in which the code has indexed by. Meanwhile, I also have a set rectangle which acts as the sprites 'hitbox'/collision rect.
However, setting the subsurface as the new image, I found that the sprite scales from the top-left corner of the collision rect. As the sprite is significantly larger than the collision rect, the collision rect is placed far away from the actual char model/sprite.
I am trying to center the subsurface/sprite image AROUND the collision rect, as opposed from scaling from the top-left corner.
Here is my code:
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.rect = self.image.get_rect()
self.vel = vec(0, 0)
self.pos = vec(x , y)
self.move = 0
def animate(self, direction):
if direction == 'right':
self.spritesheet = pg.image.load('walk right.png') # Loading the right directional movement spritesheet into the variable
if direction == 'left':
self.spritesheet = pg.image.load('walk left.png')
if direction == 'up':
self.spritesheet = pg.image.load('walk up.png')
if direction == 'down':
self.spritesheet = pg.image.load('walk down.png')
self.frames = [] # List which will contain each cell of the spritesheet
# Adding the cells to the list #
self.frames.append(self.spritesheet.subsurface(pg.Rect(0, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(61, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(122, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(183, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(244, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(305, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(366, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(427, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(488, 0, 61, 67)).convert_alpha())
# Number of frames/cells
self.frames_number = len(self.frames)
# Current animation frame
self.current_frame = 0
# Frame rectangle
self.frame_rect = self.frames[0].get_rect()
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
self.move += 1
self.moving = 'left' # Uses different spritesheet depending on direction
elif keys[pg.K_d]: # Const. adds player speed value to velocity (E.g. Moves sprite to the right)
self.vel.x= PLAYER_SPEED
self.move += 1
self.moving = 'right'
elif keys[pg.K_w]: # Const. subtracts player speed value from y velocity (Moves player upwards; opposite)
self.vel.y= -PLAYER_SPEED
self.move += 1
self.moving = 'up'
elif keys[pg.K_s]: # Const. adds player speed value to y velocity (Moves player downwards; opposite)
self.vel.y= PLAYER_SPEED
self.move += 1
self.moving = 'down'
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):
# frame updates
self.moving = 'idle'
self.animate('down') # Sets the down spritesheet as default
self.get_keys()
if self.moving == 'up':
self.animate(self.moving) # Uses the up-movement spritesheet if char moving upwards
if self.moving == 'down':
self.animate(self.moving) # Same as above, different direction
if self.moving == 'left':
self.animate(self.moving)
if self.moving == 'right':
self.animate(self.moving)
# frame updates
self.ifColliding = False
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)
self.current_frame = (self.current_frame + self.move) % self.frames_number
if self.moving == 'idle':
self.current_frame = 0
self.image = self.frames[self.current_frame] # Image of sprite changes as program cycles through the sheet
In short, Id like to center the self.image surface on the self.rect (Collision rect).
[EDIT]
I have attempted to change references to self.rect within the colliion functions (collide_with_player2, collide_with_walls) to self.col_rect in hopes this would work, but found this not to be the case.
As stated I have created the new rectangle i'd like to use for collision so that self.rect is used for the image blitting, and self.col_rect is used for collision. Although inefficient, I would still like to allow for this as a temporary fix to the problem. I am new to pygame, so I was hoping someone could help me in changing the rectangle used in collision from self.rect, to self.col_rect instead.
Again, any feedback would be greatly appreciated!
Updated code:
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, game.bothplayers
pg.sprite.Sprite.__init__(self, self.groups)
self.game = game
self.image = pg.Surface((61, 67))
self.rect = self.image.get_rect()
self.col_rect = self.rect.inflate(-40, -40)
self.vel = vec(0, 0)
self.pos = vec(x , y)
self.move = 0
def animate(self, direction):
if direction == 'right':
self.spritesheet = pg.image.load('walk right civ.png') # Loading the right directional movement spritesheet into the variable
if direction == 'left':
self.spritesheet = pg.image.load('walk left civ.png')
if direction == 'up':
self.spritesheet = pg.image.load('walk up civ.png')
if direction == 'down':
self.spritesheet = pg.image.load('walk down civ.png')
self.frames = [] # List which will contain each cell of the spritesheet
# Adding the cells to the list #
self.frames.append(self.spritesheet.subsurface(pg.Rect(0, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(61, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(122, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(183, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(244, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(305, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(366, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(427, 0, 61, 67)).convert_alpha())
self.frames.append(self.spritesheet.subsurface(pg.Rect(488, 0, 61, 67)).convert_alpha())
# Number of frames/cells
self.frames_number = len(self.frames)
# Current animation frame
self.current_frame = 0
# Frame rectangle
self.frame_rect = self.frames[0].get_rect()
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
self.move += 1
self.moving = 'left' # Uses different spritesheet depending on direction
elif keys[pg.K_d]: # Const. adds player speed value to velocity (E.g. Moves sprite to the right)
self.vel.x= PLAYER_SPEED
self.move += 1
self.moving = 'right'
elif keys[pg.K_w]: # Const. subtracts player speed value from y velocity (Moves player upwards; opposite)
self.vel.y= -PLAYER_SPEED
self.move += 1
self.moving = 'up'
elif keys[pg.K_s]: # Const. adds player speed value to y velocity (Moves player downwards; opposite)
self.vel.y= PLAYER_SPEED
self.move += 1
self.moving = 'down'
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):
# frame updates
self.moving = 'idle'
self.animate('down') # Sets the down spritesheet as default
self.get_keys()
if self.moving == 'up':
self.animate(self.moving) # Uses the up-movement spritesheet if char moving upwards
if self.moving == 'down':
self.animate(self.moving) # Same as above, different direction
if self.moving == 'left':
self.animate(self.moving)
if self.moving == 'right':
self.animate(self.moving)
self.ifColliding = False
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.col_rect.centerx = self.rect.centerx
self.rect.y = self.pos.y
self.collide_with_walls('y'), self.collide_with_player2('y', self.ifColliding)
self.col_rect.centery = self.rect.centery
if self.ifColliding == True:
Thief.health -= COL_DAMAGE
print(Thief.health)
self.current_frame = (self.current_frame + self.move) % self.frames_number
if self.moving == 'idle':
self.current_frame = 0
self.image = self.frames[self.current_frame] # Image of sprite changes as program cycles through the sheet
If you want a scaled collision rect/hitbox, you need to give your sprites a second rect (I call it hitbox here). You have to do that because pygame blits the images/surfaces at the topleft coords of the self.rect. So the first rect self.rect serves as the blit position and the self.hitbox is used for the collision detection.
You also need to define a custom callback function for the collision detection that you have to pass to pygame.sprite.spritecollide as the fourth argument.
def collided(sprite, other):
"""Check if the `hitbox` rects of the two sprites collide."""
return sprite.hitbox.colliderect(other.hitbox)
collided_sprites = pg.sprite.spritecollide(player, enemies, False, collided)
Here's a complete example (the self.rects are the green rectangles and the self.hitboxes are the reds):
import pygame as pg
from pygame.math import Vector2
class Entity(pg.sprite.Sprite):
def __init__(self, pos, *groups):
super().__init__(*groups)
self.image = pg.Surface((70, 50))
self.image.fill((0, 80, 180))
self.rect = self.image.get_rect(center=pos)
# A inflated copy of the rect as the hitbox.
self.hitbox = self.rect.inflate(-42, -22)
self.vel = Vector2(0, 0)
self.pos = Vector2(pos)
def update(self):
self.pos += self.vel
self.rect.center = self.pos
self.hitbox.center = self.pos # Also update the hitbox coords.
def collided(sprite, other):
"""Check if the hitboxes of the two sprites collide."""
return sprite.hitbox.colliderect(other.hitbox)
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
all_sprites = pg.sprite.Group()
player = Entity((300, 200), all_sprites)
enemies = pg.sprite.Group(
Entity((100, 250), all_sprites),
Entity((400, 300), all_sprites),
)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.MOUSEMOTION:
player.pos = event.pos
all_sprites.update()
# Pass the custom collided callback function to spritecollide.
collided_sprites = pg.sprite.spritecollide(
player, enemies, False, collided)
for sp in collided_sprites:
print('Collision', sp)
screen.fill((30, 30, 30))
all_sprites.draw(screen)
for sprite in all_sprites:
# Draw rects and hitboxes.
pg.draw.rect(screen, (0, 230, 0), sprite.rect, 2)
pg.draw.rect(screen, (250, 30, 0), sprite.hitbox, 2)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()

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.

Categories

Resources