2013-01-16 32 views
29

好吧,我在下面列出了我的項目的代碼,我只是在pygame上做了一些關於製作平臺遊戲的實驗。我試圖弄清楚如何跟隨玩家進行一些非常簡單的滾動操作,所以玩家是相機的中心,並且會彈跳/跟隨他。誰能幫我?向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() 
+0

而不是移動玩家,你將所有其他玩法移動到「玩法速度」。如果您擁有更高的關卡,那麼只需將屏幕上的東西擋住即可。 –

+0

我很困惑你想說什麼,你可以擴展一點嗎? – user1758231

回答

84

您需要將偏移量應用於繪製它們時您實體的位置。我們稱爲偏移量 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函數中派上用場。這是我們在屏幕上重新計算一個實體的位置到申請的滾動。

每次迭代主循環一次,我們需要更新攝像頭的位置,因此有update函數。它只是通過調用camera_func函數來改變狀態,它會爲我們做所有的努力。我們稍後再執行它。

讓我們創建相機的instace:

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): 
    l, t, _, _ = target_rect 
    _, _, w, h = camera 
    l, t, _, _ = -l+HALF_WIDTH, -t+HALF_HEIGHT, w, h # center player 

    l = min(0, l)       # stop scrolling at the left edge 
    l = max(-(camera.width-WIN_WIDTH), l) # stop scrolling at the right edge 
    t = max(-(camera.height-WIN_HEIGHT), t) # stop scrolling at the bottom 
    t = min(0, t)       # stop scrolling at the top 

    return Rect(l, t, w, h) 

在這裏,我們簡單地使用min/max功能,以確保我們不會滾動出水平。

通過創建你的相機像這樣試試:

camera = Camera(complex_camera, total_level_width, total_level_height) 

有我們新的滾動的動作小動畫:

enter image description here

這裏再次的完整代碼(注意,我改變了你級別稍大一些,並有更多的平臺):

#! /usr/bin/python 

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 = [ 
     "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",] 
    # 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 

    total_level_width = len(level[0])*32 
    total_level_height = len(level)*32 
    camera = Camera(complex_camera, total_level_width, total_level_height) 
    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 

     # draw background 
     for y in range(32): 
      for x in range(32): 
       screen.blit(bg, (x * 32, y * 32)) 

     camera.update(player) 

     # update player, draw everything else 
     player.update(up, down, left, right, running, platforms) 
     for e in entities: 
      screen.blit(e.image, camera.apply(e)) 

     pygame.display.update() 

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) 

def simple_camera(camera, target_rect): 
    l, t, _, _ = target_rect 
    _, _, w, h = camera 
    return Rect(-l+HALF_WIDTH, -t+HALF_HEIGHT, w, h) 

def complex_camera(camera, target_rect): 
    l, t, _, _ = target_rect 
    _, _, w, h = camera 
    l, t, _, _ = -l+HALF_WIDTH, -t+HALF_HEIGHT, w, h 

    l = min(0, l)       # stop scrolling at the left edge 
    l = max(-(camera.width-WIN_WIDTH), l) # stop scrolling at the right edge 
    t = max(-(camera.height-WIN_HEIGHT), t) # stop scrolling at the bottom 
    t = min(0, t)       # stop scrolling at the top 
    return Rect(l, t, w, h) 

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() 
+2

這是驚人的,超級信息非常感謝你這個答案!並感謝你清楚地解釋所有事情,我真的能夠閱讀這些內容並理解你所做的一切,以及你爲什麼這樣做! – user1758231

+0

終於找到了我在找的東西,謝謝dom。 – Paxwell

+0

這種已獲得派送餡餅的回覆。 – droidballoon

0

既然知道對不對,你有一個靜態背景,而玩家自己控制,在他的位置圖混合,你有2個選項始終顯示在中間的角色。

  1. 如果映射是小到足夠多的,你可以有一個大的IMG A,並從中獲得一個長方形的基礎上,播放器將在屏幕的尺寸的位置。這樣,玩家將永遠處於中間。 Rect.clamp(Rect)或Rect.clamp_ip(Rect)將幫助你。

  2. 另一種方法是在屏幕上放置不同的元組。玩家在屏幕中心會有一個常數值,而背景位置則是玩家位置的負值。

+0

好吧,因爲地圖在最終設計中通常不會很小,我認爲選項2會更好。我將如何實現這一點?我主要困惑,因爲你正在使用內置的pygame精靈,偏移或屏幕更改將應用​​於 – user1758231

+0

,但沒有一種簡單的方法可以實現此目的。您可以重寫這些精靈中的繪圖函數,以便在不同的位置對圖像進行blit處理。你可以通過(對象的位置 - player_pos)推導surrond對象的位置 –

0

要做到這一點的唯一方法是從地圖上的物理位置分離邏輯位置。

與屏幕上實際繪製地圖相關的任何代碼 - 在您的案例中,所有的精靈的.rect屬性 - 必須基於屏幕實際使用的yor地圖的哪個部分的偏移來實現。

例如,您的屏幕可能會在左上方的位置(10,10)開始顯示您的地圖 - 所有與顯示相關的代碼(在上述情況下爲.rect屬性)應該從當前的邏輯位置 - (說人物在地圖座標(12,15) - 所以,它應該畫在(12,15) - (10,10) - >(2,5)* BLOCK_SIZE) 在你的例子中上面的BLOCK_SIZE被硬編碼爲32,32,所以你想畫在物理像素位置(2 * 32,5 * 32)在顯示器上)

(提示:避免硬編碼的東西,使它成爲一個常量聲明在你的代碼的開頭)