2

So I have been using a slightly modified version of the python script from the first answer here and I added a class called "SpeechBlock" by adding it here:

    level = [
        "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",
        "P                                                             P",
        "P                                                             P",
        "P                                                             P",
        "P                                                             P",
        "P                                                             P",
        "P                                                             P",
        "P                                                             P",
        "PPPPPPPPSPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",]
    # build the level
    for row in level:
        for col in row:
            if col == "P":
                p = Platform(x, y)
                platforms.append(p)
                entities.add(p)
            if col == "E":
                e = ExitBlock(x, y)
                platforms.append(e)
                entities.add(e)
            if col == "S":
                s = SpeechBlock(x, y)
                platforms.append(s)
                entities.add(s

and making it a class:

class SpeechBlock(Platform):
    def __init__(self, x, y):
        Platform.__init__(self, x, y)
        self.image.fill(Color("#0033FF"))
        self.x=x
        self.y=y

    def speak(self):
        self.events = [
            "test",
            ]
        for row in self.events:
            image=pygame.image.load(row+".png")
            screen.blit(image, (self.x,self.y))

The script already had a method for collisions and I added the last 3-lines here:

ef collide(self, xvel, yvel, platforms):
        for p in platforms:
            if pygame.sprite.collide_rect(self, p):
                if isinstance(p, ExitBlock):
                    pygame.event.post(pygame.event.Event(QUIT))
                if xvel > 0:
                    self.rect.right = p.rect.left
                    print("collide right")
                if xvel < 0:
                    self.rect.left = p.rect.right
                    print ("collide left")
                if yvel > 0:
                    self.rect.bottom = p.rect.top
                    self.onGround = True
                    self.yvel = 0
                if yvel < 0:
                    self.rect.top = p.rect.bottom
                if isinstance(p, SpeechBlock):
                    SpeechBlock.speak(self)
                    pygame.display.update()

What I wanted and still want to achieve is that when the player is stands over a SpeechBlock an image of a speech bubble is blitted onto the screen. Instead what happens is that when the player stands on the edge of a SpeechBlock the image will appear for a short moment and then disappear and then reappear for a moment and so on... What have I done wrong? I'm pretty new to pygame so I don't really know much about how blitting and display updating/flipping works. Any help would be greatly appreciated.

1 Answers1

2

You have to learn that your game runs in a loop. Each iteration of this loop is called a frame. And in each frame you clean everything from the screen, handle any events, update your game world, and draw everything again.

So when you call SpeechBlock.speak(self) and pygame.display.update() you draw the images (from the speak method) to the screen, but they will get erased in the next frame.

You should not call pygame.display.update() more than once per frame. What you should do is to introduce some new state that changes how SpeechBlock behaves when you touch it.

tl;dr Example below:

import pygame
from pygame import *
import sys

SCREEN_SIZE = pygame.Rect((0, 0, 800, 640))
TILE_SIZE = 32 
GRAVITY = pygame.Vector2((0, 0.3))

class CameraAwareLayeredUpdates(pygame.sprite.LayeredUpdates):
    def __init__(self, target, world_size):
        super().__init__()
        self.target = target
        self.cam = pygame.Vector2(0, 0)
        self.world_size = world_size
        if self.target:
            self.add(target)

    def update(self, *args):
        super().update(*args)
        if self.target:
            x = -self.target.rect.center[0] + SCREEN_SIZE.width/2
            y = -self.target.rect.center[1] + SCREEN_SIZE.height/2
            self.cam += (pygame.Vector2((x, y)) - self.cam) * 0.05
            self.cam.x = max(-(self.world_size.width-SCREEN_SIZE.width), min(0, self.cam.x))
            self.cam.y = max(-(self.world_size.height-SCREEN_SIZE.height), min(0, self.cam.y))

    def draw(self, surface):
        spritedict = self.spritedict
        surface_blit = surface.blit
        dirty = self.lostsprites
        self.lostsprites = []
        dirty_append = dirty.append
        init_rect = self._init_rect
        for spr in self.sprites():
            rec = spritedict[spr]
            newrect = surface_blit(spr.image, spr.rect.move(self.cam))
            if rec is init_rect:
                dirty_append(newrect)
            else:
                if newrect.colliderect(rec):
                    dirty_append(newrect.union(rec))
                else:
                    dirty_append(newrect)
                    dirty_append(rec)
            spritedict[spr] = newrect
        return dirty            

def main():
    pygame.init()
    screen = pygame.display.set_mode(SCREEN_SIZE.size)
    pygame.display.set_caption("Use arrows to move!")
    timer = pygame.time.Clock()

    level = [
        "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                    PPPPPPPPPPP           P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P    PPPPPPPP                              P",
        "P                                          P",
        "P                          PPPPPPP         P",
        "P                 PPPPPP                   P",
        "P                                          P",
        "P         PPPPPPP                          P",
        "P                                          P",
        "P                     PPPPPP               P",
        "P                                          P",
        "P   PPPPPPPPPPP                            P",
        "P                                          P",
        "P                 PPPPPPPPPPP              P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "P                                          P",
        "PPPPPPPPPSPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",]


    platforms = pygame.sprite.Group()
    player = Player(platforms, (TILE_SIZE, TILE_SIZE))
    level_width  = len(level[0])*TILE_SIZE
    level_height = len(level)*TILE_SIZE
    entities = CameraAwareLayeredUpdates(player, pygame.Rect(0, 0, level_width, level_height))

    # build the level
    x = y = 0
    for row in level:
        for col in row:
            if col == "P":
                Platform((x, y), entities, platforms)
            if col == "S":
                SpeechBlock((x, y), entities, platforms)
            x += TILE_SIZE
        y += TILE_SIZE
        x = 0

    dt = 0

    while 1:
        events = pygame.event.get()
        for e in events:
            if e.type == QUIT: 
                return
            if e.type == KEYDOWN and e.key == K_ESCAPE:
                return

        entities.update(dt, events)
        screen.fill((0, 0, 0))
        entities.draw(screen)
        pygame.display.update()
        dt = timer.tick(60)

class Entity(pygame.sprite.Sprite):
    def __init__(self, color, pos, *groups):
        super().__init__(*groups)
        self.image = Surface((TILE_SIZE, TILE_SIZE))
        self.image.fill(color)
        self.rect = self.image.get_rect(topleft=pos)

    def update(self, dt, events):
        pass

class Player(Entity):
    def __init__(self, platforms, pos, *groups):
        super().__init__(Color("#0000FF"), pos)
        self.vel = pygame.Vector2((0, 0))
        self.onGround = False
        self.platforms = platforms
        self.speed = 8
        self.jump_strength = 10

    def update(self, dt, events):
        pressed = pygame.key.get_pressed()
        up = pressed[K_UP]
        left = pressed[K_LEFT]
        right = pressed[K_RIGHT]
        running = pressed[K_SPACE]

        if up:
            # only jump if on the ground
            if self.onGround: self.vel.y = -self.jump_strength
        if left:
            self.vel.x = -self.speed
        if right:
            self.vel.x = self.speed
        if running:
            self.vel.x *= 1.5
        if not self.onGround:
            # only accelerate with gravity if in the air
            self.vel += GRAVITY
            # max falling speed
            if self.vel.y > 100: self.vel.y = 100

        if not(left or right):
            self.vel.x = 0
        # increment in x direction
        self.rect.left += self.vel.x * dt/10.
        # do x-axis collisions
        self.collide(self.vel.x, 0, self.platforms)
        # increment in y direction
        self.rect.top += self.vel.y * dt/10.
        # assuming we're in the air
        self.onGround = False;
        # do y-axis collisions
        self.collide(0, self.vel.y, self.platforms)

    def collide(self, xvel, yvel, platforms):
        for p in platforms:
            if pygame.sprite.collide_rect(self, p):
                if isinstance(p, SpeechBlock):
                    p.trigger()
                if xvel > 0:
                    self.rect.right = p.rect.left
                if xvel < 0:
                    self.rect.left = p.rect.right
                if yvel > 0:
                    self.rect.bottom = p.rect.top
                    self.onGround = True
                    self.yvel = 0
                if yvel < 0:
                    self.rect.top = p.rect.bottom

class Platform(Entity):
    def __init__(self, pos, *groups):
        super().__init__(Color("#DDDDDD"), pos, *groups)

class TextBlock(pygame.sprite.Sprite):
    def __init__(self, pos, text, *groups):
        super().__init__(*groups)
        self.image = Surface((200, 100))
        self.rect = self.image.get_rect(center=pos)
        self.image.fill(Color("white"))
        # todo: don't load font everytime
        self.image.blit(pygame.font.SysFont(None, 32).render(text, True, Color("Black")), (10, 10))

class SpeechBlock(Entity):
    def __init__(self, pos, *groups):
        super().__init__(Color("orange"), pos, *groups)
        self.cooldown = 0
        self.text = None

    def trigger(self):
        # do nothing if already triggered
        if self.cooldown: return

        self.cooldown = 2000
        # first group is the entity group
        self.text = TextBlock(self.rect.move(0, -150).center, 'Ouch!', self.groups()[0])

    def update(self, dt, events):

        if self.cooldown:
            self.cooldown -= dt
            if self.cooldown <= 0:
                self.text.kill()
                self.text = None
                self.cooldown = 0

if __name__ == "__main__":
    main()

Here you see that we create a new Entity that displays the text when the player steps in the SpeechBlock. After two seconds, the SpeechBlock calls kill to remove it from the game.

I also updated the code a little bit to use delta time so we can easily check if 2 seconds have passed.

enter image description here

sloth
  • 99,095
  • 21
  • 171
  • 219
  • Wow, thanks a lot! One more question if: If I wanted to use images for sprites instead of just colored blocks how would I implement that? – Green Chili Sep 18 '19 at 13:12
  • @GreenChili Each sprite has a `image` attribute, which is the Surface that is going to be drawn on the screen. So it doesnot matter if that Surface is created manually (e.g. `self.image = Surface(...)`) or if you create the Surface by loading an image from disk (e.g. `self.image = pygame.image.load(...).convert()`) – sloth Sep 18 '19 at 13:20