如何以矩形为中心使表面(次表面)居中?(缩放精灵碰撞盒/碰撞矩形) [英] How do I center a surface (subsurface) around a rectangle? (scaled sprite hitbox / collision rect)

查看:27
本文介绍了如何以矩形为中心使表面(次表面)居中?(缩放精灵碰撞盒/碰撞矩形)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目前我有工作代码,它会在 spritesheet 中循环,将每个单元格/图像(总共 9 个)作为子表面添加到列表中.随着游戏的更新,我将 Player 图像设置为代码索引的当前单元格.同时,我还有一个矩形作为精灵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.

但是,将次表面设置为新图像时,我发现精灵从碰撞矩形的左上角开始缩放.由于精灵明显大于碰撞矩形,碰撞矩形放置在远离实际字符模型/精灵的位置.

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

我尝试将碰撞函数(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,你需要给你的精灵第二个 rect(我在这里称之为 hitbox).您必须这样做,因为 pygame 会在 self.rect 的左上角坐标处对图像/表面进行 blit.所以第一个矩形 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.

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

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

Here's a complete example (the self.rects are the green rectangles and the self.hitboxes 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()

这篇关于如何以矩形为中心使表面(次表面)居中?(缩放精灵碰撞盒/碰撞矩形)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆