我們講解了俄羅斯方塊的各個巨集觀的部分,這次就是更細緻的程式設計了,不過程式碼量實在不小,如果完全貼出來估計會嚇退很多人,所以我打算這裡只貼出資料和方法名,至於方法裡的程式碼就省略了,一切有興趣的朋友,請參考最後放出來的原始檔。
這個是main呼叫的Tetris類,這個類實現了我們所看到的遊戲畫面,是整個俄羅斯方塊遊戲的核心程式碼。為了明晰,它還會呼叫shape類來實現當前的shape,下面會講:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
class Tetris(object): W = 12 # board區域橫向多少個格子 H = 20 # 縱向多少個格子 TILEW = 20 # 每個格子的高/寬的畫素數 START = (100, 20) # board在螢幕上的位置 SPACE = 1000 # 方塊在多少毫秒內會落下(現在是level 1) def __init__(self, screen): pass def update(self, elapse): # 在遊戲階段,每次都會呼叫這個,用來接受輸入,更新畫面 pass def move(self, u, d, l, r): # 控制當前方塊的狀態 pass def check_line(self): # 判斷已經落下方塊的狀態,然後呼叫kill_line pass def kill_line(self, filled=[]): # 刪除填滿的行,需要播放個消除動畫 pass def get_score(self, num): # 計算得分 pass def add_to_board(self): # 將觸底的方塊加入到board陣列中 pass def create_board_image(self): # 創造出一個穩定方塊的影象 pass def next(self): # 產生下一個方塊 pass def draw(self): # 把當前狀態畫出來 pass def display_info(self): # 顯示各種資訊(分數,等級等),呼叫下面的_display*** pass def _display_score(self): pass def _display_next(self): pass def game_over(self): # 遊戲結束 pass |
這裡的東西基本都是和python語言本身相關的,pygame的內容並不多,所以就不多講了。看一下__init__的內容,瞭解了結構和資料,整個運作也就能明白了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
def __init__(self, screen) self.stat = "game" self.WIDTH = self.TILEW * self.W self.HEIGHT = self.TILEW * self.H self.screen = screen # board陣列,空則為None self.board = [] for i in xrange(self.H): line = [ None ] * self.W self.board.append(line) # 一些需要顯示的資訊 self.level = 1 self.killed = 0 self.score = 0 # 多少毫秒後會落下,當然在init裡肯定是不變的(level總是一) self.time = self.SPACE * 0.8 ** (self.level - 1) # 這個儲存自從上一次落下後經歷的時間 self.elapsed = 0 # used for judge pressed firstly or for a long time self.pressing = 0 # 當前的shape self.shape = Shape(self.START, (self.WIDTH, self.HEIGHT), (self.W, self.H)) # shape需要知道周圍世界的事情 self.shape.set_board(self.board) # 這個是“世界”的“快照” self.board_image = pygame.Surface((self.WIDTH, self.HEIGHT)) # 做一些初始化的繪製 self.screen.blit(pygame.image.load( util.file_path("background.jpg")).convert(), (0, 0)) self.display_info() |
注意我們這裡update方法的實現有些不同,並不是等待一個事件就立刻相應。記得一開是說的左右移動的對應麼?按下去自然立刻移動,但如果按下了沒有釋放,那麼方塊就會持續移動,為了實現這一點,我們需要把event.get和get_pressed混合使用,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
def update(self, elapse): for e in pygame.event.get(): # 這裡是普通的 if e.type == KEYDOWN: self.pressing = 1 # 一按下,記錄“我按下了”,然後就移動 self.move(e.key == K_UP, e.key == K_DOWN, e.key == K_LEFT, e.key == K_RIGHT) if e.key == K_ESCAPE: self.stat = 'menu' elif e.type == KEYUP and self.pressing: self.pressing = 0 # 如果釋放,就撤銷“我按下了”的狀態 elif e.type == QUIT: self.stat = 'quit' if self.pressing: # 即使沒有獲得新的事件,也要根據“我是否按下”來檢視 pressed = pygame.key.get_pressed() # 把按鍵狀態交給move self.move(pressed[K_UP], pressed[K_DOWN], pressed[K_LEFT], pressed[K_RIGHT]) self.elapsed += elapse # 這裡是在指定時間後讓方塊自動落下 if self.elapsed >= self.time: self.next() self.elapsed = self.elapsed - self.time self.draw() return self.stat |
稍微看一下消除動畫的實現,效果就是如果哪一行填滿了,就在把那行刪除前閃兩下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
def kill_line(self, filled=[]): if len(filled) == 0: return # 動畫的遮罩 mask = pygame.Surface((self.WIDTH, self.TILEW), SRCALPHA, 32) for i in xrange(5): if i % 2 == 0: # 比較透明 mask.fill((255, 255, 255, 100)) else: # 比較不透明 mask.fill((255, 255, 255, 200)) self.screen.blit(self.board_image, self.START) # 覆蓋在滿的行上面 for line in filled: self.screen.blit(mask, ( self.START[0], self.START[1] + line * self.TILEW)) pygame.display.update() pygame.time.wait(80) # 這裡是使用刪除填滿的行再在頂部填空行的方式,比較簡單 # 如果清空再讓方塊下落填充,就有些麻煩了 [self.board.pop(l) for l in sorted(filled, reverse=True)] [self.board.insert(0, [None] * self.W) for l in filled] self.get_score(len(filled)) |
這個類本身沒有操縱shape的能力,第一塊程式碼中move的部分,其實是簡單的呼叫了self.shape的方法。而shape則響應當前的按鍵,做各種動作。同時,shape還有繪製自身和下一個影象的能力。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
class Shape(object): # shape是畫在一個矩陣上面的 # 因為我們有不同的模式,所以矩陣的資訊也要詳細給出 SHAPEW = 4 # 這個是矩陣的寬度 SHAPEH = 4 # 這個是高度 SHAPES = ( ( ((0,0,0,0), # (0,1,1,0), # [][] (0,1,1,0), # [][] (0,0,0,0),), # ), # 還有很多圖形,省略,具體請檢視程式碼 ), ) COLORS = ((0xcc, 0x66, 0x66), # 各個shape的顏色 ) def __init__(self, board_start, (board_width, board_height), (w, h)): self.start = board_start self.W, self.H = w, h # board的橫、縱的tile數 self.length = board_width / w # 一個tille的長寬(正方形) self.x, self.y = 0, 0 # shape的起始位置 self.index = 0 # 當前shape在SHAPES內的索引 self.indexN = 0 # 下一個shape在SHAPES內的索引 self.subindex = 0 # shape是在怎樣的一個朝向 self.shapes = [] # 記錄當前shape可能的朝向 self.color = () self.shape = None # 這兩個Surface用來存放當前、下一個shape的影象 self.image = pygame.Surface( (self.length * self.SHAPEW, self.length * self.SHAPEH), SRCALPHA, 32) self.image_next = pygame.Surface( (self.length * self.SHAPEW, self.length * self.SHAPEH), SRCALPHA, 32) self.board = [] # 外界資訊 self.new() # let's dance! def set_board(self, board): # 接受外界狀況的陣列 pass def new(self): # 新產生一個方塊 # 注意這裡其實是新產生“下一個”方塊,而馬上要落下的方塊則 # 從上一個“下一個”方塊那裡獲得 pass def rotate(self): # 翻轉 pass def move(self, r, c): # 左右下方向的移動 def check_legal(self, r=0, c=0): # 用在上面的move判斷中,“這樣的移動”是否合法(如是否越界) # 合法才會實際的動作 pass def at_bottom(self): # 是否已經不能再下降了 pass def draw_current_shape(self): # 繪製當前shhape的影象 pass def draw_next_shape(self): # 繪製下一個shape的影象 pass def _draw_shape(self, surface, shape, color): # 上兩個方法的支援方法 # 注意這裡的繪製是繪製到一個surface中方便下面的draw方法blit # 並不是畫到螢幕上 pass def draw(self, screen): # 更新shape到螢幕上 pass |
框架如上所示,一個Shape類主要是有移動旋轉和標識自己的能力,當使用者按下按鍵時,Tetris會把這些按鍵資訊傳遞給Shape,然後它相應之後在返回到螢幕之上。
這樣的Tetris和Shape看起來有些複雜,不過想清楚了還是可以接受的,主要是因為我們得提供多種模式,所以分割的細一些容易繼承和發展。比如說,我們實現一種方塊一落下就消失的模式,之需要這樣做就可以了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Tetris1(Tetris): """ 任何方塊一落下即消失的模式,只需要覆蓋check_line方法, 不是返回一個填滿的行,而是返回所有有東西的行 """ def check_line(self): self.add_to_board() filled = [] for i in xrange(self.H-1, -1, -1): line = self.board[i] sum = 0 for t in line: sum += 1 if t else 0 if sum != 0: # 這裡與一般的不同 filled.append(i) else: break if i == 0 and sum !=0: self.game_over() self.create_board_image() # used for killing animation self.kill_line(filled) self.create_board_image() # used for update |
這次全是程式碼,不禁讓人感到索然。
遊戲玩起來很開心,開發嘛,說白了就是艱難而持久的戰鬥(個人開發還要加上“孤獨”這個因素),程式碼是絕對不可能缺少的。所以大家也就寬容一點,網上找個遊戲玩了幾下感覺不行就不要罵街了,多多鼓勵:)誰來做都不容易啊!
我們這次基本就把程式碼都實現了,下一次就有個完整的可以動作的東西了。