我是一個典型的80后,年輕時玩過了特別多的游戲,所以這幾天用Python3+pygame實現了一個另外小游戲”坦克大戰“(其他的游戲,請翻閱我的博客)
本實例代碼量有些多,完整的版本在1000行左右(當然了如果再次優化的話 會減少一部分)
分享出來,希望能幫助到大家,畢竟自己做教育行業做了這么多年,還是教育情懷的,哈哈哈哈哈
一、顯示效果
二、代碼
下面代碼用到了一些素材(游戲背景音樂、圖片等等),可以到我的網站https://www.itprojects.cn/web/material/details.html?id=7下載,謝謝大家的支持
完整代碼如下(注意:為了方便下載以及編寫更簡單,沒有采用多模塊的方式,全部代碼全部放到main.py文件中)
""" 作者:it項目實例網 網站:wwww.itprojects.cn """ import random import sys import pygame # 屏幕的寬、高 WIDTH = 630 HEIGHT = 630 # 邊界值 BORDER_LEN = 3 # 字體 FONTPATH = 'resources/font/font.ttf' class Iron(pygame.sprite.Sprite): """ 鐵牆類 """ # 定義精靈組,將所有的磚牆實例對象添加到里面 group = pygame.sprite.Group() def __init__(self, position): # 調用父類的初始化方法,這樣才能夠實現必要的初始化操作 super().__init__() self.image = pygame.image.load("resources/images/scene/iron.png") # 當使用碰撞判斷方法時,pygame就需要知道當前要檢測的物體的位置,所以這個rect屬性一定要設置 self.rect = self.image.get_rect() self.rect.topleft = position # 添加到精靈組 self.group.add(self) @classmethod def show(cls, screen): for temp in cls.group: screen.blit(temp.image, temp.rect) class Ice(pygame.sprite.Sprite): """ 冰類 """ # 定義精靈組,將所有的實例對象添加到里面 group = pygame.sprite.Group() def __init__(self, position): # 調用父類的初始化方法,這樣才能夠實現必要的初始化操作 super().__init__() # 因為是12x12的小圖片,所以需要制作一個24x24的image image = pygame.Surface((24, 24)) for i in range(2): for j in range(2): image.blit(pygame.image.load("resources/images/scene/ice.png"), (12 * i, 12 * j)) self.image = image # 當使用碰撞判斷方法時,pygame就需要知道當前要檢測的物體的位置,所以這個rect屬性一定要設置 self.rect = self.image.get_rect() self.rect.topleft = position # 添加到精靈組 self.group.add(self) @classmethod def show(cls, screen): for temp in cls.group: screen.blit(temp.image, temp.rect) class River(pygame.sprite.Sprite): """ 河流類 """ # 定義精靈組,將所有的實例對象添加到里面 group = pygame.sprite.Group() def __init__(self, position): # 調用父類的初始化方法,這樣才能夠實現必要的初始化操作 super().__init__() # 因為是12x12的小圖片,所以需要制作一個24x24的image image = pygame.Surface((24, 24)) for i in range(2): for j in range(2): image.blit(pygame.image.load("resources/images/scene/river1.png"), (12 * i, 12 * j)) self.image = image # 當使用碰撞判斷方法時,pygame就需要知道當前要檢測的物體的位置,所以這個rect屬性一定要設置 self.rect = self.image.get_rect() self.rect.topleft = position # 添加到精靈組 self.group.add(self) @classmethod def show(cls, screen): for temp in cls.group: screen.blit(temp.image, temp.rect) class Tree(pygame.sprite.Sprite): """ 樹類 """ # 定義精靈組,將所有的實例對象添加到里面 group = pygame.sprite.Group() def __init__(self, position): # 調用父類的初始化方法,這樣才能夠實現必要的初始化操作 super().__init__() # 因為是12x12的小圖片,所以需要制作一個24x24的image image = pygame.Surface((24, 24)) for i in range(2): for j in range(2): image.blit(pygame.image.load("resources/images/scene/tree.png"), (12 * i, 12 * j)) self.image = image # 當使用碰撞判斷方法時,pygame就需要知道當前要檢測的物體的位置,所以這個rect屬性一定要設置 self.rect = self.image.get_rect() self.rect.topleft = position # 添加到精靈組 self.group.add(self) @classmethod def show(cls, screen): for temp in cls.group: screen.blit(temp.image, temp.rect) class Brick(pygame.sprite.Sprite): """ 磚牆類 """ # 定義精靈組,將所有的磚牆實例對象添加到里面 group = pygame.sprite.Group() def __init__(self, position): # 調用父類的初始化方法,這樣才能夠實現必要的初始化操作 super().__init__() self.image = pygame.image.load("resources/images/scene/brick.png") # 當使用碰撞判斷方法時,pygame就需要知道當前要檢測的物體的位置,所以這個rect屬性一定要設置 self.rect = self.image.get_rect() self.rect.topleft = position # 添加到精靈組 self.group.add(self) @classmethod def show(cls, screen): for temp in cls.group: screen.blit(temp.image, temp.rect) class Bullet(pygame.sprite.Sprite): """ 子彈類 """ # 定義精靈組,將所有的磚牆實例對象添加到里面 group = pygame.sprite.Group() group_enemy = pygame.sprite.Group() def __init__(self, _type, direction, position): super().__init__() # 子彈圖片 if direction == "up": image_path = "resources/images/bullet/bullet_up.png" elif direction == "down": image_path = "resources/images/bullet/bullet_down.png" elif direction == "left": image_path = "resources/images/bullet/bullet_left.png" elif direction == "right": image_path = "resources/images/bullet/bullet_right.png" self.image = pygame.image.load(image_path) # 子彈位置 self.rect = self.image.get_rect() self.rect.center = position # 設置子彈的初始位置的中心點 # 子彈方向 self.direction = direction # 子彈移動速度 self.speed = 8 # 將子彈添加到精靈組 if _type == "player": self.group.add(self) else: self.group_enemy.add(self) @classmethod def auto_move(cls): for temp in cls.group: if temp.rect.x < BORDER_LEN or temp.rect.x > WIDTH - BORDER_LEN or temp.rect.y < BORDER_LEN or temp.rect.y > HEIGHT - BORDER_LEN: cls.group.remove(temp) print("子彈超出邊界,移除子彈") continue if temp.direction == "up": temp.rect = temp.rect.move((0, -temp.speed)) elif temp.direction == "down": temp.rect = temp.rect.move((0, temp.speed)) elif temp.direction == "left": temp.rect = temp.rect.move((-temp.speed, 0)) elif temp.direction == "right": temp.rect = temp.rect.move((temp.speed, 0)) for temp in cls.group_enemy: if temp.rect.x < BORDER_LEN or temp.rect.x > WIDTH - BORDER_LEN or temp.rect.y < BORDER_LEN or temp.rect.y > HEIGHT - BORDER_LEN: cls.group_enemy.remove(temp) print("子彈超出邊界,移除子彈") continue if temp.direction == "up": temp.rect = temp.rect.move((0, -temp.speed)) elif temp.direction == "down": temp.rect = temp.rect.move((0, temp.speed)) elif temp.direction == "left": temp.rect = temp.rect.move((-temp.speed, 0)) elif temp.direction == "right": temp.rect = temp.rect.move((temp.speed, 0)) # 子彈碰磚牆(如果相碰,那么就移除當前子彈以及磚牆) pygame.sprite.groupcollide(cls.group, Brick.group, True, True) pygame.sprite.groupcollide(cls.group_enemy, Brick.group, True, True) # 子彈碰鐵牆(如果相碰,那么只移除子彈) for bullet in cls.group: if pygame.sprite.spritecollide(bullet, Iron.group, False, None): cls.group.remove(bullet) for bullet in cls.group_enemy: if pygame.sprite.spritecollide(bullet, Iron.group, False, None): cls.group_enemy.remove(bullet) @classmethod def show(cls, screen): """ 顯示子彈 """ for temp in cls.group: screen.blit(temp.image, temp.rect) for temp in cls.group_enemy: screen.blit(temp.image, temp.rect) @classmethod def move_and_show(cls, screen): """ 移動、顯示子彈 """ cls.auto_move() cls.show(screen) class PlayerTank(pygame.sprite.Sprite): """ 我方坦克類 """ # 定義類屬性,存儲我方坦克(如果是單人模式就只有1個,如果是雙人模式就有2個) player_group = list() # 定義精靈組,用來碰撞等判斷 group = pygame.sprite.Group() def __init__(self, player, top_left): """ 實現初始化功能 """ # 調用父類的初始化方法,這樣才能夠實現必要的初始化操作 super().__init__() # 坦克的圖片 image_path = "resources/images/playerTank/tank_T1_0.png" if player == "player1" else "resources/images/playerTank/tank_T2_0.png" self.tank_all_image = pygame.image.load(image_path).convert_alpha() self.image = self.tank_all_image.subsurface((0, 0), (48, 48)) # 當使用碰撞判斷方法時,pygame就需要知道當前要檢測的物體的位置,所以這個rect屬性一定要設置 self.rect = self.image.get_rect() self.rect.topleft = top_left # 記錄初始位置,以便在被擊中后能夠重新在默認位置出現 self.origin_position = top_left # 定義移動的步長 self.step_length = 8 # 坦克的默認方向 self.direction = "up" # 默認朝上 # 移動緩沖, 用於避免坦克連續移動過快導致不方便調整位置 self.move_cache_time = 4 self.move_cache_count = 0 # 坦克輪子轉動效果 self.switch_count = 0 self.switch_time = 2 self.switch_image_index = False self.image_postion_index = 0 # 發射子彈的間隔 self.is_bullet_cooling = False # 如果是第一次發射子彈,則不在冷卻時間內,可以正常發射 self.bullet_cooling_count = 0 self.bullet_cooling_time = 30 # 我方坦克生命次數 self.life_num = 3 # 標記此坦克是否顯示 self.is_show_flag = True # 將當前對象添加到類屬性中,這樣就可以通過類對象訪問到我方坦克 self.__class__.player_group.append(self) # 或者self.player_group.append(self)也是可以的 # 添加到精靈組 self.group.add(self) def update_direction(self): """ 更新坦克的朝向 """ if self.direction == 'up': self.image = self.tank_all_image.subsurface((0, 0), (48, 48)) self.image_postion_index = 0 elif self.direction == 'down': self.image = self.tank_all_image.subsurface((0, 48), (48, 48)) self.image_postion_index = 48 elif self.direction == 'left': self.image = self.tank_all_image.subsurface((0, 96), (48, 48)) self.image_postion_index = 96 elif self.direction == 'right': self.image = self.tank_all_image.subsurface((0, 144), (48, 48)) self.image_postion_index = 144 def move(self, direction, group_list): """ 根據鍵盤調整坦克方向,然后移動 """ # 如果要移動的方向與當前坦克的朝向不同,則先調整朝向 if self.direction != direction: self.direction = direction self.update_direction() return # 移動緩沖 self.move_cache_count += 1 if self.move_cache_count < self.move_cache_time: return else: self.move_cache_count = 0 # 移動坦克 # 復制一份當前玩家坦克的坐標,如果碰到障礙物之后,可以進行恢復 rect_ori = self.rect if direction == "up": self.rect = self.rect.move((0, -self.step_length)) elif direction == "down": self.rect = self.rect.move((0, self.step_length)) elif direction == "left": self.rect = self.rect.move((-self.step_length, 0)) elif direction == "right": self.rect = self.rect.move((self.step_length, 0)) # 檢測碰撞"磚牆"、"鐵牆"、"冰"、"河流"。"樹"無需檢查 for group in group_list: if pygame.sprite.spritecollide(self, group, False, None): self.rect = rect_ori # 判斷碰撞到邊界 if self.rect.top < BORDER_LEN: self.rect.top = BORDER_LEN elif self.rect.bottom > HEIGHT - BORDER_LEN: self.rect.bottom = HEIGHT - BORDER_LEN elif self.rect.left < BORDER_LEN: self.rect.left = BORDER_LEN elif self.rect.right > WIDTH - BORDER_LEN: self.rect.right = WIDTH - BORDER_LEN # 為坦克輪動特效切換圖片 self.switch_count += 1 if self.switch_count > self.switch_time: self.switch_count = 0 self.switch_image_index = not self.switch_image_index self.image = self.tank_all_image.subsurface((48 * int(self.switch_image_index), self.image_postion_index), (48, 48)) def fire(self): """ 發射子彈 """ if not self.is_bullet_cooling: if self.direction == "up": position = (self.rect.centerx, self.rect.y) elif self.direction == "down": position = (self.rect.centerx, self.rect.y + 48) elif self.direction == "left": position = (self.rect.x, self.rect.centery) elif self.direction == "right": position = (self.rect.x + 48, self.rect.centery) Bullet("player", self.direction, position) print("我方坦克發射子彈") @classmethod def move_player_tank(cls, is_dual_mode, group_list): """ 控制我方坦克移動 """ # 檢查用戶按鍵,從而控制坦克移動 key_pressed = pygame.key.get_pressed() # 定義移動的步長 step_length = 8 # 復制一份當前玩家1的坐標,如果碰到障礙物之后,可以進行恢復 # rect_ori = cls.player_group[0].rect # 玩家一, ASWD移動 if key_pressed[pygame.K_w]: cls.player_group[0].move("up", group_list) elif key_pressed[pygame.K_s]: cls.player_group[0].move("down", group_list) elif key_pressed[pygame.K_a]: cls.player_group[0].move("left", group_list) elif key_pressed[pygame.K_d]: cls.player_group[0].move("right", group_list) elif key_pressed[pygame.K_SPACE]: # 如果按下了空格鍵,那么就發射子彈 cls.player_group[0].fire() # 檢查玩家1是否碰撞到障礙物 # # 檢測碰撞"磚牆" # if pygame.sprite.spritecollide(cls.player_group[0], brick_group, False, None): # print("玩家1碰到了磚牆", cls.player_group[0].rect) # cls.player_group[0].rect = rect_ori # 玩家二, ↑↓←→移動 if is_dual_mode: # 復制一份當前玩家2的坐標,如果碰到障礙物之后,可以進行恢復 # rect_ori = cls.player_group[1].rect if key_pressed[pygame.K_UP]: cls.player_group[1].move("up", group_list) elif key_pressed[pygame.K_DOWN]: cls.player_group[1].move("down", group_list) elif key_pressed[pygame.K_LEFT]: cls.player_group[1].move("left", group_list) elif key_pressed[pygame.K_RIGHT]: cls.player_group[1].move("right", group_list) elif key_pressed[pygame.K_KP0]: # 如果按下了數字0,那么就發射子彈 cls.player_group[1].fire() # 檢查玩家2是否碰撞到障礙物 # 檢測碰撞"磚牆" # if pygame.sprite.spritecollide(cls.player_group[1], brick_group, False, None): # cls.player_group[1].rect = rect_ori def bullet_cooling(self): """ 判斷發射子彈的冷卻時間是否達到 """ # 對發射子彈的冷卻時間計數 self.bullet_cooling_count += 1 if self.bullet_cooling_count > self.bullet_cooling_time: self.is_bullet_cooling = False # 不在冷卻狀態,即意味着可以發射子彈 self.bullet_cooling_count = 0 print("冷卻完畢...") else: self.is_bullet_cooling = True # 不能發射,正在冷卻 def judge_bomb(self): """ 判斷是否被擊中 """ # 判斷碰撞到敵方坦克子彈 if pygame.sprite.spritecollide(self, Bullet.group_enemy, True, None): self.life_num -= 1 # 如果被擊中,那么就生命值-1 if self.life_num == 0: self.is_show_flag = False # 如果已經沒有了生命值,那么就標記為不顯示 # 重新設置位置為初始位置 self.rect.topleft = self.origin_position @classmethod def show(cls, screen, is_dual_mode): """ 顯示我方坦克 """ if cls.player_group: if cls.player_group[0].is_show_flag: screen.blit(cls.player_group[0].image, cls.player_group[0].rect) # 對發射子彈的冷卻時間計數 cls.player_group[0].bullet_cooling() # 判斷是否被擊中 cls.player_group[0].judge_bomb() if is_dual_mode and cls.player_group[1].is_show_flag: # 如果是雙人模式 screen.blit(cls.player_group[1].image, cls.player_group[1].rect) # 對發射子彈的冷卻時間計數 cls.player_group[1].bullet_cooling() # 判斷是否被擊中 cls.player_group[1].judge_bomb() class PlayerHome(pygame.sprite.Sprite): """ 我方大本營 """ home = None def __init__(self, position): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load("resources/images/home/home1.png") self.rect = self.image.get_rect() self.rect.left, self.rect.top = position self.__class__.home = self @classmethod def show(cls, screen): """ 顯示大本營 """ screen.blit(cls.home.image, cls.home.rect) class EnemyTank(pygame.sprite.Sprite): """ 敵人坦克類 """ # 定義精靈組,將所有的實例對象添加到里面 group = pygame.sprite.Group() def __init__(self, position): # 調用父類的初始化方法,這樣才能夠實現必要的初始化操作 super().__init__() # 坦克默認的朝向 self.direction = random.choice(["up", "down", "left", "right"]) self.tank_all_image = pygame.image.load("resources/images/enemyTank/enemy_1_0.png") self.image = None self.update_direction() # 根據隨機朝向,計算出要使用的坦克圖片 self.rect = self.image.get_rect() self.rect.topleft = position # 坦克默認的速度 self.speed = random.choice([2, 4]) # 發射子彈的間隔 self.is_bullet_cooling = True self.bullet_cooling_count = 0 self.bullet_cooling_time = 5 # 添加到精靈組 self.group.add(self) def update_direction(self): """ 更新坦克的朝向 """ if self.direction == 'up': self.image = self.tank_all_image.subsurface((0, 0), (48, 48)) elif self.direction == 'down': self.image = self.tank_all_image.subsurface((0, 48), (48, 48)) elif self.direction == 'left': self.image = self.tank_all_image.subsurface((0, 96), (48, 48)) elif self.direction == 'right': self.image = self.tank_all_image.subsurface((0, 144), (48, 48)) @classmethod def show(cls, screen): for temp in cls.group: screen.blit(temp.image, temp.rect) def move(self, group_list): """ 敵方坦克自動移動 :return: """ # 記錄位置,以便在碰到障礙物之后,能夠恢復 rect_ori = self.rect if self.direction == "up": self.rect = self.rect.move((0, -self.speed)) elif self.direction == "down": self.rect = self.rect.move((0, self.speed)) elif self.direction == "left": self.rect = self.rect.move((-self.speed, 0)) elif self.direction == "right": self.rect = self.rect.move((self.speed, 0)) # 檢測碰撞"磚牆"、"鐵牆"、"冰"、"河流"。"樹"無需檢查 for group in group_list: if pygame.sprite.spritecollide(self, group, False, None): self.rect = rect_ori # 隨機得到新的朝向 self.direction = random.choice(["up", "down", "left", "right"]) self.update_direction() # 碰撞到其他敵方坦克 # 先將本坦克從精靈組中移除 self.group.remove(self) # 然后再判斷是否碰撞到其他敵方坦克 if pygame.sprite.spritecollide(self, self.group, False, None): self.rect = rect_ori # 隨機得到新的朝向 self.direction = random.choice(["up", "down", "left", "right"]) self.update_direction() # 判斷之后再將本坦克添加到精靈組 self.group.add(self) # 碰撞到玩家坦克 if pygame.sprite.spritecollide(self, PlayerTank.group, False, None): self.rect = rect_ori # 隨機得到新的朝向 self.direction = random.choice(["up", "down", "left", "right"]) self.update_direction() # 碰撞到我方子彈 if pygame.sprite.spritecollide(self, Bullet.group, True, None): self.group.remove(self) # 判斷碰撞到邊界,然后再次隨機新朝向 if self.rect.top < BORDER_LEN: self.rect.top = BORDER_LEN # 隨機得到新的朝向 self.direction = random.choice(["up", "down", "left", "right"]) self.update_direction() elif self.rect.bottom > HEIGHT - BORDER_LEN: self.rect.bottom = HEIGHT - BORDER_LEN # 隨機得到新的朝向 self.direction = random.choice(["up", "down", "left", "right"]) self.update_direction() elif self.rect.left < BORDER_LEN: self.rect.left = BORDER_LEN # 隨機得到新的朝向 self.direction = random.choice(["up", "down", "left", "right"]) self.update_direction() elif self.rect.right > WIDTH - BORDER_LEN: self.rect.right = WIDTH - BORDER_LEN # 隨機得到新的朝向 self.direction = random.choice(["up", "down", "left", "right"]) self.update_direction() @classmethod def auto_move(cls, group_list): for temp in cls.group: temp.move(group_list) @classmethod def auto_move_and_show(cls, screen, group_list): cls.auto_move(group_list) cls.show(screen) def judge_cooling_and_fire(self): """ 判斷是否達到冷卻時間(即發射子彈要有間隔),然后發射 """ self.bullet_cooling_count += 1 if self.bullet_cooling_count > self.bullet_cooling_time: self.bullet_cooling_count = 0 if random.randint(1, 10) == 6: # 如果隨機得到的數字恰巧是6,那么就表示冷卻時間到 self.is_bullet_cooling = False if not self.is_bullet_cooling: # 創建子彈對象 if self.direction == "up": position = (self.rect.centerx, self.rect.y) elif self.direction == "down": position = (self.rect.centerx, self.rect.y + 48) elif self.direction == "left": position = (self.rect.x, self.rect.centery) elif self.direction == "right": position = (self.rect.x + 48, self.rect.centery) Bullet("enemy", self.direction, position) # 發射完畢后,立刻設置為冷卻狀態 self.is_bullet_cooling = True @classmethod def fire(cls): """ 發射子彈 """ for temp in cls.group: temp.judge_cooling_and_fire() class Game(object): """ 游戲控制類 """ def __init__(self): """ 初始化工作 """ # 游戲初始化 pygame.init() # 創建用來顯示畫面的對象(理解為相框) self.screen = pygame.display.set_mode((WIDTH, HEIGHT)) def game_start_interface(self): """ 顯示游戲開始畫面(讓用戶選擇游戲人數) :return:False為單人模式,True為雙人模式 """ # 准備用到的圖片 background_img = pygame.image.load("resources/images/others/background.png") logo_img = pygame.image.load("resources/images/others/logo.png") logo_img = pygame.transform.scale(logo_img, (446, 70)) # 圖片縮小1倍,即將892x140-->446x70 logo_rect = logo_img.get_rect() # 得到這個圖片的左上角的坐標(默認是(0,0)點),以及寬高 # 為了能夠讓logo圖片在合適的位置顯示,我們可以設置它的中心點的坐標,它會根據自身的寬高自動計算出左上角的坐標 logo_rect.centerx, logo_rect.centery = WIDTH / 2, HEIGHT // 4 # print(logo_rect.topleft) # 在終端中看到,此時輸出的值是:(92, 122) # 准備要顯示文本(1player、2players) # 字體 font = pygame.font.Font(FONTPATH, 60) # 60表示要顯示的字體大小 font_color_white = (255, 255, 255) # 要顯示的字體顏色為白色 # 1player one_player_text = font.render('1 PLAYER', True, font_color_white) one_player_rect = one_player_text.get_rect() one_player_rect.left, one_player_rect.top = WIDTH / 2 - 50, HEIGHT / 2 - 60 # 2players two_players_text = font.render('2 PLAYERS', True, font_color_white) two_players_rect = two_players_text.get_rect() two_players_rect.left, two_players_rect.top = WIDTH / 2 - 50, HEIGHT / 2 # 游戲人數選擇時的圖片 select_player_num_tank = pygame.image.load("resources/images/playerTank/tank_T1_0.png").convert_alpha().subsurface((0, 144), (48, 48)) select_player_num_tank_rect = select_player_num_tank.get_rect() # 游戲開始提示 game_tip = font.render('press <Enter> to start', True, font_color_white) game_tip_rect = game_tip.get_rect() game_tip_rect.centerx, game_tip_rect.top = WIDTH / 2, HEIGHT / 1.4 # 創建一個計時器,用來實現更加方便的延時(防止while循環過快,占用太多CPU的問題) clock = pygame.time.Clock() # 存儲游戲人數(False單人,True雙人) is_dual_mode = False # 主循環 while True: # 事件檢測(例如點擊了鍵盤、鼠標點擊等) for event in pygame.event.get(): if event.type == pygame.QUIT: # 如果用鼠標點擊了❌,那么就退出程序 pygame.quit() sys.exit() # 退出程序 elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RETURN: return is_dual_mode elif event.key == pygame.K_UP or event.key == pygame.K_DOWN or event.key == pygame.K_w or event.key == pygame.K_s: is_dual_mode = not is_dual_mode # print("當前選擇的游戲人數是(True雙人、False單人)", is_dual_mode) # 從(0,0)點(即左上角)開始貼一張圖片(理解為在screen這個相框中從左上角開始貼一張照片) self.screen.blit(background_img, (0, 0)) # 顯示logo self.screen.blit(logo_img, logo_rect) # 顯示游戲人數文字 self.screen.blit(one_player_text, one_player_rect) self.screen.blit(two_players_text, two_players_rect) # 顯示標記選擇的人數的tank if is_dual_mode: # 雙人模式 select_player_num_tank_rect.right, select_player_num_tank_rect.top = two_players_rect.left - 10, two_players_rect.top self.screen.blit(select_player_num_tank, select_player_num_tank_rect) else: # 單人模式 select_player_num_tank_rect.right, select_player_num_tank_rect.top = one_player_rect.left - 10, one_player_rect.top self.screen.blit(select_player_num_tank, select_player_num_tank_rect) # 顯示提示 self.screen.blit(game_tip, game_tip_rect) # 顯示screen這個相框的內容(此時在這個相框中的內容像照片、文字等會顯示出來) pygame.display.update() # FPS(每秒鍾顯示畫面的次數) clock.tick(60) # 通過一定的延時,實現1秒鍾能夠循環60次 def game_end_interface(self, is_win): """ 顯示游戲結束畫面 """ # 背景 background_img = pygame.image.load("resources/images/others/background.png") # 游戲失敗圖 game_over_img = pygame.image.load("resources/images/others/gameover.png") game_over_img = pygame.transform.scale(game_over_img, (150, 75)) game_over_img_rect = game_over_img.get_rect() game_over_img_rect.midtop = WIDTH / 2, HEIGHT / 8 # 游戲勝利、失敗字體 color_white = (255, 255, 255) font = pygame.font.Font(FONTPATH, 60) # 游戲勝利與否的提示 if is_win: font_render = font.render('Congratulations, You win!', True, color_white) else: font_render = font.render('Sorry, You fail!', True, color_white) font_rect = font_render.get_rect() font_rect.centerx, font_rect.centery = WIDTH / 2, HEIGHT / 3 # 用於選擇退出或重新開始 # 用於選擇的坦克光標 tank_cursor = pygame.image.load("resources/images/playerTank/tank_T1_0.png").convert_alpha().subsurface((0, 144), (48, 48)) tank_rect = tank_cursor.get_rect() # 重新運行 restart_render_white = font.render('RESTART', True, color_white) restart_rect = restart_render_white.get_rect() restart_rect.left, restart_rect.top = WIDTH / 2.4, HEIGHT / 2 # 退出 quit_render_white = font.render('QUIT', True, color_white) quit_rect = quit_render_white.get_rect() quit_rect.left, quit_rect.top = WIDTH / 2.4, HEIGHT / 1.6 # 標記當前選擇的是退出還是繼續游戲 is_quit_game = False # 創建計時器對象,用於控制刷新頻率 clock = pygame.time.Clock() # 主循環 while True: # 檢查鍵盤事件 for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RETURN: return is_quit_game elif event.key == pygame.K_UP or event.key == pygame.K_DOWN or event.key == pygame.K_w or event.key == pygame.K_s: is_quit_game = not is_quit_game # 顯示背景 self.screen.blit(background_img, (0, 0)) self.screen.blit(game_over_img, game_over_img_rect) self.screen.blit(font_render, font_rect) if not is_quit_game: tank_rect.right, tank_rect.top = restart_rect.left - 10, restart_rect.top self.screen.blit(tank_cursor, tank_rect) self.screen.blit(quit_render_white, quit_rect) self.screen.blit(restart_render_white, restart_rect) else: tank_rect.right, tank_rect.top = quit_rect.left - 10, quit_rect.top self.screen.blit(tank_cursor, tank_rect) self.screen.blit(quit_render_white, quit_rect) self.screen.blit(restart_render_white, restart_rect) # 刷新顯示畫面,此時才會真正的顯示 pygame.display.update() # 控制頻率,FPS為60,每秒鍾60次刷新 clock.tick(60) def parse_game_level_file(self): """ 解析關卡文件 """ # 每個地圖元素占用的像素(配置文件,例如1.lvl中注釋里說明了Grid SIZE: 24 * 24 pixels) grid_size = 24 # 定義大本營 # home_dict = dict() with open("./levels/3.lvl", errors='ignore') as f: num_row = -1 # 用來標記地圖元素是整個地圖的第幾行(總共26行,26列) for line in f.readlines(): line = line.strip('\n') # 切除每行行尾的換行符 # 如果當前要處理的行是地圖元素,那么就繼續處理 if line[0] in ["S", "B", "I", "R", "C", "T"]: # 地圖元素 num_row += 1 # print("當前是第%d行" % num_row) for num_col, elem in enumerate(line.split(' ')): # print("當前是第%d行,第%d列" % (num_row, num_col)) position = BORDER_LEN + num_col * grid_size, BORDER_LEN + num_row * grid_size if elem == 'B': # 創建磚牆對象,然后添加到精靈組 Brick(position) elif elem == 'I': # 創建鐵牆對象,然后添加到精靈組 Iron(position) elif elem == 'R': # 創建河流對象,然后添加到精靈組 River(position) elif elem == 'C': # 創建冰對象,然后添加到精靈組 Ice(position) elif elem == 'T': # 創建樹對象,然后添加到精靈組 Tree(position) elif line.startswith('%HOMEPOS'): # 大本營位置 home_position = line.split(':')[-1] home_position = int(home_position.split(',')[0]), int(home_position.split(',')[1]) home_position = (BORDER_LEN + home_position[0] * grid_size, BORDER_LEN + home_position[1] * grid_size) # 創建大本營類 PlayerHome(home_position) elif line.startswith('%PLAYERTANKPOS'): # 我方坦克初始位置 player_tank_positions = line.split(':')[-1] player_tank_positions = [(int(pos.split(',')[0]), int(pos.split(',')[1])) for pos in player_tank_positions.split(' ')] player_tank_positions = [[BORDER_LEN + pos[0] * grid_size, BORDER_LEN + pos[1] * grid_size] for pos in player_tank_positions] # 從這個圖片中切除一部分來當做要使用的圖片,即玩家1的坦克 # image = pygame.image.load("resources/images/playerTank/tank_T1_0.png").convert_alpha() # image = image.subsurface((0, 0), (48, 48)) # player1_dict = { # "image": image, # "top_left": player_tank_positions[0] # } # 創建我方玩家1的坦克,然后添加到列表中 PlayerTank("player1", player_tank_positions[0]) # 從這個圖片中切除一部分來當做要使用的圖片,即玩家2的坦克 # image = pygame.image.load("resources/images/playerTank/tank_T2_0.png").convert_alpha() # image = image.subsurface((0, 0), (48, 48)) # player2_dict = { # "image": image, # "top_left": player_tank_positions[1] # } # player_group.append(player1_dict) # player_group.append(player2_dict) # 創建我方玩家2的坦克,然后添加到列表中 PlayerTank("player2", player_tank_positions[1]) elif line.startswith('%ENEMYTANKPOS'): # 敵方坦克初始位置 position = line.split(':')[-1] position = [[int(pos.split(',')[0]), int(pos.split(',')[1])] for pos in position.split(' ')] position = [(BORDER_LEN + pos[0] * grid_size, BORDER_LEN + pos[1] * grid_size) for pos in position] # 根據敵方坦克的初始位置創建多個坦克 for pos in position: EnemyTank(pos) def game_run_level(self, is_dual_mode): """ 運行游戲 """ # 背景圖片 background_img = pygame.image.load("resources/images/others/background.png") # 調用解析關卡配置文件 self.parse_game_level_file() # 幀率控制對象 clock = pygame.time.Clock() # 運行游戲的主循環 is_win = False is_running = True while is_running: # 事件檢測(例如點擊了鍵盤、鼠標點擊等) for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() # 通過鍵盤控制坦克移動 PlayerTank.move_player_tank(is_dual_mode, [Brick.group, River.group, Ice.group, Iron.group]) # 敵方坦克開發 EnemyTank.fire() # 碰撞檢測 # 子彈碰大本營(無論是我方還是敵方子彈,只要碰到都認為本關卡游戲結束) if pygame.sprite.spritecollide(PlayerHome.home, Bullet.group, True, None) or pygame.sprite.spritecollide(PlayerHome.home, Bullet.group_enemy, True, None): is_running = False # 如果碰撞到大本營,那么通過修改這個變量為False讓while循環結束,游戲即將結束運行 is_win = False # 如果敵方坦克沒有了,則認為我方勝利 if len(EnemyTank.group) == 0: is_running = False is_win = True # 如果我方坦克沒有了生命值,則認為游戲輸了 if (not is_dual_mode and PlayerTank.player_group[0].life_num == 0) or \ (is_dual_mode and PlayerTank.player_group[0].life_num == 0 and PlayerTank.player_group[1].life_num == 0): is_running = False is_win = False # 顯示游戲背景 self.screen.blit(background_img, (0, 0)) # 顯示磚牆 Brick.show(self.screen) # 顯示鐵牆 Iron.show(self.screen) # 顯示河流 River.show(self.screen) # 顯示冰 Ice.show(self.screen) # 顯示樹 Tree.show(self.screen) # 顯示大本營 PlayerHome.show(self.screen) # 顯示我方坦克 PlayerTank.show(self.screen, is_dual_mode) # 顯示我方坦克發射的子彈 Bullet.move_and_show(self.screen) # 顯示敵方坦克 EnemyTank.auto_move_and_show(self.screen, [Brick.group, River.group, Ice.group, Iron.group]) # 刷新要顯示的內容,從而真正的顯示 pygame.display.update() # 每秒鍾控制為60幀 clock.tick(60) return is_win def clean(self): """ 清理上一次游戲留下的殘留 """ EnemyTank.group.empty() PlayerTank.group.empty() PlayerTank.player_group.clear() Brick.group.empty() Ice.group.empty() River.group.empty() Iron.group.empty() Tree.group.empty() Bullet.group.empty() Bullet.group_enemy.empty() def run(self): # 顯示游戲開始畫面,讓用戶選擇游戲人數 is_dual_mode = self.game_start_interface() # 調用游戲關卡函數,從而開始游戲 is_win = self.game_run_level(is_dual_mode) # 接下來根據is_win來顯示對應的輸贏界面 while True: if self.game_end_interface(is_win): # 如果返回為True,那么就意味着退出游戲,否則繼續游戲 break # 繼續重新游戲 # 清理上一次游戲的殘留 self.clean() # 創建用來顯示畫面的對象(理解為相框) pygame.display.set_mode((WIDTH, HEIGHT)) # 顯示游戲開始畫面,讓用戶選擇游戲人數 is_dual_mode = self.game_start_interface() # 調用游戲關卡函數,從而開始游戲 is_win = self.game_run_level(is_dual_mode) if __name__ == '__main__': """ 整體流程的控制 """ game = Game() game.run()