49

さて、以下に私のプロジェクトのコードを含めました。プラットフォーマーを作成するために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 __name__ == "__main__":
    main()
4

4 に答える 4

124

エンティティを描画するときは、エンティティの位置にオフセットを適用する必要があります。これを使って達成したい効果なので、オフセットをaと呼びましょう。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ます。ここで、画面上のエンティティの位置を再計算して、スクロールを適用します。

メインループの反復ごとに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機能を提供することにより)、プレーヤーだけでなく、任意のスプライトを追跡できます。実行時にこれを変更することもできます。

次に、の実装について説明し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)

最後のスクロールの動作のアニメーションが少しあります。

ここに画像の説明を入力してください

これが完全なコードです。私がいくつかのことを変更したことに注意してください:

  • レベルが大きくなり、プラットフォームが増える
  • Python3を使用する
  • スプライトグループを使用してカメラを処理する
  • いくつかの重複コードをリファクタリングしました
  • 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.vel.y = 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(Entity):
    def __init__(self, pos, *groups):
        super().__init__(Color("#0033FF"), pos, *groups)

if __name__ == "__main__":
    main()
于 2013-01-16T11:19:42.770 に答える
0

当然のことながら、あなたには静的な背景があり、あなたがコントロールするプレイヤーは、彼がいる位置でブリットされます。あなたは常にキャラクターを真ん中に表示するための2つのオプションがあります。

  1. マップが十分に小さい場合は、大きなimg Aを作成し、画面のサイズとなるプレーヤーの位置に基づいて長方形を導出できます。そうすれば、プレイヤーは常に真ん中になります。Rect.clamp(Rect)またはRect.clamp_ip(Rect)は、そのために役立ちます。

  2. 別のアプローチは、画面上の位置に異なるタプルを使用することです。プレーヤーは画面の中央で一定の値になりますが、背景の位置はプレーヤーの位置の負の値になります。

于 2013-01-16T09:06:54.523 に答える
0

これを行う唯一の方法は、マップ内の論理的な位置を画面上の物理的な位置から分離することです。

画面上に実際にマップを描画することに関連するコード(この場合はスプライトのすべての.rect属性)は、画面が実際に使用しているマップの部分のオフセットに基づいて描画する必要があります。

たとえば、画面に左上の位置(10,10)で始まるマップが表示されている場合があります-すべての表示関連コード(上記の場合は.rect属性)は、現在の論理位置から画面オフセットを差し引く必要があります-(文字がマップ座標(12,15)にあると言う-したがって、(12,15)-(10、10)->(2、5)* BLOCK_SIZE)で描画する必要があります上記の例では、BLOCK_SIZEは32にハードコードされています、32なので、ディスプレイ上の物理的なピクセル位置(2 * 32、5 * 32)に描画します)

(ヒント:この方法でハードコーディングすることは避け、コードの先頭で常に宣言するようにしてください)

于 2013-01-16T11:06:55.810 に答える
0

残念ながら、Pygameにはこの問題に対する組み込みのソリューションがありません。Pygameは、 spygame.sprite.Spriteで編成されたオブジェクトを使用します。スプライトの属性は、オブジェクトの描画とオブジェクト間の衝突テストに使用されます。描画する前にオブジェクトの座標を画面の座標に変換できる組み込みの機能はありません。 Pygameの開発者への提案として:メソッドにカメラオフセットのオプションの引数があると便利です。pygame.sprite.Group.rect
pygame.sprite.Group.draw

別のアプローチがあります:

  • プレーヤーを動かす代わりに、シーン内の任意のオブジェクトを反対方向に動かすことができます。これはすべてのアプローチの中で最悪です。これを行わないことを強くお勧めします。新しいオブジェクトを追加するたびに、プレーヤーの移動に合わせてオブジェクトが移動することを確認する必要があります。オブジェクトのアニメーションや浮動小数点の精度を扱うことは、悪夢になる可能性があります。

  • 世界の仮想画面サイズを作成し、仮想画面に画面全体を描画します。各フレームの終わりに、マップのサブセクションが画面に表示されます。

    virtual_screen = pygame.Surface((map_width, map_height))
    

    2つの可能性があります。area引数blitを指定することにより、仮想画面の領域を画面上に直接表示できます。

    camera_area = pygame.Rect(camera_x, camera_y, camera_width, camera_height)
    screen.blit(virtual_screen, (0, 0), camera_area)
    

    subsurfaceもう1つの可能性は、次の方法を使用してソースサーフェスに直接リンクされているサブサーフェスを定義することです。

    camera_area = pygame.Rect(camera_x, camera_y, camera_width, camera_height)
    camera_subsurf = source_surf.subsurface(camera_area)
    
    screen.blit(camera_subsurf, (0, 0))
    

    このアプローチの欠点は、メモリフットプリントが非常に大きくなる可能性があることです。仮想画面が巨大な場合、ゲームは遅れます。このソリューションは、ゲーム領域のサイズが画面よりもそれほど大きくない場合にのみ適しています。経験則として、再生領域が画面の2倍以上のサイズである場合は、このようにしないでください(領域の幅と高さの長さの2倍ではなく、領域の2倍のサイズについて話します) 。

  • 大きな遊び場の場合、使用できる唯一のアプローチは、描画する前にオブジェクトにオフセットを追加することです。

    offset_x = -camera_x
    offset_y = -camera_y
    for object in objects:
        screen.blit(object.image, (object.rect.x + offset_x, object.rect.y + offset_y))
    

    残念ながらpygame.sprite.Group.draw、この場合は直接使用できません。このアプローチは、評価の高い回答で詳しく説明されています。
    または、描画する前にすべてのスプライトを移動することもできます。

    all_sprites = pygame.sprite.Group()
    
    for sprite in all_sprites:
        all_sprites.rect.move_ip(-camera_x, -camera_y)
    all_sprites.draw(screen)    
    for sprite in all_sprites:
        all_sprites.rect.move_ip(camera_x, camera_y)
    

最後に、ダーティメカニズムと部分的な画面更新についてのコメント:プレーヤーが移動するとすぐに、画面全体がダーティになり、更新する必要があります。したがって、部分的な更新メカニズムにリソースを投資する必要があるかどうかは疑問です。これらのアルゴリズムの実行にも時間がかかります。非常に動的なシーンでは、アルゴリズムの結果はすべてを更新することです。

于 2021-05-15T07:23:16.807 に答える