Python+Pygame实战:手把手教你打造简易第一人称射击游戏
1. 项目概述与核心价值
看到“Python+Pygame实战:手把手教你打造简易第一人称射击游戏”这个标题,很多朋友可能会觉得,用Python这种“胶水语言”做3D游戏是不是有点不切实际?尤其是第一人称射击(FPS)这种对实时渲染和交互要求极高的类型。但恰恰相反,这个项目最大的魅力就在于“化繁为简”,它不追求媲美商业引擎的华丽效果,而是聚焦于用最核心的代码逻辑,亲手搭建起一个FPS游戏的骨架。这就像用乐高积木搭建一个城市模型,虽然细节比不上专业沙盘,但你能清晰地知道每一块积木的作用,理解整个城市运转的原理。
这个项目的核心价值,在于“理解”而非“复刻”。通过Pygame这个2D图形库,我们将模拟出第一人称的3D视觉体验,实现移动、视角转动、射击判定等核心玩法。整个过程,你会深刻理解游戏循环(Game Loop)、事件处理、精灵(Sprite)管理、碰撞检测以及最关键的——伪3D视角的渲染原理。这些知识是通用的,无论你未来是使用Unity、Unreal Engine还是Godot,底层逻辑都是相通的。对于Python初学者而言,这是一个绝佳的、综合性极强的练手项目;对于有经验的开发者,它能帮你跳出框架,重新审视游戏开发的基础构件。
我完成这个项目后,最大的收获不是做出了一个多好玩的游戏,而是彻底弄明白了“第一人称”这个视角到底是怎么通过数学计算“骗”过我们眼睛的。接下来,我就把这套“骗术”,连同所有踩过的坑和优化技巧,毫无保留地分享给你。
2. 环境搭建与Pygame核心机制解析
工欲善其事,必先利其器。在动手写代码之前,我们需要一个干净、可复现的Python环境。我强烈建议使用venv创建虚拟环境,这能避免不同项目间的包版本冲突。
2.1 创建虚拟环境与安装依赖
打开你的终端或命令提示符,跟着以下步骤操作:
# 1. 创建一个新的项目目录并进入 mkdir pygame_fps_project cd pygame_fps_project # 2. 创建Python虚拟环境(假设你已安装Python 3.8+) python -m venv venv # 3. 激活虚拟环境 # 在Windows上: venv\Scripts\activate # 在macOS/Linux上: source venv/bin/activate # 激活后,命令行提示符前通常会显示 (venv) # 4. 安装Pygame # 使用清华镜像源加速下载(国内用户推荐) pip install pygame -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后,可以通过python -m pygame.examples.aliens命令运行一个Pygame自带的示例游戏,来验证安装是否成功。
注意:很多新手在这一步会遇到问题,比如提示“python不是内部或外部命令”。这通常是因为Python没有被添加到系统环境变量PATH中。解决方法是重新安装Python,并在安装向导中务必勾选“Add Python to PATH”选项。另一个常见问题是pip版本过旧,可以先运行
python -m pip install --upgrade pip进行升级。
2.2 Pygame的核心循环与事件驱动模型
Pygame程序的核心是一个“游戏循环”(Game Loop)。这个循环每秒钟会运行几十次(由帧率控制),在每一次循环中,程序按顺序做四件事:处理事件、更新游戏状态、渲染画面、控制帧率。
import pygame import sys # 初始化 pygame.init() screen = pygame.display.set_mode((800, 600)) clock = pygame.time.Clock() running = True # 游戏主循环 while running: # 1. 处理事件 for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # 可以在这里处理键盘、鼠标事件 # 2. 更新游戏状态(比如玩家位置、敌人AI) # update_game_state() # 3. 渲染画面 screen.fill((0, 0, 0)) # 用黑色清屏 # draw_everything(screen) # 4. 控制帧率并更新屏幕 pygame.display.flip() clock.tick(60) # 将帧率限制在60FPS pygame.quit() sys.exit()理解这个循环是掌握Pygame的钥匙。pygame.event.get()会捕获所有发生的事件(如按键、鼠标移动、退出),我们需要遍历并处理它们。pygame.display.flip()的作用是将我们在内存中绘制好的这一帧画面,一次性更新到显示器上,这是双缓冲技术,能避免画面闪烁。clock.tick(60)则告诉循环:“慢一点,确保每秒最多循环60次”,这是控制游戏速度的关键。
对于FPS游戏,事件处理的重点是键盘(控制移动)和鼠标(控制视角)。Pygame提供了两种获取键盘状态的方式:事件 (KEYDOWN/KEYUP) 和状态数组 (pygame.key.get_pressed())。在FPS这种需要持续响应的场景中,使用状态数组是更合适的选择,因为它能检测到按键的持续按住状态。
3. 第一人称视角与2.5D渲染原理实现
这是本项目最硬核、也最有趣的部分。我们如何用2D画布营造出3D的沉浸感?答案是一种被称为“2.5D渲染”或“伪3D”的技术,其核心是光线投射(Raycasting)。别被名字吓到,我们实现的是一种极度简化的版本。
3.1 视角与投影平面
想象你站在游戏世界中,面前有一扇窗户(即我们的屏幕)。你的眼睛是摄像机。从你眼睛出发,穿过窗户上的每一个像素点,向世界发射一条射线(Ray),这条射线碰到的第一个物体,就决定了这个像素点应该显示什么颜色和亮度。
在代码中,我们不会真的去计算复杂的3D几何相交。我们用一个取巧的方法:
- 游戏世界:用一个二维数组(列表的列表)
world_map来表示。比如1代表墙,0代表空地。
这定义了一个5x5的封闭房间。world_map = [ [1,1,1,1,1], [1,0,0,0,1], [1,0,0,0,1], [1,0,0,0,1], [1,1,1,1,1], ] - 玩家状态:我们需要记录玩家的位置
(pos_x, pos_y)和视线方向angle(弧度制)。 - 投射每一列:我们把屏幕宽度分成若干列(比如640像素就分640列)。对于每一列,我们计算出一条从玩家位置出发,方向为
(angle + column_angle_offset)的射线。 - 光线步进:让这条射线以很小的步长(如0.01)在
world_map中一步步前进,检查它当前所在的网格(int(ray_x), int(ray_y))是否是墙(值是否为1)。一旦碰到墙,就停止。 - 计算墙高:根据射线走过的距离
perp_distance(为了消除鱼眼效应,需要一点三角计算),计算出这面墙在屏幕上应该绘制的高度wall_height = screen_height / perp_distance。距离越远,墙越矮。 - 绘制竖条:在屏幕的当前列,从中间向上下各绘制
wall_height/2个像素,这样就得到了一面竖着的墙片。通过改变颜色深浅(基于距离),还能模拟出简单的光照和深度感。
3.2 代码实现:简化的光线投射循环
以下是核心渲染循环的简化代码片段,它省略了纹理、地板/天花板绘制等高级特性,但清晰地展示了原理:
def cast_rays(screen, player_pos, player_angle, world_map, screen_width, screen_height): fov = math.pi / 3 # 视野范围,90度 half_fov = fov / 2 for x in range(screen_width): # 计算当前列对应的射线角度(从屏幕左边到右边) ray_angle = (player_angle - half_fov) + (x / screen_width) * fov # 初始化射线起点和方向向量 ray_x, ray_y = player_pos ray_dir_x, ray_dir_y = math.cos(ray_angle), math.sin(ray_angle) distance = 0 max_depth = 20 # 射线最大探测距离,防止无限循环 hit_wall = False # 光线步进 (Digital Differential Analysis, DDA) while not hit_wall and distance < max_depth: distance += 0.01 # 步长,越小精度越高,性能消耗越大 test_x = int(ray_x + ray_dir_x * distance) test_y = int(ray_y + ray_dir_y * distance) # 检查是否超出地图边界或撞墙 if test_x < 0 or test_x >= len(world_map[0]) or test_y < 0 or test_y >= len(world_map): hit_wall = True distance = max_depth elif world_map[test_y][test_x] == 1: hit_wall = True # 计算投影高度并消除鱼眼效应 perp_distance = distance * math.cos(ray_angle - player_angle) wall_height = min(int(screen_height / (perp_distance + 0.0001)), screen_height) # 防止除零 # 计算绘制墙体的上下边界 wall_top = (screen_height - wall_height) // 2 wall_bottom = wall_top + wall_height # 根据距离决定颜色深浅 color_intensity = max(255 - int(distance * 20), 50) wall_color = (color_intensity, color_intensity // 2, 0) # 一个棕色的渐变 # 在屏幕上绘制这一列墙体 pygame.draw.line(screen, wall_color, (x, wall_top), (x, wall_bottom), 1)实操心得:这里的步长
0.01和最大深度20是需要权衡的参数。步长越小,渲染精度越高,墙体的边缘越平滑,但计算量呈指数级增长,帧率会暴跌。在我的测试中,对于640x480的窗口,步长0.01在普通笔记本上尚可维持60帧。如果感觉卡顿,可以尝试增大步长到0.02或0.05,或者减少屏幕渲染的列数(比如每两列渲染一次,宽度减半,性能提升近一倍,但画面会变粗糙)。这是一种经典的“性能 vs 画质”的取舍。
4. 玩家控制与游戏物理系统
有了画面,接下来就要让玩家能与之交互。FPS的控制核心是:用键盘WASD控制移动,用鼠标控制视角旋转。
4.1 平滑的键盘移动控制
移动逻辑不仅仅是改变pos_x和pos_y。为了更真实,我们需要引入玩家的朝向向量,并据此计算前进、后退、左右平移(Strafe)的方向。
def handle_movement(keys, player_pos, player_angle, delta_time, move_speed=5.0): pos_x, pos_y = player_pos # 计算前进方向向量 forward_x = math.cos(player_angle) forward_y = math.sin(player_angle) # 计算右方向向量(与前进方向垂直) right_x = math.cos(player_angle + math.pi/2) right_y = math.sin(player_angle + math.pi/2) moved = False if keys[pygame.K_w]: # 前进 pos_x += forward_x * move_speed * delta_time pos_y += forward_y * move_speed * delta_time moved = True if keys[pygame.K_s]: # 后退 pos_x -= forward_x * move_speed * delta_time pos_y -= forward_y * move_speed * delta_time moved = True if keys[pygame.K_a]: # 左平移 pos_x += right_x * move_speed * delta_time pos_y += right_y * move_speed * delta_time moved = True if keys[pygame.K_d]: # 右平移 pos_x -= right_x * move_speed * delta_time pos_y -= right_y * move_speed * delta_time moved = True # 碰撞检测(简易版):检查移动后的位置是否在墙内 new_map_x, new_map_y = int(pos_x), int(pos_y) if moved and world_map[new_map_y][new_map_x] == 0: # 新位置是空地,移动有效 player_pos = (pos_x, pos_y) # 如果撞墙,则player_pos保持不变 return player_pos这里的关键是引入了delta_time。delta_time是上一帧到当前帧所经历的时间(秒)。通过move_speed * delta_time,我们确保了无论电脑快慢(30帧还是60帧),玩家每秒移动的距离是恒定的,这称为“与时间无关的运动”,是游戏开发的基础常识。
4.2 鼠标视角控制与光标隐藏
为了让视角转动更符合FPS直觉,我们需要捕捉鼠标的相对移动量,并隐藏系统光标。
# 在主循环初始化部分 pygame.mouse.set_visible(False) # 隐藏鼠标光标 pygame.event.set_grab(True) # 将鼠标锁定在窗口内 # 在事件处理部分 for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.MOUSEMOTION: # 获取鼠标移动量 mouse_x, mouse_y = event.rel # 根据水平移动量调整视角角度,灵敏度可调 sensitivity = 0.002 player_angle += mouse_x * sensitivity # 可选:根据垂直移动量调整俯仰角(需要更复杂的投影计算,这里暂不实现) # 在主循环末尾,为了持续流畅,也可以使用pygame.mouse.get_rel()来获取累积的移动量。注意事项:
pygame.event.set_grab(True)在某些系统或窗口模式下可能不工作,或者导致无法用Alt+Tab切换窗口。一个更健壮的做法是,在游戏失去焦点时自动禁用鼠标锁定和隐藏。可以监听pygame.ACTIVEEVENT事件,当event.gain == 0时表示窗口失去焦点,此时应调用pygame.mouse.set_visible(True)和pygame.event.set_grab(False);当窗口重新获得焦点时再恢复。
5. 射击机制、敌人AI与游戏性构建
一个FPS游戏不能只有走路看风景。我们需要可交互的目标——敌人,以及与之交互的手段——射击。
5.1 基于射线的射击判定
在3D游戏中,射击判定通常使用射线检测(Raycast)。巧了,我们渲染墙壁用的正是光线投射!我们可以复用这套逻辑来实现射击。
- 定义敌人:敌人可以简单地表示为一个在地图网格上的位置
(ex, ey)和一个状态(如 alive=True)。 - 射击事件:当玩家点击鼠标左键时,从玩家位置
(pos_x, pos_y)向当前视线方向player_angle发射一条检测射线。 - 射线步进检测:和渲染射线类似,让步进射线在场景中前进。但这次我们不仅检测墙,还检测是否击中了敌人。
- 击中判定:在射线步进的每一步,计算当前位置与每个存活敌人位置的距离。如果距离小于某个阈值(比如0.5),就认为击中了该敌人。
def shoot(player_pos, player_angle, enemies): hit_enemy = None min_hit_distance = float('inf') # 发射一条精细的检测射线 ray_x, ray_y = player_pos ray_dir_x, ray_dir_y = math.cos(player_angle), math.sin(player_angle) distance = 0 max_shoot_range = 10.0 while distance < max_shoot_range: distance += 0.05 # 射击检测可以比渲染更粗糙一些 test_x = ray_x + ray_dir_x * distance test_y = ray_y + ray_dir_y * distance # 1. 先检测是否击中墙 map_x, map_y = int(test_x), int(test_y) if world_map[map_y][map_x] == 1: break # 射线被墙挡住,停止检测 # 2. 检测是否击中敌人 for enemy in enemies: if enemy['alive']: # 计算射线点到敌人的距离 dx = enemy['x'] - test_x dy = enemy['y'] - test_y dist_to_enemy = math.sqrt(dx*dx + dy*dy) if dist_to_enemy < 0.5: # 击中判定阈值 if distance < min_hit_distance: min_hit_distance = distance hit_enemy = enemy if hit_enemy: print("Hit!") hit_enemy['alive'] = False # 可以在这里触发敌人死亡动画、音效、得分增加等 else: print("Miss!") # 播放未命中音效5.2 简易敌人AI与精灵绘制
敌人需要能“看”到玩家并做出反应。我们可以实现一个最简单的AI:如果敌人与玩家之间没有墙壁阻挡(用射线检测),且距离在一定范围内,敌人就朝玩家移动。
同时,我们需要在伪3D场景中绘制敌人。一种常见的方法是“精灵渲染”:计算敌人相对于玩家的角度和距离,然后像绘制墙一样,计算出敌人在屏幕上的投影位置和大小,最后绘制一个2D图像(如一个矩形或圆形)在那个位置。
def update_enemies(enemies, player_pos): px, py = player_pos for enemy in enemies: if not enemy['alive']: continue ex, ey = enemy['x'], enemy['y'] # 计算到玩家的距离和方向 dx, dy = px - ex, py - ey distance = math.sqrt(dx*dx + dy*dy) angle_to_player = math.atan2(dy, dx) # 简单AI:如果距离小于5且视线无阻挡,则向玩家移动 if distance < 5 and not check_wall_between((ex, ey), player_pos): move_speed = 0.02 enemy['x'] += math.cos(angle_to_player) * move_speed enemy['y'] += math.sin(angle_to_player) * move_speed def draw_enemies(screen, player_pos, player_angle, enemies, screen_width, screen_height, fov): for enemy in enemies: if not enemy['alive']: continue ex, ey = enemy['x'], enemy['y'] px, py = player_pos # 1. 计算敌人相对于玩家的向量 dx, dy = ex - px, ey - py # 2. 计算敌人相对于玩家视野的角度 enemy_angle = math.atan2(dy, dx) # 3. 计算角度差,并转换为屏幕上的x坐标 angle_diff = enemy_angle - player_angle # 将角度差规范化到 [-π, π] 区间,避免环绕问题 angle_diff = (angle_diff + math.pi) % (2 * math.pi) - math.pi # 如果敌人在视野范围内(角度差小于一半FOV) if abs(angle_diff) < fov / 2: # 将角度差映射到屏幕X坐标 screen_x = (screen_width / 2) * (1 + angle_diff / (fov / 2)) # 计算敌人到玩家的实际距离(用于确定大小) distance = math.sqrt(dx*dx + dy*dy) # 根据距离计算敌人在屏幕上的大小(透视投影) enemy_size = screen_height / (distance + 0.001) * 0.5 enemy_size = int(max(10, min(enemy_size, 100))) # 限制最大最小尺寸 # 计算屏幕Y坐标(通常固定在水平线附近) screen_y = screen_height // 2 # 绘制一个简单的矩形代表敌人 enemy_rect = pygame.Rect(0, 0, enemy_size, enemy_size) enemy_rect.center = (int(screen_x), int(screen_y)) pygame.draw.rect(screen, (255, 0, 0), enemy_rect) # 用红色表示敌人踩坑记录:精灵渲染中最容易出错的点是角度差的计算和规范化。因为
math.atan2返回的范围是[-π, π],而玩家的角度player_angle在持续旋转中可能会超过这个范围(比如从3.1弧度旋转到-3.1弧度)。如果不做规范化处理,当玩家角度跨过π和-π的边界时,计算出的angle_diff会突然从很小的正值跳变成很大的负值(接近2π),导致屏幕上的敌人位置瞬间横跳。上面代码中的(angle_diff + math.pi) % (2 * math.pi) - math.pi这行公式,就是用来将任意角度规范到[-π, π]区间的标准做法,务必牢记。
6. 性能优化、调试与常见问题排查
当游戏功能基本完成后,你可能会发现帧率(FPS)不太理想,或者在特定情况下会出现奇怪的画面错误。别担心,这是优化和调试的开始。
6.1 性能分析与优化策略
Pygame本身性能有限,我们的光线投射又是计算密集型操作。优化是必须的。
降低分辨率渲染:这是提升帧率最有效的方法。我们可以在一个更小的Surface(比如320x240)上渲染游戏画面,然后在显示时用
pygame.transform.scale()放大到窗口尺寸(如640x480)。虽然画面会变模糊,但计算量减少为原来的1/4。# 创建一个小尺寸的渲染表面 render_surface = pygame.Surface((320, 240)) # 在主循环中,所有绘制操作都针对 render_surface cast_rays(render_surface, ...) draw_enemies(render_surface, ...) # 最后,将小表面放大到主屏幕 scaled_surface = pygame.transform.scale(render_surface, (screen_width, screen_height)) screen.blit(scaled_surface, (0, 0))减少投射射线数量:不必为屏幕的每一个像素列都投射一条射线。可以每隔一列或两列投射一条,然后用这一列的结果填充相邻的几列。这被称为“射线步长倍增”,会带来明显的竖条纹感,但性能提升显著。
优化碰撞检测和AI:
- 对于敌人AI的视线检测 (
check_wall_between),不要每帧都对每个敌人都做一次完整的DDA射线步进。可以缓存结果,或者降低检测频率(比如每5帧检测一次)。 - 在射击检测时,设置合理的
max_shoot_range(最大射程)和更大的步长,避免不必要的长距离精细计算。
- 对于敌人AI的视线检测 (
使用PyPy解释器:如果你的代码是纯Python的(没有用到某些仅支持CPython的C扩展库),尝试使用PyPy来运行你的游戏。PyPy的即时编译(JIT)特性可以显著提升这类计算密集型循环的性能,有时能有数倍的提升。
6.2 调试技巧与常见问题速查表
开发过程中,你一定会遇到各种诡异的问题。下面这个表格整理了我遇到的一些典型问题及解决方法:
| 问题现象 | 可能原因 | 排查与解决方法 |
|---|---|---|
| 墙体闪烁或出现裂缝 | 1. 光线投射步长 (distance += step) 设置过大,导致射线“跳过”了薄墙。2. 浮点数精度问题,在计算 int(ray_x)时因精度误差取整到了墙的另一侧。 | 1.减小步长,例如从0.05减到0.01或0.005。 2. 在计算地图网格坐标时,根据射线方向进行微调。例如: map_x = int(ray_x - 1e-6 if ray_dir_x < 0 else ray_x + 1e-6) |
| 移动时感觉“卡顿”或速度不一致 | 没有使用delta_time进行与时间无关的运动计算。帧率高时移动快,帧率低时移动慢。 | 在主循环中计算delta_time:delta_time = clock.tick(60) / 1000.0(返回毫秒,需转秒)。所有速度相关的计算都乘以delta_time。 |
| 鼠标控制视角时旋转不跟手或跳跃 | 1. 鼠标灵敏度 (sensitivity) 参数不合适。2. 没有处理鼠标累积事件,或者 pygame.event.set_grab(True)失效。 | 1. 调整sensitivity,通常在0.001到0.005之间尝试。2. 使用 pygame.mouse.get_rel()替代event.rel来获取自上一帧以来的累积移动量,更稳定。 |
| 敌人精灵位置计算错误,在屏幕边缘乱跳 | 角度差未规范化,当player_angle跨过-π和π的边界时发生突变。 | 务必使用规范化公式:angle_diff = (enemy_angle - player_angle)angle_diff = (angle_diff + math.pi) % (2 * math.pi) - math.pi |
| 射击检测有时打不中近处的敌人 | 射击射线步长太大,或者敌人碰撞体积(判定阈值)设置太小。 | 1. 减小射击检测的步长。 2. 适当增大击中判定的距离阈值(如从0.3调到0.5)。 3. 可以尝试在敌人周围用多个点进行检测,而不是只用中心点。 |
| 游戏窗口失去焦点后鼠标失控 | 未处理窗口激活/失活事件,鼠标锁定和隐藏状态未同步更新。 | 监听ACTIVEEVENT:if event.type == pygame.ACTIVEEVENT:if event.gain == 0: # 失去焦点pygame.mouse.set_visible(True)pygame.event.set_grab(False) |
调试时,一个非常有效的方法是使用“调试绘制”。比如,在屏幕上实时打印玩家的坐标、角度、帧率等信息。
font = pygame.font.SysFont(None, 24) def draw_debug_info(screen, player_pos, player_angle, clock): fps = int(clock.get_fps()) pos_text = font.render(f'Pos: {player_pos[0]:.2f}, {player_pos[1]:.2f}', True, (255, 255, 255)) angle_text = font.render(f'Angle: {math.degrees(player_angle):.1f}°', True, (255, 255, 255)) fps_text = font.render(f'FPS: {fps}', True, (255, 255, 255)) screen.blit(pos_text, (10, 10)) screen.blit(angle_text, (10, 40)) screen.blit(fps_text, (10, 70))把这些信息显示在屏幕角落,能帮你快速定位很多逻辑问题。
7. 项目扩展与进阶思路
完成基础版本后,你可以以此为骨架,添加更多元素,让它更像一个完整的游戏。
添加纹理:现在的墙是单色的。你可以为每种墙类型(比如
world_map中的1,2,3)定义不同的颜色甚至图片纹理。在光线投射击中墙时,不仅要记录距离,还要记录击中的是墙的哪一面(南北墙还是东西墙),以及击中点的精确坐标。用这个坐标去采样对应的纹理图片,就能绘制出有图案的墙壁。这需要更精细的射线与网格交点计算。绘制地板和天花板:目前我们只画了墙,地板和天花板是黑色的。你可以用另一种投射方式,或者直接用渐变色填充上下两部分,来模拟简单的地板和天空。更复杂的做法是为地板和天花板也应用透视纹理。
引入声音:使用
pygame.mixer模块为射击、脚步声、敌人中弹、背景音乐添加音效,沉浸感会大幅提升。注意控制同时播放的音效数量,避免混音混乱。设计关卡与地图编辑器:硬编码
world_map很不方便。可以设计一个简单的文本或图形化地图编辑器,将地图数据保存为文件,游戏启动时加载。你甚至可以设计多个关卡,让玩家有探索的欲望。更复杂的敌人AI:给敌人添加状态机(如“巡逻”、“警戒”、“追击”、“攻击”),让它们的行为更丰富。还可以为敌人添加生命值,而不是一击必杀。
图形界面(GUI):使用
pygame.draw和pygame.font来绘制血条、弹药量、准星、小地图和开始/结束菜单。
这个项目就像一颗种子,你已经掌握了让它生根发芽的核心方法。从理解游戏循环,到实现伪3D渲染,再到处理交互和AI,每一步都是游戏开发中不可或缺的环节。我建议你先完全理解并复现这个基础版本,确保每一行代码你都明白其作用。然后,挑选上面的一两个扩展点去实践,在动手的过程中,你会遇到更多具体的问题,解决它们就是你最大的成长。