わかりましたので、以下に私のプロジェクトのコードを含めました。プラットフォーマーを作成するために、pygameでいくつかの実験を行っています。プレイヤーに追従する非常に簡単なスクロールを行う方法を見つけようとしているので、プレイヤーはカメラの中心であり、バウンド/フォローします。誰も私を助けることができますか?
import pygame
from pygame import *
WIN_WIDTH = 800
WIN_HEIGHT = 640
HALF_WIDTH = int(WIN_WIDTH / 2)
HALF_HEIGHT = int(WIN_HEIGHT / 2)
DISPLAY = (WIN_WIDTH, WIN_HEIGHT)
DEPTH = 32
FLAGS = 0
CAMERA_SLACK = 30
def main():
global cameraX, cameraY
pygame.init()
screen = pygame.display.set_mode(DISPLAY, FLAGS, DEPTH)
pygame.display.set_caption("Use arrows to move!")
timer = pygame.time.Clock()
up = down = left = right = running = False
bg = Surface((32,32))
bg.convert()
bg.fill(Color("#000000"))
entities = pygame.Sprite.Group()
player = Player(32, 32)
platforms = []
x = y = 0
level = [
"PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",
"P P",
"P P",
"P P",
"P P",
"P P",
"P P",
"P P",
"P PPPPPPPPPPP P",
"P P",
"P P",
"P P",
"P P",
"PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",]
# 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)
x += 32
y += 32
x = 0
entities.add(player)
while 1:
timer.tick(60)
for e in pygame.event.get():
if e.type == QUIT: raise SystemExit, "QUIT"
if e.type == KEYDOWN and e.key == K_ESCAPE:
raise SystemExit, "ESCAPE"
if e.type == KEYDOWN and e.key == K_UP:
up = True
if e.type == KEYDOWN and e.key == K_DOWN:
down = True
if e.type == KEYDOWN and e.key == K_LEFT:
left = True
if e.type == KEYDOWN and e.key == K_RIGHT:
right = True
if e.type == KEYDOWN and e.key == K_SPACE:
running = True
if e.type == KEYUP and e.key == K_UP:
up = False
if e.type == KEYUP and e.key == K_DOWN:
down = False
if e.type == KEYUP and e.key == K_RIGHT:
right = False
if e.type == KEYUP and e.key == K_LEFT:
left = False
if e.type == KEYUP and e.key == K_RIGHT:
right = False
# draw background
for y in range(32):
for x in range(32):
screen.blit(bg, (x * 32, y * 32))
# update player, draw everything else
player.update(up, down, left, right, running, platforms)
entities.draw(screen)
pygame.display.update()
class Entity(pygame.Sprite.Sprite):
def __init__(self):
pygame.Sprite.Sprite.__init__(self)
class Player(Entity):
def __init__(self, x, y):
Entity.__init__(self)
self.xvel = 0
self.yvel = 0
self.onGround = False
self.image = Surface((32,32))
self.image.fill(Color("#0000FF"))
self.image.convert()
self.rect = Rect(x, y, 32, 32)
def update(self, up, down, left, right, running, platforms):
if up:
# only jump if on the ground
if self.onGround: self.yvel -= 10
if down:
pass
if running:
self.xvel = 12
if left:
self.xvel = -8
if right:
self.xvel = 8
if not self.onGround:
# only accelerate with gravity if in the air
self.yvel += 0.3
# max falling speed
if self.yvel > 100: self.yvel = 100
if not(left or right):
self.xvel = 0
# increment in x direction
self.rect.left += self.xvel
# do x-axis collisions
self.collide(self.xvel, 0, platforms)
# increment in y direction
self.rect.top += self.yvel
# assuming we're in the air
self.onGround = False;
# do y-axis collisions
self.collide(0, self.yvel, 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
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
class Platform(Entity):
def __init__(self, x, y):
Entity.__init__(self)
self.image = Surface((32, 32))
self.image.convert()
self.image.fill(Color("#DDDDDD"))
self.rect = Rect(x, y, 32, 32)
def update(self):
pass
class ExitBlock(Platform):
def __init__(self, x, y):
Platform.__init__(self, x, y)
self.image.fill(Color("#0033FF"))
if __== "__main__":
main()
エンティティを描画するときは、エンティティの位置にoffsetを適用する必要があります。これをoffset a camera
と呼びましょう。これはこれで達成したい効果だからです。
まず、スプライトはその位置(draw
)が意図する位置ではないことを知る必要がないため、Spriteグループの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
関数で役立ちます。ここで、画面上のエンティティの位置を再計算して、applyスクロールします。
メインループの反復ごとに1回、カメラの位置を更新する必要があります。したがって、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
関数を提供することにより)さまざまな種類のスクロールを使用でき、プレーヤーだけでなく、任意のSpriteを追跡できます。実行時にこれを変更することもできます。
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
関数を使用して、スクロールしないようにしますoutside out level。
次のようにカメラを作成して試してください。
camera = Camera(complex_camera, total_level_width, total_level_height)
最後のスクロール動作のアニメーションが少しあります:
再び完全なコードを示します。注:私はいくつかのことを変更しました:
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 __== "__main__":
main()
それを行う唯一の方法は、画面上の物理的な位置からマップ内の論理的な位置を分離することです。
画面に実際に地図を描くことに関連するコード-あなたの場合、すべての.rect
スプライトの属性-画面が実際に使用しているマップのどの部分のオフセットに基づいて行う必要があります。
たとえば、画面の左上に位置(10,10)で始まる地図が表示される場合があります。すべての関連コードが表示されます(上記の場合は.rect
attributes)は、現在の論理位置からスクリーンオフセットを減算する必要があります(たとえば、キャラクターはマップcoords(12,15)にあります-したがって、(12,15)-(10、10)->(2 、5)* BLOCK_SIZE)上記の例では、BLOCK_SIZEは32,32にハードコーディングされているため、ディスプレイ上の物理ピクセル位置(2 * 32、5 * 32)に描画します)
(ヒント:このようにハードコーディングすることは避け、コードの先頭で定数宣言にしてください)
当然のことながら、静的な背景があり、あなたがコントロールするプレイヤーは自分がいる位置でブリットされているので、常に中央にキャラクターを表示する2つのオプションがあります。
マップが十分に小さい場合、画面のサイズになるプレーヤーの位置に基づいて、大きなimg Aを作成し、長方形を導出できます。そうすれば、プレイヤーは常に真ん中にいます。 Rect.clamp(Rect)またはRect.clamp_ip(Rect)はそれを支援します。
別のアプローチは、画面上の位置に異なるタプルを使用することです。プレーヤーは画面の中央に一定の値を持ち、背景の位置はプレーヤーの位置の負の値になります。