C++17与SFML实战:从TreeTycoon源码学大亨游戏开发

简介:TreeTycoon 是一套基于 C++17 与 SFML 开发的 2D 模拟经营游戏源码项目,面向有一定 C++ 基础、希望研究游戏引擎架构与经营类玩法实现的开发者与学习者。项目围绕树木种植与经营主题,采用自定义游戏框架,涵盖场景栈管理、资源加载、音频控制、地块存储与游戏对象等模块,文档以 Markdown 编写,便于理解整体设计思路。压缩包共 121 个文件,以 39 个 cpp 源文件与 37 个 hpp 头文件为核心,辅以 15 个 md 说明文档、Visual Studio 工程文件及少量 png、wav 等资源,整体约 4.61MB,结构清晰,适合按模块阅读。目前已有 116 人学习下载。通过该源码可了解 C++17 特性在游戏开发中的实际运用、SFML 图形与音频接口的封装方式,以及引擎迁移与重构后的目录组织,是研究 2D 游戏框架与经营玩法实现的一份实用参考。

1. 从一份 C++17 大亨游戏源码说起:TreeTycoon 能跑起来吗

前阵子有个做后端的朋友问我,想用 C++ 写个经营类小游戏练手,但不知道从哪下手。我翻了一圈开源项目,发现大部分要么是纯引擎(SFML、SDL 的 demo),要么是教程级的贪吃蛇、俄罗斯方块,真正带完整游戏循环、经济系统、UI 交互的“大亨类”项目少得可怜。TreeTycoon 就是在这个背景下进入视野的——一个用 C++17 加 SFML 搭起来的 2D 大亨游戏框架,带 GDD(游戏设计文档)和可编译的源码包。

它解决的核心问题很具体:给你一个能跑通的、结构不算太烂的“种树赚钱”模拟器骨架,让你看到资源生产、时间推进、数值增长、界面刷新这条链路在 C++ 里怎么串起来。适合谁?一是想从“写算法题”过渡到“写有状态的小游戏”的 C++ 学习者,二是需要个轻量框架改造成自己题材(比如开矿、养鱼、搞物流)的独立开发者。SFML 本身跨平台,Windows 和 Linux 下编译都不算折腾,但前提是你得把依赖和构建顺序搞对,否则第一关就是链接报错。

2. 拆开 TreeTycoon 的骨架:C++17 与 SFML 怎么分工

2.1 为什么是 SFML 而不是 SDL 或纯 OpenGL

选 SFML 做 2D 大亨游戏,理由很实际。SDL 更底层,你要自己封装纹理加载、字体渲染、事件循环的 C++ 包装,写起来啰嗦;纯 OpenGL 更不用说,一个按钮的点击检测都能耗掉半天。SFML 提供了sf::RenderWindow、sf::Sprite、sf::Text、sf::Clock这些开箱即用的类,正好覆盖大亨游戏最常用的“画图 + 写字 + 计时 + 收鼠标键盘事件”四件事。

TreeTycoon 的源码里,main.cpp通常只做三件事:初始化窗口、创建游戏对象、跑主循环。真正的逻辑被拆到Game、World、UI这几个类里。C++17 在这里的贡献不是语法炫技,而是几个实用特性:std::optional用来处理“可能没有选中任何地块”的返回值,std::filesystem用来扫描资源目录,结构化绑定让遍历 map 时少写两行。这些在游戏状态管理里能减少不少空指针判断的噪音。

提示:如果你之前只用过 C++11,升级到 C++17 后编译 SFML 项目基本无痛,但记得在 CMake 里把标准设对,否则std::filesystem会报链接错误。

2.2 源码目录里每个文件在干什么

拿到源码包后,别急着编译。先花五分钟把目录结构看一遍,能省掉后面半小时的“文件找不到”报错。典型的 TreeTycoon 布局是这样的:

路径作用是否要改
src/main.cpp程序入口,窗口创建与主循环改窗口尺寸时动
src/Game.h/.cpp游戏状态机,管理开始/运行/暂停加新状态时动
src/World.h/.cpp地块、树木、资源数据改玩法核心
src/UI.h/.cpp按钮、文本、面板绘制调界面时动
assets/字体、贴图、音效替换素材
CMakeLists.txt构建脚本加库时动
docs/GDD.md设计文档,数值和规则改数值前必读

GDD 不是摆设。大亨游戏的核心是数值曲线——树多久长一阶、每阶卖多少钱、升级成本怎么递增。这些在 GDD 里通常有表格,你改代码前先对一遍,不然调出来的经济系统要么通胀要么死水。

2.3 编译前必须确认的三个环境项

SFML 的编译翻车,九成出在版本和链接顺序上。下面这套检查流程,我每次在新机器上配环境都会走一遍。

# 1. 确认编译器支持 C++17 g++ --version # 期望输出 g++ 7 以上,clang 5 以上,MSVC 2017 以上 # 2. 确认 SFML 已安装且版本匹配 # Linux 下用包管理器装最省事 sudo apt install libsfml-dev # 装完检查版本,TreeTycoon 一般要求 SFML 2.5+ pkg-config --modversion sfml-graphics # 3. 确认 CMake 能找到 SFML cmake --version # 3.10 以上即可

逻辑说明:第一步排除编译器太老导致std::optional不识别;第二步确保 SFML 的头文件和库文件都在系统路径里,pkg-config能返回版本号说明安装正确;第三步是构建工具的门槛,CMake 3.10 之后对find_package的支持才稳定。如果第二步报“找不到包”,别急着编译项目,先把 SFML 装明白。

参数说明:pkg-config --modversion后面跟的模块名要和你 CMakeLists 里find_package(SFML COMPONENTS graphics window system)写的组件一致。少写一个window,链接时就会报sf::RenderWindow未定义。

3. 从零编译到窗口弹出:CMake 配置与主循环拆解

3.1 CMakeLists 里三行关键配置

很多新手拿到源码直接cmake .然后make,结果一堆 undefined reference。问题通常不在代码,在 CMakeLists 的链接顺序和标准设置。下面是一个能跑通的最小配置片段:

cmake_minimum_required(VERSION 3.10) project(TreeTycoon CXX) # 关键行1:强制 C++17,不写这行 std::filesystem 会挂 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 关键行2:找 SFML,组件要写全 find_package(SFML 2.5 COMPONENTS graphics window system REQUIRED) # 关键行3:可执行文件链接时,SFML 库放在源文件后面 add_executable(TreeTycoon src/main.cpp src/Game.cpp src/World.cpp src/UI.cpp) target_link_libraries(TreeTycoon sfml-graphics sfml-window sfml-system)

逻辑说明:CMAKE_CXX_STANDARD 17是硬门槛,不设的话编译器可能按默认的 C++14 处理,std::optional直接报错。find_package的REQUIRED让配置阶段就失败,而不是等到链接。target_link_libraries的顺序在 Linux 下敏感,库必须写在目标文件之后,否则链接器会“看过就忘”。

参数说明:SFML 2.5是版本下限,如果你系统装的是 2.6 也能兼容。组件里graphics依赖window,window依赖system,所以三个都要写。Windows 下用 MSVC 的话,把sfml-graphics换成sfml-graphics-d可以链接调试版,但需要 SFML 的调试库也装好。

3.2 主循环里的时间步长与事件处理

大亨游戏和动作游戏不同,不需要每帧都重算物理,但时间推进必须稳。TreeTycoon 的主循环通常长这样:

#include <SFML/Graphics.hpp> #include "Game.h" int main() { sf::RenderWindow window(sf::VideoMode(1280, 720), "TreeTycoon"); window.setFramerateLimit(60); // 限制帧率,省 CPU Game game(window); // 游戏对象持有窗口引用 sf::Clock clock; // 用于计算 deltaTime while (window.isOpen()) { sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); game.handleInput(event); // 把事件转给游戏逻辑 } float dt = clock.restart().asSeconds(); // 上一帧耗时 game.update(dt); // 按时间推进经济系统 window.clear(); game.render(); // 绘制地块和 UI window.display(); } return 0; }

逻辑说明:setFramerateLimit(60)防止循环空转吃满 CPU,对经营类游戏足够。clock.restart()返回上一帧到现在的时长,传给update后,树木生长、金钱累积都按真实秒数算,而不是按帧数——这样在不同性能的机器上游戏速度一致。handleInput单独抽出来,是为了让 UI 和世界对象自己决定怎么响应点击,主循环不掺和具体逻辑。

参数说明:dt单位是秒,如果你在update里写money += rate * dt,rate 就是“每秒收入”。窗口尺寸 1280x720 是常见起点,改大改小后记得检查 UI 布局有没有写死坐标。pollEvent必须循环到返回 false,否则事件队列会堆积,点击响应变迟钝。

3.3 第一次运行会看到的画面与验证点

编译成功后运行,你应该看到一个窗口,里面至少有一块地、一个数值面板(显示金钱或木材)、可能还有几个按钮。验证顺序建议这样:

  1. 窗口能否正常关闭——点右上角 X,程序退出无卡死。
  2. 鼠标点击地块是否有反馈——比如高亮或数值变化。
  3. 等待十秒,看资源数值是否在涨——验证update里的时间累积逻辑。
  4. 按 ESC 或点击暂停按钮,数值是否停止——验证状态切换。

如果窗口弹出但全黑,先检查assets/路径。SFML 加载字体和贴图时,相对路径是相对于“工作目录”而不是可执行文件所在目录。在 IDE 里运行和双击 exe 运行,工作目录可能不同,这是最常见的“资源加载失败”原因。

注意:如果编译时报undefined reference to sf::...,九成是target_link_libraries里库的顺序或组件缺失。把sfml-graphics sfml-window sfml-system按这个顺序写,且放在add_executable之后。

4. 避坑与排查:SFML 项目里最容易翻车的五件事

4.1 现象:编译通过但运行时报“Failed to load font”

原因:字体文件路径写的是相对路径,而程序的工作目录不是项目根目录。比如代码里写font.loadFromFile("assets/font.ttf"),在 IDE 里运行可能没问题,但直接跑 exe 时工作目录变成了build/,找不到assets/。

解决:要么在代码里用std::filesystem::current_path()打印当前工作目录,确认实际位置;要么把资源路径改成基于可执行文件位置的绝对路径。更稳妥的做法是在 CMake 里用configure_file把资源目录复制到构建输出目录旁边。

4.2 现象:窗口显示但画面撕裂或闪烁

原因:没有开垂直同步,或者window.display()调用时机不对。SFML 默认双缓冲,但如果你在clear()和display()之间做了耗时操作,帧率不稳就会闪。

解决:window.setVerticalSyncEnabled(true)替代setFramerateLimit,让显卡控制刷新节奏。但注意,垂直同步在部分虚拟机上会失效,那就退回帧率限制。另外确保所有绘制都在clear()之后、display()之前完成,不要跨帧累积绘制命令。

4.3 现象:点击按钮没反应,或者点一次触发多次

原因:事件处理写在了pollEvent循环外面,或者用了sf::Mouse::isButtonPressed这种状态查询而不是事件驱动。状态查询在每帧都返回 true,导致一次点击被处理几十次。

解决:坚持用event.type == sf::Event::MouseButtonPressed来判断点击,并且只在pollEvent循环内处理。如果 UI 有多个按钮,在handleInput里按层级从上层往下传,一旦某个按钮消费了事件就停止传递,避免穿透。

4.4 现象:数值增长越来越快,或者暂停后还在涨

原因:dt没有在暂停时清零,或者clock.restart()在暂停分支里也被调用了。暂停时如果继续累积dt,恢复后一次性补上,数值就会跳变。

解决:在Game::update开头判断状态,如果暂停就直接return,但clock.restart()仍然要在主循环里每帧调用,保证dt不累积。或者用一个sf::Time累加器,只在运行状态下累加。

4.5 现象:换台机器编译,报“找不到 SFML 头文件”

原因:SFML 的安装路径没有进系统的 include 搜索路径,或者 CMake 的find_package找不到配置文件。Linux 下用包管理器装通常没问题,Windows 下手动解压的 SFML 需要设SFML_DIR环境变量。

解决:Windows 下在 CMake 命令里加-DSFML_DIR="C:/SFML/lib/cmake/SFML",指向解压目录里的 cmake 配置文件夹。macOS 下用 Homebrew 装完,CMake 一般能自动找到,如果不行就加-DCMAKE_PREFIX_PATH=/opt/homebrew。跨平台项目最好在 README 里写清每个系统的依赖安装命令,省得每换一台机器就翻车一次。

5. 把 TreeTycoon 改成你自己的题材:数值表与扩展点

5.1 先改 GDD 再改代码,顺序不能反

大亨游戏的乐趣全在数值曲线。TreeTycoon 默认是种树卖木材,你想改成养鱼卖鱼,代码结构其实不用大动,但 GDD 里的表格必须先重写。常见做法是维护一张 CSV 或 Markdown 表,列清楚每个阶段的“产出速率、升级成本、解锁条件”。比如:

阶段产出/秒升级成本解锁条件
树苗0.510初始
小树250累计产出 100
大树8200累计产出 500

这张表直接映射到代码里的struct TreeLevel { float rate; int cost; int unlockAt; },用std::vector存起来,update里按当前等级查表。改题材时只动数据和贴图,逻辑一行不用碰。

5.2 加一个新资源类型的三个改动点

假设你想加“水”作为第二资源,需要动三个地方。第一,在World类里加float water_成员和对应的 getter。第二,在update里加water_ += waterRate * dt,waterRate从当前地块等级查表。第三,在UI的绘制函数里加一行sf::Text显示水量,位置避开原有面板。这三步做完,编译运行就能看到水在涨。如果想加消耗逻辑,比如升级树木需要同时扣钱和水,就在升级按钮的回调里加判断。

5.3 用std::function做按钮回调,避免 UI 和逻辑耦合

TreeTycoon 的 UI 类如果直接调用World的方法,改玩法时就得同时改两个类。更干净的做法是给按钮绑std::function<void()>:

// UI.h class Button { public: sf::RectangleShape shape; sf::Text label; std::function<void()> onClick; // 回调,由外部注入 }; // 在 Game 初始化时绑定 ui.upgradeButton.onClick = [this]() { if (world.getMoney() >= world.getUpgradeCost()) { world.spendMoney(world.getUpgradeCost()); world.upgradeTree(); } };

逻辑说明:std::function可以存 lambda,lambda 里捕获this后就能访问world。这样 UI 类不需要知道World的存在,只负责画按钮和触发回调。改玩法时只改绑定那几行,UI 代码不动。

参数说明:lambda 捕获this时要注意生命周期,确保Game对象比按钮活得久。如果按钮在Game析构后还被触发(比如异步事件),就会访问悬空指针。单线程游戏循环里一般不会出这个问题,但加多线程加载时要留意。

5.4 验证改动是否生效的最小闭环

每次改完数值或加完资源,别急着做完整测试。跑一个最小闭环:启动游戏,等 10 秒,看新资源是否按预期速率增长;点一次升级,看成本和产出是否同步变化;关掉再开,看初始状态是否正确。这三步能覆盖 80% 的低级错误。如果数值不对,先在update里加一行std::cout打印dt和当前值,确认时间步长没跑偏。从那以后我每次改经济系统,都强制先跑这个十秒闭环,再去看界面好不好看。希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

YOLOv8+MSGA:髋关节X光片股骨检测的全局感知优化方案

YOLOv8+MSGA:髋关节X光片股骨检测的全局感知优化方案

1. 项目概述在医学影像里&#xff0c;X光片仍然是骨科疾病筛查的第一道关卡。髋关节的股骨位置识别与定位&#xff0c;看着好像只是画个框&#xff0c;实际牵扯到后续一系列自动化测量的地基&#xff1a;髋关节发育不良的角度计算、股骨颈骨折的检测、置换术后的假体评估&#…

2026/10/20 4:36:45 阅读更多 →
ok-ww鸣潮自动战斗与刷声骸实战指南:一键日常完整配置清单

ok-ww鸣潮自动战斗与刷声骸实战指南:一键日常完整配置清单

ok-ww鸣潮自动战斗与刷声骸实战指南&#xff1a;一键日常完整配置清单 【免费下载链接】ok-wuthering-waves 鸣潮 后台自动战斗 自动刷声骸 一键日常 Automation for Wuthering Waves 项目地址: https://gitcode.com/GitHub_Trending/ok/ok-wuthering-waves ok-ww 是一款…

2026/10/20 4:36:45 阅读更多 →
让AI读懂本地一万份文件:解析、索引与问答实战

让AI读懂本地一万份文件:解析、索引与问答实战

1. “更懂你”的前提&#xff1a;它得先读完你这一万份文件每次看到AI产品宣传“懂你”&#xff0c;我都想反问一句&#xff1a;它读过我电脑里那堆资料吗&#xff1f;没有。大多数号称懂你的AI&#xff0c;懂的只是你在对话框里喂给它的那几句话&#xff0c;顶多加上你点过几个…

2026/10/20 4:36:45 阅读更多 →

最新新闻

视觉模型重塑时间序列分析:从离散Token到连续视觉的范式转换

视觉模型重塑时间序列分析:从离散Token到连续视觉的范式转换

1. 从离散Token到连续视觉&#xff1a;时间序列分析的新思路拆解时间序列分析这个领域&#xff0c;做的人多&#xff0c;但真正让人眼前一亮的突破少。传统方法从统计模型一路走到深度学习&#xff0c;Transformer架构进来之后&#xff0c;大家发现一个尴尬的事实&#xff1a;文…

2026/10/20 5:21:04 阅读更多 →
向AI提交创作需求:关键信息清单助你生成优质博客

向AI提交创作需求:关键信息清单助你生成优质博客

抱歉&#xff0c;我还没有收到您的项目标题和相关描述。麻烦您将以下信息发给我&#xff0c;我立刻开始创作&#xff1a;项目标题: [在这里填写标题] 项目正文: [零散描述也可以] 关键词: [关键词1, 关键词2, ...] 摘要描述: [一句话简介]收到后我会直接输出一篇完整的、可直接…

2026/10/20 5:21:04 阅读更多 →
PyTorch nn.DataParallel原理与实战:多卡训练的底层基石

PyTorch nn.DataParallel原理与实战:多卡训练的底层基石

1. 为什么今天还要深挖nn.DataParallel&#xff1f;它真过时了吗&#xff1f;在 PyTorch 生态里&#xff0c;提到多卡训练&#xff0c;很多人第一反应是DistributedDataParallel&#xff08;简称 DDP&#xff09;&#xff0c;社区文档、新教程、论文复现代码几乎清一色推荐它。…

2026/10/20 5:21:04 阅读更多 →
OpenCode接入Qwen模型实战:从选型配置到避坑指南

OpenCode接入Qwen模型实战:从选型配置到避坑指南

最近在折腾终端里的 AI 编程 Agent 工具 OpenCode&#xff0c;有一件事绕不开&#xff1a;模型配置。OpenCode 这类工具默认的模型接入方式对国内开发者不算友好&#xff0c;网络、计费、可用性都有门槛。好在这类工具大多支持自定义模型接入&#xff0c;而这几年国产开源模型里…

2026/10/20 5:21:04 阅读更多 →
榜单第一不等于实战能打:从Argon看大模型代码能力评价误区

榜单第一不等于实战能打:从Argon看大模型代码能力评价误区

开篇想聊个最近在圈子里传得挺开的事&#xff1a;某头部厂商放出了新的旗舰模型Argon&#xff0c;官方口径是“榜单18项第一”&#xff0c;从通用问答到复杂推理再到多模态理解&#xff0c;几乎把所有权威评测的榜首都揽了一遍。可等开发者们真把它拖到真实的代码仓库里跑任务&…

2026/10/20 5:21:04 阅读更多 →
智能支持员实战:从意图识别到多轮诊断的AI排障系统搭建

智能支持员实战:从意图识别到多轮诊断的AI排障系统搭建

做了这么多年技术支持&#xff0c;我对一件事特别有感触&#xff1a;真正占时间的不是那些疑难杂症&#xff0c;而是每天反复出现的“老三样”——服务起不来、配置不生效、报错信息看不懂。团队决定做“智能支持员”这个项目的时候&#xff0c;我的第一反应不是“又要上机器人…

2026/10/20 5:20:04 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →