问题描述
我正在制作一个平台游戏,并有一个 blob(外星人)作为障碍物在平台上来回移动
对于碰撞,我遇到了一个问题,即我的外星精灵碰撞箱很大,一旦我的玩家进入 0.75 格范围内,他就会立即死亡。我希望hitBox小得多
目前我正在使用 pygame.sprite.spritecollide 来处理我的玩家和 blob 之间的碰撞
无论如何我改变这个我无法修复hitBoxes
有没有办法解决这个问题,我相信 pygame.sprite.collide_rect 或 collide_rect_ratio 可能有效,但我不知道如何使用该函数
有人可以解释一下我应该在我的代码中更改什么来解决这个问题(hitBoxes)
TLDR;我想修复我糟糕的碰撞箱,你能用简单的术语解释一下吗
这是碰撞的代码
#check for collision with enemies
if pygame.sprite.spritecollide(self,blob_group,False):
game_over = -1
game_over_fx.play()
这是我的完整代码
#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 = 7
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,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,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,y):
self.reset(x,y)
def update(self,game_over):
dx = 0 #delta x
dy = 0 #delta y
walk_cooldown = 4 #speed
col_thresh = 20
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.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,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 exit
if pygame.sprite.spritecollide(self,exit_group,False):
game_over = 1
#platform collision
for platform in platform_group:
#check for x collision
if platform.rect.colliderect(self.rect.x + dx,self.height):
dx = 0
# y collision
if platform.rect.colliderect(self.rect.x,self.height):
#check if below platform
if abs((self.rect.top + dy) - platform.rect.bottom) < col_thresh:
self.vel_y = 0
dy = platform.rect.bottom - self.rect.top
#check if above platform
elif abs((self.rect.bottom + dy) - platform.rect.top) < col_thresh:
self.rect.bottom = platform.rect.top - 1
self.in_air = False
dy = 0
#Move sideways w platform
if platform.move_x != 0:
self.rect.x += platform.move_direction
#if gameover (recall gameover = -1 gamerunning = 0)
elif game_over == -1:
self.image = self.dead_image
draw_text('GAME OVER!',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),self.rect,2)
return game_over
def reset(self,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,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,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 == 4:
platform = Platform(col_count * tile_size,row_count * tile_size,1,0) # y direction
platform_group.add(platform)
if tile == 5:
platform = Platform(col_count * tile_size,1)
platform_group.add(platform)
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,tile[1],1)
#ENEMY SPRITE class
class Enemy(pygame.sprite.Sprite):
def __init__(self,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
class Platform(pygame.sprite.Sprite):
def __init__(self,move_x,move_y):
pygame.sprite.Sprite.__init__(self)
img = pygame.image.load('img/platform.png')
self.image = pygame.transform.scale(img,tile_size // 2))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.move_counter = 0
self.move_direction = 1
self.move_x = move_x
self.move_y = move_y
def update(self): #update enemy (movement)
self.rect.x += self.move_direction * self.move_x
self.rect.y += self.move_direction * self.move_y
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,y):
pygame.sprite.Sprite.__init__(self)
img = pygame.image.load('img/lava2.jpg')
self.image = pygame.transform.scale(img,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,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,y):
pygame.sprite.Sprite.__init__(self)
img = pygame.image.load('img/exit.png')
self.image = pygame.transform.scale(img,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()
platform_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,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))
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()
platform_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)
platform_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!',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
这是我的外星人精灵
这是我的外星人类的代码
class Enemy(pygame.sprite.Sprite):
def __init__(self,y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('img/blob.png')
self.image = pygame.transform.scale(self.image,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
这是我的播放器类的代码(我称之为重置)
def reset(self,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
解决方法
使用 inflate
缩小矩形大小。
但是,由于 rect
属性用于碰撞检测和绘制精灵,因此您需要创建一个较小的图像。使用 pygame.Surface.subsurface
创建一个引用原始表面父级的新表面:
class Enemy(pygame.sprite.Sprite):
def __init__(self,x,y):
pygame.sprite.Sprite.__init__(self)
self.image_orig = pygame.image.load('img/blob.png')
self.image_orig = pygame.transform.scale(self.image_orig,(65,65))
self.rect = self.image.get_rect().inflate(-30,-20)
self.image = self.image_orig.subsurface(self.rect)
self.rect.topleft = x,y
self.move_direction = 1
self.move_counter = 0
可以使用pygame.mask.Mask
和pygame.mask.from_surface
自动检测hitbox的大小:
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)
class Enemy(pygame.sprite.Sprite):
def __init__(self,y):
pygame.sprite.Sprite.__init__(self)
self.image_orig = pygame.image.load('img/blob.png')
self.image_orig = pygame.transform.scale(self.image_orig,65))
self.rect = hitbox_from_image(self.image_orig)
self.image = self.image_orig.subsurface(self.rect)
self.rect.topleft = x,y
self.move_direction = 1
self.move_counter = 0
对于 Player
类,我建议您做类似的事情。加载后分别裁剪每个图像(功能 crop_image
)。
在绘制精灵之前更新 rect
函数中的 update
属性:
def crop_image(surf):
rect = hitbox_from_image(surf)
return surf.subsurface(self.rect).copy()
class Player():
# [...]
def update(self,game_over):
# [...]
self.rect.x += dx
self.rect.y += dy
self.rec = self.image.get_rect(center = self.rect.center)
screen.blit(self.image,self.rect)
def reset(self,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 = crop_image(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 = crop_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