遊戲技術相關研究

2018年10月20日 星期六

【python】那些年我們一起玩過的遊戲-太空侵略者

上午3:47 Posted by Channel Chung No comments

    可能是初老症發作的的原因,最近玩到這類懷舊遊戲就很容易陷入某種懷念舊時光的情緒中,一不小心就會玩到忘了時間,所以就興起了何不自己動手把童年那些有印象的街機遊戲全部實作一遍的念頭呢?不囉嗦以下就是這系列的第一款,我會先從提案企劃→執行企劃→製作日誌一一作紀錄,希望對想學作遊戲的同好有所幫助, 另外,我也不確定能堅持幾款,只希望不要一款就沒後續就好,以下開始正文。。

在開始之前我們先來看看完成後的遊戲影片:


 提案企劃書

【一句話形容這個遊戲】

  • 勾起老遊戲人復古情懷的小品遊戲

【遊戲類型】

  • 射擊類

【遊戲特色】

  • 懷舊復古風
  • 點陣圖 

【發想概念】  

  • 致敬1978年由Taito在日本製造和銷售的太空侵略者(Space Invaders)
  • 藉以重製太空侵略者(Space Invaders)遊戲學習Python語言

【遊戲玩法】 

  • 玩家控制飛機左右移動,發射子彈射擊上方的飛機,示意圖如下:

【目標族群】 

  • 想用Python學做遊戲的人
  • 勾起老玩家回憶,發現往事不堪回首的人

【發行平台】

  • Windows
  • MAC
  • Linux

【預計製作期】 

  • 1個月

【美術風格】

  •  2D點陣圖風格

【技術特色】

  • 實現將圖像編碼成字串並內崁在程式碼內,以取代讀外部圖檔的功能

【遊戲周邊】

  • 作手機吊
  • LINE貼圖

【製作人員需求】 

  • 企劃
    • 茄諾
  • 美術
    • 茄諾
  • 程式
    • 茄諾

【收費方式】

  •  佛系遊戲緣分到了自動有收入

 【製作預算】 

  • 不要問你會怕

【參考資料】



執行企劃書

【前言】

  • 本遊戲玩法大致上與Taito在1978年發行的太空侵略者(Space Invaders)相同,只是在玩法上作了一些簡化,其調整內容後續會在陸續說明。

【遊戲流程】


【遊戲玩法說明】

  • 遊戲畫面示意圖
  • 玩家相關說明
    • 玩家操作飛機左右移動,一邊閃躲敵人發射下來子彈一邊發射子彈射擊敵人
    • 玩家飛機一次只能發射一發子彈,要等子彈擊中敵機或超出畫面消失後才能再發射下一發子彈 
    • 玩家飛機死亡條件
      • 被敵人子彈打中
      • 被任一隻敵人碰到
    • 遊戲一開始玩家一共有三條命,全部死亡後即遊戲結束
  • 敵人相關說明
    • 敵人會有五列,每列11隻敵人,排列方式如下:
      • 敵人1 x 11
      • 敵人2 x 11
      • 敵人2 x 11
      • 敵人3 x 11
      • 敵人3 x 11
    •  敵人移動方式
      • 畫面上的敵人會從最後一排整列往右移動8像素,移動完後換最後第二排往右移動8像素,接下來陸續最後第三排、第四排、第五排,然後在回到最後一排如此重複移動,直到畫面上的所有敵人移動到離右邊界100像素後,全部敵人往下移動20像素,然後在依上面規則開始往左移動,直到移動到離左邊界100像素後,全部敵人往下移動20像素,然後就重複以上規則在開始右移,如此反覆移動
      • 每下降一次速度就變快一點
    • 飛碟每隔10秒會從左邊慢慢往右邊移動
    • 擊中敵人加分表如下:
      • 敵人1  加30分
      • 敵人2  加20分
      • 敵人3  加10分
      • 飛碟    加60分
    • 分數每增加2000分會增加一台玩家飛機生命,上限為5台
    • 敵人發射子彈機率
      • 每隔1秒從场景中挑出一位敵人發射子彈
      • 全部敵人每下降一次發射時間就縮短一點
  •  過關條件
    • 將畫面上所有敵機摧毀後就會進入下一關
    • 下一關只是復原原本生出的敵機繼續讓玩家射擊,如此循環直到玩家飛機全部死亡為止
  •  遊戲結束條件
    •  玩家飛機隻數全部死亡
    •  敵機下降到底線

【遊戲操作】

  • 滑鼠
    • 遊戲中
      • 左右移動操作 玩家飛機
      • 點擊滑鼠左鍵發射子彈
    • GameOver畫面
      • 點擊滑鼠左鍵重新開始遊戲
  • 鍵盤 
    •      F鍵:開關遊戲FPS顯示
    • Esc 鍵:離開遊戲

【遊戲系統】

  • 點陣動畫系統
    • 本專案使用的圖形都是一些較簡單的單色點陣圖,所以希望能研發一工具可將圖檔傳換成字串,然後將此圖檔以字串方式內崁入到程式碼內,也就是說不以讀外部圖檔的方式來讀圖檔
    • 圖檔編碼方式說明如下:
      • 以下為將格子填上數字的圖檔,我們先挑出填滿黑色格子內的數字:
        • 依照規則第一張圖挑出填滿黑色格子內的數字號碼為:
          • (3,4,10,11,12,13,17,18,19,20,21,22,24,25,27,28,30,31,32,33,34,35,36,37,38,39,42,45,49,51,52,54,56,58,61,63)
        • 依照規則第二張圖挑出填滿黑色格子內的數字號碼為:
          • (3,4,10,11,12,13,17,18,19,20,21,22,24,25,27,28,30,31,32,33,34,35,36,37,38,39,41,43,44,46,48,55,57,62)
        • 程式讀入以上數字編碼字串後,在依照數字編碼位置畫出黑色Box就可以重現圖案內容的圖像了 
      • 程式提供播放動畫功能
      • 程式提供移動功能
      • 程式提供改變顏色功能  
      • 程式提供判斷碰撞功能
    • 計分系統
      • 玩家操作飛機擊中敵人後會累積分數
        • 擊中敵人分數計算方式如下:
        • 敵人1 - 30分
        • 敵人2 - 20分
        • 敵人3 - 10分
        • 飛碟   - 60分 
        • 最高分數紀錄
        • GameOver後系統會將這局玩的分數與最高分數作比較,如果大於最高分數,就會更新為本局分數,並顯示在畫面上。

    【遊戲介面】

    【美術風格】


    【美術元件列表】

    • 敵人1
      • 像素:8 x 8
      • 格數:2 frame
    • 敵人2
      • 像素:11 x 8
      • 格數:2 frame
    • 敵人3
      • 像素:12 x 8
      • 格數:2 frame
    • 敵人子彈
      • 像素:3 x 7
      • 格數:2 frame  
    • 飛碟
      • 像素:16 x 7
      • 格數:1 frame
    • 主角飛機
      • 像素:15 x 8
      • 格數:1 frame
    • 主角飛彈
      • 像素:1 x 4
      • 格數:1 frame
    • 爆炸效果
      • 像素:11 x 9
      • 格數:1 frame


    製作日誌

    • 使用Python版本
      • Python 3.7.0
    • 第1~2天
      • "點陣動畫系統"是我在這個專案第一完成的系統,在看完執行企劃後,就決定用pygame的基礎繪圖功能來製作重繪點陣區塊,實作後的效果其實還不錯,以下是程式碼與說明: 
    [drewEngine.py ]
    # encoding: utf-8
    #-------------------------------------------------------------------------
    # 點陣動畫系統.
    #-------------------------------------------------------------------------
    class Sprite(object):
        #-------------------------------------------------------------------------
        # 建構式.
        #   pygame      : pygame.
        #   canvas      : 畫佈.
        #   column      : 列.
        #   row         : 行.
        #   rect        : 位置、磚塊大小.
        #   color       : 磚塊顏色.
        #   aniArray    : 動畫敘述陣列.
        #   data        : 資料.
        #-------------------------------------------------------------------------
        def __init__(self, pygame,  canvas, column, row, rect, color, aniArray, data=[]):
            # 初始變數.
            self.__pygame = pygame
            self.__canvas = canvas
            self.__column = column
            self.__row = row
            self.__rect = rect
            self.__aniArray = aniArray
    
            # 顯示動畫frame.
            self.__frame = 0        
            # 紀錄時間.
            self.__last_time = self.__pygame.time.get_ticks()
            # 碰撞區域.
            self.__collision_rect = self.__pygame.Rect(self.__rect[0], self.__rect[1], self.__rect[2] * column, self.__rect[3] * row)        
    
            # 資料.
            self.data = data
            # 播放速度(1/1000)
            self.speed = 1000
            # 顏色.
            self.color = color
            # 是否顯示.
            self.visible = True
            # 設定除錯模式.
            self.debug = False
            # 顯示座標.
            self.x = self.__rect[0]
            self.y = self.__rect[1]
    
        #-------------------------------------------------------------------------
        # 設定播放的frame.
        #-------------------------------------------------------------------------
        def setFrame(self, frame):
            self.__frame = frame
    
        #-------------------------------------------------------------------------
        # 取得碰撞區域.
        #-------------------------------------------------------------------------
        def getCollisionRect(self):
            return self.__collision_rect
    
        #-------------------------------------------------------------------------
        # 取得寬.
        #-------------------------------------------------------------------------
        def getWidth(self):
            return self.__collision_rect.width
    
        #-------------------------------------------------------------------------
        # 取得高.
        #-------------------------------------------------------------------------
        def getHeight(self):
            return self.__collision_rect.height
    
        #-------------------------------------------------------------------------
        # 測試碰撞(兩個矩形是否重疊).
        #-------------------------------------------------------------------------
        def colliderect(self, collideRect):
            return self.__collision_rect.colliderect(collideRect)
    
        #-------------------------------------------------------------------------
        # 更新.
        #-------------------------------------------------------------------------
        def update(self):
            # 是否顯示.
            if(self.visible):
                # 判斷時脈播放下一個frame.
                if ((self.__pygame.time.get_ticks() - self.__last_time) >= self.speed):
                    # 播放下一個framel.
                    self.__frame += 1
                    # 巡迴播放.
                    if(self.__frame >= len(self.__aniArray)):
                        self.__frame = 0
                    # 紀錄時脈.
                    self.__last_time = self.__pygame.time.get_ticks()
    
                # 設定座標.
                self.__rect[0] = self.__collision_rect.x = self.x
                self.__rect[1] = self.__collision_rect.y = self.y
    
                # 畫方塊.
                for i in range(0,len(self.__aniArray[self.__frame])):
                    # 轉換要畫得方塊位置.
                    y = int(self.__aniArray[self.__frame][i] / self.__column)
                    x = int(self.__aniArray[self.__frame][i] % self.__column)
                    rect = [(x*self.__rect[2]) + self.__rect[0], (y*self.__rect[3]) + self.__rect[1], self.__rect[2], self.__rect[3]]
                    self.__pygame.draw.rect( self.__canvas, self.color, rect)
    
                # 除錯模式.
                if(self.debug):
                    # 畫出碰撞區.
                    self.__pygame.draw.lines( self.__canvas, (255, 0, 0), True, 
                        [
                        [self.__collision_rect.x, self.__collision_rect.y],
                        [self.__collision_rect.x+self.__collision_rect.width, self.__collision_rect.y], 
                        [self.__collision_rect.x+self.__collision_rect.width, self.__collision_rect.y+self.__collision_rect.height], 
                        [self.__collision_rect.x, self.__collision_rect.y+self.__collision_rect.height]
                        ], 1)

    • 第3~9天
      • 接下來要開始實作遊戲Gameplay的部分,要實作的功能不少,以下先列出程式碼,後面再針對需要特別說明的程式區段拉出特別解說
    [play.py ]
    # encoding: utf-8
    import random, sys, os, pygame
    from pygame.locals import *
    from drewEngine import *
    
    #-------------------------------------------------------------------------
    # 常數.
    #-------------------------------------------------------------------------
    # 顏色.
    CONST_BLOCK     = (0,0,0)
    CONST_WHITE     = (255,255,255)
    CONST_RED       = (255, 0, 0)
    CONST_GREEN     = (47, 248, 3)
    
    # 玩家生命數.
    COMST_PLAYER_LIFE = 2
    # 玩家重生時間(1/1000).
    COMST_PLAYER_REBIRTH_TIME = 1000
    # 敵人移動速度.
    CONST_ENEMY_SPEED = 300
    # 敵人發射子彈時脈.
    CONST_ENEMY_FIRE_TICK = 900
    # 爆炸效果停留時間(1/1000).
    CONST_BOOM_TIME = 200
    # 飛碟出現時間.
    CONST_UFO_TIME = 10000
    # 增加隻數分數(每N分增加一隻).
    CONST_ADD_1Up_SCORE = 2000
    
    # FPS.
    CONST_FPS = 60
    # 敵人移動次數.
    CONST_ENEMY_MOVE_NUMBER = 60
    
    #-------------------------------------------------------------------------
    # 像素陣列.
    #-------------------------------------------------------------------------
    # 玩家飛機.
    CONST_AIRCRAFT_PIXEL = [
    [7,21,22,23,36,37,38,46,47,48,49,50,51,52,53,54,55,56,57,58,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119]]
    # 玩家飛機-子彈.
    CONST_AIRCRAFT_BULLET_PIXEL = [[0,1,2,3]]
    # 敵人像素-1.
    CONST_ENEMY_PIXEL_1 = [
    [3,4,10,11,12,13,17,18,19,20,21,22,24,25,27,28,30,31,32,33,34,35,36,37,38,39,42,45,49,51,52,54,56,58,61,63],
    [3,4,10,11,12,13,17,18,19,20,21,22,24,25,27,28,30,31,32,33,34,35,36,37,38,39,41,43,44,46,48,55,57,62]]
    # 敵人像素-2.
    CONST_ENEMY_PIXEL_2 = [
    [2,8,11,14,18,21,22,24,25,26,27,28,29,30,32,33,34,35,37,38,39,41,42,43,44,45,46,47,48,49,50,51,52,53,54,56,57,58,59,60,61,62,63,64,68,74,78,86],
    [2,8,14,18,24,25,26,27,28,29,30,34,35,37,38,39,41,42,44,45,46,47,48,49,50,51,52,53,54,55,57,58,59,60,61,62,63,65,66,68,74,76,80,81,83,84]]
    # 敵人像素-3.
    CONST_ENEMY_PIXEL_3 = [
    [4,5,6,7,13,14,15,16,17,18,19,20,21,22,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,41,42,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,62,63,64,67,68,69,73,74,77,78,81,82,86,87,92,93],
    [4,5,6,7,13,14,15,16,17,18,19,20,21,22,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,41,42,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,63,64,67,68,74,75,77,78,80,81,84,85,94,95]]
    # 飛碟.
    CONST_ENEMY_UFO = [[5,6,7,8,9,10,19,20,21,22,23,24,25,26,27,28,34,35,36,37,38,39,40,41,42,43,44,45,49,50,52,53,55,56,58,59,61,62,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,82,83,84,87,88,91,92,93,99,108]]
    # 敵人子彈像素.
    CONST_ENEMY_BULLET_PIXEL = [
    [0,4,8,10,12,16,20],
    [2,4,6,10,14,16,18]]
    # 敵人爆炸像素.
    CONST_ENEMY_BOOM_PIXEL = [
    [3,7,12,15,17,20,24,30,36,40,44,45,53,54,58,62,68,74,78,81,83,86,91,95]]
    
    #-------------------------------------------------------------------------
    # 變數.
    #-------------------------------------------------------------------------
    # 視窗大小.
    canvas_width = 800
    canvas_height = 600
    
    # FPS開關.
    enable_fps = True
    
    # 時脈.
    # 0:移動敵人時脈.
    # 1:敵人發射時間時脈.
    # 2~5:敵人爆炸時間時脈.
    # 6:玩家死亡重生時間時脈.
    # 7:飛碟出現時間時脈.
    last_time = []
    
    # 遊戲模式.
    # 10 : 遊戲中.
    # 20 : GameOver.
    # 30 : 下一關.
    game_mode = 10
    # 敵人-數量.
    enemy_quantity = 0
    # 敵人-移動列.
    enemy_move_begin = 0
    enemy_move_end = 0
    # 敵人-移動像素.
    enemy_move_pixel = 0
    # 敵人-移動次數.
    enemy_move_number = 0
    # 敵人-移動速度.
    enemy_speed = 0
    # 敵人-發射子彈時脈.
    enemy_fire_tick = 0
    
    # 玩家生命.
    player_life = COMST_PLAYER_LIFE
    # 玩家分數.
    player_score = 0
    # 玩家最高分數.
    player_hi_score = 0
    # 加隻分數.
    next_add_1up_score =  CONST_ADD_1Up_SCORE
    
    #-------------------------------------------------------------------------
    # 函數:初始遊戲.
    #-------------------------------------------------------------------------
    def initGame():
        global game_mode, enemy_quantity, enemy_move_begin, enemy_move_end, enemy_move_pixel, enemy_move_number, enemy_speed, enemy_fire_tick
        global enemy_sprite
    
        # 遊戲模式.
        game_mode = 10
        # 敵人-數量.
        enemy_quantity = 55
        # 敵人-移動列.
        enemy_move_begin = 44
        enemy_move_end = 55
        # 敵人-移動像素.
        enemy_move_pixel = 8
        # 敵人-移動次數.
        enemy_move_number = CONST_ENEMY_MOVE_NUMBER
        # 敵人-移動速度.
        enemy_speed = CONST_ENEMY_SPEED
        # 敵人-發射子彈時脈.
        enemy_fire_tick = CONST_ENEMY_FIRE_TICK
        # 初始敵人位置.
        x = 85
        y = 120
        for i in range(0,55):
            # 換行.
            if( i==11 or i==22 or i==33 or i==44):
                x  = 85
                y += 45
            # 設定敵人.
            enemy_sprite[i].x = x
            enemy_sprite[i].y = y
            enemy_sprite[i].setFrame(0)
            enemy_sprite[i].visible = True
            #enemy_sprite[i].debug = True
            x += 50
        # 關閉飛碟.
        enemy_ufo_sprite.visible = False
        # 初始時脈.
        last_time[7] = pygame.time.get_ticks()
        enemy_ufo_sprite.x = -16
        # 關閉敵人子彈
        for i in range(0,32):
            enemy_bullet_sprite[i].visible = False
    
    #-------------------------------------------------------------------------
    # 函數:秀字.
    #-------------------------------------------------------------------------
    def showFont( text, color, x, y):
        global canvas    
        text = font.render(text, 1, color) 
        canvas.blit( text, (x,y))
    
    #-------------------------------------------------------------------------
    # 函數:顯示爆炸.
    #-------------------------------------------------------------------------
    def setBoom( x, y, color):
        for j in range(0,len(enemy_boom_sprite)):
            if (not enemy_boom_sprite[j].visible):
                enemy_boom_sprite[j].x = x 
                enemy_boom_sprite[j].y = y 
                enemy_boom_sprite[j].color = color
                enemy_boom_sprite[j].visible = True
                last_time[j+2] = pygame.time.get_ticks()
                break
    
    #-------------------------------------------------------------------------
    # 函數:增加生命.
    #-------------------------------------------------------------------------
    def add1Up():
        global player_life, player_score , next_add_1up_score
        if( player_score >= next_add_1up_score and  (player_score % CONST_ADD_1Up_SCORE) < 60):
            next_add_1up_score += CONST_ADD_1Up_SCORE
            player_life += 1
            if(player_life > 5):
                player_life = 5
    
    #-------------------------------------------------------------------------
    # 初始.
    #-------------------------------------------------------------------------
    # 初始pygame.
    pygame.init()
    # 顯示Title.
    pygame.display.set_caption(u"太空侵略者")
    # 建立畫佈大小.
    canvas = pygame.display.set_mode((canvas_width, canvas_height))
    # 時脈.
    clock = pygame.time.Clock()
    
    # 設定字型.
    #font = pygame.font.SysFont(pygame.font.match_font('bitstreamverasans'), 24)
    font = pygame.font.SysFont('arial',18)
    
    # 加入時脈.
    for i in range( 0, 8):
        last_time.append(pygame.time.get_ticks())
    
    # 玩家飛機.
    aircraft_sprite = Sprite(pygame, canvas, 15, 8, [0, 550, 3, 3], CONST_GREEN, CONST_AIRCRAFT_PIXEL) 
    # 玩家飛機-飛彈.
    aircraft_bullet_sprite = Sprite(pygame, canvas, 1, 4, [0, 0, 2, 2], CONST_WHITE, CONST_AIRCRAFT_BULLET_PIXEL)
    aircraft_bullet_sprite.visible = False
    # 玩家生命.
    aircraft_life_sprite = []
    x = 8
    y = 8
    for i in range(0, 5):
        aircraft_life_sprite.append([])
        aircraft_life_sprite[i] = Sprite(pygame, canvas, 15, 8, [x, y, 2, 2], CONST_GREEN, CONST_AIRCRAFT_PIXEL) 
        x += 35
    # 飛碟.
    enemy_ufo_sprite = Sprite(pygame, canvas, 16, 7, [-16, 60, 3, 3], CONST_RED, CONST_ENEMY_UFO, [60])
    enemy_ufo_sprite.visible = False
    
    # 敵人.
    enemy_sprite = []
    for i in range(0,55):
        # 敵人1-1.
        if (i < 11):
            enemy_sprite.append([])
            enemy_sprite[i] = Sprite(pygame, canvas, 8, 8, [0, 0, 3, 3], CONST_WHITE, CONST_ENEMY_PIXEL_1, [30])
        # 敵人2-2,3.
        elif (i < 33):
            enemy_sprite.append([])
            enemy_sprite[i] = Sprite(pygame, canvas, 11, 8, [0, 0, 3, 3], CONST_WHITE, CONST_ENEMY_PIXEL_2, [20])
        # 敵人3-4,5.
        elif (i < 55):
            enemy_sprite.append([])
            enemy_sprite[i] = Sprite(pygame, canvas, 12, 8, [0, 0, 3, 3], CONST_WHITE, CONST_ENEMY_PIXEL_3, [10])
    # 敵人子彈
    enemy_bullet_sprite = []
    for i in range(0,32):
        enemy_bullet_sprite.append([])
        enemy_bullet_sprite[i] = Sprite(pygame, canvas, 3, 7, [0, 0, 3, 3], CONST_WHITE, CONST_ENEMY_BULLET_PIXEL)
        enemy_bullet_sprite[i].speed = 50
        enemy_bullet_sprite[i].visible = False
    
    # 敵人爆炸像素.
    enemy_boom_sprite = []
    for i in range(0,4):
        enemy_boom_sprite.append([])
        enemy_boom_sprite[i] = Sprite(pygame, canvas, 11, 9, [0, 0, 3, 3], CONST_WHITE, CONST_ENEMY_BOOM_PIXEL)
        enemy_boom_sprite[i].visible = False
     
    # 初始遊戲.
    initGame()
    
    #-------------------------------------------------------------------------    
    # 主迴圈.
    #-------------------------------------------------------------------------
    running = True
    while running:
        # 清除畫面.
        canvas.fill(CONST_BLOCK)    
        #---------------------------------------------------------------------
        # 判斷輸入.
        #---------------------------------------------------------------------
        for event in pygame.event.get():
            # 離開遊戲.
            if event.type == pygame.QUIT:
                running = False
            # 判斷按下按鈕.
            if event.type == pygame.KEYDOWN:
                # 判斷按下ESC按鈕.
                if event.key == pygame.K_ESCAPE:
                    running = False
                # 開關FPS.
                elif event.key == pygame.K_f:
                    enable_fps = not enable_fps
                    
            # 判斷Mouse.
            if event.type == pygame.MOUSEBUTTONDOWN:
                # 10 : 遊戲中.
                if(game_mode == 10):
                    # 發射飛彈.
                    if aircraft_sprite.visible and not aircraft_bullet_sprite.visible:
                        aircraft_bullet_sprite.visible = True
                        aircraft_bullet_sprite.x = aircraft_sprite.x + (aircraft_sprite.getWidth() >> 1)
                        aircraft_bullet_sprite.y = aircraft_sprite.y
                # 20 : GameOver.
                elif (game_mode == 20):
                    # 玩家生命.
                    player_life = COMST_PLAYER_LIFE
                    # 玩家分數.
                    player_score = 0
                    # 開啟玩家飛機.
                    aircraft_sprite.visible = True
                    # 初始加隻分數.
                    next_add_1up_score =  CONST_ADD_1Up_SCORE
                    # 初始遊戲.
                    initGame()
    
        # 10 : 遊戲中.
        if(game_mode == 10):
            #---------------------------------------------------------------------    
            # Gameplay.
            #---------------------------------------------------------------------    
            # 設定玩家飛機跟隨滑鼠.
            aircraftX = pygame.mouse.get_pos()[0] - (aircraft_sprite.getWidth() >> 1)
            # 左邊界.
            if(aircraftX < (aircraft_sprite.getWidth() >> 1)):
                aircraftX = pygame.mouse.get_pos()[0]
            # 右邊界.
            elif (aircraftX > (canvas_width - aircraft_sprite.getWidth()) ):
                aircraftX = canvas_width - aircraft_sprite.getWidth()        
            # 移動玩家飛機.
            aircraft_sprite.x = aircraftX
    
            # 發射玩家飛彈.
            if aircraft_bullet_sprite.visible:
                # 移動飛彈.
                aircraft_bullet_sprite.y -= 12
                # 超出邊界.
                if aircraft_bullet_sprite.y < 0:
                    aircraft_bullet_sprite.visible = False
            # 移動敵人.
            if((pygame.time.get_ticks() - last_time[0]) >= enemy_speed):
                # 移動列.
                for i in range(enemy_move_begin, enemy_move_end):
                    enemy_sprite[i].x += enemy_move_pixel
                    last_time[0] = pygame.time.get_ticks()
                # 移動單列.
                enemy_move_begin -= 11
                enemy_move_end -= 11
                if(enemy_move_begin < 0):
                    enemy_move_begin = 44
                    enemy_move_end = 55
                # 左右變換方向移動.
                enemy_move_number -= 1
                if(enemy_move_number <= 0):
                    # 左右反轉.
                    enemy_move_pixel = -enemy_move_pixel
                    enemy_move_number = CONST_ENEMY_MOVE_NUMBER
                    # 每下降一次速度就變快一點.
                    enemy_speed -= 40
                    if(enemy_speed < 0):
                        enemy_speed = 1
                    # 每下降一次敵人子彈發射時間縮短. 
                    enemy_fire_tick -= 200
                    if(enemy_fire_tick < 200):
                        enemy_fire_tick = 200
                    # 改變所有敵人.
                    for i in range(0,len(enemy_sprite)):
                        # 敵人下降.
                        enemy_sprite[i].y += 20
                        # 判斷敵人是否碰到主角飛機(GameOver).
                        if enemy_sprite[i].visible and aircraft_sprite.colliderect(enemy_sprite[i].getCollisionRect()):                        
                            game_mode = 20
                        # 敵人超出下邊界.             
                        elif enemy_sprite[i].visible and (enemy_sprite[i].y >= (canvas_height - enemy_sprite[i].getHeight())):
                            game_mode = 20
    
            # 出現飛碟.
            if((pygame.time.get_ticks() - last_time[7]) >= CONST_UFO_TIME):
                enemy_ufo_sprite.visible = True
    
            #---------------------------------------------------------------------    
            # 更新畫面.
            #---------------------------------------------------------------------    
            # 更新主角飛機.
            aircraft_sprite.update()
            # 更新主角飛機子彈.
            aircraft_bullet_sprite.update()
    
            # 飛碟.
            if(enemy_ufo_sprite.visible):
                enemy_ufo_sprite.x += 2
                if(enemy_ufo_sprite.x > canvas_width):
                    enemy_ufo_sprite.visible = False
                    last_time[7] = pygame.time.get_ticks()
                    enemy_ufo_sprite.x = -16
                # 更新.
                enemy_ufo_sprite.update()
    
            # 活著的敵人編號.
            enemy_alive_id = []
            # 更新敵人.
            for i in range(0,len(enemy_sprite)):
                # 判斷子彈是否打中敵人.
                if (enemy_sprite[i].visible and aircraft_bullet_sprite.visible and aircraft_bullet_sprite.colliderect(enemy_sprite[i].getCollisionRect()) ):
                    # 關閉敵人.
                    enemy_sprite[i].visible = False
                    # 關閉子彈.
                    aircraft_bullet_sprite.visible = False                
                    # 敵人爆炸像素.
                    setBoom( enemy_sprite[i].x, enemy_sprite[i].y, CONST_WHITE)
                    # 分數.
                    player_score += int(enemy_sprite[i].data[0])
                    # 敵人數量減1.
                    enemy_quantity -= 1
                    # 判斷增加生命.
                    add1Up()                
                    # 30 : 下一關.
                    if(enemy_quantity <= 0):
                        game_mode = 30
    
                # 存入活著的敵人編號.
                if (enemy_sprite[i].visible):
                    enemy_alive_id.append(i)
                # 更新.
                enemy_sprite[i].update()
            
            # 敵人發射子彈.        
            if( (len(enemy_alive_id) > 0) and (pygame.time.get_ticks() - last_time[1]) >= enemy_fire_tick):
                last_time[1] = pygame.time.get_ticks()
                id = random.randint(0,len(enemy_alive_id)) - 1
                if (enemy_sprite[enemy_alive_id[id]].visible):
                    # 找出未使用的敵人子彈.
                    for i in range(0,len(enemy_bullet_sprite)):
                        if(not enemy_bullet_sprite[i].visible):
                            # 依照敵人位置設定發射子彈位置.
                            enemy_bullet_sprite[i].x = enemy_sprite[enemy_alive_id[id]].x + (enemy_sprite[enemy_alive_id[id]].getWidth() >> 1)
                            enemy_bullet_sprite[i].y = enemy_sprite[enemy_alive_id[id]].y + enemy_sprite[enemy_alive_id[id]].getHeight()
                            enemy_bullet_sprite[i].visible = True
                            break
            # 清除活著的敵人編號陣列.
            enemy_alive_id.clear()
            # 敵人子彈
            for i in range(0,len(enemy_bullet_sprite)):
                # 判斷開啟的子彈才處理.
                if(enemy_bullet_sprite[i].visible):
                    # 更新子彈.
                    enemy_bullet_sprite[i].update()
                    # 移動子彈.
                    enemy_bullet_sprite[i].y += 4
                    if(enemy_bullet_sprite[i].y > canvas_height):
                        enemy_bullet_sprite[i].visible = False
                    # 判斷子彈是否碰到玩家飛機
                    if(aircraft_sprite.visible and enemy_bullet_sprite[i].colliderect(aircraft_sprite.getCollisionRect()) ):
                        # 紀錄時脈.
                        last_time[6] = pygame.time.get_ticks()                    
                        # 關閉玩家飛機.
                        aircraft_sprite.visible = False
                        # 顯示爆炸.
                        setBoom( aircraft_sprite.x, aircraft_sprite.y, CONST_GREEN)
    
            # 敵人爆炸像素.
            for i in range(0,len(enemy_boom_sprite)):
                # 判斷爆炸是否開啟
                if(enemy_boom_sprite[i].visible):
                    # 判斷停留時間是否以到
                    if((pygame.time.get_ticks() - last_time[i+2]) >= CONST_BOOM_TIME):
                        enemy_boom_sprite[i].visible = False
                    enemy_boom_sprite[i].update()
    
            # 處理玩家飛機重生.
            if(not aircraft_sprite.visible):
                if((pygame.time.get_ticks() - last_time[6]) >= COMST_PLAYER_REBIRTH_TIME):
                    # 扣生命.
                    player_life -= 1
                    if(player_life < 0):
                        player_life = 0
                        # GameOver
                        game_mode = 20
                    else:    
                        # 開啟玩家飛機.
                        aircraft_sprite.visible = True
    
            # 判斷子彈是否打中飛碟.
            if (enemy_ufo_sprite.visible and aircraft_bullet_sprite.colliderect(enemy_ufo_sprite.getCollisionRect()) ):
                # 關閉子彈.
                aircraft_bullet_sprite.visible = False                
                # 關閉飛碟.
                enemy_ufo_sprite.visible = False
                # 飛碟爆炸像素.
                setBoom( enemy_ufo_sprite.x, enemy_ufo_sprite.y, CONST_RED)
                # 分數.
                player_score += int(enemy_ufo_sprite.data[0])
                # 判斷增加生命.
                add1Up()                
                # 初始時脈.
                last_time[7] = pygame.time.get_ticks()
                enemy_ufo_sprite.x = -16
        # 20 : GameOver.
        elif (game_mode == 20):
            # 紀錄玩家最高分數.
            if(player_score > player_hi_score):
                player_hi_score = player_score
            # 顯示訊息
            showFont( u"Game Over", CONST_WHITE, 350 , 250)
        # 30 : 下一關.
        elif (game_mode == 30):
            # 初始遊戲.
            initGame()
    
        #---------------------------------------------------------------------    
        # 更新UI.
        #---------------------------------------------------------------------    
        # 玩家生命.
        for i in range(0,player_life):
            aircraft_life_sprite[i].update()
        # 分數.
        showFont( u"SCORE", CONST_WHITE, 200, 8)
        showFont( str(player_score), CONST_WHITE, 200, 24)
        # 剩餘數量.
        showFont( u"QUANTITY", CONST_WHITE, 370, 8)
        showFont( str(enemy_quantity), CONST_WHITE, 370, 24)
        # 最高分數.
        showFont( u"HI-SCORE", CONST_WHITE, 540, 8)
        showFont( str(player_hi_score), CONST_WHITE, 540, 24)
        # 顯示FPS.
        if(enable_fps):
            showFont( u"FPS:" + str(int(clock.get_fps())), CONST_RED, 8, 8)            
            
        # 更新畫面.
        pygame.display.update()
        clock.tick(CONST_FPS)
    
    # 離開遊戲.
    pygame.quit()
    

    • 15 ~ 33:遊戲參數設定,調整這邊的數值可以改變遊戲平衡
    • 35 ~ 63:這邊針對執行企劃文件內"點陣動畫系統"部分,所敘述的將圖形編碼成字串方式針對每張圖作編碼並存放在陣列內,一維以上的陣列會循環撥放,以達到動畫效果
    • 75:時脈陣列,紀錄遊戲裡面所有需要計算時間的時脈紀錄,舉例一下,請看以下程式碼:
      • # 移動敵人.
        if((pygame.time.get_ticks() - last_time[0]) >= enemy_speed):
      •  這邊會將現在時間減掉last_time[0]時脈後如果大於設定的enemy_speed就會進入判斷式內執行,執行完記的要將現在時間寫回去,如下:
      • last_time[0] = pygame.time.get_ticks()
    • 209 ~ 254:產生所有遊戲所有需要用到的圖形物件
    • 263 ~ 512:遊戲主迴圈
    • 270 ~ 280:判斷鍵盤輸入
    • 283 ~ 302:判斷滑鼠輸入,可由game_mode變數判斷遊戲狀態,在game_mode = 20時表示遊戲進入Game Over狀態,在這狀態下玩家按下Mouse左鍵就可以重新開始遊戲
    • 309 ~ 318:處理讓玩家飛機跟隨滑鼠游標
    • 321 ~ 326:處理玩家飛彈移動
    • 327 ~ 362:處理敵人移動與每下降一次速度變快、子彈速度變短,跟敵人碰到主角飛機還有敵人超出下邊界時將狀態設定成Game Over模式(game_mode = 20)
    • 365 ~ 366:處理每隔N秒出現飛碟
    • 388 ~ 412:處理更新所有敵人,並判斷玩家發射的子彈有無碰撞到敵人,跟打死敵人加分,分數是2000分倍數就加一隻,還有敵人全滅處理遊戲初始設定並繼續進入下一關
    • 409 ~ 410:存入活著的敵人編號
    • 415 ~ 446:處理敵人發射子彈,移動子彈,判斷發射的子彈是否碰到玩家
    • 448 ~ 455:顯示開啟的爆炸圖像,N秒後會自動關閉
    • 457 ~ 468:處理玩家飛機重新,扣生命,如果已經沒生命數了就進入Game Over模式(game_mode = 20)
    • 470 ~ 484:判斷玩家發射的子彈是否打中飛碟,打中後處理顯示爆炸圖像與加分數,跟加隻判斷
    •  485 ~ 491:顯示Game Over畫面,並判斷此局分數是否有大於最高分,如果大於最高分的話就將本局分數更新為最高分數
    • 497 ~ 514:顯示畫面UI,如分數、剩餘數量、最高分等。。。 

    GitHub下載原始碼


    點我到GitHub下載


    後記

    第一款完成,第二款也正在構思中,希望能有時間盡快付諸行動,以上,下次見。。。

    版本更新

    • 2018/10/20
      • 新增灰階版本(play_gray_scale.py)以更新至GitHub
      •  



    2017年11月17日 星期五

    【python】初體驗-俄羅斯方塊遊戲

    上午7:44 Posted by Channel Chung No comments

    這次還是繼續選用遊戲為主題來學習Python,俄羅斯方塊這個耳熟能詳的遊戲,相信大家多多少少都有玩過,每次學習新程式語言,都會忍不住拿出來致敬一次;以下就開始這次的教程,先來看看完成後的遊戲畫面。。。
    接下來先貼出所有程式碼,後面會在針對重點作講解,記的搭配程式碼內的註解一起服用:
    [play.py ]
    # encoding: utf-8
    import os, sys, random
    import time
    import pygame 
    from pygame.locals import *
    from drew import *
    
    # 常數-磚塊快速下降速度.
    BRICK_DROP_RAPIDLY   = 0.01
    # 常數-磚塊正常下降速度.
    BRICK_DOWN_SPEED_MAX = 0.5
    
    # 視窗大小.
    canvas_width = 800
    canvas_height = 600
    
    # 顏色.
    color_block         = (0,0,0)
    color_white         = (255, 255, 255)
    color_red           = (255, 0, 0)
    color_gray          = (107,130,114)
    color_gray_block    = (20,31,23)
    color_gray_green    = (0, 255, 0)
    
    # 定義磚塊.
    brick_dict = {
        "10": ( 4, 8, 9,13), "11": ( 9,10,12,13),
        "20": ( 5, 8, 9,12), "21": ( 8, 9,13,14),
        "30": ( 8,12,13,14), "31": ( 4, 5, 8,12), "32": (8,  9, 10, 14), "33": (5,  9, 12, 13),
        "40": (10,12,13,14), "41": ( 4, 8,12,13), "42": (8,  9, 10, 12), "43": (4,  5,  9, 13),
        "50": ( 9,12,13,14), "51": ( 4, 8, 9,12), "52": (8,  9, 10, 13), "53": (5,  8,  9, 13),
        "60": ( 8, 9,12,13),
        "70": (12,13,14,15), "71": ( 1, 5, 9,13)
    }
    
    # 方塊陣列(10x20).
    bricks_array = []
    for i in range(10):
        bricks_array.append([0]*20)
    # 方塊陣列(4x4).
    bricks = []
    for i in range(4):
        bricks.append([0]*4)
    # 下一個方塊陣列(4x4).
    bricks_next = []
    for i in range(4):
        bricks_next.append([0]*4)
    # 下一個方塊圖形陣列(4x4).
    bricks_next_object = []
    for i in range(4):
        bricks_next_object.append([0]*4)    
    # 磚塊數量串列.
    bricks_list = []
    for i in range(10):
        bricks_list.append([0]*20)
    
    # 方塊在容器的位置.
    # (-2~6)(  為6的時候不能旋轉方塊).
    container_x = 3
    # (-3~16)(-3表示在上邊界外慢慢往下掉).
    container_y =-4
    
    # 除錯訊息.
    debug_message = False
    # 判斷遊戲結束.
    game_over = False
    
    # 磚塊下降速度.
    brick_down_speed = BRICK_DOWN_SPEED_MAX
    
    # 方塊編號(1~7).
    brick_id = 1
    # 方塊狀態(0~3).
    brick_state = 0
    
    # 下一個磚塊編號(1~7).
    brick_next_id = 1
    
    # 最大連線數.
    lines_number_max = 0
    # 本場連線數.
    lines_number = 0
    
    # 遊戲狀態.
    # 0:遊戲進行中.
    # 1:清除磚塊.
    game_mode = 0
    
    #-------------------------------------------------------------------------
    # 函數:秀字.
    # 傳入:
    #   text    : 字串.
    #   x, y    : 坐標.
    #   color   : 顏色.
    #-------------------------------------------------------------------------
    def showFont( text, x, y, color):
        global canvas    
        text = font.render(text, 1, color) 
        canvas.blit( text, (x,y))
    
    #-------------------------------------------------------------------------
    # 函數:取得磚塊索引陣列.
    # 傳入:
    #   brickId : 方塊編號(1~7).
    #   state   : 方塊狀態(0~3).
    #-------------------------------------------------------------------------
    def getBrickIndex( brickId, state):
        global brick_dict
    
        # 組合字串.
        brickKey = str(brickId)+str(state)
        # 回傳方塊陣列.
        return brick_dict[brickKey]
    
    #-------------------------------------------------------------------------
    # 轉換定義方塊到方塊陣列.
    # 傳入:
    #   brickId : 方塊編號(1~7).
    #   state   : 方塊狀態(0~3).
    #-------------------------------------------------------------------------
    def transformToBricks( brickId, state):
        global bricks
    
        # 清除方塊陣列.
        for x in range(4):
            for y in range(4):
                bricks[x][y] = 0
         
        # 取得磚塊索引陣列.
        p_brick = getBrickIndex(brickId, state)
        
        # 轉換方塊到方塊陣列.
        for i in range(4):        
            bx = int(p_brick[i] % 4)
            by = int(p_brick[i] / 4)
            bricks[bx][by] = brickId
    
        """
        # 印出訊息.
        for y in range(4): 
            s = ""
            for x in range(4): 
                s = s + str(bricks[x][y]) + ","       
            print(s)
        """
    
    #-------------------------------------------------------------------------
    # 判斷是否可以複製到容器內.
    # 傳出:
    #   true    : 可以.
    #   false   : 不可以.
    #-------------------------------------------------------------------------
    def ifCopyToBricksArray():
        global bricks, bricks_array
        global container_x, container_y
    
        posX = 0
        posY = 0
        for x in range(4):
            for y in range(4):
               if (bricks[x][y] != 0):
                    posX = container_x + x
                    posY = container_y + y
                    if (posX >= 0 and posY >= 0):
                        try:
                            if (bricks_array[posX][posY] != 0):
                                return False
                        except:
                            return False
        return True
    
    #-------------------------------------------------------------------------
    # 複製方塊到容器內.
    #-------------------------------------------------------------------------
    def copyToBricksArray():
        global bricks, bricks_array
        global container_x, container_y
        
        posX = 0
        posY = 0
        for x in range(4):
            for y in range(4):
                if (bricks[x][y] != 0):
                    posX = container_x + x
                    posY = container_y + y
                    if (posX >= 0 and posY >= 0):
                        bricks_array[posX][posY] = bricks[x][y]
         
    #-------------------------------------------------------------------------
    # 初始遊戲.
    #-------------------------------------------------------------------------
    def resetGame():
        global BRICK_DOWN_SPEED_MAX
        global bricks_array, bricks, lines_number, lines_number_max
    
        # 清除磚塊陣列.
        for x in range(10):
            for y in range(20):
                bricks_array[x][y] = 0
                
        # 清除方塊陣列.
        for x in range(4):
            for y in range(4):
                bricks[x][y] = 0
    
        # 初始磚塊下降速度.
        brick_down_speed = BRICK_DOWN_SPEED_MAX
    
        # 最大連線數.
        if(lines_number > lines_number_max):
            lines_number_max = lines_number
        # 連線數.
        lines_number = 0
    
    #---------------------------------------------------------------------------
    # 判斷與設定要清除的方塊.
    # 傳出:
    #   連線數
    #---------------------------------------------------------------------------
    def ifClearBrick():
        pointNum = 0
        lineNum = 0
        for y in range(20):
            for x in range(10):
                if (bricks_array[x][y] > 0):
                    pointNum = pointNum + 1
                if (pointNum == 10):
                    for i in range(10):
                        lineNum = lineNum + 1
                        bricks_array[i][y] = 9
            pointNum = 0
        return lineNum
    
    #-------------------------------------------------------------------------
    # 更新下一個磚塊.
    #-------------------------------------------------------------------------
    def updateNextBricks(brickId):
        global bricks_next
        
        # 清除方塊陣列.
        for y in range(4):
            for x in range(4):
                bricks_next[x][y] = 0
    
        # 取得磚塊索引陣列.
        pBrick = getBrickIndex(brickId, 0)
    
        # 轉換方塊到方塊陣列.
        for i in range(4):
            bx = int(pBrick[i] % 4)
            by = int(pBrick[i] / 4)
            bricks_next[bx][by] = brickId
    
        # 更新背景區塊.
        background_bricks_next.update()
    
        # 更新磚塊圖.
        pos_y = 52
        for y in range(4):
            pos_x = 592
            for x in range(4):
                if(bricks_next[x][y] != 0):
                    bricks_next_object[x][y].rect[0] = pos_x
                    bricks_next_object[x][y].rect[1] = pos_y
                    bricks_next_object[x][y].update()
                pos_x = pos_x + 28        
            pos_y = pos_y + 28
                    
    #-------------------------------------------------------------------------
    # 產生新磚塊.
    #-------------------------------------------------------------------------
    def brickNew():
        global game_over, container_x, container_y, brick_id, brick_next_id, brick_state
        global lines_number, game_mode
    
        # 判斷遊戲結束.
        game_over = False
        if (container_y < 0):
            game_over = True
    
        # 複製方塊到容器內.
        container_y = container_y - 1
        copyToBricksArray()  
        
        #------------------------------------------------    
        # 判斷與設定要清除的方塊.
        lines = ifClearBrick() / 10;        
        if (lines > 0):
            # 消除連線數量累加.
            lines_number =  lines_number + lines
            # 修改連線數量.
            #modifyLabel(linesNumber, fontLinesNumber)
            # 1:清除磚塊.
            game_mode = 1
    
        # 初始方塊位置.
        container_x = 3
        container_y =-4
    
        # 現在出現方塊.
        brick_id = brick_next_id
    
        # 下個出現方塊.
        # 方塊編號(1~7).
        brick_next_id = random.randint( 1, 7)
        
        # 初始方塊狀態.
        brick_state = 0
    
        # GameOver.
        if (game_over):
            # 重新開始遊戲.
            resetGame()
        
    #-------------------------------------------------------------------------
    # 清除的方塊.
    #-------------------------------------------------------------------------
    def clearBrick():
        global bricks_array
        # 一列一列判斷清除方塊.
        temp = 0    
        for x in range(10):
            for i in range(19):
                for y in range(20):
                    if (bricks_array[x][y] == 9):
                        if (y > 0):
                            temp = bricks_array[x][y - 1]
                            bricks_array[x][y - 1] = bricks_array[x][y]
                            bricks_array[x][y] = temp
                            y = y - 1
                bricks_array[x][0] = 0
    #-------------------------------------------------------------------------
    # 初始.
    pygame.init()
    # 顯示Title.
    pygame.display.set_caption(u"俄羅斯方塊遊戲")
    # 建立畫佈大小.
    canvas = pygame.display.set_mode((canvas_width, canvas_height))
    # 時脈.
    clock = pygame.time.Clock()
    
    # 設定字型-黑體.
    font = pygame.font.SysFont('simhei', 26)
    
    # 將繪圖方塊放入陣列.
    for y in range(20):
        for x in range(10):
            bricks_list[x][y] = Box(pygame, canvas, "brick_x_" + str(x) + "_y_" + str(y), [ 0, 0, 26, 26], color_gray_block)
    
    # 將繪圖方塊放入陣列.
    for y in range(4):
        for x in range(4):
            bricks_next_object[x][y] = Box(pygame, canvas, "brick_next_x_" + str(x) + "_y_" + str(y), [ 0, 0, 26, 26], color_gray_block)
    
    # 背景區塊.
    background = Box(pygame, canvas, "background", [ 278, 18, 282, 562], color_gray)
    
    # 背景區塊.
    background_bricks_next = Box(pygame, canvas, "background_bricks_next", [ 590, 50, 114, 114], color_gray)
    
    # 方塊編號(1~7).
    brick_next_id = random.randint( 1, 7)
    # 產生新磚塊.
    brickNew()
    
    #-------------------------------------------------------------------------    
    # 主迴圈.
    #-------------------------------------------------------------------------
    running = True
    time_temp = time.time()
    time_now = 0
    while running:
        # 計算時脈.
        time_now = time_now + (time.time() - time_temp)
        time_temp = time.time()
        #---------------------------------------------------------------------
        # 判斷輸入.
        #---------------------------------------------------------------------
        for event in pygame.event.get():
            # 離開遊戲.
            if event.type == pygame.QUIT:
                running = False        
            # 判斷按下按鈕
            if event.type == pygame.KEYDOWN:
                #-----------------------------------------------------------------
                # 判斷按下ESC按鈕
                if event.key == pygame.K_ESCAPE:
                    running = False
                # 除錯訊息開關.
                elif event.key == pygame.K_d:
                    debug_message = not debug_message                
                #-----------------------------------------------------------------
                # 變換方塊-上.
                elif event.key == pygame.K_UP and game_mode == 0:
                    # 在右邊界不能旋轉.
                    if (container_x == 8):
                        break
                    # 判斷磚塊N1、N2、I.
                    if (brick_id == 1 or brick_id == 2 or brick_id == 7):
                        # 長條方塊旋轉例外處理.
                        if (brick_id == 7):
                            if (container_x < 0 or container_x == 7):
                                break
                        # 旋轉方塊.
                        brick_state = brick_state + 1
                        if (brick_state > 1):
                            brick_state = 0                    
                        # 轉換定義方塊到方塊陣列.
                        transformToBricks(brick_id, brick_state)
                        # 碰到磚塊.
                        if (not ifCopyToBricksArray()):
                            brick_state = brick_state - 1
                            if (brick_state < 0):
                                brick_state = 1
                    # 判斷磚跨L1、L2、T.                                
                    elif (brick_id == 3 or brick_id == 4 or brick_id == 5):
                        # 旋轉方塊.
                        brick_state = brick_state + 1
                        if (brick_state > 3):
                            brick_state = 0                    
                        # 轉換定義方塊到方塊陣列.
                        transformToBricks(brick_id, brick_state)
                        # 碰到磚塊.
                        if (not ifCopyToBricksArray()):
                            brick_state = brick_state - 1
                            if (brick_state < 0):
                                brick_state = 3
                #-----------------------------------------------------------------
                # 快速下降-下.
                elif event.key == pygame.K_DOWN and game_mode == 0:
                    # 磚塊快速下降.
                    brick_down_speed = BRICK_DROP_RAPIDLY
                #-----------------------------------------------------------------
                # 移動方塊-左.
                elif event.key == pygame.K_LEFT and game_mode == 0:
                    container_x = container_x - 1
                    if (container_x < 0):
                        if (container_x == -1):
                            if (bricks[0][0] != 0 or bricks[0][1] != 0 or bricks[0][2] != 0 or bricks[0][3] != 0):
                                container_x = container_x + 1
                        elif (container_x == -2): 
                            if (bricks[1][0] != 0 or bricks[1][1] != 0 or bricks[1][2] != 0 or bricks[1][3] != 0):
                                container_x = container_x + 1
                        else:
                            container_x = container_x + 1
                    # 碰到磚塊.
                    if (not ifCopyToBricksArray()):
                        container_x = container_x + 1
                #-----------------------------------------------------------------
                # 移動方塊-右.
                elif event.key == pygame.K_RIGHT and game_mode == 0:
                    container_x = container_x + 1
                    if (container_x > 6):
                        if (container_x == 7):
                            if (bricks[3][0] != 0 or bricks[3][1] != 0 or bricks[3][2] != 0 or bricks[3][3] != 0):
                                container_x = container_x - 1;                        
                        elif (container_x == 8):
                            if (bricks[2][0] != 0 or bricks[2][1] != 0 or bricks[2][2] != 0 or bricks[2][3] != 0):
                                container_x = container_x - 1                        
                        else:
                            container_x = container_x - 1
                    # 碰到磚塊.
                    if (not ifCopyToBricksArray()):
                        container_x = container_x - 1                    
            #-----------------------------------------------------------------
            # 判斷放開按鈕
            if event.type == pygame.KEYUP:
                # 快速下降-下.
                if event.key == pygame.K_DOWN:
                    # 恢復正常下降速度.
                    brick_down_speed = BRICK_DOWN_SPEED_MAX
            
        #---------------------------------------------------------------------    
        # 清除畫面.
        canvas.fill(color_block)
    
        # 遊戲中.
        if (game_mode == 0):
            # 處理磚塊下降.
            if(time_now >= brick_down_speed):
                # 往下降.
                container_y = container_y + 1; 
                # 碰到磚塊.
                if (not ifCopyToBricksArray()):
                    #產生新塊.
                    brickNew()            
                # 轉換定義方塊到方塊陣列(bricks).
                transformToBricks( brick_id, brick_state)
                # 清除時脈.
                time_now = 0
        # 清除磚塊.
        elif (game_mode == 1):
            # 清除的方塊.
            clearBrick()
            # 遊戲中.
            game_mode = 0
            # 轉換定義方塊到方塊陣列.
            transformToBricks(brick_id, brick_state)
    
        #---------------------------------------------------------------------    
        # 更新下一個磚塊圖形.
        updateNextBricks(brick_next_id)
        # 更新繪圖.
        pos_y = 20
        # 更新背景區塊.
        background.update()
        for y in range(20):
            pos_x = 280
            for x in range(10):
                if(bricks_array[x][y] != 0):
                    bricks_list[x][y].rect[0] = pos_x
                    bricks_list[x][y].rect[1] = pos_y
                    bricks_list[x][y].update()
                pos_x = pos_x + 28        
            pos_y = pos_y + 28    
        # 更新方塊
        for y in range(4):
            for x in range(4):            
                if (bricks[x][y] != 0):
                    posX = container_x + x
                    posY = container_y + y
                    if (posX >= 0 and posY >= 0):
                        bricks_list[posX][posY].rect[0] = (posX * 28) + 280
                        bricks_list[posX][posY].rect[1] = (posY * 28) + 20
                        bricks_list[posX][posY].update()
        #---------------------------------------------------------------------    
        # 除錯訊息.
        if(debug_message):
            # 更新容器.
            str_x = ""
            pos_x = 15
            pos_y = 20
            for y in range(20):
                str_x = ""
                for x in range(10):
                    str_x = str_x + str(bricks_array[x][y]) + " "
                showFont( str_x, pos_x, pos_y, color_red)
                pos_y = pos_y + 28
                
            # 更新方塊
            posX = 0
            posY = 0    
            for y in range(4):
                str_x = ""
                for x in range(4):            
                    if (bricks[x][y] != 0):
                        posX = container_x + x
                        posY = container_y + y
                        if (posX >= 0 and posY >= 0):
                            str_x = str_x + str(bricks[x][y]) + " "
                    else:
                        str_x = str_x + "  "
                pos_x = 15 + (container_x * 26)
                pos_y = 20 + (posY * 28)
                showFont( str_x, pos_x, pos_y, color_white)
    
        # 顯示訊息.
        showFont( u"下次出現方塊", 588, 16, color_gray)
    
        showFont( u"最大連線數", 588, 190, color_gray)
        showFont( str(int(lines_number_max)), 588, 220, color_gray)
    
        showFont( u"本局連線數", 588, 260, color_gray)
        showFont( str(int(lines_number)), 588, 290, color_gray)
    
        # 顯示FPS.
        # 除錯訊息.
        if(debug_message):    
            showFont( u"FPS:" + str(clock.get_fps()), 6, 0, color_gray_green)    
    
        # 更新畫面.
        pygame.display.update()
        clock.tick(60)
    
    # 離開遊戲.
    pygame.quit()
    quit() 
    25~34行:這邊定義了所有方塊的編碼:
    從上面一系列圖式不難觀察出方塊的編碼是以"方塊代號":(方塊格子編號,...)的方式作編碼。
    其他部分想信只要詳細看過程式碼內的註解就不難了解程式的運作原理。
    [drew.py ]
    # encoding: utf-8
    
    #-------------------------------------------------------------------------
    # 畫Box.
    #-------------------------------------------------------------------------
    class Box(object):
        #-------------------------------------------------------------------------
        # 建構式.
        #   pygame    : pygame.
        #   canvas    : 畫佈.
        #   name    : 物件名稱.
        #   rect      : 位置、大小.
        #   color     : 顏色.
        #-------------------------------------------------------------------------
        def __init__( self, pygame, canvas, name, rect, color):
            self.pygame = pygame
            self.canvas = canvas
            self.name = name
            self.rect = rect
            self.color = color
    
            self.visivle = True
            
        #-------------------------------------------------------------------------
        # 更新.
        #-------------------------------------------------------------------------
        def update(self):
            if(self.visivle):
                self.pygame.draw.rect( self.canvas, self.color, self.rect)
    
    drew.py主要是處理畫方塊

    最後請在在命令列輸入以下指令以執行俄羅斯方塊遊戲:

     python play.py

    操作方式:
    Esc鍵:離開遊戲
    D 鍵:除錯訊息開關
    左右:移動方塊
    上:旋轉方塊
    下:方塊快速往下移動

    以下是試玩影片

    有興趣的同學一樣可以到GitHub下載程式碼來研究

    點我到GitHub下載範例程式

    2017年10月15日 星期日

    【python】初體驗-打磚塊遊戲

    上午8:07 Posted by Channel Chung 7 comments
    Python。。是的,這次要來玩的就是Python了,礙於每天看一些程式技術網站這個語言出現率非很高,所以就新起了一探究竟的慾望,廢話不多說,直接至Python官網下載安裝包,一路Next後安裝完成,接下開始填寫第一支測試程式Hello World。。。

    >>> print ("Hello World!!")


    打完收工。。。喂!!給我認真一點啦!!

    好啦,那就依照慣例來個打磚塊遊戲好了,google了一下發現大家在Python上寫2D Game幾乎都是用pygame套件,那我們一樣也用這套件來寫這次的主角打磚塊遊戲好了,首先用pip安裝pygame套件,請在命令列輸入以下指令:

    pip install pygame 

    安裝完畢後就可以開啟您習慣的編輯器鍵入程式碼:
    將以下程式碼存成play.py檔名
    # encoding: utf-8
    
    import os, sys, random
    import pygame 
    from pygame.locals import *
    
    from drew import *
    
    # 視窗大小.
    canvas_width = 800
    canvas_height = 600
    
    # 顏色.
    block = (0,0,0)
    
    # 磚塊數量串列.
    bricks_list = []
    
    # 移動速度.
    dx =  8
    dy = -8
    
    # 遊戲狀態.
    # 0:等待開球
    # 1:遊戲進行中
    game_mode = 0
    
    #-------------------------------------------------------------------------
    # 函數:秀字.
    #-------------------------------------------------------------------------
    def showFont( text, x, y):
        global canvas    
        text = font.render(text, 1, (255, 0, 0)) 
        canvas.blit( text, (x,y))
    
    #-------------------------------------------------------------------------
    # 函數:碰撞判斷.
    #   x       : x 
    #   y       : y 
    #   boxRect : 矩形
    #-------------------------------------------------------------------------
    def isCollision( x, y, boxRect):
        if (x >= boxRect[0] and x <= boxRect[0] + boxRect[2] and y >= boxRect[1] and y <= boxRect[1] + boxRect[3]):
            return True;          
        return False;  
    
    #-------------------------------------------------------------------------
    # 函數:初始遊戲.
    #-------------------------------------------------------------------------
    def resetGame():
        # 宣告使用全域變數.
        global game_mode, brick_num, bricks_list, dx, dy
    
        # 磚塊
        for bricks in bricks_list:
            # 亂數磚塊顏色
            r = random.randint(100,200)
            g = random.randint(100,200)
            b = random.randint(100,200)
            bricks.color = [r,g,b]        
            # 開啟磚塊.
            bricks.visivle = True
        # 0:等待開球
        game_mode = 0
        # 磚塊數量.
        brick_num = 99    
        # 移動速度.
        dx =  8
        dy = -8
    
    # 初始.
    pygame.init()
    # 顯示Title.
    pygame.display.set_caption(u"打磚塊遊戲")
    # 建立畫佈大小.
    canvas = pygame.display.set_mode((canvas_width, canvas_height))
    # 時脈.
    clock = pygame.time.Clock()
    
    # 設定字型-黑體.
    font = pygame.font.SysFont('simhei', 18)
    
    # 底板.
    paddle_x = 0
    paddle_y = (canvas_height - 48)
    paddle = Box(pygame, canvas, "paddle", [paddle_x, paddle_y, 100, 24], (255,255,255))
    
    # 球.
    ball_x = paddle_x
    ball_y = paddle_y
    ball   = Circle(pygame, canvas, "ball", [ball_x, ball_x], 8, (255,255,255))
    
    # 建立磚塊
    brick_num = 0
    brick_x = 70
    brick_y = 60
    brick_w = 0
    brick_h = 0
    for i in range( 0, 99):
        if((i % 11)==0):
            brick_w = 0
            brick_h = brick_h + 18        
        bricks_list.append (Box(pygame, canvas, "brick_"+str(i), [  brick_w + brick_x, brick_h+ brick_y, 58, 16], [255,255,255]))
        brick_w = brick_w + 60
    # 初始遊戲.
    resetGame()
    
    #-------------------------------------------------------------------------    
    # 主迴圈.
    #-------------------------------------------------------------------------
    running = True
    while running:
        #---------------------------------------------------------------------
        # 判斷輸入.
        #---------------------------------------------------------------------
        for event in pygame.event.get():
            # 離開遊戲.
            if event.type == pygame.QUIT:
                running = False
            # 判斷按下按鈕
            if event.type == pygame.KEYDOWN:
                # 判斷按下ESC按鈕
                if event.key == pygame.K_ESCAPE:
                    running = False
                    
            # 判斷Mouse.
            if event.type == pygame.MOUSEMOTION:
                paddle_x = pygame.mouse.get_pos()[0] - 50
            if event.type == pygame.MOUSEBUTTONDOWN:
                if(game_mode == 0):
                    game_mode = 1
    
        #---------------------------------------------------------------------    
        # 清除畫面.
        canvas.fill(block)
        
        # 磚塊
        for bricks in bricks_list:
            # 球碰磚塊.
            if(isCollision( ball.pos[0], ball.pos[1], bricks.rect)):
                if(bricks.visivle):                
                    # 扣除磚塊.
                    brick_num = brick_num -1
                    # 初始遊戲.
                    if(brick_num <= 0):
                        resetGame()
                        break
                    # 球反彈.
                    dy = -dy; 
                # 關閉磚塊.
                bricks.visivle = False
    
            # 更新磚塊.        
            bricks.update()
                
        #顯示磚塊數量.
        showFont( u"磚塊數量:"+str(brick_num),   8, 20)            
    
        # 秀板子.
        paddle.rect[0] = paddle_x
        paddle.update()
    
        # 碰撞判斷-球碰板子.
        if(isCollision( ball.pos[0], ball.pos[1], paddle.rect)):        
            # 球反彈.
            dy = -dy;         
                
        # 球.
        # 0:等待開球
        if(game_mode == 0):
            ball.pos[0] = ball_x = paddle.rect[0] + ( (paddle.rect[2] - ball.radius) >> 1 )
            ball.pos[1] = ball_y = paddle.rect[1] - ball.radius        
        # 1:遊戲進行中
        elif(game_mode == 1):
            ball_x += dx
            ball_y += dy
            #判斷死亡.
            if(ball_y + dy > canvas_height - ball.radius):
                game_mode = 0        
            # 右牆或左牆碰撞.
            if(ball_x + dx > canvas_width - ball.radius or ball_x + dx < ball.radius):
                dx = -dx
            # 下牆或上牆碰撞
            if(ball_y + dy > canvas_height - ball.radius or ball_y + dy < ball.radius):        
                dy = -dy
            ball.pos[0] = ball_x
            ball.pos[1] = ball_y
    
        # 更新球.
        ball.update()
    
        # 顯示中文.
        showFont( u"FPS:" + str(clock.get_fps()), 8, 2)    
        # 更新畫面.
        pygame.display.update()
        clock.tick(60)
    
    # 離開遊戲.
    pygame.quit()
    quit()
    

    將以下程式碼存成drew.py檔名
    # encoding: utf-8
    
    #-------------------------------------------------------------------------
    # 畫Box.
    #-------------------------------------------------------------------------
    class Box(object):
        #-------------------------------------------------------------------------
        # 建構式.
        #   pygame    : pygame.
        #   canvas    : 畫佈.
        #   name    : 物件名稱.
        #   rect      : 位置、大小.
        #   color     : 顏色.
        #-------------------------------------------------------------------------
        def __init__( self, pygame, canvas, name, rect, color):
            self.pygame = pygame
            self.canvas = canvas
            self.name = name
            self.rect = rect
            self.color = color
    
            self.visivle = True
            
        #-------------------------------------------------------------------------
        # 更新.
        #-------------------------------------------------------------------------
        def update(self):
            if(self.visivle):
                self.pygame.draw.rect( self.canvas, self.color, self.rect)
    
    #-------------------------------------------------------------------------
    # 畫圓.
    #-------------------------------------------------------------------------
    class Circle(object):
        #-------------------------------------------------------------------------
        # 建構式.
        #   pygame  : pygame.
        #   canvas  : 畫佈.
        #   name    : 物件名稱.
        #   pos     : 位置.  
        #   radius  : 大小.
        #   color   : 顏色.    
        #-------------------------------------------------------------------------
        def __init__( self, pygame, canvas, name, pos, radius, color):
            self.pygame = pygame
            self.canvas = canvas
            self.name = name
            self.pos = pos
            self.radius = radius
            self.color = color
            
            self.visivle = True
    
        #-------------------------------------------------------------------------
        # 更新.
        #-------------------------------------------------------------------------
        def update(self):
            if(self.visivle):
                self.pygame.draw.circle( self.canvas, self.color, self.pos , self.radius)
    

    在命令列輸入以下指令以執行打磚塊遊戲:

    python play.py

    以下是試玩影片

    運作原理就不多說了,有興趣的同學可以到GitHub下載程式碼來研究,程式碼裡面寫了大量的註解與說明,相信不難理解,那就先這樣咱們下次見囉。

    點我到GitHub下載範例程式

    -2017/10/23 更新
    [新增經典灰階美術風格]
    在命令列輸入以下指令以執行遊戲:

    python play_old_school.py


















    2017年8月9日 星期三

    【node.js】自製小型網路引擎-打磚塊連線(2)

    上午8:01 Posted by Channel Chung No comments


    繼續上一篇的主題,只是這次我們將打磚塊遊戲改寫成可以同時看到其他玩家玩打磚塊的畫面,如下圖所示,中間為玩家可以操作的部分,左右兩邊可以看到其他玩家玩打磚塊的過程。

    (以上畫面是開了三個Chrome瀏覽器來模擬三個玩家)

    以下是實際運作的影片:

     

    因為使用socket.io套件關係所以我們的打磚塊遊戲也可以在手機上的瀏覽器運作,並且可以與PC瀏覽器內的打磚塊遊戲連線,影片如下:



    經過這兩個打磚塊遊戲的連線練習後對Node.js的socket.io套件也已經有了初步的了解,接下來會在抽空試試使用net套件來跟Unity作溝通。

    2017年7月30日 星期日

    【node.js】自製小型網路引擎-打磚塊連線(1)

    上午12:20 Posted by Channel Chung No comments

    是的,又開始手癢想要玩些新東西了,所以趁著我們家遊戲上線這段小小的空檔趕快來稿一下,這次要玩的是好久沒碰的網路引擎,剛好又一直想要找時間摸摸node.js,那就順水推舟這次Server端語言就用node.js,Client端就用之前使用three.js寫的打磚塊來作連線的練習,大至上想要達成的目標如下:


    上圖中間是玩家操作的打磚塊,左右兩邊的打磚塊是準備接受Server的廣播後同步玩家操作的狀態;在玩家操作部份我們這次只會同步球的X、Y座標跟板子的X座標,Server接收到訊息後會立刻廣播球跟板子的座標給左右兩邊的打磚塊遊戲,這樣看左右兩邊的打磚塊運作就會跟玩家操作的打磚塊畫面一樣了,至於左右兩邊碰撞到磚塊會不見的原理是我們同步玩家球的位置,所以左右兩邊在球碰撞到磚塊的時候程式就會自動執行讓磚塊消失的邏輯,以下是這次實作的影片:



    影片最後會發現我們我們將Server程式關閉後,左右兩邊的打磚塊就不動了,主要是因為玩家操作的打磚塊訊息已經無法透過Server將資料同步給左右兩邊的打磚塊,所以左右兩邊的打磚塊就停止運作了。

    其實只同步球跟板子的作法並不完美,一來需要頻繁的傳送同步訊息,二來只要網路延遲就有可能導致左右兩邊的磚塊數量不同步,因為球有可能會穿透或在未碰到磚塊時接收到反彈球訊息等等狀況導致,至於解決方法也不難,就是拿掉左右兩邊打磚塊遊戲的磚塊碰撞判斷然後由中間玩家發出碰到那些磚塊訊息給左右兩端的打

     (右邊的打磚塊已經出現不同步的狀況)

    磚塊遊戲就可以解決磚塊不同步問題了,這次的練習就先到這裡,下次會繼續實作讓三個不同玩家,同時連線在畫面上的三個不同區域玩打磚塊遊戲,咱們下次見囉。

    2017年5月13日 星期六

    【three.js】最強WebGL封裝套件-致敬經典俄羅斯方塊

    上午2:01 Posted by Channel Chung No comments

    點我玩遊戲

    操作說明:
       上 or W:旋轉方塊
       下 or S :控制方塊快速下降
       左 or A :左移動方塊
       右 or D :右移動方塊

    遊戲介紹:
        今天就繼續以個人最愛的經典遊戲來練習three.js,俄羅斯方塊大家因該多多少少都有看過或玩過吧,這也是今天的練習主題,廢話就不多說先上遊戲畫面圖:


    下面是是遊戲影片:


    另外這次程式碼有檔手持裝置,所以使用手持裝置開啟網址時會跳出警告視窗,其實three.js也是支援手持裝置的,只是我還沒處理觸碰螢幕的操作輸入,所以就暫時先過濾掉手持裝置了,等哪天想到再來加觸碰螢幕的方塊操作功能。

    有興趣的同學也一樣可以到GitHub下載原始碼,只限用於學術研究用,請勿使用在商業用途上。
    點我至GitHub下載原始碼


    -v0.02 版本更新
    [新增設定視窗]
    •   cameraZoom:縮放攝影機
      •  speed:磚塊下降速度
      •  AI:AI開啟狀態
        • 無:不使用AI
        • mode 1:使用第一種AI
          • 參考網址:
        • mode 2:使用第二種AI
          • 參考網址:
      • Close Controls:關閉設定視窗
      •  Open Controls:開啟設定視窗
     -影片如下:

     












     

    2017年4月30日 星期日

    【three.js】最強WebGL封裝套件-致敬經典打磚塊

    上午4:41 Posted by Channel Chung No comments
     
    點我玩遊戲

    操作說明:
        滑鼠左鍵:開球
        滑鼠游標:版子跟隨滑鼠游標左右移動

    遊戲介紹:
        每位遊戲設計人員心中都有幾款影響自己深遠的經典遊戲,對於我來說紅白機快打磚塊這款遊戲在我心中就是名列前茅的少數難忘經典遊戲,以至於在往後的人生中只要接觸新的程式語言我都會毫不猶豫的在把這款心中的經典在"致敬"一次,此次初嘗three.js在一邊摸索下也簡單的把打磚塊雛形實作了出來,遊戲一共有8個關卡  如下:




     同時支援PC與Mobile的瀏覽器:


    有興趣的同學也可以到GitHub下載原始碼,不過只限用於學術研究用,請勿使用在商業用途上。
    點我至GitHub下載原始碼

    另外如果您也喜歡打磚塊遊戲的話TAITO公司在IOS上也已經推出了重製版的ARKANOID遊戲,下載網址如下:
    https://itunes.apple.com/us/app/arkanoid/id328321410?mt=8

    -v0.02 版本更新
    [新增設定視窗]
    • 設定視窗內容說明:
      • cameraZoom:縮放攝影機
      • AI:開關AI
      • Close Controls:關閉設定視窗
      •  Open Controls:開啟設定視窗
      -影片如下: