如何围绕矩形居中放置一个表面(子表面)? (缩放的精灵Hitbox/碰撞矩形) [英] How do I center a surface (subsurface) around a rectangle? (scaled sprite hitbox / collision rect)
问题描述
目前,我有工作代码,可以循环通过一个Spritesheet,将每个单元格/图像(总共9个)作为子表面添加到列表中.随着游戏的更新,我将Player图像设置为代码在其中索引的当前单元格.同时,我还有一个设置为sprites'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.
但是,将次表面设置为新图像,我发现精灵从碰撞矩形的左上角缩放.由于子图形比碰撞矩形大得多,因此将碰撞矩形放置在远离实际char模型/子图形的位置.
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.
这是我的代码:
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
简而言之,我想将self.image表面居中在self.rect(碰撞矩形)上.
In short, Id like to center the self.image surface on the self.rect (Collision rect).
我试图将colliion函数(collide_with_player2,collide_with_walls)中对self.rect的引用更改为self.col_rect,希望这样做可以,但事实并非如此.
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.
如上所述,我创建了一个新的矩形用于碰撞,以便将self.rect用于图像模糊处理,并将self.col_rect用于碰撞.尽管效率低下,但我仍然希望将此作为解决此问题的临时方法.我是pygame的新手,所以我希望有人可以帮助我将碰撞中使用的矩形从self.rect更改为self.col_rect. 再次,任何反馈将不胜感激!
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!
更新的代码:
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
推荐答案
如果您想使用比例缩放的rect/hitbox,则需要为您的sprite提供第二个rect(我在这里将其称为hitbox
).您必须这样做,因为pygame会在self.rect
的左上角坐标处遮挡图像/表面.因此,第一个rect self.rect
用作blit位置,而self.hitbox
用于碰撞检测.
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)
这是一个完整的示例(self.rect
是绿色矩形,self.hitbox
es是红色矩形):
Here's a complete example (the self.rect
s are the green rectangles and the self.hitbox
es 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()
这篇关于如何围绕矩形居中放置一个表面(子表面)? (缩放的精灵Hitbox/碰撞矩形)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!