在pygame中,当我与某物发生碰撞并使其停留时,如何将图像blit?
In pygame, how do I blit an image when I collide with something and make it stay?
所以我一直在使用第一个答案 here 中的 python 脚本的稍微修改的版本,我通过在此处添加一个名为 "SpeechBlock" 的 class :
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
并使其成为 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))
脚本已经有一个碰撞方法,我在这里添加了最后 3 行:
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()
我想要并且仍然想要实现的是,当玩家站在 SpeechBlock 上方时,一个对话泡泡的图像被 blit 到屏幕上。取而代之的是,当玩家站在 SpeechBlock 的边缘时,图像会出现一小会儿,然后消失,然后再次出现,等等......
我做错了什么?我是 pygame 的新手,所以我不太了解 blitting 和显示 updating/flipping 的工作原理。任何帮助将不胜感激。
你必须知道你的游戏是循环运行的。此循环的每次迭代称为一个帧。在每一帧中,您都可以清除屏幕上的所有内容、处理任何事件、更新您的游戏世界并重新绘制所有内容。
因此,当您调用 SpeechBlock.speak(self)
和 pygame.display.update()
时,您将图像(来自 speak
方法)绘制到屏幕上,但它们将在下一帧中被擦除。
每帧调用 pygame.display.update()
的次数不应超过一次。你应该做的是引入一些新的状态来改变你触摸它时 SpeechBlock
的行为。
tl;dr 以下示例:
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()
在这里您看到我们创建了一个新的 Entity
,当玩家踏入 SpeechBlock
时显示文本。两秒后,SpeechBlock
调用 kill
将其从游戏中移除。
我还稍微更新了代码以使用增量时间,这样我们就可以轻松检查是否已经过了 2 秒。
所以我一直在使用第一个答案 here 中的 python 脚本的稍微修改的版本,我通过在此处添加一个名为 "SpeechBlock" 的 class :
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
并使其成为 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))
脚本已经有一个碰撞方法,我在这里添加了最后 3 行:
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()
我想要并且仍然想要实现的是,当玩家站在 SpeechBlock 上方时,一个对话泡泡的图像被 blit 到屏幕上。取而代之的是,当玩家站在 SpeechBlock 的边缘时,图像会出现一小会儿,然后消失,然后再次出现,等等...... 我做错了什么?我是 pygame 的新手,所以我不太了解 blitting 和显示 updating/flipping 的工作原理。任何帮助将不胜感激。
你必须知道你的游戏是循环运行的。此循环的每次迭代称为一个帧。在每一帧中,您都可以清除屏幕上的所有内容、处理任何事件、更新您的游戏世界并重新绘制所有内容。
因此,当您调用 SpeechBlock.speak(self)
和 pygame.display.update()
时,您将图像(来自 speak
方法)绘制到屏幕上,但它们将在下一帧中被擦除。
每帧调用 pygame.display.update()
的次数不应超过一次。你应该做的是引入一些新的状态来改变你触摸它时 SpeechBlock
的行为。
tl;dr 以下示例:
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()
在这里您看到我们创建了一个新的 Entity
,当玩家踏入 SpeechBlock
时显示文本。两秒后,SpeechBlock
调用 kill
将其从游戏中移除。
我还稍微更新了代码以使用增量时间,这样我们就可以轻松检查是否已经过了 2 秒。