Im making a plat former game with pygame
So im creating sprites using
self.rect = self.image.get_rect()
then to get it to scale the hotbox im using
self.rect.inflate(-40,-20)
but this only seems to alter the hotbox from the right side and I think the bottom (Because the x,y starts in the top left of the sprite)
so now my hotbox for my enemy sprite is fine on the left side but still unproportionate on the left side
I hear theres a way to make the hitbox start in the centre of the sprite, How would I do this?
If not how should I go about fixing hotboxes
Thanks in advance,
edit: this is my code for my sprite
#ENEMY SPRITE class
class Enemy(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('img/blob.png')
self.image = pygame.transform.scale(self.image, (65,35))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.move_direction = 1
self.move_counter = 0
def update(self): #update enemy (movement)
self.rect.x += self.move_direction
self.move_counter += 1
if abs(self.move_counter) > 50:
self.move_direction *= -1
self.move_counter *= -1
Full code
#import modules
import pygame
from pygame.locals import *
from pygame import mixer
import pickle
from os import path
#initiliaze pygamee
pygame.mixer.pre_init(44100,-16,2,512) #volume control
mixer.init()
pygame.init()
#fps
clock = pygame.time.Clock()
font = pygame.font.SysFont('Bauhaus 93', 70)
font_score = pygame.font.SysFont('Bauhaus 93',30)
#screen creation/global variables
screen_width = 800
screen_height = 800
tile_size = 40
fps = 60
game_over = 0
main_menu = True
level = 1
max_levels = 7
score = 0
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption('Crashlandingv6')
#color
white = (255,255,255)
red = (255,15,15)
blue = (0,0,200)
#load images
bg_img = pygame.image.load('img/background.jpg')
bg_img = pygame.transform.scale(bg_img, (1000,1000))
earth_img = pygame.image.load('img/earth.png')
earth_img = pygame.transform.scale(earth_img, (100,100))
rect = bg_img.get_rect()
restart_img = pygame.image.load('img/restart_btn.png')
start_img = pygame.image.load('img/start_btn.png')
exit_img = pygame.image.load('img/exit_btn.png')
#Load sounds
pygame.mixer.music.load('img/music.wav')
pygame.mixer.music.play(-1,0.0,15000)
coin_fx = pygame.mixer.Sound('img/coin.wav')
coin_fx.set_volume(0.4)
jump_fx = pygame.mixer.Sound('img/jump.wav')
jump_fx.set_volume(0.4)
game_over_fx = pygame.mixer.Sound('img/gameover.wav')
game_over_fx.set_volume(0.5)
def draw_text(text,font,text_col,x,y):
img = font.render(text,True, text_col)
screen.blit(img,(x,y))
#function to reset level
def reset_level(level):
player.reset(100, screen_height - 130)
blob_group.empty()
lava_group.empty()
exit_group.empty()
if path.exists(f'level{level}_data'):
pickle_in = open(f'level{level}_data', 'rb')
world_data = pickle.load(pickle_in)
world = World(world_data)
return world
#create button class
class Button():
def __init__(self,x,y,image):
self.image = image
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.clicked = False
def draw(self):
action = False
pos = pygame.mouse.get_pos()
#check for button collision (if button was clicked {action}
if self.rect.collidepoint(pos):
if pygame.mouse.get_pressed()[0] == 1 and self.clicked == False:
action = True
self.clicked = True
if pygame.mouse.get_pressed()[0] == 0:
self.clicked = False
#draw button to screen
screen.blit(self.image,self.rect)
return action
#class for player
class Player():
def __init__(self, x, y):
self.reset(x,y)
def update(self,game_over):
dx = 0 #delta x
dy = 0 #delta y
walk_cooldown = 4 #speed
if game_over == 0: #if game is running gameover = 0 if game is over gameover = -1
#get keypresses (controls)
key = pygame.key.get_pressed()
if key[pygame.K_SPACE] and self.jumped == False:
jump_fx.play()
self.vel_y = -15
self.jumped = True
if key[pygame.K_LEFT]:
dx -= 5
self.counter += 1
self.direction = -1
if key[pygame.K_RIGHT]:
dx += 5
self.counter += 1
self.direction = 1
if key[pygame.K_LEFT] == False and key[pygame.K_RIGHT] == False:
self.counter = 0
self.index = 0
if self.direction == 1:
self.image = self.images_right[self.index]
if self.direction == -1:
self.image = self.images_left[self.index]
#TO DO < insert here !!
# add idle player animation if all buttons are false set player to idle
# players animation
if self.counter > walk_cooldown:
self.counter = 0
self.index += 1
if self.index >= len(self.images_right):
self.index = 0
if self.direction == 1:
self.image = self.images_right[self.index]
if self.direction == -1:
self.image = self.images_left[self.index]
#add gravity
self.vel_y += 1
if self.vel_y > 10:
self.vel_y = 10
dy += self.vel_y
#check for collision
for tile in world.tile_list:
#x direction collision
if tile[1].colliderect(self.rect.x + dx, self.rect.y, self.width, self.height):
dx=0
#y direction collision
if tile[1].colliderect(self.rect.x, self.rect.y + dy, self.width, self.height):
#check if below ground (jumping)
if self.vel_y <0:
dy = tile[1].bottom - self.rect.top
self.vel_y = 0
#check if above ground(falling)
elif self.vel_y >= 0:
dy = tile[1].top - self.rect.bottom
self.jumped = False
#check for collision with enemies
if pygame.sprite.spritecollide(self, blob_group, False):
game_over = -1
game_over_fx.play()
#check for collision with lava
if pygame.sprite.spritecollide(self,lava_group,False):
game_over = -1
# check for collision with lava
if pygame.sprite.spritecollide(self, exit_group, False):
game_over = 1
#if gameover (recall gameover = -1 gamerunning = 0)
elif game_over == -1:
self.image = self.dead_image
draw_text('GAME OVER!', font, red, (screen_width //2) - 200, screen_height //2)
if self.rect.y > 200:
self.rect.y -= 5
#update player coordinates
self.rect.x += dx
self.rect.y += dy
#draw player onto screen
screen.blit(self.image, self.rect)
#for rect outlines uncomment #pygame.draw.rect(screen,(255,255,255), self.rect, 2)
return game_over
def reset(self,x,y): #Player class under reset button , when player class is created info gets called from reset for efficiency purposes (instead of typing out twice)
self.images_right = []
self.images_left = []
self.index = 0
self.counter = 0
for num in range(1, 7):
img_right = pygame.image.load(f'img/guy{num}.png')
img_right = pygame.transform.scale(img_right, (40, 80))
img_left = pygame.transform.flip(img_right, True, False) # flips right image on the x axis {true} and not y axis {false}
self.images_right.append(img_right)
self.images_left.append(img_left)
self.dead_image = pygame.image.load('img/ghost.png')
self.image = self.images_right[self.index]
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.width = self.image.get_width()
self.height = self.image.get_height()
self.vel_y = 0
self.jumped = False
self.direction = 0
#class for tiles
class World():
def __init__(self,data):
self.tile_list = []
#load images
dirt_img = pygame.image.load('img/dirt.png')
moonrock_img = pygame.image.load('img/moonrock.png')
#game map
row_count = 0
for row in data:
col_count = 0
for tile in row:
if tile == 1: #replace with dirt
img = pygame.transform.scale(dirt_img,(tile_size,tile_size))
img_rect = img.get_rect()
img_rect.x = col_count * tile_size
img_rect.y = row_count * tile_size
tile = (img,img_rect)
self.tile_list.append(tile)
if tile == 2: #replace with moonrock
img = pygame.transform.scale(moonrock_img, (tile_size, tile_size))
img_rect = img.get_rect()
img_rect.x = col_count * tile_size
img_rect.y = row_count * tile_size
tile = (img, img_rect)
self.tile_list.append(tile)
if tile == 3: #replace with alien
blob = Enemy(col_count * tile_size, row_count * tile_size + 10)
blob_group.add(blob)
if tile == 6: #replace with acid
lava = Lava(col_count * tile_size, row_count * tile_size+(tile_size //2))
lava_group.add(lava)
if tile == 7:
coin = Coin(col_count * tile_size + (tile_size //2), row_count * tile_size + (tile_size // 2))
coin_group.add(coin)
if tile == 8:
exit = Exit(col_count * tile_size, row_count * tile_size - (tile_size//2))
exit_group.add(exit)
col_count += 1
row_count += 1
def draw(self): #draws tiles to screen
for tile in self.tile_list:
screen.blit(tile[0],tile[1])
#for rectangle outlines uncomment #pygame.draw.rect(screen,(255,255,255), tile[1], 1)
def hitbox_from_image(surf):
image_mask = pygame.mask.from_surface(surf)
rect_list = image_mask.get_bounding_rects()
return rect_list[0].unionall(rect_list)
#ENEMY SPRITE class
class Enemy(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('img/blob.png')
self.image = pygame.transform.scale(self.image, (65,35))
self.rect = hitbox_from_image(self.image)
self.rect.x = x
self.rect.y = y
self.move_direction = 1
self.move_counter = 0
def update(self): #update enemy (movement)
self.rect.x += self.move_direction
self.move_counter += 1
if abs(self.move_counter) > 50:
self.move_direction *= -1
self.move_counter *= -1
#LIQUID SPRITE (acid)
class Lava(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
img = pygame.image.load('img/lava2.jpg')
self.image = pygame.transform.scale(img, (tile_size, tile_size//2))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
class Coin(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
img = pygame.image.load('img/coin.png')
self.image = pygame.transform.scale(img, (tile_size//2, tile_size//2))
self.rect = self.image.get_rect()
self.rect.center = (x,y)
class Exit(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
img = pygame.image.load('img/exit.png')
self.image = pygame.transform.scale(img, (tile_size, int(tile_size * 1.5)))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
player = Player(100,screen_height - 130)
blob_group = pygame.sprite.Group()
lava_group = pygame.sprite.Group()
coin_group = pygame.sprite.Group()
exit_group = pygame.sprite.Group()
#score coin dumby coin
score_coin = Coin(tile_size//2, tile_size//2)
coin_group.add(score_coin)
#load in level data and create world
if path.exists(f'level{level}_data'):
pickle_in = open(f'level{level}_data', 'rb')
world_data = pickle.load(pickle_in)
world = World(world_data)
#create buttons
restart_button = Button(screen_width // 2 - 50, screen_height // 2 + 100, restart_img)
start_button = Button(screen_width// 2 - 350, screen_height // 2, start_img)
exit_button = Button(screen_width// 2 + 100, screen_height // 2, exit_img)
#main loop/ WHILE GAME IS RUNNING DO THIS
run = True
while run:
clock.tick(fps) #run the fps timers
screen.blit(bg_img,rect) #add bg img
screen.blit(earth_img,(100,100))
if main_menu == True:
if exit_button.draw():
run = False
if start_button.draw():
main_menu = False
else:
world.draw() #draw the world tiles
if game_over == 0: # while alive / not dead
blob_group.update()
#update score and checking for coin collection
if pygame.sprite.spritecollide(player,coin_group,True):
score += 1
coin_fx.play()
draw_text("X " + str(score), font_score ,white, tile_size - 10, 10)
blob_group.draw(screen)
lava_group.draw(screen)
coin_group.draw(screen)
exit_group.draw(screen)
game_over = player.update(game_over)
#if player is dead
if game_over == -1:
if restart_button.draw():
world_data = []
world = reset_level(level)
game_over = 0
score = 0
#If level complete reset and next level
if game_over == 1:
level += 1
if level <= max_levels:
#reset level
world_date = []
world = reset_level(level)
game_over = 0
else:
draw_text('WINNER WINNER!', font, blue, (screen_width //2) - 140, screen_height // 2)
#restart game
if restart_button.draw():
level = 1
# reset level
world_date = []
world = reset_level(level)
game_over = 0
score = 0
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.display.update() #update display
pygame.quit() #quit game