1

I made a simple game, where you have to jump over the stones moving towards you. The problem is that one of those sprites hitboxes is broken, even if the sprite mask is used. The player sprite is generated just dividing the player sprite sheet into 4 columns. Maybe there the problem is hiding? Pictures for the program: https://drive.google.com/drive/folders/1We6RDMs3Cwwprf1OY0ow9WCTHF9MHzne?usp=sharing

import pygame, os, random

pygame.init()

W, H = 800,600
HW, HH = W/2,H/2
AREA = W * H

WHITE = (255,255,255)
GREEN = (69,139,0)

FPS = 60

bg = pygame.image.load(os.path.join('Pildid', 'Taust3.png'))
bg = pygame.transform.scale(bg, (2000, 600))

DS = pygame.display.set_mode((W,H))
clock = pygame.time.Clock()


class Player(pygame.sprite.Sprite):
    def __init__(self, x, y, py, player, veerg, rida):
        super(Player,self).__init__()
        '''Mangija huppamine'''
        clock.tick(5)
        self.x = x
        self.y = y

        self.jumping = False
        self.platform_y = py
        self.velocity_index = 0

        '''Sprite sheet'''
        self.player = pygame.image.load(os.path.join('Pildid', 'karakter.png')).convert_alpha()#pildi uleslaadimine
        #self.player = pygame.transform.scale(self.player,(200,100)) 
        self.rect = self.player.get_rect()

        '''Sprite sheeti piltide jaotamine pikslite jargi'''
        self.veerg = veerg
        self.rida = rida
        self.kokku = veerg * rida

        self.rect = self.player.get_rect()
        L = self.veergL = self.rect.width/veerg
        K = self.weegK = self.rect.height/rida
        PL,PK = self.veergKeskel = (L/2,K/2)

        self.veerg = list([(index % veerg * L, int(index/veerg) * K,L,K )for index in range(self.kokku)])
        self.handle = list([ #pildi paigutamise voimalikud positsioonid
            (0, 0), (-PL, 0), (-L, 0),
            (0, -PK), (-PL, -PK), (-L, -PK),
            (0, -L), (-PL, -K), (-L, -K),])

        self.mask = pygame.mask.from_surface(self.player)

    def do_jumpt(self):
        '''Huppamine: kiirus, korgus, platvorm'''
        global velocity
        if self.jumping:
            self.y += velocity[self.velocity_index]
            self.velocity_index += 1
            if self.velocity_index >= len(velocity) - 1:
                self.velocity_index = len(velocity) - 1
            if self.y > self.platform_y:
                self.y = self.platform_y
                self.jumping = False
                self.velocity_index = 0



    def draw(self, DS,veergindex,x,y,handle=0):
        DS.blit(self.player,(self.x+self.handle[handle][0], self.y + self.handle[handle][1]),self.veerg[veergindex])

    def do(self):
        '''Funktsioonide kokkupanek'''
        self.do_jumpt()
        p.draw(DS,index%p.kokku,190, 359,4)

    def update(self):
        self.rect.center = self.x, self.y



def keys(player):
    keys = pygame.key.get_pressed()
    if keys[pygame.K_SPACE] or keys[pygame.K_UP] and player.jumping == False:
         player.jumping = True




class Obsticles(pygame.sprite.Sprite):
    '''Game obsticles: **'''
    #img = pygame.image.load(os.path.join('images', 'box.png'))
    def __init__(self, x, y):
        super(Obsticles,self).__init__()
        self.img = pygame.image.load(os.path.join('Pildid', 'kivi.png')).convert_alpha()
        self.img = pygame.transform.scale(self.img, (90,90))
        self.rect = self.img.get_rect(center=(x, y))
        self.x = x
        self.y = y
        self.mask = pygame.mask.from_surface(self.img)

    def draw(self, DS):
        '''Obsticle img blitting and hitbox'''
        DS.blit(self.img, (self.x, self.y))

    def update(self):
        if self.x < -64:  # Delete the obstacle.
            # `kill()` removes this obstacle sprite from all sprite groups.
            self.kill()

        self.x += speed  # Move the obstacle.
            # Update the rect because it's used to blit the
            # sprite and for the collision detection.
        self.rect.center = self.x, self.y

def redrawWindow():
    '''Obsticle loop'''
    for i in ob:
        i.draw(DS)

def text_objects(text, font):
    textSurface = font.render(text, True, WHITE)
    return textSurface, textSurface.get_rect()

def message_display(text):
    largeText = pygame.font.Font('freesansbold.ttf',60)
    TextSurf, TextRect = text_objects(text, largeText)
    TextRect.center = ((W/2),(H/4))
    DS.blit(TextSurf, TextRect)
    pygame.display.update()
    pygame.time.wait(3000)

def crash():
    message_display('Failed')


pygame.time.set_timer(pygame.USEREVENT+2, random.choice([2500, 3000, 1500]))

velocity = list([(i/ 1)-20 for i in range (0,60)])  #Huppe ulatus
index = 3

obsticles = Obsticles(832, 363)
p = Player(190, 359, 359, 'karakter.png', 4, 1)

all_sprites = pygame.sprite.Group(p, obsticles)
ob = pygame.sprite.Group(obsticles)

x = 0
x -= 1
speed = -5

running = True
while running:

    # ---Handle the events.---
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.USEREVENT+2:
            r = random.randrange(0, 2)
            if r == 0:
                obsticles = Obsticles(832, 363)
                # Add the obstacle to both groups.
                ob.add(obsticles)
                all_sprites.add(obsticles)


    speed += -0.008
    # ---Game logic.---
    all_sprites.update()

    collided = pygame.sprite.spritecollide(p, ob, True, 
    pygame.sprite.collide_mask)

    if collided:
        crash()

    index += 1
    # Background movement.

    back_x = x % bg.get_rect().width
    x -= 2
    # ---Draw everything.---
    DS.blit(bg, (back_x - bg.get_rect().width, 0))
    if back_x < W:
        DS.blit(bg, (back_x, 0))


    keys(p)
    p.do()
    redrawWindow()


    pygame.display.update()
    clock.tick(60)


pygame.quit()
quit()
Taavi Raudkivi
  • 71
  • 1
  • 2
  • 6
  • The pictures for the program: https://drive.google.com/drive/folders/1We6RDMs3Cwwprf1OY0ow9WCTHF9MHzne?usp=sharing – Taavi Raudkivi May 30 '18 at 23:27
  • Please always try to reduce your code to the absolute [minimum](https://stackoverflow.com/help/mcve) that is necessary to demonstrate the problem. – skrx May 31 '18 at 10:07

1 Answers1

1

The problem is that you use the original image to create the Player.mask. The resulting mask covers all four poses of the character, so even if only one of the sprite sheet frames is visible, the complete original image is used for the collision detection. Your mask looks like the green area in this picture:

pygame mask

You should also use the rect (i.e. the rect.topleft coords) as the blit position, because it is used for the collision detection as well (to find the location of the mask).

I've got a fixed, shortened version of your program in which I've changed a few things to show you how I would solve the problem. First of all, I load the sprite sheet and cut it into several subsurfaces, then I assign the first surface/image to the self.image attribute of the Player. This image can be used to create the rect and the mask.

In the update method I increment the frame_index and assign the current image to self.image in order to animate the sprite. BTW, it would be a good idea to create separate masks for every image and swap them as well during the animation. (I'm just using the first mask here, since the animation frames are so similar.)

Note that if you give your sprites an image and a rect attribute, you can just call all_sprites.update(DS) to blit all sprite images at their corresponding rects.

import random
import pygame


pygame.init()
FPS = 60
DS = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()
PLAYER_SHEET = pygame.image.load('karakter.png').convert_alpha()
# Cut the sprite sheet and append the subsurfaces to this list.
PLAYER_IMAGES = []
width = PLAYER_SHEET.get_width() / 4
height = PLAYER_SHEET.get_height()
for x in range(4):
    PLAYER_IMAGES.append(PLAYER_SHEET.subsurface(x*width, 0, width, height))


class Player(pygame.sprite.Sprite):
    def __init__(self, x, y, py):
        super(Player,self).__init__()
        self.x = x
        self.y = y

        self.jumping = False
        self.platform_y = py
        self.velocity_index = 0
        self.velocity = list([(i/ 1)-20 for i in range (0,60)])

        self.frame_index = 0
        self.image = PLAYER_IMAGES[self.frame_index]
        self.rect = self.image.get_rect()
        self.mask = pygame.mask.from_surface(self.image)

    def do_jump(self):
        if self.jumping:
            self.y += self.velocity[self.velocity_index]
            self.velocity_index += 1
            if self.velocity_index >= len(self.velocity) - 1:
                self.velocity_index = len(self.velocity) - 1
            if self.y > self.platform_y:
                self.y = self.platform_y
                self.jumping = False
                self.velocity_index = 0

    def update(self):
        self.rect.center = self.x, self.y
        self.do_jump()
        # Update the animation frame.
        self.frame_index += 1
        self.frame_index %= len(PLAYER_IMAGES) * 7  # * 7 to slow it down.
        # Swap the image.
        self.image = PLAYER_IMAGES[self.frame_index//7]  # // 7 to slow it down.


def keys(player):
    keys = pygame.key.get_pressed()
    if keys[pygame.K_SPACE] or keys[pygame.K_UP] and player.jumping == False:
         player.jumping = True


class Obstacle(pygame.sprite.Sprite):
    """Game Obstacle."""

    def __init__(self, x, y):
        super(Obstacle,self).__init__()
        self.image = pygame.Surface((90, 90), pygame.SRCALPHA)
        self.image.fill((100, 150, 0))
        self.image = pygame.transform.scale(self.image, (90,90))
        self.rect = self.image.get_rect(center=(x, y))
        self.x = x
        self.y = y
        self.mask = pygame.mask.from_surface(self.image)

    def update(self):
        if self.x < -64:
            self.kill()

        self.x += speed
        self.rect.center = self.x, self.y


pygame.time.set_timer(pygame.USEREVENT+2, random.choice([2500, 3000, 1500]))
index = 3

obstacle = Obstacle(832, 363)
player = Player(190, 359, 359)

all_sprites = pygame.sprite.Group(player, obstacle)
obstacles = pygame.sprite.Group(obstacle)
speed = -5

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.USEREVENT+2:
            r = random.randrange(0, 2)
            if r == 0:
                obstacle = Obstacle(832, 363)
                # Add the obstacle to both groups.
                obstacles.add(obstacle)
                all_sprites.add(obstacle)

    keys(player)

    # ---Game logic.---
    speed += -0.008

    all_sprites.update()

    collided = pygame.sprite.spritecollide(player, obstacles, True,
        pygame.sprite.collide_mask)

    if collided:
        print('crash')

    index += 1

    # ---Draw everything.---
    DS.fill((30, 30, 30))
    all_sprites.draw(DS)

    # Here I draw the outline (points) of the player's mask.
    px, py = player.rect.topleft
    for point in player.mask.outline(8):
        x, y = point[0] + px, point[1] + py
        pygame.draw.circle(DS, (250, 250, 0), (x, y), 2)

    pygame.draw.rect(DS, (0, 0, 250), player.rect, 1)
    # Draw the outlines of the obstacles.
    for obstac in obstacles:
        pygame.draw.rect(DS, (150, 250, 0), obstac.rect, 1)
        ox, oy = obstac.rect.topleft
        for point in obstac.mask.outline(6):
            pygame.draw.circle(DS, (250, 0, 0), (point[0]+ox, point[1]+oy), 2)

    pygame.display.update()
    clock.tick(60)


pygame.quit()
skrx
  • 19,980
  • 5
  • 34
  • 48