Godot多人游戏暂停菜单实现与性能优化实战
1. 项目概述从单机到多人的暂停菜单挑战最近在做一个Godot的多人游戏练习项目做到第24节时遇到了一个看似简单、实则暗藏玄机的问题暂停菜单。在单机游戏里暂停游戏无非就是调用get_tree().paused true整个世界就静止了UI弹出来玩家可以调整设置或退出。但在多人网络游戏里这个逻辑就完全行不通了。你不能让一个玩家暂停就把服务器上其他所有玩家的游戏体验都给“冻住”。这个练习的核心就是解决如何在多人异步环境下实现一个只对本地玩家生效、且不影响其他玩家的“伪暂停”系统并在这个过程中对游戏进行一系列必要的优化。这不仅仅是加个UI面板那么简单。它涉及到网络状态同步、本地游戏逻辑与网络逻辑的解耦、UI的响应式设计以及如何在不暂停物理引擎和网络通信的前提下模拟出“暂停”的体验。同时随着功能增加代码开始变得臃肿性能问题也开始冒头所以“游戏优化”成了这个阶段必须完成的功课。如果你也在用Godot做多人游戏或者你的单机游戏暂停逻辑总觉得哪里别扭那么这次关于“多人游戏暂停菜单”的实践和后续的优化思路应该能给你不少启发。2. 核心设计思路解耦、状态与本地化多人游戏的暂停本质上是玩家客户端的一个本地化行为。服务器和其他客户端不应该感知到“某个玩家暂停了”这个事件除非是游戏逻辑需要如投票暂停。因此我们的设计必须围绕“本地状态”展开。2.1 传统暂停为何在多人游戏中失效在Godot单机项目中典型的暂停代码如下func _input(event): if event.is_action_pressed(ui_cancel): # 比如ESC键 get_tree().paused !get_tree().paused $PauseMenu.visible get_tree().pausedget_tree().paused是一个全局开关。一旦设置为true整个场景树中所有节点的_process,_physics_process以及_input函数都会停止执行除了那些设置了process_mode为ALWAYS的节点。在多人游戏中这个“场景树”通常包含了网络处理逻辑、其他玩家角色的同步信息。如果盲目暂停会导致网络消息积压、其他玩家的位置更新停滞等你恢复时可能会看到其他玩家“瞬移”或者直接网络超时断开。2.2 我们的解决方案一个独立的状态管理器我们的思路是引入一个专门的GameStateManager单例Autoload。它不依赖于get_tree().paused而是维护一套自己的游戏状态枚举例如PLAYING,PAUSED_LOCAL,GAME_OVER等。# GameStateManager.gd (作为Autoload) extends Node enum GameState {PLAYING, PAUSED_LOCAL, MENU} var current_state: GameState GameState.PLAYING signal state_changed(new_state) func set_state(new_state: GameState): if current_state ! new_state: current_state new_state state_changed.emit(new_state) _handle_state_change(new_state) func _handle_state_change(state): match state: GameState.PAUSED_LOCAL: # 1. 暂停本地游戏逻辑非物理、非网络 # 2. 显示暂停菜单UI # 3. 捕获并处理UI输入忽略游戏世界输入 Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) GameState.PLAYING: # 1. 恢复本地游戏逻辑 # 2. 隐藏暂停菜单 # 3. 恢复游戏世界输入捕获 Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) # 如果是FPS/3D游戏这个管理器成为游戏状态的唯一权威来源。任何需要根据游戏状态改变行为的系统如UI、角色控制器、音效都去监听state_changed信号并做出相应调整而不是去检查一个全局的暂停变量。2.3 输入处理的精细化控制这是实现“本地暂停”的关键。我们需要区分“游戏操作输入”和“UI操作输入”。当状态为PAUSED_LOCAL时前者应该被屏蔽后者则需要被激活。方法一使用Action的优先级Godot 4.x 推荐在Godot 4中输入映射Input Map中的Action可以设置优先级。我们可以创建两套Actionmove_left,jump,shoot(优先级: 0) - 游戏操作pause_menu_up,pause_menu_confirm(优先级: -1) - UI操作在PAUSED_LOCAL状态我们通过代码动态禁止低优先级的Actionfunc _handle_state_change(state): match state: GameState.PAUSED_LOCAL: # 禁用所有游戏相关的Action for action in [move_left, move_right, jump, shoot]: InputMap.action_set_deadzone(action, 99999) # 一个取巧但有效的方法让输入失效 GameState.PLAYING: # 恢复游戏Action for action in [move_left, move_right, jump, shoot]: InputMap.action_set_deadzone(action, 0.5) # 恢复默认死区注意直接修改InputMap会影响所有场景确保在游戏退出或场景切换时恢复原状。更精细的做法是在玩家控制器节点中根据状态忽略输入事件。方法二在节点层面处理在本地玩家角色脚本中func _input(event): if GameStateManager.current_state GameStateManager.GameState.PAUSED_LOCAL: return # 在暂停状态直接忽略所有角色控制输入 # 正常的输入处理逻辑...同时暂停菜单UI节点需要设置process_mode为ALWAYS以确保它在游戏“逻辑暂停”时仍能接收并处理输入。3. 暂停菜单UI的实现与网络考量UI部分本身并不复杂但需要考虑其在多人环境下的表现。3.1 菜单场景结构创建一个独立的PauseMenu.tscn场景。其根节点建议使用CanvasLayer并设置layer为一个较高的值如128确保它显示在最前面。PauseMenu (CanvasLayer) ├── ColorRect (全屏半透明遮罩) ├── CenterContainer │ └── VBoxContainer (主菜单面板) │ ├── Label (标题“游戏暂停”) │ ├── Button (“继续游戏”) │ ├── Button (“设置”) │ ├── Button (“返回主菜单”) │ └── Button (“退出游戏”)将PauseMenu.tscn实例化到主游戏场景中但默认隐藏。3.2 按钮功能的实现每个按钮的功能需要谨慎处理特别是涉及网络的操作。继续游戏最简单调用GameStateManager.set_state(GameStateManager.GameState.PLAYING)。设置可以弹出另一个子菜单如SettingsMenu.tscn用于调整音量、画面等本地设置。这些设置应通过ConfigFile保存到本地。返回主菜单这是最需要小心的地方。在多人游戏中这通常意味着“离开房间”或“断开连接”。func _on_return_to_main_menu_pressed(): # 1. 通知服务器玩家即将离开如果使用权威服务器 if multiplayer.has_multiplayer_peer(): # 发送一个自定义的RPC告知服务器“玩家自愿离开” rpc_id(1, player_leave_gracefully, multiplayer.get_unique_id()) # 或者直接断开连接 multiplayer.multiplayer_peer.close() # 2. 清理网络相关资源 GameStateManager.set_state(GameStateManager.GameState.MENU) # 3. 切换场景到主菜单 get_tree().change_scene_to_file(res://scenes/ui/MainMenu.tscn)关键点一定要先进行网络清理再切换状态和场景。直接切场景可能导致网络资源泄露或服务器端还保留着你的玩家对象。退出游戏调用get_tree().quit()。在退出前最好也发送一个离开通知给服务器。3.3 UI与状态的绑定使用信号是最佳实践。在PauseMenu.gd中func _ready(): GameStateManager.state_changed.connect(_on_game_state_changed) hide() # 初始隐藏 func _on_game_state_changed(new_state): match new_state: GameStateManager.GameState.PAUSED_LOCAL: show() # 获取焦点到第一个按钮方便手柄/键盘操作 $CenterContainer/VBoxContainer/ContinueButton.grab_focus() GameStateManager.GameState.PLAYING: hide()同时在游戏主场景中监听ESC键来触发暂停状态切换func _input(event): # 只有PLAYING和PAUSED_LOCAL状态之间能用ESC切换 if event.is_action_pressed(ui_pause) and GameStateManager.current_state in [GameStateManager.GameState.PLAYING, GameStateManager.GameState.PAUSED_LOCAL]: var new_state GameStateManager.GameState.PAUSED_LOCAL if GameStateManager.current_state GameStateManager.GameState.PLAYING else GameStateManager.GameState.PLAYING GameStateManager.set_state(new_state) get_viewport().set_input_as_handled() # 阻止输入进一步传播4. 游戏优化实战从功能实现到性能提升当暂停菜单等核心功能完成后游戏往往已经初具规模此时是进行系统性优化的最佳时机。优化不是一蹴而就的需要 profiling性能剖析和针对性改进。4.1 Godot Profiler 是你的第一工具在编辑器里运行游戏然后打开调试器(Debugger)面板切换到分析器(Profiler)标签。这里能看到帧时间Frame Time的详细分布。Physics物理计算耗时。如果过高检查物理体数量、碰撞形状复杂度、是否每帧都在移动静态物体。Script脚本逻辑耗时。这是优化重点。Rendering渲染耗时。与draw call、材质、阴影、屏幕分辨率有关。实操心得不要凭感觉优化。先运行Profiler找到最耗时的“瓶颈”bottleneck。通常遵循“二八定律”20%的代码消耗80%的性能。优化瓶颈的收益最大。4.2 脚本性能优化技巧减少_process和_physics_process中的计算避免在这些函数中进行复杂的查找如get_node()遍历很深的路径、昂贵的数学运算。将不变的计算结果缓存Cache起来。例如一个敌人的索敌范围检查不需要每帧都计算距离可以每5-10帧检查一次。var target: Node2D null var search_cooldown: float 0.0 func _process(delta): search_cooldown - delta if search_cooldown 0: _search_for_target() # 这是一个比较耗时的函数 search_cooldown 0.2 # 0.2秒检查一次善用信号Signal而非轮询Polling不要每帧去检查“血量是否小于0”而是在血量被修改的函数里发出一个health_depleted信号。这能极大减少无意义的条件判断。对象池Object Pooling处理高频创建/销毁子弹、特效、伤害数字这类频繁生成和消失的对象不要用instantiate()和queue_free()。游戏初始化时预先创建一批如20颗子弹放入一个数组池子中并隐藏。需要时从池中取一个可用的显示并激活用完后再隐藏并放回池中。var bullet_pool: Array[Area2D] [] const POOL_SIZE 20 func _ready(): for i in range(POOL_SIZE): var bullet preload(res://bullet.tscn).instantiate() bullet.hide() bullet.tree_exiting.connect(_on_bullet_exiting.bind(bullet)) # 监听如果意外被释放 add_child(bullet) bullet_pool.append(bullet) func fire(): var bullet _get_available_bullet() if bullet: bullet.global_position gun_tip.global_position bullet.show() # ... 设置子弹速度等 func _get_available_bullet() - Area2D: for bullet in bullet_pool: if not bullet.visible: return bullet # 池子不够用可以动态扩容一个但说明池子大小可能需要调整 var new_bullet preload(res://bullet.tscn).instantiate() add_child(new_bullet) bullet_pool.append(new_bullet) return new_bullet func _on_bullet_exiting(bullet: Area2D): # 如果子弹被其他地方queue_free了从池中移除并补充一个新的 bullet_pool.erase(bullet)4.3 渲染与场景优化控制Draw Call使用Atlas Texture纹理图集。将多个小精灵sprite的图片合并到一张大图上在Godot中配置SpriteFrames或使用TextureAtlas资源。这能显著减少GPU的纹理切换。对静态背景元素考虑使用BackBufferCopy节点2D或将静态部分烘焙到Lightmap3D。使用VisibilityNotifier2D/VisibilityNotifier3D对于屏幕外off-screen的复杂物体如敌人生成器、粒子系统、复杂逻辑的NPC将其作为VisibilityNotifier的子节点。连接screen_entered和screen_exited信号来控制它们的process_mode或直接暂停其_process函数甚至隐藏它们。func _ready(): $VisibilityNotifier2D.screen_entered.connect(_on_screen_entered) $VisibilityNotifier2D.screen_exited.connect(_on_screen_exited) func _on_screen_entered(): set_physics_process(true) show() func _on_screen_exited(): set_physics_process(false) hide() # 可选节省渲染开销简化碰撞形状在保证游戏体验的前提下使用尽可能简单的碰撞形状RectangleShape2D,CapsuleShape3D优于ConvexPolygonShape2D和ConcavePolygonShape3D。对于复杂图形可以使用多个简单形状组合CollisionShape2D的兄弟节点或者使用CollisionPolygon2D手动简化轮廓。4.4 网络优化针对多人游戏状态同步频率不是所有数据都需要每帧同步。玩家的位置可能需要高频同步如每秒10-30次但玩家的血量、状态如是否在攻击可以低频同步每秒2-5次。使用插值Interpolation和外推Extrapolation来平滑低频同步带来的卡顿感。RPC调用优化使用rpc注解时考虑使用call_local和call_remote模式。不需要所有客户端都执行的逻辑如本地特效播放就用call_remote。对于高频同步的数据如位置使用rpc(“any_peer”, “unreliable_ordered”)而不是reliable。unreliable允许丢包对于实时位置数据收到最新的比保证收到每一个旧数据更重要。序列化数据最小化只同步变化了的数据。可以设计一个位掩码bitmask来标识哪些字段被更新了。5. 常见问题排查与调试技巧在实现暂停菜单和优化过程中你肯定会遇到各种奇怪的问题。这里记录几个我踩过的坑和解决方法。5.1 问题暂停后其他玩家的角色还在动/网络延迟剧增原因错误地使用了get_tree().paused true或者你的网络处理逻辑如_process中的网络消息处理没有在本地暂停状态下被正确跳过。排查检查你的GameStateManager状态切换是否生效。在负责接收网络RPC和同步玩家位置的脚本中开头加入状态判断func _process(delta): if GameStateManager.current_state GameStateManager.GameState.PAUSED_LOCAL: return # ... 正常的网络位置插值逻辑确保物理模拟没有被暂停。我们的PAUSED_LOCAL状态不应该影响_physics_process否则其他玩家的物理同步会出问题。5.2 问题暂停菜单按钮点击无反应或者游戏输入和UI输入冲突原因输入处理层级混乱。UI按钮可能没有正确获取焦点或者游戏世界的输入事件在暂停时没有被屏蔽。解决确保UI获取焦点在显示暂停菜单时主动调用$SomeButton.grab_focus()。使用accept_event()在UI按钮的_input_event或_gui_input函数中处理完点击后调用accept_event()防止事件继续传递到游戏场景。检查InputMap的死区设置如果用了前面提到的修改死区的方法确保在退出暂停状态时准确恢复了原值。一个更稳健的方法是使用Input.set_default_cursor_shape或自定义的输入处理层。5.3 问题优化后游戏逻辑出现错误如敌人不攻击了原因优化时过于激进破坏了原有的逻辑依赖。比如你把敌人的AI决策从_process移到了_physics_process但它的攻击动画是在_process里更新的导致不同步。排查流程回退法注释掉最近的优化代码看问题是否消失。日志法在关键逻辑点添加print()或使用更高级的日志系统输出变量的状态和函数的调用频率对比优化前后。Profiler确认用Profiler看看是不是优化过度把必要的逻辑也禁用了。5.4 性能优化中的“负优化”典型场景为了减少Draw Call你把所有背景图拼成一张巨大的图集结果因为这张图太大超出了GPU的偏好纹理尺寸导致加载慢、内存占用高反而降低了性能。原则优化后一定要用Profiler再跑一次确认帧时间确实下降了并且没有引入新的卡顿如加载时的卡顿。优化是一个平衡的艺术需要在CPU、GPU、内存和加载时间之间做权衡。6. 项目总结与扩展思考实现一个多人游戏的暂停菜单远不止是显示/隐藏一个面板。它迫使你重新思考游戏的状态管理架构将本地表现与网络核心逻辑清晰地分离开。这套基于GameStateManager和精细化输入管理的方案不仅解决了暂停问题也为将来添加其他状态如对话中、过场动画、死亡观察打下了坚实的基础。而随之进行的游戏优化更像是一次对项目代码的“体检”。通过Profiler你能真切地看到每一行代码的性能成本。优化过程中学到的缓存、信号代替轮询、对象池、可见性控制等技巧是写出高效、专业级Godot项目的必备技能。记住最好的优化往往是设计层面的优化比如减少不必要的计算、选择更高效的数据结构这些在项目初期就应考虑进去。这个练习项目走到这里已经从一个简单的Demo向一个具备健壮架构的多人游戏原型迈出了一大步。你可以尝试在此基础上为暂停菜单添加更多的子页面比如“按键设置”、“图形设置”甚至是一个“玩家列表”显示当前房间内所有玩家的延迟和状态。这些功能的添加因为有了清晰的状态管理都会变得有条不紊。

相关新闻

DeepSeek    LeetCode 3786. 树组的交互代价总和 Java实现

DeepSeek LeetCode 3786. 树组的交互代价总和 Java实现

问题描述给定一棵 n 个节点的无向树(节点编号 0 到 n-1),以及一个长度相同的数组 group,group[i] 表示节点 i 的分组标签。两个节点 u 和 v 若 group[u] group[v],则它们属于同一组。交互代价定义为树上两节点之间唯一…

2026/7/31 7:14:36阅读更多 →
QueryExcel:三分钟搞定Excel海量数据检索的智能工具

QueryExcel:三分钟搞定Excel海量数据检索的智能工具

QueryExcel:三分钟搞定Excel海量数据检索的智能工具 【免费下载链接】QueryExcel 多Excel文件内容查询工具。 项目地址: https://gitcode.com/gh_mirrors/qu/QueryExcel 你是否曾面对成百上千个Excel文件,需要查找某个关键信息却无从下手&#xf…

2026/7/31 7:14:36阅读更多 →
数据库中一些常用英文单词含义

数据库中一些常用英文单词含义

Schema schema 通常指“结构定义”。 在数据库里,它可能有几层意思: 数据库里的命名空间 比如 PostgreSQL 里可以有: public.users sales.orders这里 public、sales 就是 schema,用来组织表。 表结构 比如一张 users 表有哪些字段…

2026/7/31 7:14:36阅读更多 →
2026年显示器推荐 IPS OLED TN三种面板 覆盖全品类定位全解析

2026年显示器推荐 IPS OLED TN三种面板 覆盖全品类定位全解析

一、开篇概述电竞显示器场景细分愈发精细,不同面板材质、刷新率规格对应的使用体验与适配人群差异明显,多数用户易因参数同质化选错机型,造成性能浪费。针对大众综合电竞、专业色彩创作、硬核FPS竞技三大核心需求,HKC推出KG273QS、…

2026/7/31 11:03:48阅读更多 →
养老机构服务管理混乱?北京华恒智信管理案例

养老机构服务管理混乱?北京华恒智信管理案例

【客户行业】养老行业【问题类型】人才管理【客户背景】某养老机构成立于2010年,坐落于宁静优美的郊区,占地面积5000平方米,配备各类养老服务设施,为老年人提供全方位、高品质的养老服务。自成立以来,机构始终秉持&quo…

2026/7/31 11:03:48阅读更多 →
如何用轻量级工具解放你的华硕笔记本性能控制

如何用轻量级工具解放你的华硕笔记本性能控制

如何用轻量级工具解放你的华硕笔记本性能控制 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, Zenbook, Expertbook, ROG Ally,…

2026/7/31 11:03:48阅读更多 →
3分钟掌握EhTagTranslator:让e绅士标签中文化变得简单高效

3分钟掌握EhTagTranslator:让e绅士标签中文化变得简单高效

3分钟掌握EhTagTranslator:让e绅士标签中文化变得简单高效 【免费下载链接】EhTagTranslator 将e绅士页面TAG换成中文,最全数据库。 项目地址: https://gitcode.com/gh_mirrors/eh/EhTagTranslator 你是否曾在浏览e绅士网站时,面对密密…

2026/7/31 11:03:47阅读更多 →
AI Agent智能体开发全流程与核心技术解析

AI Agent智能体开发全流程与核心技术解析

1. AI Agent(智能体)产业链全景解析AI Agent(智能体)正在成为继大模型之后的下一个技术爆发点。作为一个完整的产业链,它包含了从底层基础设施到上层应用的全套技术栈。与传统的AI应用开发不同,AI Agent更强…

2026/7/31 11:03:47阅读更多 →
CPU集成HDMI/DP控制器测试全解析

CPU集成HDMI/DP控制器测试全解析

1. 项目概述在当今的计算机系统中,CPU集成的HDMI和DP控制器已成为视频输出的核心组件。作为一名长期从事显示接口测试的工程师,我经常需要对这些控制器进行全面的协议诊断分析和性能测试。这不仅关系到显示质量,更直接影响用户体验和系统稳定…

2026/7/31 11:01:46阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/30 15:03:16阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/30 12:22:27阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/30 15:13:02阅读更多 →
物理复制比逻辑复制好在哪?数据库复制原理详解

物理复制比逻辑复制好在哪?数据库复制原理详解

数据库复制是把主库数据同步到备库的机制,分为逻辑复制和物理复制两种。逻辑复制传输的是 SQL 语句或行变更事件,物理复制传输的是存储引擎底层的物理日志。阿里云 PolarDB(云原生数据库)采用物理复制,在同步延迟、数据…

2026/7/31 0:00:40阅读更多 →
BilibiliDown:3分钟学会B站视频下载的终极指南

BilibiliDown:3分钟学会B站视频下载的终极指南

BilibiliDown:3分钟学会B站视频下载的终极指南 【免费下载链接】BilibiliDown (GUI-多平台支持) B站 哔哩哔哩 视频下载器。支持稍后再看、收藏夹、UP主视频批量下载|Bilibili Video Downloader 😳 项目地址: https://gitcode.com/gh_mirrors/bi/Bilib…

2026/7/31 0:00:41阅读更多 →
有哪些游戏数据AI平台?游戏行业Data+AI融合方案盘点

有哪些游戏数据AI平台?游戏行业Data+AI融合方案盘点

当前,游戏行业的“DataAI融合”已从概念验证进入价值落地阶段。根据IDC 2025年数据,中国AI游戏云市场规模已达18.6亿元;同时,游戏研发环节AI渗透率高达86%,生成式AI内容普及率超过50%。面对庞大的市场,游戏…

2026/7/31 0:00:41阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/31 0:49:33阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/31 5:08:18阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/30 15:43:46阅读更多 →