Python3+pygame實現的90坦克大戰 代碼完整 有演示效果


我是一個典型的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()

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM