用Python和Pygame从零实现坦克大战

用Python和Pygame从零实现坦克大战

还记得小时候在红白机上奋战《坦克大战》的时光吗?今天,我们将用Python和Pygame库,从零开始重构这个经典游戏。这不仅仅是一个编程练习,更是对游戏开发核心机制的一次深入探索。

游戏概览

我们实现的这款坦克大战游戏包含以下核心要素:

  • 玩家控制一辆蓝色坦克,拥有3点生命值
  • 敌方红色AI坦克会自主移动和攻击
  • 两种墙壁:可破坏的棕色木质墙和不可破坏的灰色石墙
  • 完整的弹药系统与得分机制
  • 3个渐进的游戏关卡

核心特性详解

1. 流畅的控制系统

玩家坦克支持八方向移动(WASD/方向键),并采用了经典的对角线速度修正(乘以0.7071),确保斜向移动不会更快。空格键发射子弹,但弹药有限(初始20发),增加了策略维度。

2. 智能化的敌人AI

敌方坦克并非简单的巡逻兵。它们的AI包含两个层次:

  • 移动决策:每帧有约1/30的概率改变移动方向(上、下、左、右)
  • 攻击逻辑 :每帧有2%的概率开火,但有冷却时间限制
    敌人会在地图上半区域随机生成,并确保与玩家保持安全距离,避免"出生杀"。

3. 交互式战场环境

战场采用网格化设计(20×20像素网格),包含两种障碍物:

  • 可破坏墙:棕色木质纹理,子弹可摧毁
  • 不可破坏墙 :灰色网格纹理,形成永久障碍
    地图边缘均为不可穿越的边界墙,构成了封闭的战斗空间。

4. 精密的碰撞系统

游戏实现了多层碰撞检测:

  • 坦克与墙壁的碰撞(移动阻挡)
  • 子弹与墙壁的碰撞(子弹消失+墙壁可能被毁)
  • 子弹与坦克的碰撞(伤害计算)
  • 边界检测(防止物体出界)

技术架构解析

面向对象设计

游戏采用清晰的类结构,职责分离明确:

python 复制代码
class Tank:      # 坦克基类,管理移动、射击、生命值
class Bullet:    # 子弹类,处理弹道和碰撞
class Wall:      # 墙壁类,区分可破坏状态
class Game:      # 游戏主控制器,协调所有子系统

游戏循环与状态管理

主循环遵循经典模式:

python 复制代码
while running:
    handle_events()  # 处理输入
    update()         # 更新游戏状态
    draw()           # 渲染画面
    clock.tick(60)   # 保持60FPS

游戏状态机清晰:

  • 进行中:玩家存活,敌人未清空
  • 游戏结束:玩家生命值归零
  • 胜利:完成3个关卡

资源管理与进度系统

  • 弹药限制:玩家需谨慎开火,摧毁敌人是唯一补充方式
  • 生命恢复:每通关一次,恢复1点生命(最多3点)
  • 难度递增:每个关卡敌人数量增加(3→4→5)
  • 敌人重生:场上敌人少于5个时,300帧后自动补充

开发亮点

1. 优雅的渲染系统

游戏画面采用分层渲染:

  1. 网格背景(增加战场纵深感)
  2. 墙壁层(带纹理区分)
  3. 子弹层(玩家黄色,敌人红色)
  4. 坦克层(带炮管方向和生命显示)
  5. UI层(分数、弹药、敌人计数)

2. 灵活的关卡生成

地图采用程序化生成,确保:

  • 玩家起始区域安全(周围100像素无墙)
  • 敌人生成在安全距离外(>200像素)
  • 墙壁分布随机但有边界保护

3. 响应式用户界面

UI元素实时反映游戏状态:

  • 左上角:分数和当前关卡
  • 右上角:弹药存量和敌人数量
  • 中央:控制提示和游戏目标
  • 游戏结束/胜利画面:清晰的操作指引

扩展可能性

这个基础框架为后续扩展提供了丰富可能:

  1. 道具系统:增加弹药箱、生命恢复、临时护盾等
  2. 坦克升级:射速提升、移动加速、穿透弹等
  3. 地图编辑器:可视化创建自定义关卡
  4. 多人模式:本地分屏对战或联机对抗
  5. 敌人多样化:快速型、重甲型、远程型等变体
  6. 环境互动 :水域减速、草丛隐身、冰面打滑

结语

通过这个项目,我们不仅复现了经典游戏玩法,更实践了完整的游戏开发流程:从物理碰撞到AI行为,从状态管理到渲染优化。代码结构清晰,模块化程度高,非常适合作为学习游戏开发的范例。

技术栈总结:Python 3 + Pygame,纯代码约400行,无外部资源依赖,开箱即用。

无论是怀旧重温,还是学习游戏开发,这个坦克大战实现都提供了一个扎实的起点。代码已准备好,战场已就绪------是时候启动你的坦克引擎了!

完整代码

python 复制代码
import pygame
import random
import math
import sys

# 初始化Pygame
pygame.init()

# 游戏窗口设置
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Tank Battle")

# 颜色定义
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 50, 50)
GREEN = (50, 255, 50)
BLUE = (50, 100, 255)
YELLOW = (255, 255, 50)
GRAY = (150, 150, 150)
BROWN = (165, 42, 42)
DARK_GREEN = (0, 100, 0)

# 游戏常量
FPS = 60
PLAYER_SPEED = 4
BULLET_SPEED = 8
ENEMY_SPEED = 2
ENEMY_FIRE_RATE = 0.02  # 每帧敌人开火的概率
PLAYER_FIRE_RATE = 15  # 玩家发射子弹的最小帧间隔
WALL_SIZE = 30
GRID_SIZE = 20
MAX_ENEMIES = 5
MAX_AMMO = 20
ENEMY_RESPAWN_TIME = 300  # 敌人重生时间(帧)


class Tank:
    def __init__(self, x, y, color, is_player=False):
        self.x = x
        self.y = y
        self.color = color
        self.width = 40
        self.height = 40
        self.direction = 0  # 0: 上, 1: 右, 2: 下, 3: 左
        self.is_player = is_player
        self.health = 3 if is_player else 1
        self.cooldown = 0
        self.alive = True
        self.speed = PLAYER_SPEED if is_player else ENEMY_SPEED

    def move(self, dx, dy, walls):
        # 计算新位置
        new_x = self.x + dx
        new_y = self.y + dy

        # 创建坦克的矩形
        tank_rect = pygame.Rect(new_x, new_y, self.width, self.height)

        # 检查是否与墙壁碰撞
        can_move = True
        for wall in walls:
            if tank_rect.colliderect(wall.rect) and wall.destructible:
                can_move = False
                break

        # 检查边界
        if new_x < 0 or new_x + self.width > WIDTH or new_y < 0 or new_y + self.height > HEIGHT:
            can_move = False

        # 如果可移动,则更新位置
        if can_move:
            self.x = new_x
            self.y = new_y

        # 更新方向
        if dx > 0:
            self.direction = 1
        elif dx < 0:
            self.direction = 3
        elif dy > 0:
            self.direction = 2
        elif dy < 0:
            self.direction = 0

    def update(self, walls):
        if not self.alive:
            return

        if self.cooldown > 0:
            self.cooldown -= 1

        # 敌人AI
        if not self.is_player and self.alive:
            # 简单AI:随机移动和开火
            move_choice = random.randint(0, 30)
            if move_choice == 0:
                self.move(-self.speed, 0, walls)
            elif move_choice == 1:
                self.move(self.speed, 0, walls)
            elif move_choice == 2:
                self.move(0, -self.speed, walls)
            elif move_choice == 3:
                self.move(0, self.speed, walls)

            # 随机开火
            if random.random() < ENEMY_FIRE_RATE and self.cooldown == 0:
                return self.fire()
        return None

    def fire(self):
        if self.cooldown > 0:
            return None

        self.cooldown = PLAYER_FIRE_RATE if self.is_player else 30

        # 根据坦克方向计算子弹起始位置
        if self.direction == 0:  # 上
            bullet_x = self.x + self.width // 2
            bullet_y = self.y
            dx, dy = 0, -1
        elif self.direction == 1:  # 右
            bullet_x = self.x + self.width
            bullet_y = self.y + self.height // 2
            dx, dy = 1, 0
        elif self.direction == 2:  # 下
            bullet_x = self.x + self.width // 2
            bullet_y = self.y + self.height
            dx, dy = 0, 1
        else:  # 左
            bullet_x = self.x
            bullet_y = self.y + self.height // 2
            dx, dy = -1, 0

        return Bullet(bullet_x, bullet_y, dx, dy, self.is_player)

    def take_damage(self):
        self.health -= 1
        if self.health <= 0:
            self.alive = False
            return True
        return False

    def draw(self, surface):
        if not self.alive:
            return

        # 绘制坦克主体
        tank_rect = pygame.Rect(self.x, self.y, self.width, self.height)
        pygame.draw.rect(surface, self.color, tank_rect)
        pygame.draw.rect(surface, WHITE, tank_rect, 2)

        # 绘制坦克炮管
        if self.direction == 0:  # 上
            pygame.draw.rect(surface, DARK_GREEN,
                             (self.x + self.width // 2 - 5, self.y - 15, 10, 20))
        elif self.direction == 1:  # 右
            pygame.draw.rect(surface, DARK_GREEN,
                             (self.x + self.width, self.y + self.height // 2 - 5, 20, 10))
        elif self.direction == 2:  # 下
            pygame.draw.rect(surface, DARK_GREEN,
                             (self.x + self.width // 2 - 5, self.y + self.height, 10, 20))
        else:  # 左
            pygame.draw.rect(surface, DARK_GREEN,
                             (self.x - 20, self.y + self.height // 2 - 5, 20, 10))

        # 绘制生命值(仅对玩家)
        if self.is_player:
            for i in range(self.health):
                pygame.draw.rect(surface, RED,
                                 (self.x + i * 12, self.y - 15, 10, 10))


class Bullet:
    def __init__(self, x, y, dx, dy, is_player):
        self.x = x
        self.y = y
        self.dx = dx
        self.dy = dy
        self.radius = 5
        self.speed = BULLET_SPEED
        self.is_player = is_player
        self.color = YELLOW if is_player else RED
        self.alive = True

    def update(self):
        self.x += self.dx * self.speed
        self.y += self.dy * self.speed

        # 检查是否出界
        if (self.x < 0 or self.x > WIDTH or
                self.y < 0 or self.y > HEIGHT):
            self.alive = False

    def draw(self, surface):
        if self.alive:
            pygame.draw.circle(surface, self.color, (int(self.x), int(self.y)), self.radius)
            pygame.draw.circle(surface, WHITE, (int(self.x), int(self.y)), self.radius, 1)

    def get_rect(self):
        return pygame.Rect(self.x - self.radius, self.y - self.radius,
                           self.radius * 2, self.radius * 2)


class Wall:
    def __init__(self, x, y, destructible=True):
        self.x = x
        self.y = y
        self.destructible = destructible
        self.color = BROWN if destructible else GRAY
        self.rect = pygame.Rect(x, y, WALL_SIZE, WALL_SIZE)
        self.alive = True

    def draw(self, surface):
        if self.alive:
            pygame.draw.rect(surface, self.color, self.rect)
            pygame.draw.rect(surface, WHITE, self.rect, 1)
            # 添加纹理
            if self.destructible:
                pygame.draw.line(surface, (139, 69, 19),
                                 (self.x + 5, self.y + 5),
                                 (self.x + WALL_SIZE - 5, self.y + WALL_SIZE - 5), 2)
                pygame.draw.line(surface, (139, 69, 19),
                                 (self.x + WALL_SIZE - 5, self.y + 5),
                                 (self.x + 5, self.y + WALL_SIZE - 5), 2)
            else:
                # 不可破坏的墙壁有网格纹理
                for i in range(0, WALL_SIZE, 5):
                    pygame.draw.line(surface, (100, 100, 100),
                                     (self.x, self.y + i),
                                     (self.x + WALL_SIZE, self.y + i), 1)


class Game:
    def __init__(self):
        self.player = Tank(WIDTH // 2, HEIGHT - 100, BLUE, True)
        self.enemies = []
        self.bullets = []
        self.walls = []
        self.score = 0
        self.level = 1
        self.ammo = MAX_AMMO
        self.game_over = False
        self.game_won = False
        self.enemy_respawn_timer = 0
        self.generate_walls()
        self.spawn_enemies(3)

    def generate_walls(self):
        # 创建边界墙
        for x in range(0, WIDTH, WALL_SIZE):
            self.walls.append(Wall(x, 0, False))
            self.walls.append(Wall(x, HEIGHT - WALL_SIZE, False))

        for y in range(WALL_SIZE, HEIGHT - WALL_SIZE, WALL_SIZE):
            self.walls.append(Wall(0, y, False))
            self.walls.append(Wall(WIDTH - WALL_SIZE, y, False))

        # 创建随机可破坏墙壁
        for _ in range(30):
            x = random.randint(3, (WIDTH // WALL_SIZE) - 4) * WALL_SIZE
            y = random.randint(3, (HEIGHT // WALL_SIZE) - 4) * WALL_SIZE

            # 确保不在玩家起始位置周围生成墙
            if (abs(x - self.player.x) > 100 or abs(y - self.player.y) > 100):
                self.walls.append(Wall(x, y, True))

        # 创建一些不可破坏的障碍物
        for _ in range(10):
            x = random.randint(2, (WIDTH // WALL_SIZE) - 3) * WALL_SIZE
            y = random.randint(2, (HEIGHT // WALL_SIZE) - 3) * WALL_SIZE

            if (abs(x - self.player.x) > 100 or abs(y - self.player.y) > 100):
                self.walls.append(Wall(x, y, False))

    def spawn_enemies(self, count):
        for _ in range(count):
            # 在远离玩家的位置生成敌人
            while True:
                x = random.randint(50, WIDTH - 50)
                y = random.randint(50, HEIGHT // 2)

                # 确保敌人不与玩家太近
                if (abs(x - self.player.x) > 200 or abs(y - self.player.y) > 200):
                    break

            self.enemies.append(Tank(x, y, RED))

    def handle_events(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return False

            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    return False

                if self.game_over or self.game_won:
                    if event.key == pygame.K_r:
                        self.__init__()  # 重置游戏
                elif event.key == pygame.K_SPACE and self.ammo > 0:
                    bullet = self.player.fire()
                    if bullet:
                        self.bullets.append(bullet)
                        self.ammo -= 1

        if not self.game_over and not self.game_won:
            keys = pygame.key.get_pressed()
            dx, dy = 0, 0

            if keys[pygame.K_LEFT] or keys[pygame.K_a]:
                dx = -PLAYER_SPEED
            if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
                dx = PLAYER_SPEED
            if keys[pygame.K_UP] or keys[pygame.K_w]:
                dy = -PLAYER_SPEED
            if keys[pygame.K_DOWN] or keys[pygame.K_s]:
                dy = PLAYER_SPEED

            # 对角线移动速度修正
            if dx != 0 and dy != 0:
                dx *= 0.7071
                dy *= 0.7071

            self.player.move(dx, dy, self.walls)

        return True

    def update(self):
        if self.game_over or self.game_won:
            return

        # 更新玩家
        self.player.update(self.walls)

        # 更新敌人
        for enemy in self.enemies[:]:
            bullet = enemy.update(self.walls)
            if bullet:
                self.bullets.append(bullet)

        # 更新子弹
        for bullet in self.bullets[:]:
            bullet.update()

            # 检查子弹是否击中墙壁
            for wall in self.walls[:]:
                if wall.alive and bullet.get_rect().colliderect(wall.rect):
                    bullet.alive = False
                    if wall.destructible:
                        wall.alive = False
                        self.walls.remove(wall)
                    break

            # 检查子弹是否击中玩家
            if not bullet.is_player and bullet.alive:
                player_rect = pygame.Rect(self.player.x, self.player.y,
                                          self.player.width, self.player.height)
                if bullet.get_rect().colliderect(player_rect) and self.player.alive:
                    bullet.alive = False
                    if self.player.take_damage():
                        self.game_over = True

            # 检查子弹是否击中敌人
            if bullet.is_player and bullet.alive:
                for enemy in self.enemies[:]:
                    enemy_rect = pygame.Rect(enemy.x, enemy.y, enemy.width, enemy.height)
                    if bullet.get_rect().colliderect(enemy_rect) and enemy.alive:
                        bullet.alive = False
                        if enemy.take_damage():
                            self.enemies.remove(enemy)
                            self.score += 100

            # 移除已消失的子弹
            if not bullet.alive and bullet in self.bullets:
                self.bullets.remove(bullet)

        # 敌人重生逻辑
        if len(self.enemies) < MAX_ENEMIES:
            self.enemy_respawn_timer += 1
            if self.enemy_respawn_timer >= ENEMY_RESPAWN_TIME:
                self.spawn_enemies(1)
                self.enemy_respawn_timer = 0

        # 检查关卡完成
        if len(self.enemies) == 0 and self.level < 3:
            self.level += 1
            self.spawn_enemies(self.level + 2)
            self.ammo = MAX_AMMO
            self.player.health = min(3, self.player.health + 1)

        # 检查游戏胜利
        if self.level >= 3 and len(self.enemies) == 0:
            self.game_won = True

    def draw(self, surface):
        # 绘制背景
        surface.fill(BLACK)

        # 绘制网格背景
        for x in range(0, WIDTH, GRID_SIZE):
            pygame.draw.line(surface, (20, 20, 20), (x, 0), (x, HEIGHT))
        for y in range(0, HEIGHT, GRID_SIZE):
            pygame.draw.line(surface, (20, 20, 20), (0, y), (WIDTH, y))

        # 绘制墙壁
        for wall in self.walls:
            wall.draw(surface)

        # 绘制子弹
        for bullet in self.bullets:
            bullet.draw(surface)

        # 绘制敌人
        for enemy in self.enemies:
            enemy.draw(surface)

        # 绘制玩家
        self.player.draw(surface)

        # 绘制UI
        self.draw_ui(surface)

        # 绘制游戏结束/胜利画面
        if self.game_over:
            self.draw_game_over(surface)
        elif self.game_won:
            self.draw_game_won(surface)

    def draw_ui(self, surface):
        # 分数
        font = pygame.font.SysFont(None, 36)
        score_text = font.render(f"score: {self.score}", True, GREEN)
        surface.blit(score_text, (10, 10))

        # 等级
        level_text = font.render(f"level: {self.level}/3", True, GREEN)
        surface.blit(level_text, (10, 50))

        # 弹药
        ammo_text = font.render(f"bullet: {self.ammo}/{MAX_AMMO}", True, YELLOW)
        surface.blit(ammo_text, (WIDTH - 150, 10))

        # 敌人数量
        enemies_text = font.render(f"enemy: {len(self.enemies)}", True, RED)
        surface.blit(enemies_text, (WIDTH - 150, 50))

        # 控制说明
        controls_font = pygame.font.SysFont(None, 24)
        controls = [
            "Controls: WASD/Arrow Keys to move, Space to fire",
            "Objective: Eliminate all enemies (3 levels)",
            "Note: Limited ammunition, can destroy brown walls"
        ]

        for i, text in enumerate(controls):
            control_text = controls_font.render(text, True, WHITE)
            surface.blit(control_text, (WIDTH // 2 - 150, 10 + i * 25))

    def draw_game_over(self, surface):
        overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 200))
        surface.blit(overlay, (0, 0))

        font_large = pygame.font.SysFont(None, 72)
        font_medium = pygame.font.SysFont(None, 36)

        game_over_text = font_large.render("game over", True, RED)
        surface.blit(game_over_text, (WIDTH // 2 - game_over_text.get_width() // 2, HEIGHT // 2 - 100))

        score_text = font_medium.render(f"finial score: {self.score}", True, WHITE)
        surface.blit(score_text, (WIDTH // 2 - score_text.get_width() // 2, HEIGHT // 2))

        restart_text = font_medium.render("Press R to restart", True, GREEN)
        surface.blit(restart_text, (WIDTH // 2 - restart_text.get_width() // 2, HEIGHT // 2 + 100))

        quit_text = font_medium.render("Press ESC to exit", True, YELLOW)
        surface.blit(quit_text, (WIDTH // 2 - quit_text.get_width() // 2, HEIGHT // 2 + 150))

    def draw_game_won(self, surface):
        overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 200))
        surface.blit(overlay, (0, 0))

        font_large = pygame.font.SysFont(None, 72)
        font_medium = pygame.font.SysFont(None, 36)

        win_text = font_large.render("Victory!", True, GREEN)
        surface.blit(win_text, (WIDTH // 2 - win_text.get_width() // 2, HEIGHT // 2 - 100))

        score_text = font_medium.render(f"Final Score: {self.score}", True, WHITE)
        surface.blit(score_text, (WIDTH // 2 - score_text.get_width() // 2, HEIGHT // 2))

        restart_text = font_medium.render("Press R to restart", True, GREEN)
        surface.blit(restart_text, (WIDTH // 2 - restart_text.get_width() // 2, HEIGHT // 2 + 100))

        quit_text = font_medium.render("Press ESC to exit", True, YELLOW)
        surface.blit(quit_text, (WIDTH // 2 - quit_text.get_width() // 2, HEIGHT // 2 + 150))


def main():
    clock = pygame.time.Clock()
    game = Game()

    running = True
    while running:
        running = game.handle_events()
        game.update()
        game.draw(screen)

        pygame.display.flip()
        clock.tick(FPS)

    pygame.quit()
    sys.exit()


if __name__ == "__main__":
    main()
相关推荐
灰色小旋风4 小时前
力扣合并K个升序链表C++
java·开发语言
_MyFavorite_4 小时前
JAVA重点基础、进阶知识及易错点总结(28)接口默认方法与静态方法
java·开发语言·windows
取码网4 小时前
最新在线留言板系统PHP源码
开发语言·php
环黄金线HHJX.4 小时前
龙虾钳足启发的AI集群语言交互新范式
开发语言·人工智能·算法·编辑器·交互
书到用时方恨少!4 小时前
Python Pandas 使用指南:数据分析的瑞士军刀
python·数据分析·pandas
不写八个5 小时前
PHP教程006:ThinkPHP项目入门
开发语言·php
_MyFavorite_5 小时前
JAVA重点基础、进阶知识及易错点总结(31)设计模式基础(单例、工厂)
java·开发语言·设计模式
A.A呐5 小时前
【C++第二十三章】C++11
开发语言·c++
智算菩萨5 小时前
【Pygame】第8章 文字渲染与字体系统(支持中文字体)
开发语言·python·pygame