现在我们的主场景中还没有敌机,我们接下来实现多个敌机从上向屏幕下方移动,并能够随机发射子弹。
1. EnemyPlane 类
该类的定义了单个敌机的实现,主要实现方法如下:
- set_used 设置飞机可用,并随机设置其初始位置
- set_unused 设置飞机不可用,并将其设置到不合法的位置
- calc_position 计算敌机位置、以及敌机发射的子弹位置
- draw_element 绘制敌机、以及敌机子弹图像
- shoot 敌机发射子弹
class EnemyPlane: def __init__(self, scene, speed=10): # 游戏主场景 self.scene = scene # 敌机资源 self.image = pygame.image.load(f'source/plane/enemy-{random.randint(1, 7)}.png') # 敌机边框 self.bbox = self.image.get_rect() # 移动速度 self.speed = speed # 是否可见 self.visible = False # 初始化子弹 self.bullet = Bullet(scene, is_enemy=True) def set_used(self, start_x, start_y): self.bbox[0] = start_x self.bbox[1] = start_y self.speed = random.randint(4, 8) self.visible = True def calc_position(self): # 计算飞机位置 if self.visible: self.bbox.move_ip(0, self.speed) if self.bbox[1] > SCENE_H: self.set_unused() # 计算子弹位置 if self.bullet.visible: self.bullet.move(0, self.speed + 5) def draw_element(self): # 绘制飞机 if self.visible: self.scene.blit(self.image, self.bbox) # 绘制子弹 if self.bullet.visible: self.bullet.draw_element() def set_unused(self): self.visible = False self.bbox[0] = -1000 self.bbox[1] = -1000 def shoot(self): if self.bullet.visible: return start_x = self.bbox[0] + self.bbox[2]/2 - self.bullet.bbox[2]/2 start_y = self.bbox[1] + self.bbox[3] - 10 self.bullet.set_used(start_x, start_y)
测试 EnemyPlane 类:
if __name__ == '__main__': pygame.init() window = pygame.display.set_mode([512, 768]) clock = pygame.time.Clock() enemy = EnemyPlane(window) enemy.set_used(random.randint(0, SCENE_W - enemy.bbox[2]), -enemy.bbox[3]) index = 0 while True: # 清空窗口 window.fill((0, 0, 0)) # 计算位置 enemy.calc_position() # 绘制敌机 enemy.draw_element() # 发射子弹 index += 1 if index > 120 and random.randint(1, 100) > 80 and enemy.bbox[1] < 300: enemy.shoot() index = 0 # 敌机复活 if not enemy.visible: enemy.set_used(random.randint(0, SCENE_W - enemy.bbox[2]), -enemy.bbox[3]) pygame.event.get() pygame.display.update() clock.tick(60)
2. EnemyManager 类
该类负责组织多个敌机的行为,例如:何时、从那个位置进入游戏场景,以及什么时候发射子弹等。
- calc_position 计算每一个敌机的位置
- draw_element 绘制每一个敌机
- set_out 飞机出场的策略,每次随机选择 1-4 个飞机,并从屏幕上方随机位置进场
- shoot 控制所有敌机的发射子弹的行为,里面使用了随机数,目的是为了让不同的飞机不要同时发射子弹
上面函数中,set_out 和 shoot 是定时器事件调用,前者每间隔 3 秒调用一次,后者每 1 秒调用一次。
class EnemyManager: ENEMY_START_EVENT = pygame.USEREVENT + 1 ENEMY_SHOOT_EVENT = pygame.USEREVENT + 2 def __init__(self, scene): # 初始化敌机 self.enemies = [EnemyPlane(scene) for _ in range(8)] # 定时器事件 pygame.time.set_timer(EnemyManager.ENEMY_START_EVENT, 2000) pygame.time.set_timer(EnemyManager.ENEMY_SHOOT_EVENT, 1000) def calc_position(self): for enemy in self.enemies: enemy.calc_position() def draw_element(self): for enemy in self.enemies: enemy.draw_element() def set_out(self): # 随机选择 1- 4 个飞机 number = random.randint(1, 4) # 候选敌机 wait_for_out = [] for enemy in self.enemies: if not enemy.visible: wait_for_out.append(enemy) if len(wait_for_out) == number: break # 敌机出发 if len(wait_for_out) == number: # 敌机位置 position_xs = [] range_distance = int((SCENE_W - 100) / number) for index in range(number): x = random.randint(index * range_distance, index * range_distance + range_distance - 100) position_xs.append(x) # 敌机出发 for enemy, x in zip(wait_for_out, position_xs): enemy.set_used(x, -enemy.bbox[3]) def shoot(self): for enemy in self.enemies: if not enemy.visible: continue if random.randint(1, 100) > 50 and enemy.bbox[1] < 500: enemy.shoot()
测试 EnemyManager 类:
if __name__ == '__main__': pygame.init() window = pygame.display.set_mode([512, 768]) clock = pygame.time.Clock() # 初始化敌机管理器 enemy = EnemyManager(window) while True: # 清空窗口 window.fill((0, 0, 0)) # 敌机位置 enemy.calc_position() # 绘制敌机 enemy.draw_element() # 处理定时器事件 events = pygame.event.get() for event in events: if event.type == EnemyManager.ENEMY_START_EVENT: enemy.set_out() if event.type == EnemyManager.ENEMY_SHOOT_EVENT: enemy.shoot() pygame.display.update() clock.tick(60)
3. 加入主场景
敌机创建好之后,接下来,我们将其初始化到主场景中。
import random import pygame from Config import * from GameMap import GameMap from HeroPlane import HeroPlane from EnemyManager import EnemyManager import sys # 主场景 class MainScene: # 初始化主场景 def __init__(self): # 初始化组件 pygame.init() # 初始化时钟 self.clock = pygame.time.Clock() # 初始化游戏窗口 self.scene = pygame.display.set_mode((SCENE_W, SCENE_H)) # 设置窗口标题 pygame.display.set_caption("飞机大战-v1.0 作者: 孟宝亮") # 初始化游戏元素 self.init_elements() # 初始化游戏元素 def init_elements(self): # 初始化游戏地图 self.map = GameMap(self.scene) # 初始化英雄飞机 self.hero = HeroPlane(self.scene) # 初始化敌机序列 self.enemy = EnemyManager(self.scene) # 计算坐标 def calc_position(self): # 计算地图坐标 self.map.calc_position() # 计算英雄弹夹坐标 self.hero.calc_position() # 计算敌机位置 self.enemy.calc_position() # 绘制元素 def draw_elements(self): # 绘制滚动地图 self.map.draw_element() # 绘制英雄飞机 self.hero.draw_element() # 绘制敌机 self.enemy.draw_element() # 处理事件 def handle_events(self): # 窗口关闭事件 events = pygame.event.get() for event in events: if event.type == pygame.QUIT: pygame.quit() sys.exit() # 敌机出发事件 if event.type == EnemyManager.ENEMY_START_EVENT: self.enemy.set_out() # 敌机发射子弹 if event.type == EnemyManager.ENEMY_SHOOT_EVENT: self.enemy.shoot() # 获得当前按下的键 keys = pygame.key.get_pressed() # 射击 if keys[pygame.K_j]: self.hero.shoot(1) if keys[pygame.K_k]: self.hero.shoot(3) if keys[pygame.K_l]: self.hero.shoot(5) # 上 if keys[pygame.K_w]: self.hero.top() # 下 if keys[pygame.K_s]: self.hero.bottom() # 左 if keys[pygame.K_a]: self.hero.left() # 右 if keys[pygame.K_d]: self.hero.right() # 碰撞检测 def detect_conlision(self): pass # 主循环 def run(self): while True: # 碰撞检测 self.detect_conlision() # 计算元素坐标 self.calc_position() # 绘制元素图片 self.draw_elements() # 处理事件 self.handle_events() # 刷新显示 pygame.display.update() # 控制帧率 self.clock.tick(60)