¿Cómo centro una superficie (subsuelo) alrededor de un rectángulo? (sprite escalado hitbox / colisión rect)

Actualmente tengo un código de trabajo que recorrerá una hoja de cálculo, agregando cada celda / imagen (9 en total) como subsuelo en una lista. A medida que el juego se actualiza, estoy configurando la imagen del jugador como la celda actual en la que se ha indexado el código. Mientras tanto, también tengo un rectángulo establecido que actúa como sprites ‘hitbox’ / collision rect.

Sin embargo, al establecer el subsuelo como la nueva imagen, encontré que el sprite se escala desde la esquina superior izquierda del rectángulo de colisión. Como el sprite es significativamente más grande que el recto de colisión, el recto de colisión se coloca lejos del modelo / sprite de char real.

Estoy tratando de centrar la imagen del subsuelo / sprite ALREDEDOR de la colisión correcta, a diferencia de la escala desde la esquina superior izquierda.

Aquí está mi código:

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 (Eg 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 (Eg 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.y = collides[0].rect.top - self.rect.height if self.vel.y  0: self.pos.x = collides[0].rect.left - self.rect.width if self.vel.x  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 

En resumen, me gusta centrar la superficie de la imagen del self en el self.rect (Collision rect).

[EDITAR]

He intentado cambiar las referencias a self.rect dentro de las funciones de colección (collide_with_player2, collide_with_walls) a self.col_rect con la esperanza de que esto funcione, pero este no es el caso.

Como dije, he creado el nuevo rectángulo que me gustaría usar para la colisión, de modo que self.rect se use para la imagen y el self.col_rect se use para la colisión. Aunque ineficiente, todavía me gustaría permitir esto como una solución temporal al problema. Soy nuevo en pygame, así que esperaba que alguien me ayudara a cambiar el rectángulo utilizado en colisión de self.rect, a self.col_rect en su lugar. Una vez más, cualquier comentario sería muy apreciado!

Código actualizado:

 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 (Eg 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 (Eg 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.y = collides[0].rect.top - self.rect.height if self.vel.y  0: self.pos.x = collides[0].rect.left - self.rect.width if self.vel.x  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 

Si quieres una colisión a la escala rect / hitbox, debes darle a tus sprites una segunda (la llamo hitbox aquí). Tienes que hacer eso porque pygame mezcla las imágenes / superficies en las cuerdas de self.rect del self.rect . Así que el primer rect self.rect sirve como la posición de blit y el self.hitbox se usa para la detección de colisiones.

También debe definir una función de callback personalizada para la detección de colisiones que debe pasar a pygame.sprite.spritecollide como el cuarto argumento.

 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) 

Aquí hay un ejemplo completo (los self.rect s son los rectangularjs verdes y los self.hitbox es los rojos):

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