|
|
马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
- # -*- coding: utf-8 -*-
- """
- ================================================================================
- 塔防游戏
- ================================================================================
- 操作说明:
- 方向键 / WASD : 移动主角色(红色正方形)
- 鼠标左键 : 在地图上放置红色三角标,小兵会围绕三角标行动
- 鼠标右键 : 取消三角标
- T 键 : 在主角色当前位置建造一座塔(红色正方形,自动攻击怪物)
- 1 键 : 生成一支近战小兵(白色正方形)
- 2 键 : 生成一支远程小兵(白色圆形)
- R 键 : 游戏结束后重新开始
- ESC 键 : 退出游戏
- ================================================================================
- """
- # 导入 pygame 库,用于创建窗口、绘制图形、处理事件等
- import pygame
- # 从 pygame.locals 导入常用常量(如按键、事件类型等)
- from pygame.locals import *
- # 导入 random 模块,用于生成随机数(如怪物生成间隔、随机偏移等)
- import random
- # 导入 math 模块,用于数学计算(如两点间距离、三角函数等)
- import math
- # 导入 sys 模块,用于系统相关操作(如退出程序)
- import sys
- # ==============================================================================
- # 全局常量定义
- # ==============================================================================
- # 屏幕宽度(像素)
- SCREEN_WIDTH = 1000
- # 屏幕高度(像素)
- SCREEN_HEIGHT = 700
- # 游戏刷新帧率(每秒多少帧)
- FPS = 60
- # 颜色定义(RGB 格式)
- COLOR_WHITE = (255, 255, 255) # 白色 - 近战小兵(正方形) / 远程小兵(圆形)
- COLOR_BLACK = (0, 0, 0) # 黑色 - 近战怪物(正方形) / 远程怪物(圆形)
- COLOR_RED = (220, 30, 30) # 红色 - 主角色(正方形) / 塔(正方形)
- COLOR_RED_DARK = (160, 20, 20) # 深红色 - 主角色子弹(小正方形)
- COLOR_GRAY = (120, 120, 120) # 灰色 - UI 文字、路径辅助线
- COLOR_GREEN = (40, 200, 40) # 绿色 - 生命条(健康)
- COLOR_YELLOW = (230, 210, 30) # 黄色 - 三角标
- COLOR_BG = (30, 30, 40) # 背景色 - 深蓝灰
- # 主角色移动速度(像素/秒)
- HERO_SPEED = 220
- # 塔的攻击范围(像素)
- TOWER_RANGE = 220
- # 塔的射击冷却时间(毫秒)
- TOWER_COOLDOWN = 700
- # 小兵围绕目标(主角色/三角标)的半径范围
- SOLDIER_ORBIT_MIN = 60
- SOLDIER_ORBIT_MAX = 130
- # 远程单位射击冷却(毫秒)
- RANGED_COOLDOWN = 1200
- # ==============================================================================
- # 工具函数
- # ==============================================================================
- def distance(p1, p2):
- """
- 计算两点之间的欧几里得距离。
- 参数:
- p1 : (x, y) 元组,第一个点
- p2 : (x, y) 元组,第二个点
- 返回:
- float : 两点间的距离
- """
- # 用勾股定理计算距离:sqrt((x2-x1)^2 + (y2-y1)^2)
- return math.hypot(p2[0] - p1[0], p2[1] - p1[1])
- def clamp(value, min_val, max_val):
- """
- 将数值限制在 [min_val, max_val] 区间内。
- 参数:
- value : 要限制的数值
- min_val : 下限
- max_val : 上限
- 返回:
- 限制后的数值
- """
- # 如果 value 小于下限,返回下限;大于上限,返回上限;否则返回原值
- if value < min_val:
- return min_val
- if value > max_val:
- return max_val
- return value
- # ==============================================================================
- # 子弹类(所有子弹统一用此类,通过 color / is_square 区分外观)
- # ==============================================================================
- class Bullet:
- """
- 子弹类:代表从塔/远程小兵/远程怪物/主角色射出的飞行道具。
- 所有阵营的子弹共用此类,通过参数区分外观和伤害归属。
- """
- def __init__(self, x, y, target_x, target_y, speed, damage, color, is_square=False, size=6):
- """
- 初始化一颗子弹。
- 参数:
- x, y : 子弹出生位置(像素坐标)
- target_x, y : 子弹飞向的目标位置(像素坐标)
- speed : 飞行速度(像素/秒)
- damage : 命中时造成的伤害值
- color : 子弹颜色 (R,G,B)
- is_square : True=正方形外观(塔/主角色子弹),False=圆形外观
- size : 子弹边长/直径(像素)
- """
- # 记录出生坐标
- self.x = x
- self.y = y
- # 计算子弹到目标的单位方向向量
- dx = target_x - x
- dy = target_y - y
- dist = math.hypot(dx, dy)
- # 避免除零(目标与出生点重合时)
- if dist == 0:
- self.vx = 0
- self.vy = 0
- else:
- # 单位向量乘以速度得到每帧位移分量
- self.vx = dx / dist * speed
- self.vy = dy / dist * speed
- # 飞行速度(像素/秒)
- self.speed = speed
- # 伤害值
- self.damage = damage
- # 子弹颜色
- self.color = color
- # 是否为正方形外观
- self.is_square = is_square
- # 子弹尺寸(边长或直径)
- self.size = size
- # 存活标记,False 时会被从列表中移除
- self.alive = True
- def update(self, dt):
- """
- 更新子弹位置。
- 参数:
- dt : 上一帧到这一帧的时间间隔(秒)
- """
- # 根据速度和时间增量移动子弹
- self.x += self.vx * dt
- self.y += self.vy * dt
- # 如果子弹飞出屏幕范围,标记为死亡
- if (self.x < -20 or self.x > SCREEN_WIDTH + 20 or
- self.y < -20 or self.y > SCREEN_HEIGHT + 20):
- self.alive = False
- def draw(self, screen):
- """
- 在屏幕上绘制子弹。
- 参数:
- screen : pygame 的 Surface 对象(游戏窗口)
- """
- if self.is_square:
- # 正方形子弹:用 pygame.draw.rect 绘制
- rect = pygame.Rect(self.x - self.size / 2, self.y - self.size / 2,
- self.size, self.size)
- pygame.draw.rect(screen, self.color, rect)
- else:
- # 圆形子弹:用 pygame.draw.circle 绘制
- pygame.draw.circle(screen, self.color, (int(self.x), int(self.y)),
- self.size // 2)
- def get_rect(self):
- """
- 获取子弹的碰撞矩形(用于检测是否击中目标)。
- 返回:
- pygame.Rect 对象
- """
- # 以子弹中心为基准,生成一个边长=size 的正方形碰撞框
- return pygame.Rect(self.x - self.size / 2, self.y - self.size / 2,
- self.size, self.size)
- # ==============================================================================
- # 小兵基类(近战小兵 & 远程小兵的公共属性和行为)
- # ==============================================================================
- class Soldier:
- """
- 小兵基类(我方单位)。
- 近战小兵 = 白色正方形;远程小兵 = 白色圆形。
- 小兵会围绕主角色(或玩家放置的红色三角标)行动,并自动攻击范围内的怪物。
- """
- def __init__(self, x, y, soldier_type):
- """
- 初始化一个小兵。
- 参数:
- x, y : 出生位置
- soldier_type : 'melee' 近战(正方形) / 'ranged' 远程(圆形)
- """
- # 出生坐标
- self.x = x
- self.y = y
- # 小兵类型
- self.soldier_type = soldier_type
- # 近战用正方形,远程用圆形
- if soldier_type == 'melee':
- self.is_square = True # 外观为正方形
- self.size = 20 # 边长
- self.max_hp = 60 # 最大生命值
- self.speed = 90 # 移动速度(像素/秒)
- self.attack_range = 28 # 近战攻击距离
- self.attack_damage = 12 # 每次攻击伤害
- self.attack_cooldown = 800 # 攻击冷却(毫秒)
- else:
- self.is_square = False # 外观为圆形
- self.size = 18 # 直径
- self.max_hp = 40
- self.speed = 75
- self.attack_range = 160 # 远程攻击距离
- self.attack_damage = 8
- self.attack_cooldown = RANGED_COOLDOWN
- # 当前生命值
- self.hp = self.max_hp
- # 是否存活
- self.alive = True
- # 攻击冷却计时器(毫秒),大于 0 时不能攻击
- self.cooldown_timer = 0
- # 当前锁定的攻击目标(怪物对象),None 表示无目标
- self.target = None
- # 围绕目标(主角色/三角标)的当前角度(弧度),用于圆周运动
- self.orbit_angle = random.uniform(0, 2 * math.pi)
- # 围绕半径(随机一个值,让小兵分散开)
- self.orbit_radius = random.uniform(SOLDIER_ORBIT_MIN, SOLDIER_ORBIT_MAX)
- def update(self, dt, monsters, marker_pos, hero_pos, bullets_soldier):
- """
- 更新小兵状态:移动 + 索敌 + 攻击。
- 参数:
- dt : 时间增量(秒)
- monsters : 怪物列表(用于索敌)
- marker_pos : 三角标位置 (x,y) 或 None
- hero_pos : 主角色位置 (x,y)
- bullets_soldier: 我方子弹列表(远程小兵射击时往里添加)
- """
- # 冷却计时器随时间递减
- if self.cooldown_timer > 0:
- self.cooldown_timer -= dt * 1000
- # 确定围绕中心:优先三角标,其次主角色
- if marker_pos is not None:
- cx, cy = marker_pos
- else:
- cx, cy = hero_pos
- # ---- 索敌:找射程内最近的怪物 ----
- closest = None
- closest_dist = self.attack_range
- for m in monsters:
- if not m.alive:
- continue
- d = distance((self.x, self.y), (m.x, m.y))
- if d < closest_dist:
- closest_dist = d
- closest = m
- self.target = closest
- if self.target is not None:
- # 有目标且在射程内:停下进行攻击
- if self.soldier_type == 'ranged':
- # 远程:发射子弹
- if self.cooldown_timer <= 0:
- b = Bullet(self.x, self.y, self.target.x, self.target.y,
- 280, self.attack_damage, COLOR_WHITE,
- is_square=False, size=6)
- bullets_soldier.append(b)
- self.cooldown_timer = self.attack_cooldown
- else:
- # 近战:直接扣血
- if self.cooldown_timer <= 0:
- self.target.take_damage(self.attack_damage)
- self.cooldown_timer = self.attack_cooldown
- else:
- # 无目标:围绕中心做圆周运动
- self.orbit_angle += dt * 0.6 # 角速度
- # 计算目标位置(圆周上的点)
- tx = cx + math.cos(self.orbit_angle) * self.orbit_radius
- ty = cy + math.sin(self.orbit_angle) * self.orbit_radius
- # 平滑移向目标点
- dx = tx - self.x
- dy = ty - self.y
- dist = math.hypot(dx, dy)
- if dist > 2:
- self.x += dx / dist * self.speed * dt
- self.y += dy / dist * self.speed * dt
- # 限制在屏幕内
- self.x = clamp(self.x, 10, SCREEN_WIDTH - 10)
- self.y = clamp(self.y, 10, SCREEN_HEIGHT - 10)
- def take_damage(self, dmg):
- """
- 受到伤害。
- 参数:
- dmg : 伤害值
- """
- self.hp -= dmg
- if self.hp <= 0:
- self.alive = False
- def draw(self, screen):
- """
- 绘制小兵。
- 参数:
- screen : 游戏窗口 Surface
- """
- if self.is_square:
- # 近战小兵:白色正方形
- rect = pygame.Rect(self.x - self.size / 2, self.y - self.size / 2,
- self.size, self.size)
- pygame.draw.rect(screen, COLOR_WHITE, rect)
- # 加一个浅灰边框便于辨识
- pygame.draw.rect(screen, (200, 200, 200), rect, 1)
- else:
- # 远程小兵:白色圆形
- pygame.draw.circle(screen, COLOR_WHITE, (int(self.x), int(self.y)),
- self.size // 2)
- pygame.draw.circle(screen, (200, 200, 200),
- (int(self.x), int(self.y)), self.size // 2, 1)
- # 绘制血条(在头顶)
- bar_w = 22
- bar_h = 3
- bx = self.x - bar_w / 2
- by = self.y - self.size / 2 - 8
- pygame.draw.rect(screen, (60, 60, 60), (bx, by, bar_w, bar_h))
- hp_ratio = max(0, self.hp / self.max_hp)
- pygame.draw.rect(screen, COLOR_GREEN, (bx, by, bar_w * hp_ratio, bar_h))
- # ==============================================================================
- # 怪物类(近战怪物 & 远程怪物)
- # ==============================================================================
- class Monster:
- """
- 怪物类(敌方单位)。
- 近战怪物 = 黑色正方形;远程怪物 = 黑色圆形。
- 怪物沿一条固定的、拐几个弯的路径从屏幕顶部向底部(或左向右)行进。
- """
- def __init__(self, x, y, monster_type, path_points):
- """
- 初始化一个怪物。
- 参数:
- x, y : 出生位置
- monster_type : 'melee' 近战(正方形) / 'ranged' 远程(圆形)
- path_points : 路径点列表 [(x1,y1),(x2,y2),...],怪物依次走过
- """
- self.x = x
- self.y = y
- self.monster_type = monster_type
- # 路径点副本
- self.path = list(path_points)
- # 当前目标路径点索引
- self.path_index = 0
- if monster_type == 'melee':
- self.is_square = True
- self.size = 22
- self.max_hp = 50
- self.speed = 60
- self.attack_range = 30
- self.attack_damage = 10
- self.attack_cooldown = 900
- else:
- self.is_square = False
- self.size = 20
- self.max_hp = 35
- self.speed = 50
- self.attack_range = 150
- self.attack_damage = 7
- self.attack_cooldown = RANGED_COOLDOWN
- self.hp = self.max_hp
- self.alive = True
- self.cooldown_timer = 0
- self.target = None # 锁定的小兵目标
- def update(self, dt, soldiers, bullets_monster):
- """
- 更新怪物:沿路径移动 + 索敌 + 攻击。
- 参数:
- dt : 时间增量(秒)
- soldiers : 我方小兵列表(用于索敌)
- bullets_monster : 敌方子弹列表(远程怪物射击时添加)
- """
- if self.cooldown_timer > 0:
- self.cooldown_timer -= dt * 1000
- # ---- 索敌:找射程内最近的小兵 ----
- closest = None
- closest_dist = self.attack_range
- for s in soldiers:
- if not s.alive:
- continue
- d = distance((self.x, self.y), (s.x, s.y))
- if d < closest_dist:
- closest_dist = d
- closest = s
- self.target = closest
- if self.target is not None:
- # 有目标:停下攻击
- if self.monster_type == 'ranged':
- if self.cooldown_timer <= 0:
- b = Bullet(self.x, self.y, self.target.x, self.target.y,
- 240, self.attack_damage, COLOR_BLACK,
- is_square=False, size=6)
- bullets_monster.append(b)
- self.cooldown_timer = self.attack_cooldown
- else:
- if self.cooldown_timer <= 0:
- self.target.take_damage(self.attack_damage)
- self.cooldown_timer = self.attack_cooldown
- else:
- # 无目标:沿路径前进
- if self.path_index < len(self.path):
- tx, ty = self.path[self.path_index]
- dx = tx - self.x
- dy = ty - self.y
- dist = math.hypot(dx, dy)
- if dist < 5:
- # 到达当前路径点,前往下一个
- self.path_index += 1
- else:
- self.x += dx / dist * self.speed * dt
- self.y += dy / dist * self.speed * dt
- def take_damage(self, dmg):
- """受到伤害。"""
- self.hp -= dmg
- if self.hp <= 0:
- self.alive = False
- def draw(self, screen):
- """绘制怪物。"""
- if self.is_square:
- # 近战怪物:黑色正方形
- rect = pygame.Rect(self.x - self.size / 2, self.y - self.size / 2,
- self.size, self.size)
- pygame.draw.rect(screen, COLOR_BLACK, rect)
- pygame.draw.rect(screen, (50, 50, 50), rect, 1)
- else:
- # 远程怪物:黑色圆形
- pygame.draw.circle(screen, COLOR_BLACK, (int(self.x), int(self.y)),
- self.size // 2)
- pygame.draw.circle(screen, (50, 50, 50),
- (int(self.x), int(self.y)), self.size // 2, 1)
- # 血条
- bar_w = 24
- bar_h = 3
- bx = self.x - bar_w / 2
- by = self.y - self.size / 2 - 8
- pygame.draw.rect(screen, (60, 60, 60), (bx, by, bar_w, bar_h))
- hp_ratio = max(0, self.hp / self.max_hp)
- pygame.draw.rect(screen, (220, 60, 60), (bx, by, bar_w * hp_ratio, bar_h))
- def reached_end(self):
- """
- 判断怪物是否已走完全部路径点(到达终点)。
- 返回:
- True/False
- """
- return self.path_index >= len(self.path)
- # ==============================================================================
- # 塔类(玩家按 T 键在主角色位置建造)
- # ==============================================================================
- class Tower:
- """
- 塔类:玩家按 T 键在主角色当前位置建造。
- 外观:红色正方形。自动攻击射程内最近的怪物,发射红色小正方形子弹。
- """
- def __init__(self, x, y):
- """初始化一座塔。"""
- self.x = x
- self.y = y
- self.size = 26 # 塔的边长
- self.range = TOWER_RANGE # 攻击范围
- self.damage = 18 # 每次命中伤害
- self.cooldown_max = TOWER_COOLDOWN # 射击冷却(毫秒)
- self.cooldown_timer = 0 # 当前冷却计时
- self.alive = True # 塔始终存活(本验证版不可被攻击)
- def update(self, dt, monsters, bullets_tower):
- """
- 塔的逻辑更新:索敌 + 射击。
- 参数:
- dt : 时间增量(秒)
- monsters : 怪物列表
- bullets_tower: 塔子弹列表(射击时添加)
- """
- if self.cooldown_timer > 0:
- self.cooldown_timer -= dt * 1000
- # 找射程内最近的怪物
- closest = None
- closest_dist = self.range
- for m in monsters:
- if not m.alive:
- continue
- d = distance((self.x, self.y), (m.x, m.y))
- if d < closest_dist:
- closest_dist = d
- closest = m
- if closest is not None and self.cooldown_timer <= 0:
- # 发射红色小正方形子弹
- b = Bullet(self.x, self.y, closest.x, closest.y,
- 320, self.damage, COLOR_RED, is_square=True, size=8)
- bullets_tower.append(b)
- self.cooldown_timer = self.cooldown_max
- def draw(self, screen):
- """绘制塔(红色正方形 + 攻击范围圈)。"""
- # 先画半透明的攻击范围圈(淡红色)
- surf = pygame.Surface((self.range * 2, self.range * 2), pygame.SRCALPHA)
- pygame.draw.circle(surf, (220, 30, 30, 25),
- (self.range, self.range), self.range)
- screen.blit(surf, (self.x - self.range, self.y - self.range))
- # 画塔主体(红色正方形)
- rect = pygame.Rect(self.x - self.size / 2, self.y - self.size / 2,
- self.size, self.size)
- pygame.draw.rect(screen, COLOR_RED, rect)
- pygame.draw.rect(screen, (255, 120, 120), rect, 2)
- # ==============================================================================
- # 主角色类(玩家操控的红色正方形)
- # ==============================================================================
- class Hero:
- """
- 主角色类:玩家用方向键/WASD 操控的红色正方形。
- 小兵默认围绕主角色行动;玩家可在地图上放置红色三角标,
- 放置后小兵改为围绕三角标行动。
- """
- def __init__(self, x, y):
- """初始化主角色。"""
- self.x = x
- self.y = y
- self.size = 24 # 边长
- self.speed = HERO_SPEED # 移动速度
- self.hp = 100 # 生命值
- self.max_hp = 100
- self.alive = True
- def update(self, dt, keys):
- """
- 根据按键更新主角色位置。
- 参数:
- dt : 时间增量(秒)
- keys : pygame 按键状态字典
- """
- dx = 0
- dy = 0
- # 检测方向键和 WASD
- if keys[pygame.K_LEFT] or keys[pygame.K_a]:
- dx -= 1
- if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
- dx += 1
- if keys[pygame.K_UP] or keys[pygame.K_w]:
- dy -= 1
- if keys[pygame.K_DOWN] or keys[pygame.K_s]:
- dy += 1
- # 归一化对角线移动(防止斜向速度过快)
- if dx != 0 and dy != 0:
- dx *= 0.7071
- dy *= 0.7071
- self.x += dx * self.speed * dt
- self.y += dy * self.speed * dt
- # 限制在屏幕内
- self.x = clamp(self.x, self.size / 2, SCREEN_WIDTH - self.size / 2)
- self.y = clamp(self.y, self.size / 2, SCREEN_HEIGHT - self.size / 2)
- def draw(self, screen):
- """绘制主角色(红色正方形 + 血条)。"""
- rect = pygame.Rect(self.x - self.size / 2, self.y - self.size / 2,
- self.size, self.size)
- pygame.draw.rect(screen, COLOR_RED, rect)
- pygame.draw.rect(screen, (255, 160, 160), rect, 2)
- # 血条
- bar_w = 30
- bar_h = 4
- bx = self.x - bar_w / 2
- by = self.y - self.size / 2 - 10
- pygame.draw.rect(screen, (60, 60, 60), (bx, by, bar_w, bar_h))
- hp_ratio = max(0, self.hp / self.max_hp)
- pygame.draw.rect(screen, COLOR_GREEN, (bx, by, bar_w * hp_ratio, bar_h))
- # ==============================================================================
- # 红色三角标类(玩家鼠标左键放置的集结点)
- # ==============================================================================
- class Marker:
- """
- 红色三角标:玩家鼠标左键点击地图后生成。
- 小兵检测到三角标存在时,改为围绕三角标而非主角色行动。
- """
- def __init__(self, x, y):
- """初始化三角标。"""
- self.x = x
- self.y = y
- self.size = 16 # 三角标尺寸
- self.alive = True
- # 脉冲动画计时(让三角标有呼吸效果)
- self.pulse = 0
- def update(self, dt):
- """更新脉冲动画计时。"""
- self.pulse += dt * 4
- def draw(self, screen):
- """绘制红色三角标(向下指的三角形 + 外圈脉冲)。"""
- # 脉冲外圈
- pulse_r = self.size + math.sin(self.pulse) * 6 + 6
- pygame.draw.circle(screen, (230, 210, 30, 60),
- (int(self.x), int(self.y)), int(pulse_r), 1)
- # 三角形三个顶点(指向下方)
- s = self.size
- p1 = (self.x, self.y - s * 0.7) # 上顶点
- p2 = (self.x - s * 0.6, self.y + s * 0.5) # 左下
- p3 = (self.x + s * 0.6, self.y + s * 0.5) # 右下
- pygame.draw.polygon(screen, COLOR_YELLOW, [p1, p2, p3])
- pygame.draw.polygon(screen, (180, 160, 20), [p1, p2, p3], 2)
- # ==============================================================================
- # 游戏主类(管理所有状态和主循环)
- # ==============================================================================
- class Game:
- """游戏主类,负责初始化、主循环、事件处理、碰撞检测等。"""
- def __init__(self):
- """初始化 pygame 和游戏状态。"""
- # 初始化 pygame 视频模块
- pygame.init()
- pygame.display.set_caption(u"塔防游戏")
- # 创建游戏窗口
- self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
- # 创建时钟对象,用于限制帧率
- self.clock = pygame.time.Clock()
- # 创建字体对象(默认字体,支持中文需系统有中文字体)
- # 使用 pygame.font.SysFont 并指定中文字体名称列表,避免中文乱码
- self.font = pygame.font.SysFont(
- "noto sans cjk sc,wenquanyi micro hei,microsoft yahei,simhei,arial",
- 20)
- self.font_small = pygame.font.SysFont(
- "noto sans cjk sc,wenquanyi micro hei,microsoft yahei,simhei,arial",
- 16)
- self.font_big = pygame.font.SysFont(
- "noto sans cjk sc,wenquanyi micro hei,microsoft yahei,simhei,arial",
- 48)
- # ---- 游戏状态变量 ----
- self.running = True # 主循环是否继续
- self.game_over = False # 是否游戏结束
- self.score = 0 # 当前得分
- self.leaked = 0 # 漏掉的怪物数
- self.max_leaked = 10 # 允许漏掉的最大值,超过则游戏结束
- self.wave = 1 # 当前波次
- self.wave_timer = 0 # 波次内计时(毫秒)
- self.spawn_timer = 0 # 怪物生成计时(毫秒)
- self.monsters_to_spawn = 0 # 当前波次剩余待生成怪物数
- # ---- 游戏对象列表 ----
- self.hero = Hero(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2)
- self.marker = None # 红色三角标(None=未放置)
- self.soldiers = [] # 我方小兵列表
- self.monsters = [] # 敌方怪物列表
- self.towers = [] # 塔列表
- self.bullets_tower = [] # 塔发射的子弹
- self.bullets_soldier = [] # 小兵发射的子弹
- self.bullets_monster = [] # 怪物发射的子弹
- # ---- 固定路径定义(怪物走这条路径,拐几个弯)----
- # 路径:从屏幕上方中间出发,先向右下,再向左,再向下出屏
- self.monster_path = [
- (SCREEN_WIDTH // 2, -20), # 起点(屏外上方)
- (SCREEN_WIDTH // 2, 120), # 第一段:向下
- (SCREEN_WIDTH - 120, 120), # 第二段:向右
- (SCREEN_WIDTH - 120, 380), # 第三段:向下
- (140, 380), # 第四段:向左
- (140, SCREEN_HEIGHT - 60), # 第五段:向下
- (SCREEN_WIDTH // 2, SCREEN_HEIGHT - 60), # 第六段:向右
- (SCREEN_WIDTH // 2, SCREEN_HEIGHT + 30), # 终点(屏外下方)
- ]
- # 启动第一波
- self._start_wave()
- # --------------------------------------------------------------------------
- # 内部方法
- # --------------------------------------------------------------------------
- def _start_wave(self):
- """开始新的一波:计算本波要生成的怪物数量和间隔。"""
- # 每波怪物数量 = 5 + 波次*2
- self.monsters_to_spawn = 5 + self.wave * 2
- # 生成间隔随波次递减(最快 600ms)
- self.spawn_interval = max(600, 1800 - self.wave * 150)
- self.spawn_timer = 0
- def _spawn_monster(self):
- """生成一只怪物(从路径起点出发)。"""
- # 起点为路径第一个点
- sx, sy = self.monster_path[0]
- # 70% 近战 + 30% 远程,随波次提高远程比例
- ranged_chance = min(0.4, 0.2 + self.wave * 0.03)
- mtype = 'ranged' if random.random() < ranged_chance else 'melee'
- # 怪物的生命值随波次提升
- m = Monster(sx, sy, mtype, self.monster_path)
- m.max_hp = int(m.max_hp * (1 + self.wave * 0.15))
- m.hp = m.max_hp
- self.monsters.append(m)
- def _handle_events(self):
- """处理 pygame 事件队列(键盘/鼠标/退出)。"""
- # 遍历所有待处理事件
- for event in pygame.event.get():
- if event.type == pygame.QUIT:
- # 点击关闭按钮
- self.running = False
- elif event.type == pygame.KEYDOWN:
- # 按键按下
- self._handle_keydown(event.key)
- elif event.type == pygame.MOUSEBUTTONDOWN:
- # 鼠标按键
- self._handle_mouse(event)
- def _handle_keydown(self, key):
- """
- 处理按键事件。
- 参数:
- key : 被按下的键(pygame 常量)
- """
- if self.game_over:
- # 游戏结束后只响应 R 键重新开始
- if key == pygame.K_r:
- self._restart()
- elif key == pygame.K_ESCAPE:
- self.running = False
- return
- if key == pygame.K_1:
- # 1 键:在主角色附近生成一支近战小兵
- self._spawn_soldier('melee')
- elif key == pygame.K_2:
- # 2 键:在主角色附近生成一支远程小兵
- self._spawn_soldier('ranged')
- elif key == pygame.K_t:
- # T 键:在主角色当前位置建造一座塔
- print(0)
- self._build_tower()
- elif key == pygame.K_ESCAPE:
- self.running = False
- def _handle_mouse(self, event):
- """
- 处理鼠标事件。
- 参数:
- event : pygame 鼠标事件对象
- """
- if self.game_over:
- return
- if event.button == 1:
- # 鼠标左键:在点击位置放置/移动红色三角标
- mx, my = event.pos
- self.marker = Marker(mx, my)
- elif event.button == 3:
- # 鼠标右键:取消三角标
- self.marker = None
- def _spawn_soldier(self, stype):
- """
- 在主角色附近随机偏移位置生成一支小兵。
- 参数:
- stype : 'melee' 或 'ranged'
- """
- # 在主角位置附近随机偏移,避免重叠
- ox = random.uniform(-30, 30)
- oy = random.uniform(-30, 30)
- s = Soldier(self.hero.x + ox, self.hero.y + oy, stype)
- self.soldiers.append(s)
- def _build_tower(self):
- """在主角色当前位置建造一座塔(最多允许 8 座)。"""
- if len(self.towers) >= 8:
- return # 达到上限
- # 检查该位置是否已有塔(避免重叠)
- print(1)
- for t in self.towers:
- if distance((t.x, t.y), (self.hero.x, self.hero.y)) < 30:
- return
- print(2)
- self.towers.append(Tower(self.hero.x, self.hero.y))
- def _update(self, dt):
- """
- 更新所有游戏对象(每帧调用一次)。
- 参数:
- dt : 时间增量(秒)
- """
- if self.game_over:
- return
- # 获取当前按键状态
- keys = pygame.key.get_pressed()
- # 更新主角色
- self.hero.update(dt, keys)
- # 更新三角标(脉冲动画)
- if self.marker:
- self.marker.update(dt)
- # ---- 更新塔 ----
- for t in self.towers:
- t.update(dt, self.monsters, self.bullets_tower)
- # ---- 更新小兵 ----
- marker_pos = (self.marker.x, self.marker.y) if self.marker else None
- hero_pos = (self.hero.x, self.hero.y)
- for s in self.soldiers:
- s.update(dt, self.monsters, marker_pos, hero_pos,
- self.bullets_soldier)
- # ---- 更新怪物(生成 + 行为)----
- self._update_monster_spawning(dt)
- for m in self.monsters:
- m.update(dt, self.soldiers, self.bullets_monster)
- # 检测是否走完全程(漏怪)
- if m.reached_end() and m.alive:
- m.alive = False
- self.leaked += 1
- self.score = max(0, self.score - 5) # 漏怪扣分
- if self.leaked >= self.max_leaked:
- self.game_over = True
- # ---- 更新所有子弹 ----
- self._update_bullets(dt)
- # ---- 清理死亡对象 ----
- self._cleanup_dead()
- # ---- 波次推进 ----
- if self.monsters_to_spawn == 0 and len(self.monsters) == 0:
- # 本波清空,进入下一波
- self.wave += 1
- self.score += 50 * self.wave # 过关奖励
- self._start_wave()
- # 得分随时间小幅增长
- self.score += dt * 2
- def _update_monster_spawning(self, dt):
- """管理怪物按间隔逐只生成。"""
- if self.monsters_to_spawn > 0:
- self.spawn_timer += dt * 1000
- if self.spawn_timer >= self.spawn_interval:
- self._spawn_monster()
- self.monsters_to_spawn -= 1
- self.spawn_timer = 0
- def _update_bullets(self, dt):
- """更新子弹位置并检测碰撞。"""
- # --- 塔子弹(打怪物)---
- for b in self.bullets_tower:
- b.update(dt)
- if not b.alive:
- continue
- # 检测是否击中某只怪物
- for m in self.monsters:
- if not m.alive:
- continue
- if b.get_rect().colliderect(m.x - m.size/2, m.y - m.size/2,
- m.size, m.size):
- m.take_damage(b.damage)
- b.alive = False
- if not m.alive:
- self.score += 10 # 击杀得分
- break
- # --- 小兵子弹(打怪物)---
- for b in self.bullets_soldier:
- b.update(dt)
- if not b.alive:
- continue
- for m in self.monsters:
- if not m.alive:
- continue
- if b.get_rect().colliderect(m.x - m.size/2, m.y - m.size/2,
- m.size, m.size):
- m.take_damage(b.damage)
- b.alive = False
- if not m.alive:
- self.score += 5
- break
- # --- 怪物子弹(打小兵)---
- for b in self.bullets_monster:
- b.update(dt)
- if not b.alive:
- continue
- for s in self.soldiers:
- if not s.alive:
- continue
- if b.get_rect().colliderect(s.x - s.size/2, s.y - s.size/2,
- s.size, s.size):
- s.take_damage(b.damage)
- b.alive = False
- break
- def _cleanup_dead(self):
- """移除所有标记为死亡的子弹/小兵/怪物,释放内存。"""
- self.bullets_tower = [b for b in self.bullets_tower if b.alive]
- self.bullets_soldier = [b for b in self.bullets_soldier if b.alive]
- self.bullets_monster = [b for b in self.bullets_monster if b.alive]
- self.soldiers = [s for s in self.soldiers if s.alive]
- self.monsters = [m for m in self.monsters if m.alive]
- def _draw(self):
- """绘制当前帧的所有内容到屏幕。"""
- # 填充背景色
- self.screen.fill(COLOR_BG)
- # 绘制路径辅助线(淡灰色虚线,便于观察怪物行进路线)
- self._draw_path()
- # 绘制三角标
- if self.marker:
- self.marker.draw(self.screen)
- # 绘制塔
- for t in self.towers:
- t.draw(self.screen)
- # 绘制小兵
- for s in self.soldiers:
- s.draw(self.screen)
- # 绘制怪物
- for m in self.monsters:
- m.draw(self.screen)
- # 绘制主角色(最后绘制,显示在最上层)
- self.hero.draw(self.screen)
- # 绘制所有子弹
- for b in self.bullets_tower:
- b.draw(self.screen)
- for b in self.bullets_soldier:
- b.draw(self.screen)
- for b in self.bullets_monster:
- b.draw(self.screen)
- # 绘制 UI(得分/波次/漏怪数/操作提示)
- self._draw_ui()
- # 如果游戏结束,绘制半透明遮罩 + 结束文字
- if self.game_over:
- self._draw_game_over()
- # 将绘制内容刷新到屏幕
- pygame.display.flip()
- def _draw_path(self):
- """绘制怪物路径的辅助线(浅灰虚线)。"""
- pts = [(int(p[0]), int(p[1])) for p in self.monster_path]
- if len(pts) < 2:
- return
- for i in range(len(pts) - 1):
- # 用浅灰色画路径线段
- pygame.draw.line(self.screen, (60, 60, 72), pts[i], pts[i+1], 2)
- # 在路径拐点画小圆点标记
- for p in pts:
- pygame.draw.circle(self.screen, (80, 80, 95), p, 4)
- def _draw_ui(self):
- """绘制顶部状态栏和操作提示。"""
- # ---- 顶部状态信息 ----
- info_lines = [
- u"得分: %d" % int(self.score),
- u"波次: %d" % self.wave,
- u"漏怪: %d/%d" % (self.leaked, self.max_leaked),
- u"小兵: %d" % len(self.soldiers),
- u"塔: %d/8" % len(self.towers),
- ]
- x0 = 10
- y0 = 8
- for i, line in enumerate(info_lines):
- surf = self.font_small.render(line, True, (220, 220, 220))
- self.screen.blit(surf, (x0, y0 + i * 20))
- # ---- 右下角操作提示 ----
- tips = [
- u"[方向键/WASD] 移动主角",
- u"[鼠标左键] 放置红色三角标",
- u"[鼠标右键] 取消三角标",
- u"[1] 生成近战小兵(白方块)",
- u"[2] 生成远程小兵(白圆)",
- u"[T] 在主角位置建塔",
- u"[R] 重新开始 [ESC] 退出",
- ]
- for i, tip in enumerate(tips):
- surf = self.font_small.render(tip, True, (170, 170, 170))
- self.screen.blit(surf, (SCREEN_WIDTH - 280,
- SCREEN_HEIGHT - 140 + i * 20))
- # 三角标状态提示
- if self.marker:
- surf = self.font_small.render(u"小兵围绕三角标行动中",
- True, COLOR_YELLOW)
- self.screen.blit(surf, (SCREEN_WIDTH // 2 - 80, 10))
- def _draw_game_over(self):
- """绘制游戏结束画面(半透明遮罩 + 文字)。"""
- # 半透明黑色遮罩
- overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT),
- pygame.SRCALPHA)
- overlay.fill((0, 0, 0, 160))
- self.screen.blit(overlay, (0, 0))
- # 游戏结束大字
- text = self.font_big.render(u"游戏结束", True, (230, 60, 60))
- rect = text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 40))
- self.screen.blit(text, rect)
- # 得分信息
- sub = self.font.render(u"最终得分: %d 到达波次: %d" %
- (int(self.score), self.wave), True,
- (220, 220, 220))
- rect2 = sub.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 10))
- self.screen.blit(sub, rect2)
- # 提示按键
- tip = self.font.render(u"按 [R] 重新开始 按 [ESC] 退出",
- True, (180, 180, 180))
- rect3 = tip.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 50))
- self.screen.blit(tip, rect3)
- def _restart(self):
- print("restart!!")
- """重置所有游戏状态,开始新一局。"""
- self.__init__()
- # --------------------------------------------------------------------------
- # 主循环
- # --------------------------------------------------------------------------
- def run(self):
- """游戏主循环:持续处理事件、更新状态、绘制画面。"""
- while self.running:
- # 计算上一帧到这一帧的时间间隔(秒),用于帧率无关运动
- dt = self.clock.tick(FPS) / 1000.0
- # 限制 dt 上限,防止切到后台后回来出现巨大跳跃
- if dt > 0.1:
- dt = 0.1
- # 处理事件
- self._handle_events()
- # 更新游戏状态
- self._update(dt)
- # 绘制当前帧
- self._draw()
- # 退出 pygame
- pygame.quit()
- sys.exit()
- # ==============================================================================
- # 程序入口
- # ==============================================================================
- if __name__ == "__main__":
- # 创建游戏实例并启动主循环
- game = Game()
- game.run()
复制代码 |
评分
-
查看全部评分
|