Ответ 1
Вам необходимо применить смещение к позиции ваших сущностей при их рисовании. Давайте назовем это смещением camera
, так как это тот эффект, которого мы хотим добиться с этим.
Прежде всего, мы не можем использовать функцию draw
группы спрайтов, поскольку спрайты не должны знать, что их позиция (rect
) не является позицией, которую они собираются рисовать на экране (В конце мы подклассы 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)
некоторые вещи, чтобы отметить здесь:
Нам нужно сохранить положение камеры, а также ширину и высоту уровня в пикселях (поскольку мы хотим прекратить прокрутку по краям уровня). Я использовал Rect
для хранения всей этой информации, но вы можете легко использовать некоторые поля.
Использование Rect
удобно в функции apply
. Здесь мы пересчитываем положение объекта на экране, чтобы применить прокрутку.
Один раз за итерацию основного цикла нам нужно обновить положение камеры, отсюда и функция update
. Он просто изменяет состояние, вызывая функцию camera_func
, которая сделает всю тяжелую работу за нас. Мы реализуем это позже.
Давайте создадим камеру камеры:
for row in level:
...
total_level_width = len(level[0])*32 # calculate size of level in pixels
total_level_height = len(level)*32 # maybe make 32 an constant
camera = Camera(*to_be_implemented*, total_level_width, total_level_height)
entities.add(player)
...
и измените наш основной цикл:
# draw background
for y in range(32):
...
camera.update(player) # camera follows player. Note that we could also follow any other sprite
# update player, draw everything else
player.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 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)
Там немного анимации нашей финальной прокрутки в действии:
Здесь снова полный код. Обратите внимание, что я изменил некоторые вещи:
- уровень больше и иметь еще несколько платформ
- использовать питон 3
- использовать группу спрайтов для управления камерой
- переработал некоторый дублирующий код
- поскольку Vector2/3 теперь стабилен, используйте его для упрощения математики
- избавиться от этого уродливого кода обработки событий и использовать вместо него
pygame.key.get_pressed
#! /usr/bin/python
import pygame
from 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 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",
"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.bottom
class 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()