Q: Offset between origins of sprite.rect and sprite.image coordinates I've found in the pygame docs that the .rect attribute of a sprite is often initialised with the same width and the same height as the .image attribute.
However, you can change the .rect width or height so that the .rect becomes larger or smaller than the .image but the origins (topleft corners) of both .image and .rect are always the same point.
So here is my question:
Is there a way to create an offset between the origins (ie topleft corners) of the .rect and the .image of a sprite?
I know you can avoid this by:


*

*creating and using a second Rect attribute (for instance a .collide_rect) that would be updated each time the .rect would

*using transparency in the provided .image 
But those methods are quite inefficient and I would really appreciate a "Yes/No" answer.
I've searched in the official docs for hours but I didn't find the way to create such an offset so I suppose the answer to my question is "No" (even the .inflate_ip() method doesn't work since it doesn't keep the center of the .rect in place as written in the docs).

A: You could give the sprite class an additional offset attribute (a Vector2) and then iterate over the sprites with a for loop and add this offset to the .rect.topleft position when you're blitting the images.
import pygame as pg
from pygame.math import Vector2


class Player(pg.sprite.Sprite):

    def __init__(self, pos, *groups):
        super().__init__(*groups)
        self.image = pg.Surface((220, 120))
        self.image.fill(pg.Color('steelblue2'))
        self.rect = self.image.get_rect(center=pos)
        self.rect.inflate_ip(-42, -72)  # Make the rect smaller.
        self.vel = Vector2(0, 0)
        self.pos = Vector2(pos)
        # Offset is half of the inflation size,
        # so that the rect will be centered.
        self.offset = Vector2(-21, -36)

    def update(self):
        self.pos += self.vel
        self.rect.center = self.pos


def main():
    screen = pg.display.set_mode((640, 480))
    clock = pg.time.Clock()
    all_sprites = pg.sprite.Group()
    player = Player((300, 200), all_sprites)

    done = False

    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True
            elif event.type == pg.KEYDOWN:
                if event.key == pg.K_d:
                    player.vel.x = 5
            elif event.type == pg.KEYUP:
                if event.key == pg.K_d:
                    player.vel.x = 0

        all_sprites.update()

        screen.fill((30, 30, 30))
        # Assign `blit` to a local variable to improve the performance.
        screen_blit = screen.blit
        for sprite in all_sprites:
            # Now blit the sprites at topleft + offset.
            screen_blit(sprite.image, sprite.rect.topleft+sprite.offset)
            pg.draw.rect(screen, (250, 30, 0), sprite.rect, 2)

        pg.display.flip()
        clock.tick(30)


if __name__ == '__main__':
    pg.init()
    main()
    pg.quit()


For the sake of completeness, here's the solution with a second, scaled "hitbox" rect and a custom collided callback function that you can pass to one of the collision detection functions like pygame.sprite.spritecollide.
import pygame as pg
from pygame.math import Vector2


class Player(pg.sprite.Sprite):

    def __init__(self, pos, *groups):
        super().__init__(*groups)
        self.image = pg.Surface((70, 40))
        self.image.fill(pg.Color('steelblue4'))
        self.rect = self.image.get_rect(center=pos)
        # A inflated rect as the hitbox.
        self.hitbox = self.rect.copy()
        self.hitbox.inflate_ip(-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 = Player((300, 200), all_sprites)
    enemies = pg.sprite.Group(
        Player((100, 250), all_sprites),
        Player((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()

