用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. 优雅的渲染系统
游戏画面采用分层渲染:
- 网格背景(增加战场纵深感)
- 墙壁层(带纹理区分)
- 子弹层(玩家黄色,敌人红色)
- 坦克层(带炮管方向和生命显示)
- UI层(分数、弹药、敌人计数)
2. 灵活的关卡生成
地图采用程序化生成,确保:
- 玩家起始区域安全(周围100像素无墙)
- 敌人生成在安全距离外(>200像素)
- 墙壁分布随机但有边界保护
3. 响应式用户界面
UI元素实时反映游戏状态:
- 左上角:分数和当前关卡
- 右上角:弹药存量和敌人数量
- 中央:控制提示和游戏目标
- 游戏结束/胜利画面:清晰的操作指引
扩展可能性
这个基础框架为后续扩展提供了丰富可能:
- 道具系统:增加弹药箱、生命恢复、临时护盾等
- 坦克升级:射速提升、移动加速、穿透弹等
- 地图编辑器:可视化创建自定义关卡
- 多人模式:本地分屏对战或联机对抗
- 敌人多样化:快速型、重甲型、远程型等变体
- 环境互动 :水域减速、草丛隐身、冰面打滑

结语
通过这个项目,我们不仅复现了经典游戏玩法,更实践了完整的游戏开发流程:从物理碰撞到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()