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