python – 如何将矩形周围的曲面(次表面)居中? (缩放sprite hitbox / collision rect)

目前我有工作代码,它将循环通过spritesheet,将每个单元格/图像(总共9个)作为子表面添加到列表中.随着游戏的更新,我将播放器图像设置为代码索引的当前单元格.同时,我还有一个设置矩形,作为精灵’hitbox’/碰撞rect.

但是,将地下设置为新图像,我发现精灵从碰撞矩形的左上角开始缩放.由于精灵明显大于碰撞rect,因此碰撞rect远离实际的char模型/精灵.

我试图将地下/精灵图像置于碰撞矩形的中心,而不是从左上角缩放.

这是我的代码:

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(Collision rect)上.

[编辑]

我试图将colliion函数(collide_with_player2,collide_with_walls)中对self.rect的引用更改为self.col_rect,希望这可行,但发现情况并非如此.

如上所述,我已创建了我想用于碰撞的新矩形,因此self.rect用于图像blitting,self.col_rect用于碰撞.虽然效率低下,但我仍然希望将此作为暂时解决问题的方法.我是pygame的新手,所以我希望有人可以帮我改变从self.rect到self.col_rect的碰撞中使用的矩形.
再次,任何反馈将不胜感激!

更新的代码:

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,你需要给你的精灵第二个矩形(我在这里称它为hitbox).你必须这样做,因为pygame在自我的直立坐标上点亮图像/表面.因此,第一个rect self.rect用作blit位置,self.hitbox用于碰撞检测.

您还需要为碰撞检测定义一个自定义回调函数,您必须将其作为第四个参数传递给pygame.sprite.spritecollide.

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.rects是绿色矩形,self.hitbox是红色):

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()
上一篇:Java 2D游戏编程 – 新手问题


下一篇:Unity - 官方2D动画(2D Animation Package)文档