# Python 專題介紹 (太空生存戰) ## 作者: 邱廉翔 ## 專案簡介 本專題以 Python 的 Pygame 函式庫開發一款太空射擊生存遊戲。玩家操作飛船閃避隕石並發射子彈摧毀隕石。遊戲具備**碰撞判斷、分數計算、音效與動畫效果**,目標是提升玩家的反應能力與遊戲樂趣。此專案只有做出影片中遊戲的基礎功能,例如:爆炸動畫、復活次數和石頭掉寶等,因為時間的關係沒有實現,有興趣的人可以參考影片中的內容。 ## 專題資料夾結構 ### space war 安裝檔所附的資料 - img (圖片) - background - player - bullet - rock0 ~ rock6 - sound (音效) - background - expl0 - expl1 - shoot - font (字體) - main (執行檔) 專案目前只有用到這些檔案,有興趣的可以參考下方影片做出更多遊戲功能(我有下載完整的資料夾,所以安裝完安裝檔後可以直接用裡面的檔案) ## 程式碼模組架構 ### 主程式初始化 - 載入所有資源(圖片、音效、音樂、字體) ``` pygame.init() ``` - 設定遊戲視窗大小與標題 ``` screen = pygame.display.set_mode((500,600)) pygame.display.set_caption("第一個遊戲") ``` - 創建 Sprite 群組管理遊戲物件 ``` all_sprites = pygame.sprite.Group() rocks = pygame.sprite.Group() bullets = pygame.sprite.Group() player = Player() all_sprites.add(player) for i in range(8): r = Rock() all_sprites.add(r) rocks.add(r) ``` ### 主要類別 #### Player (玩家) - 控制飛船移動(左右) 限制邊界 ``` if key_pressed[pygame.K_d]: self.rect.x += self.speedx if key_pressed[pygame.K_a]: self.rect.x -= self.speedx if self.rect.right > WIDTH: self.rect.right = WIDTH if self.rect.left < 0: self.rect.left = 0 ``` - ```shoot()``` 方法負責產生子彈 ``` def shoot(self): bullet = Bullet(self.rect.centerx,self.rect.top) all_sprites.add(bullet) bullets.add(bullet) ``` #### Rock (隕石) - 隨機生成位置、移動速度、旋轉角度 ``` self.rect.x = random.randrange(0, WIDTH - self.rect.width) self.rect.y = random.randrange(-100, -40) self.speedy = random.randrange(2,10) self.speedx = random.randrange(-3,3) self.total_degree = 0 self.rot_degree = random.randrange(-3,3) ``` - 落出畫面自動重新出現在頂端 ``` if self.rect.top > HEIGHT or self.rect.left > WIDTH or self.rect.right < 0: self.rect.x = random.randrange(0, WIDTH - self.rect.width) self.rect.y = random.randrange(-180, -100) self.speedy = random.randrange(2,10) self.speedx = random.randrange(-3,3) ``` #### Bullet(子彈) - 自動往上移動 飛出畫面即消失 ``` def update(self): self.rect.y += self.speedy # self.speedy = -10 if self.rect.bottom < 0: self.kill() ``` ### 遊戲迴圈 1. 處理鍵盤輸入、事件偵測 2. 更新所有遊戲物件狀態 3. 判斷碰撞: - 子彈和隕石(加分、產生新隕石) - 玩家和隕石(扣血、產生新隕石,血量歸零結束遊戲) 4. 畫面更新(分數顯示、血量條、遊戲背景) ## 使用到的主要功能 - Sprite 分群管理 - 物件導向 - 動態動畫旋轉效果 - 音效與背景音樂 - 分數與血量條 UI 元素 ## 心得 這次是我第一次上HackMD撰寫文章,也是我寫的第一個專題,為了做這個專題還去特別學習class(類別)怎麼用,因為我在打競程時幾乎不會碰到物件導向,這門課似乎在大學是很重要的,同時,這次專題也讓我了解到英文的重要性,常常在看Pygame documentation的時候需要用到翻譯,另外,在寫程式的過程中也讓我感受到Python的方便性,Python可以引入需多方便的函式,來加速我寫專案的速度,不用每一個都用自訂函式自己寫,最後,經過這次專題實作,讓我對製作遊戲更感興趣,也讓我之後想開發難度更高的3D類型的遊戲,也讓我對進入資工系更加嚮往。 ## 完整程式碼 ```python import pygame import random import os # 變數全部大寫的意思是此變數不可做更改,是一種做專案的習慣 FPS = 60 WIDTH = 500 HEIGHT = 600 BLACK = (0,0,0) GREEN = (0,255,0) WHITE = (255,255,255) RED = (255,0,0) YELLOW = (255,255,0) # 遊戲的初始化 and 創建視窗 pygame.init() pygame.mixer.init() screen = pygame.display.set_mode((500,600)) pygame.display.set_caption("第一個遊戲") clock = pygame.time.Clock() # 載入圖片 background_img = pygame.image.load(os.path.join("img","background.png")).convert() player_img = pygame.image.load(os.path.join("img","player.png")).convert() bullet_img = pygame.image.load(os.path.join("img","bullet.png")).convert() rock_imgs = [] for i in range(7): rock_imgs.append(pygame.image.load(os.path.join("img",f"rock{i}.png")).convert()) # 載入音樂 shoot_sound = pygame.mixer.Sound(os.path.join("sound","shoot.wav")) expl_sounds = [ pygame.mixer.Sound(os.path.join("sound","expl0.wav")), pygame.mixer.Sound(os.path.join("sound","expl1.wav")) ] pygame.mixer.music.load(os.path.join("sound","background.ogg")) pygame.mixer.music.set_volume(0.1) font_name = os.path.join("font.ttf") def draw_text(surf,text,size,x,y): font = pygame.font.Font(font_name,size) text_surface = font.render(text,True,WHITE) text_rect = text_surface.get_rect() text_rect.centerx = x text_rect.top = y surf.blit(text_surface,text_rect) def new_rock(): r = Rock() all_sprites.add(r) rocks.add(r) def draw_health(surf , hp ,x , y): if hp < 0: hp = 0 BAR_LENGTH = 100 BAR_HEIGHT = 10 fill = (hp/100)*BAR_LENGTH outline_rect = pygame.Rect(x,y,BAR_LENGTH,BAR_HEIGHT) fill_rect = pygame.Rect(x,y,fill,BAR_HEIGHT) pygame.draw.rect(surf,GREEN,fill_rect) # 填滿的方形 pygame.draw.rect(surf,WHITE,outline_rect,2) # 外框 def draw_init(): screen.blit(background_img,(0,0)) draw_text(screen,"太空生存戰!",64,WIDTH/2,HEIGHT/4) draw_text(screen,"a、d鍵移動飛船 空白鍵發射子彈~",22,WIDTH/2,HEIGHT/2) draw_text(screen,"按任意鍵開始遊戲!",18,WIDTH/2,HEIGHT*3/4) pygame.display.update() waiting = True while waiting: # 一秒鐘執行FPS次 clock.tick(FPS) # 目的是讓所有遊玩者有相同的體驗,不同電腦的遊玩體驗可能有所不同,為了達到讓所有遊玩者有相同的體驗這邊的函式會讓程式 1秒鐘只能被執行FPS次 # 取得輸入 for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() return True elif event.type == pygame.KEYUP: waiting = False return False class Player(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) self.image = pygame.transform.scale(player_img , (50,38)) self.image.set_colorkey(BLACK) self.rect = self.image.get_rect() self.radius = 25 self.rect.centerx = WIDTH/2 self.rect.bottom = HEIGHT - 10 self.speedx = 8 self.health = 100 def update(self): key_pressed = pygame.key.get_pressed() # print(key_pressed[pygame.K_RIGHT],key_pressed[pygame.K_LEFT]) # print(key_pressed[pygame.K_d],key_pressed[pygame.K_a]) if key_pressed[pygame.K_d]: self.rect.x += self.speedx if key_pressed[pygame.K_a]: self.rect.x -= self.speedx if self.rect.right > WIDTH: self.rect.right = WIDTH if self.rect.left < 0: self.rect.left = 0 def shoot(self): bullet = Bullet(self.rect.centerx,self.rect.top) all_sprites.add(bullet) bullets.add(bullet) shoot_sound.play() class Rock(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) self.image_ori = random.choice(rock_imgs) self.image_ori.set_colorkey(BLACK) self.image = self.image_ori.copy() self.rect = self.image.get_rect() self.radius = self.rect.width * 0.85 / 2 self.rect.x = random.randrange(0, WIDTH - self.rect.width) self.rect.y = random.randrange(-100, -40) self.speedy = random.randrange(2,10) self.speedx = random.randrange(-3,3) self.total_degree = 0 self.rot_degree = random.randrange(-3,3) def rotate(self): self.total_degree += self.rot_degree self.total_degree = self.total_degree % 360 self.image = pygame.transform.rotate(self.image_ori,self.total_degree) center = self.rect.center self.rect = self.image.get_rect() self.rect.center = center def update(self): self.rotate() self.rect.y += self.speedy self.rect.x += self.speedx if self.rect.top > HEIGHT or self.rect.left > WIDTH or self.rect.right < 0: self.rect.x = random.randrange(0, WIDTH - self.rect.width) self.rect.y = random.randrange(-180, -100) self.speedy = random.randrange(2,10) self.speedx = random.randrange(-3,3) class Bullet(pygame.sprite.Sprite): def __init__(self,x,y): pygame.sprite.Sprite.__init__(self) self.image = bullet_img self.image.set_colorkey(BLACK) self.rect = self.image.get_rect() self.rect.centerx = x self.rect.bottom = y self.speedy = -10 def update(self): self.rect.y += self.speedy if self.rect.bottom < 0: self.kill() # sprite 群組 all_sprites = pygame.sprite.Group() rocks = pygame.sprite.Group() bullets = pygame.sprite.Group() player = Player() all_sprites.add(player) for i in range(8): new_rock() score = 0 pygame.mixer.music.play(-1) # -1 代表無限播放 # 遊戲迴圈 show_init = True running = True while running : if show_init: close = draw_init() show_init = False if close: break # 一秒鐘執行FPS次 clock.tick(FPS) # 目的是讓所有遊玩者有相同的體驗,不同電腦的遊玩體驗可能有所不同,為了達到讓所有遊玩者有相同的體驗這邊的函式會讓程式 1秒鐘只能被執行FPS次 # 取得輸入 for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE: player.shoot() # 更新遊戲 all_sprites.update() hits = pygame.sprite.groupcollide(rocks,bullets,True,True) # 碰撞 for hit in hits: random.choice(expl_sounds).play() score += hit.radius new_rock() # 矩形碰撞判斷改圓形 hits = pygame.sprite.spritecollide(player,rocks, True, pygame.sprite.collide_circle) for hit in hits: new_rock() player.health -= hit.radius if player.health <= 0: running = False # 畫面顯示 screen.fill( BLACK ) screen.blit(background_img,(0,0)) all_sprites.draw(screen) draw_text(screen,str(int(score)), 18, WIDTH/2, 10) draw_health(screen, player.health,5 ,15) pygame.display.update() pygame.quit() ``` ## 遊戲安裝檔 [space war](https://drive.google.com/file/d/1FHv2R5S2MQbRsumSphIkZ8RCIOWiB1b5/view?usp=sharing) ## 參考資料 pygame 3小時製作一個遊戲 <https://youtu.be/61eX0bFAsYs?si=4MFWTXZ3jYy2RK_T> Python 零基礎新手入門 \#10 Class (類別) <https://youtu.be/AJfZvl9Hsn4?si=lH0eLgaxXvViw13R> Pygame documentation <https://www.pygame.org/docs/>