【Pygame】第5章 键盘与鼠标事件处理(附有2D射击游戏)

摘要

用户输入是游戏交互的核心,本章将全面介绍Pygame中处理键盘和鼠标输入的各种方法。我们将详细讲解事件驱动和状态查询两种输入处理模式,探讨如何实现平滑的角色移动控制,介绍鼠标位置跟踪和点击检测,以及如何处理鼠标滚轮和相对移动。同时,本章还将涵盖游戏手柄的支持方法。此外,本章将展示如何使用GPT-5.4来生成复杂的输入处理代码和控制方案。由于国内无法访问OpenAI官网,因此使用国内镜像站可以合法注册使用GPT-5.4最新模型。重要提示:翻墙行为违反中国法律法规,请大家不要翻墙,选择合法的国内镜像站使用AI服务 。注册入口:AIGCBAR镜像站。通过本章的学习,读者将能够创建响应灵敏、操作流畅的游戏控制系统。


5.1 输入处理概述

游戏输入处理主要有两种模式:事件驱动模式和状态查询模式。理解这两种模式的特点和适用场景对于设计良好的控制系统至关重要。

5.1.1 事件驱动vs状态查询

特性 事件驱动模式 状态查询模式
触发时机 输入状态改变时 每帧查询时
适用场景 单次动作(如跳跃、射击) 持续动作(如移动、瞄准)
响应延迟 可能有延迟 实时响应
实现复杂度 需要维护事件队列 直接查询当前状态

5.1.2 选择合适的处理模式

在实际游戏中,通常需要结合使用两种模式:

python 复制代码
# 事件驱动:处理单次动作
for event in pygame.event.get():
    if event.type == pygame.KEYDOWN:
        if event.key == pygame.K_SPACE:
            player.jump()  # 跳跃(单次动作)
        elif event.key == pygame.K_f:
            player.shoot()  # 射击(单次动作)

# 状态查询:处理持续动作
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
    player.move_left()  # 持续左移
if keys[pygame.K_RIGHT]:
    player.move_right()  # 持续右移

5.2 键盘输入处理

Pygame提供了丰富的键盘输入处理功能。

5.2.1 键盘事件处理

键盘事件在按键按下和释放时触发:

python 复制代码
# 按键状态显示
key_status = {}

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        
        # 按键按下事件
        elif event.type == pygame.KEYDOWN:
            key_status[event.key] = "按下"
            
            # 特殊按键处理
            if event.key == pygame.K_ESCAPE:
                running = False
            elif event.key == pygame.K_SPACE:
                print("空格键按下")
            elif event.key == pygame.K_RETURN:
                print("回车键按下")
            elif event.key == pygame.K_BACKSPACE:
                print("退格键按下")
            
            # 检测组合键
            if event.mod & pygame.KMOD_CTRL:
                print("Ctrl键被按住")
            if event.mod & pygame.KMOD_SHIFT:
                print("Shift键被按住")
            if event.mod & pygame.KMOD_ALT:
                print("Alt键被按住")
        
        # 按键释放事件
        elif event.type == pygame.KEYUP:
            key_status[event.key] = "释放"
    
    screen.fill((50, 50, 50))
    
    # 显示按键状态
    font = pygame.font.SysFont(None, 24)
    y = 10
    for key, status in list(key_status.items())[-10:]:
        key_name = pygame.key.name(key)
        text = font.render(f"{key_name}: {status}", True, (255, 255, 255))
        screen.blit(text, (10, y))
        y += 30
    
    pygame.display.flip()
    clock.tick(60)

5.2.2 键盘状态查询

使用pygame.key.get_pressed()获取所有按键的当前状态:

python 复制代码
import pygame
import sys

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

# 玩家位置
player_x = 400
player_y = 300
player_speed = 5

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    
    # 获取按键状态
    keys = pygame.key.get_pressed()
    
    # 八方向移动
    if keys[pygame.K_LEFT] or keys[pygame.K_a]:
        player_x -= player_speed
    if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
        player_x += player_speed
    if keys[pygame.K_UP] or keys[pygame.K_w]:
        player_y -= player_speed
    if keys[pygame.K_DOWN] or keys[pygame.K_s]:
        player_y += player_speed
    
    # 加速
    if keys[pygame.K_LSHIFT] or keys[pygame.K_RSHIFT]:
        player_speed = 8
    else:
        player_speed = 5
    
    # 边界限制
    player_x = max(25, min(775, player_x))
    player_y = max(25, min(575, player_y))
    
    # 绘制
    screen.fill((50, 50, 50))
    pygame.draw.circle(screen, (0, 255, 0), (player_x, player_y), 25)
    
    # 显示操作说明
    font = pygame.font.SysFont(None, 24)
    help_text = "WASD或方向键移动,Shift加速"
    text = font.render(help_text, True, (255, 255, 255))
    screen.blit(text, (10, 10))
    
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

5.2.3 常用按键常量

Pygame定义了大量的按键常量:

常量 说明 常量 说明
K_a - K_z 字母键 K_0 - K_9 数字键
K_F1 - K_F15 功能键 K_SPACE 空格键
K_RETURN 回车键 K_ESCAPE Esc键
K_BACKSPACE 退格键 K_TAB Tab键
K_LSHIFT 左Shift K_RSHIFT 右Shift
K_LCTRL 左Ctrl K_RCTRL 右Ctrl
K_LALT 左Alt K_RALT 右Alt
K_UP 方向键上 K_DOWN 方向键下
K_LEFT 方向键左 K_RIGHT 方向键右
K_HOME Home键 K_END End键
K_PAGEUP Page Up K_PAGEDOWN Page Down
K_INSERT Insert键 K_DELETE Delete键

5.2.4 按键重复设置

可以设置按键重复,使得按住按键时自动重复触发KEYDOWN事件:

python 复制代码
# 设置按键重复(延迟毫秒,间隔毫秒)
pygame.key.set_repeat(500, 100)

# 取消按键重复
pygame.key.set_repeat()

5.2.5 文本输入处理

处理文本输入需要特殊处理:

python 复制代码
import pygame
import sys

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

# 启用文本输入事件
pygame.key.start_text_input()

input_text = ""
cursor_visible = True
cursor_timer = 0

running = True
while running:
    dt = clock.tick(60)
    
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        
        # 文本输入事件
        elif event.type == pygame.TEXTINPUT:
            input_text += event.text
        
        # 按键事件(处理特殊键)
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_BACKSPACE:
                input_text = input_text[:-1]
            elif event.key == pygame.K_RETURN:
                print(f"输入完成: {input_text}")
                input_text = ""
            elif event.key == pygame.K_ESCAPE:
                running = False
    
    # 光标闪烁
    cursor_timer += dt
    if cursor_timer > 500:
        cursor_timer = 0
        cursor_visible = not cursor_visible
    
    screen.fill((50, 50, 50))
    
    # 绘制输入框
    pygame.draw.rect(screen, (100, 100, 100), (50, 250, 700, 60))
    pygame.draw.rect(screen, (200, 200, 200), (50, 250, 700, 60), 2)
    
    # 绘制文本
    font = pygame.font.SysFont(None, 36)
    text_surface = font.render(input_text, True, (255, 255, 255))
    screen.blit(text_surface, (60, 260))
    
    # 绘制光标
    if cursor_visible:
        text_width = text_surface.get_width()
        pygame.draw.line(screen, (255, 255, 255), 
                        (60 + text_width, 260), 
                        (60 + text_width, 295), 2)
    
    # 显示提示
    hint = font.render("输入文本,回车确认,退格删除", True, (200, 200, 200))
    screen.blit(hint, (50, 200))
    
    pygame.display.flip()

pygame.key.stop_text_input()
pygame.quit()
sys.exit()

5.3 鼠标输入处理

鼠标是PC游戏中最常用的输入设备之一。

5.3.1 鼠标事件处理

python 复制代码
import pygame
import sys

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

events_log = []

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        
        # 鼠标移动事件
        elif event.type == pygame.MOUSEMOTION:
            events_log.append(f"鼠标移动到: {event.pos}")
            # event.rel: 相对移动距离
            # event.buttons: 按钮状态 (左, 中, 右)
        
        # 鼠标按下事件
        elif event.type == pygame.MOUSEBUTTONDOWN:
            button_name = ["左键", "中键", "右键"][event.button - 1]
            events_log.append(f"{button_name}在{event.pos}按下")
            
            # event.button: 1=左键, 2=中键, 3=右键
            # 4=滚轮上, 5=滚轮下
        
        # 鼠标释放事件
        elif event.type == pygame.MOUSEBUTTONUP:
            button_name = ["左键", "中键", "右键"][event.button - 1]
            events_log.append(f"{button_name}在{event.pos}释放")
    
    # 限制日志数量
    if len(events_log) > 15:
        events_log = events_log[-15:]
    
    screen.fill((50, 50, 50))
    
    # 显示事件日志
    font = pygame.font.SysFont(None, 24)
    y = 10
    for log in events_log:
        text = font.render(log, True, (255, 255, 255))
        screen.blit(text, (10, y))
        y += 25
    
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

5.3.2 鼠标状态查询

python 复制代码
import pygame
import sys

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    
    # 获取鼠标位置
    mouse_x, mouse_y = pygame.mouse.get_pos()
    
    # 获取鼠标按钮状态
    left_pressed, middle_pressed, right_pressed = pygame.mouse.get_pressed()
    
    # 获取相对移动
    rel_x, rel_y = pygame.mouse.get_rel()
    
    # 检查鼠标是否在窗口内
    mouse_focus = pygame.mouse.get_focused()
    
    screen.fill((50, 50, 50))
    
    # 绘制鼠标位置
    pygame.draw.circle(screen, (255, 0, 0), (mouse_x, mouse_y), 10, 2)
    
    # 显示信息
    font = pygame.font.SysFont(None, 24)
    info = [
        f"鼠标位置: ({mouse_x}, {mouse_y})",
        f"相对移动: ({rel_x}, {rel_y})",
        f"左键: {'按下' if left_pressed else '释放'}",
        f"中键: {'按下' if middle_pressed else '释放'}",
        f"右键: {'按下' if right_pressed else '释放'}",
        f"窗口焦点: {'是' if mouse_focus else '否'}"
    ]
    
    y = 10
    for line in info:
        text = font.render(line, True, (255, 255, 255))
        screen.blit(text, (10, y))
        y += 30
    
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

5.3.3 鼠标可见性控制

python 复制代码
# 隐藏鼠标光标
pygame.mouse.set_visible(False)

# 显示鼠标光标
pygame.mouse.set_visible(True)

# 获取当前可见性状态
is_visible = pygame.mouse.get_visible()

5.3.4 鼠标位置设置

python 复制代码
# 设置鼠标位置
pygame.mouse.set_pos(400, 300)

# 将鼠标限制在窗口内(需要配合其他逻辑)

5.3.5 滚轮处理

python 复制代码
import pygame
import sys

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

scroll_y = 0
content_offset = 0

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        
        # 鼠标滚轮事件
        elif event.type == pygame.MOUSEWHEEL:
            # event.y: 垂直滚动(正值向上,负值向下)
            # event.x: 水平滚动(部分鼠标支持)
            # event.flipped: 是否翻转(macOS)
            
            scroll_y += event.y
            content_offset += event.y * 30
            content_offset = max(-500, min(0, content_offset))
    
    screen.fill((50, 50, 50))
    
    # 绘制可滚动内容
    font = pygame.font.SysFont(None, 24)
    for i in range(30):
        y = 50 + i * 40 + content_offset
        if 0 < y < 600:
            text = font.render(f"内容项 {i + 1}", True, (255, 255, 255))
            screen.blit(text, (100, y))
    
    # 显示滚动信息
    info = font.render(f"滚动值: {scroll_y}, 偏移: {content_offset}", True, (255, 255, 0))
    screen.blit(info, (10, 10))
    
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

5.4 拖拽操作实现

拖拽是游戏中常见的交互方式。

python 复制代码
import pygame
import sys

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

class DraggableRect:
    def __init__(self, x, y, w, h, color):
        self.rect = pygame.Rect(x, y, w, h)
        self.color = color
        self.dragging = False
        self.drag_offset_x = 0
        self.drag_offset_y = 0
    
    def handle_event(self, event):
        if event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1 and self.rect.collidepoint(event.pos):
                self.dragging = True
                self.drag_offset_x = self.rect.x - event.pos[0]
                self.drag_offset_y = self.rect.y - event.pos[1]
        
        elif event.type == pygame.MOUSEBUTTONUP:
            if event.button == 1:
                self.dragging = False
        
        elif event.type == pygame.MOUSEMOTION:
            if self.dragging:
                self.rect.x = event.pos[0] + self.drag_offset_x
                self.rect.y = event.pos[1] + self.drag_offset_y
    
    def draw(self, surface):
        pygame.draw.rect(surface, self.color, self.rect)
        if self.dragging:
            pygame.draw.rect(surface, (255, 255, 255), self.rect, 3)

# 创建可拖拽矩形
rects = [
    DraggableRect(100, 100, 100, 80, (255, 0, 0)),
    DraggableRect(300, 200, 120, 100, (0, 255, 0)),
    DraggableRect(500, 150, 80, 120, (0, 0, 255))
]

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        
        for rect in rects:
            rect.handle_event(event)
    
    screen.fill((50, 50, 50))
    
    for rect in rects:
        rect.draw(screen)
    
    # 显示提示
    font = pygame.font.SysFont(None, 24)
    hint = font.render("拖拽矩形移动", True, (255, 255, 255))
    screen.blit(hint, (10, 10))
    
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

5.5 游戏手柄支持

Pygame支持多种游戏手柄和摇杆设备。

python 复制代码
import pygame
import sys

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

# 初始化摇杆模块
pygame.joystick.init()

# 获取连接的手柄数量
joystick_count = pygame.joystick.get_count()
print(f"连接的手柄数量: {joystick_count}")

# 初始化手柄
joysticks = []
for i in range(joystick_count):
    joystick = pygame.joystick.Joystick(i)
    joystick.init()
    joysticks.append(joystick)
    print(f"手柄 {i}: {joystick.get_name()}")
    print(f"  轴数量: {joystick.get_numaxes()}")
    print(f"  按钮数量: {joystick.get_numbuttons()}")
    print(f"  帽子数量: {joystick.get_numhats()}")

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        
        # 手柄按钮事件
        elif event.type == pygame.JOYBUTTONDOWN:
            print(f"手柄{event.joy} 按钮{event.button} 按下")
        
        elif event.type == pygame.JOYBUTTONUP:
            print(f"手柄{event.joy} 按钮{event.button} 释放")
        
        # 手柄轴事件
        elif event.type == pygame.JOYAXISMOTION:
            if abs(event.value) > 0.1:  # 忽略小值
                print(f"手柄{event.joy} 轴{event.axis} 值{event.value:.2f}")
        
        # 手柄帽子事件(方向键)
        elif event.type == pygame.JOYHATMOTION:
            print(f"手柄{event.joy} 帽子{event.hat} 值{event.value}")
    
    screen.fill((50, 50, 50))
    
    # 显示手柄状态
    font = pygame.font.SysFont(None, 24)
    y = 10
    
    for i, joystick in enumerate(joysticks):
        text = font.render(f"手柄 {i}: {joystick.get_name()}", True, (255, 255, 255))
        screen.blit(text, (10, y))
        y += 30
        
        # 显示轴值
        for axis in range(joystick.get_numaxes()):
            value = joystick.get_axis(axis)
            text = font.render(f"  轴{axis}: {value:.2f}", True, (200, 200, 200))
            screen.blit(text, (10, y))
            y += 25
        
        # 显示按钮状态
        button_states = []
        for button in range(joystick.get_numbuttons()):
            if joystick.get_button(button):
                button_states.append(str(button))
        if button_states:
            text = font.render(f"  按下的按钮: {', '.join(button_states)}", True, (255, 255, 0))
            screen.blit(text, (10, y))
            y += 25
        
        y += 10
    
    pygame.display.flip()
    clock.tick(60)

# 清理
for joystick in joysticks:
    joystick.quit()
pygame.joystick.quit()
pygame.quit()
sys.exit()

5.6 使用GPT-5.4生成输入处理代码

本节展示如何使用GPT-5.4来生成复杂的输入处理代码。

5.6.1 生成控制方案代码

提示词示例

复制代码
请用Pygame实现一个完整的玩家控制系统,要求:
1. 支持键盘WASD和方向键双方案控制
2. 支持鼠标瞄准和射击
3. 实现平滑的加速和减速
4. 添加冲刺功能(双击方向键或按Shift)
5. 代码结构清晰,包含详细中文注释

5.6.2 生成输入管理器

提示词示例

复制代码
请设计一个Pygame的输入管理器类,要求:
1. 统一管理键盘、鼠标和手柄输入
2. 支持按键绑定和自定义配置
3. 实现输入缓冲和组合键检测
4. 提供事件和状态两种查询接口
5. 代码包含详细中文注释

5.7 综合示例:完整控制系统

本节提供一个完整的游戏控制系统示例。

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

pygame.init()
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Shooter Game")
clock = pygame.time.Clock()


class Player:
    def __init__(self):
        self.x = SCREEN_WIDTH // 2
        self.y = SCREEN_HEIGHT // 2
        self.speed = 0
        self.max_speed = 8
        self.acceleration = 0.5
        self.friction = 0.9
        self.angle = 0
        self.radius = 20

        # Dash
        self.dashing = False
        self.dash_speed = 15
        self.dash_duration = 200
        self.dash_cooldown = 1000
        self.dash_start = 0
        self.last_dash = 0

    def update(self, dt):
        keys = pygame.key.get_pressed()
        mouse_x, mouse_y = pygame.mouse.get_pos()

        # Calculate angle toward mouse
        dx = mouse_x - self.x
        dy = mouse_y - self.y
        self.angle = math.atan2(-dy, dx)

        # Movement input
        move_x = 0
        move_y = 0
        if keys[pygame.K_a] or keys[pygame.K_LEFT]:
            move_x -= 1
        if keys[pygame.K_d] or keys[pygame.K_RIGHT]:
            move_x += 1
        if keys[pygame.K_w] or keys[pygame.K_UP]:
            move_y -= 1
        if keys[pygame.K_s] or keys[pygame.K_DOWN]:
            move_y += 1

        # Normalize movement vector
        if move_x != 0 or move_y != 0:
            length = math.sqrt(move_x ** 2 + move_y ** 2)
            move_x /= length
            move_y /= length

        # Dash detection
        now = pygame.time.get_ticks()
        if keys[pygame.K_LSHIFT] and now - self.last_dash > self.dash_cooldown:
            self.dashing = True
            self.dash_start = now
            self.last_dash = now

        # Update dash state
        if self.dashing:
            if now - self.dash_start > self.dash_duration:
                self.dashing = False

        # Apply movement
        current_max = self.dash_speed if self.dashing else self.max_speed

        if move_x != 0 or move_y != 0:
            self.speed = min(self.speed + self.acceleration, current_max)
        else:
            self.speed *= self.friction

        self.x += move_x * self.speed
        self.y += move_y * self.speed

        # Boundary clamp
        self.x = max(self.radius, min(SCREEN_WIDTH - self.radius, self.x))
        self.y = max(self.radius, min(SCREEN_HEIGHT - self.radius, self.y))

    def draw(self, surface):
        # Draw player as triangle pointing toward mouse
        points = []
        for i in range(3):
            angle = self.angle + i * 2 * math.pi / 3
            px = self.x + self.radius * math.cos(angle)
            py = self.y - self.radius * math.sin(angle)
            points.append((px, py))

        color = (255, 255, 0) if self.dashing else (0, 255, 0)
        pygame.draw.polygon(surface, color, points)

        # Draw dash cooldown arc indicator
        if pygame.time.get_ticks() - self.last_dash < self.dash_cooldown:
            ratio = (pygame.time.get_ticks() - self.last_dash) / self.dash_cooldown
            pygame.draw.arc(surface, (100, 100, 255),
                            (self.x - 30, self.y - 30, 60, 60),
                            0, ratio * 2 * math.pi, 3)


class Bullet:
    def __init__(self, x, y, angle):
        self.x = x
        self.y = y
        self.angle = angle
        self.speed = 15
        self.radius = 5
        self.lifetime = 2000
        self.created = pygame.time.get_ticks()

    def update(self):
        self.x += math.cos(self.angle) * self.speed
        self.y -= math.sin(self.angle) * self.speed

    def is_alive(self):
        return pygame.time.get_ticks() - self.created < self.lifetime

    def draw(self, surface):
        pygame.draw.circle(surface, (255, 255, 0), (int(self.x), int(self.y)), self.radius)


class Enemy:
    def __init__(self):
        # Spawn randomly on one of the four screen edges
        side = random.randint(0, 3)
        if side == 0:    # Top edge
            self.x = random.randint(0, SCREEN_WIDTH)
            self.y = -20
        elif side == 1:  # Bottom edge
            self.x = random.randint(0, SCREEN_WIDTH)
            self.y = SCREEN_HEIGHT + 20
        elif side == 2:  # Left edge
            self.x = -20
            self.y = random.randint(0, SCREEN_HEIGHT)
        else:            # Right edge
            self.x = SCREEN_WIDTH + 20
            self.y = random.randint(0, SCREEN_HEIGHT)

        self.speed = random.uniform(1.5, 3.5)
        self.radius = 15

    def update(self, player):
        # Chase the player
        dx = player.x - self.x
        dy = player.y - self.y
        dist = math.sqrt(dx ** 2 + dy ** 2)
        if dist > 0:
            self.x += (dx / dist) * self.speed
            self.y += (dy / dist) * self.speed

    def collides_with_bullet(self, bullet):
        # Circle-circle collision check
        dx = self.x - bullet.x
        dy = self.y - bullet.y
        return math.sqrt(dx ** 2 + dy ** 2) < self.radius + bullet.radius

    def draw(self, surface):
        pygame.draw.circle(surface, (220, 50, 50), (int(self.x), int(self.y)), self.radius)
        pygame.draw.circle(surface, (255, 120, 120), (int(self.x), int(self.y)), self.radius, 2)
        # Draw X eyes
        cx, cy = int(self.x), int(self.y)
        pygame.draw.line(surface, (30, 30, 30), (cx - 7, cy - 5), (cx - 3, cy - 1), 2)
        pygame.draw.line(surface, (30, 30, 30), (cx - 3, cy - 5), (cx - 7, cy - 1), 2)
        pygame.draw.line(surface, (30, 30, 30), (cx + 3, cy - 5), (cx + 7, cy - 1), 2)
        pygame.draw.line(surface, (30, 30, 30), (cx + 7, cy - 5), (cx + 3, cy - 1), 2)


# ── Game objects ──────────────────────────────────────────────
player = Player()
bullets = []
enemies = []

# Shooting control
last_shot = 0
shoot_delay = 150

# Enemy spawn control
last_spawn = 0
spawn_interval = 2000   # one new enemy every 2 seconds

score = 0

font = pygame.font.Font(None, 24)
font_score = pygame.font.Font(None, 36)

# ── Game loop ─────────────────────────────────────────────────
running = True
while running:
    dt = clock.tick(60)
    now = pygame.time.get_ticks()

    # ── Events ────────────────────────────────────────────────
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:  # Single left-click shot
                if now - last_shot > shoot_delay:
                    last_shot = now
                    bullets.append(Bullet(player.x, player.y, player.angle))

    # Continuous fire while holding left mouse button
    if pygame.mouse.get_pressed()[0]:
        if now - last_shot > shoot_delay:
            last_shot = now
            bullets.append(Bullet(player.x, player.y, player.angle))

    # ── Spawn enemies ─────────────────────────────────────────
    if now - last_spawn > spawn_interval:
        last_spawn = now
        enemies.append(Enemy())

    # ── Update ────────────────────────────────────────────────
    player.update(dt)

    # Update bullets; remove expired ones
    for bullet in bullets[:]:
        bullet.update()
        if not bullet.is_alive():
            bullets.remove(bullet)

    # Update enemies; one-shot kill on bullet contact
    for enemy in enemies[:]:
        enemy.update(player)
        for bullet in bullets[:]:
            if enemy.collides_with_bullet(bullet):
                enemies.remove(enemy)
                if bullet in bullets:
                    bullets.remove(bullet)
                score += 1
                break

    # ── Draw ──────────────────────────────────────────────────
    screen.fill((30, 30, 30))

    # Draw grid (subtle background)
    for gx in range(0, SCREEN_WIDTH, 50):
        pygame.draw.line(screen, (40, 40, 40), (gx, 0), (gx, SCREEN_HEIGHT))
    for gy in range(0, SCREEN_HEIGHT, 50):
        pygame.draw.line(screen, (40, 40, 40), (0, gy), (SCREEN_WIDTH, gy))

    for enemy in enemies:
        enemy.draw(screen)

    for bullet in bullets:
        bullet.draw(screen)

    player.draw(screen)

    # Crosshair
    mouse_x, mouse_y = pygame.mouse.get_pos()
    pygame.draw.circle(screen, (255, 0, 0), (mouse_x, mouse_y), 10, 2)
    pygame.draw.line(screen, (255, 0, 0), (mouse_x - 15, mouse_y), (mouse_x + 15, mouse_y), 1)
    pygame.draw.line(screen, (255, 0, 0), (mouse_x, mouse_y - 15), (mouse_x, mouse_y + 15), 1)

    # ── HUD ───────────────────────────────────────────────────
    info = [
        "WASD / Arrow Keys : Move",
        "Mouse             : Aim",
        "Left Click        : Shoot",
        "Left Shift        : Dash",
    ]
    for i, line in enumerate(info):
        screen.blit(font.render(line, True, (200, 200, 200)), (10, 10 + i * 22))

    # Score (top-right)
    screen.blit(font_score.render(f"Score: {score}", True, (255, 215, 0)),
                (SCREEN_WIDTH - 150, 10))
    # Enemy count (below score)
    screen.blit(font.render(f"Enemies: {len(enemies)}", True, (220, 80, 80)),
                (SCREEN_WIDTH - 150, 48))

    pygame.display.flip()

pygame.quit()
sys.exit()

5.8 本章总结

本章全面介绍了Pygame的输入处理功能。我们学习了键盘输入的事件驱动和状态查询两种模式,掌握了鼠标的位置跟踪、点击检测和滚轮处理方法,了解了游戏手柄的支持方式。通过拖拽示例和综合控制系统,我们展示了如何将各种输入处理技术组合起来创建流畅的游戏控制体验。良好的输入处理是游戏可玩性的关键,掌握这些技术对于开发高质量的游戏至关重要。

本章知识点回顾

知识点 主要内容
键盘事件 KEYDOWN、KEYUP事件处理
键盘状态 get_pressed()查询按键状态
鼠标事件 MOUSEMOTION、MOUSEBUTTONDOWN/UP
鼠标状态 get_pos()、get_pressed()、get_rel()
手柄支持 Joystick类的使用

课后练习

  1. 实现一个虚拟摇杆系统,用鼠标模拟游戏手柄。
  2. 创建一个可配置的按键绑定系统,允许玩家自定义控制方案。
  3. 实现双击检测和长按检测功能。
  4. 使用GPT-5.4生成一个手势识别系统代码。
  5. 创建一个多点触控模拟系统(使用多个鼠标按钮模拟)。

下章预告

在下一章中,我们将深入学习Pygame的碰撞检测系统,包括矩形碰撞、圆形碰撞、像素精确碰撞等多种检测方法。

相关推荐
芙莉莲教你写代码2 小时前
Flutter 框架跨平台鸿蒙开发 - 宝石消除游戏
flutter·游戏·华为·harmonyos
智算菩萨2 小时前
【Pygame】第2章 Pygame基础概念与游戏循环
python·游戏·pygame
long_songs11 小时前
手柄键盘映射器【github链接见文末 】
python·游戏·计算机外设·pygame·软件推荐·手柄映射键盘
QYR_1111 小时前
2026年显示器支架底座市场深度分析:人体工学升级与多屏协同下的产业机遇
计算机外设·市场调研
书到用时方恨少!11 小时前
计算机键盘各个按键功能及常用组合键详解
计算机外设·计算机基础·键盘按键·组合键
有度即时通官方14 小时前
合规刚需下,游戏行业适合的内网通讯软件怎么选
科技·游戏·软件需求
yingxiao88817 小时前
“下载量消退”与“新游激增”双重承压,如何破局海外移动游戏市场?
游戏·市场洞察·手游·手游出海·海外市场·游戏市场
Ulyanov17 小时前
Pymunk 2D物理游戏开发教程系列 第一篇:物理引擎入门篇 -《弹球大作战》
python·pygame·雷达电子战·仿真引擎
杨柳轻扬17 小时前
从0开始搭建泰拉瑞亚tModLoader服务器(Linux)
linux·服务器·游戏