绘制实体时,你需要将偏移量应用于实体的位置。我们称这个偏移量为
a camera,因为这是我们想要达到的效果。
首先,我们无法使用
drawSprite群组的功能,因为
Sprite不需要知道其位置
(rect)并非要在屏幕上绘制的位置(最后,我们ll对该
Group类进行子类化,并重新实现它draw以了解摄像头,但让我们开始慢一点)。
让我们从创建一个
Camera类开始,以保存要应用于实体位置的偏移量的状态:
class Camera(object): def __init__(self, camera_func, width, height): self.camera_func = camera_func self.state = Rect(0, 0, width, height) def apply(self, target): return target.rect.move(self.state.topleft) def update(self, target): self.state = self.camera_func(self.state, target.rect)
这里要注意一些事情:
我们需要存储摄像机的位置以及水平的宽度和高度(以像素为单位)(因为我们要停止在水平边缘进行滚动)。我曾经使用
a Rect来存储所有这些信息,但是你可以轻松地使用一些字段。
使用
Rect派上用场的
apply功能。在这里我们重新计算实体在屏幕上的位置以应用滚动。
每个主循环迭代一次,我们需要更新摄影机的位置,因此有了
update功能。它只是通过调用
camera_func函数来改变状态,这将为我们完成所有艰苦的工作。我们稍后实施。
让我们创建一个摄影机的实例:
for row in level: ...total_level_width = len(level[0])*32 # calculate size of level in pixelstotal_level_height = len(level)*32 # maybe make 32 an constantcamera = Camera(*to_be_implemented*, total_level_width, total_level_height)entities.add(player)...
并更改我们的主循环:
# draw backgroundfor y in range(32): ...camera.update(player) # camera follows player. Note that we could also follow any other sprite# update player, draw everything elseplayer.update(up, down, left, right, running, platforms)for e in entities: # apply the offset to each entity. # call this for everything that should scroll, # which is basically everything other than GUI/HUD/UI screen.blit(e.image, camera.apply(e)) pygame.display.update()
我们的摄像头课程已经非常灵活,但是非常简单。它可以使用不同种类的滚动(通过提供不同的
camera_func功能),并且可以跟随任何变态的精灵,而不仅仅是播放器。你甚至可以在运行时更改此设置。
现在用于实施
camera_func。一种简单的方法是将播放器(或我们要跟随的任何实体)居中放在屏幕上,实现很简单:
def simple_camera(camera, target_rect): l, t, _, _ = target_rect # l = left, t = top _, _, w, h = camera # w = width, h = height return Rect(-l+HALF_WIDTH, -t+HALF_HEIGHT, w, h)
我们只是将放在位置target,然后加上一半的总屏幕尺寸。你可以通过以下方式创建相机来尝试:
camera = Camera(simple_camera, total_level_width, total_level_height)
到目前为止,一切都很好。但是也许我们不想看到关卡外面的黑色背景?怎么样:
def complex_camera(camera, target_rect): # we want to center target_rect x = -target_rect.center[0] + WIN_WIDTH/2 y = -target_rect.center[1] + WIN_HEIGHT/2 # move the camera. Let's use some vectors so we can easily substract/multiply camera.topleft += (pygame.Vector2((x, y)) - pygame.Vector2(camera.topleft)) * 0.06 # add some smoothness coolnes # set max/min x/y so we don't see stuff outside the world camera.x = max(-(camera.width-WIN_WIDTH), min(0, camera.x)) camera.y = max(-(camera.height-WIN_HEIGHT), min(0, camera.y)) return camera
在这里,我们仅使用min/ max函数以确保我们不会向外滚动。
像这样创建相机来尝试一下:
camera = Camera(complex_camera, total_level_width, total_level_height)
我们的最终滚动效果有点动画:
在此处输入图片说明
再次是完整的代码。注意我更改了一些内容:
- 级别更大,并拥有更多平台
- 使用python 3
- 使用精灵组来处理相机
- 重构了一些重复的代码
- 由于Vector2 / 3现在很稳定,因此请使用它们来简化数学运算
- 摆脱丑陋的事件处理代码,
pygame.key.get_pressed
而改用
#! /usr/bin/pythonimport pygamefrom pygame import *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 PPPPPPPPPPPP", "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", "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",] 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), platforms, entities) if col == "E": ExitBlock((x, y), platforms, entities) x += TILE_SIZE y += TILE_SIZE x = 0 while 1: for e in pygame.event.get(): if e.type == QUIT: return if e.type == KEYDOWN and e.key == K_ESCAPE: return entities.update() screen.fill((0, 0, 0)) entities.draw(screen) pygame.display.update() 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)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): 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 print(self.vel.y) if not(left or right): self.vel.x = 0 # increment in x direction self.rect.left += self.vel.x # do x-axis collisions self.collide(self.vel.x, 0, self.platforms) # increment in y direction self.rect.top += self.vel.y # 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, ExitBlock): pygame.event.post(pygame.event.Event(QUIT)) 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.bottomclass Platform(Entity): def __init__(self, pos, *groups): super().__init__(Color("#DDDDDD"), pos, *groups)class ExitBlock(Platform): def __init__(self, pos, *groups): super().__init__(Color("#0033FF"), pos, *groups)if __name__ == "__main__": main()


