news 2026/10/1 1:53:04

C++控制台小游戏实战:贪吃蛇、扫雷、2048从环境配置到完整实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++控制台小游戏实战:贪吃蛇、扫雷、2048从环境配置到完整实现

贪吃蛇能玩不代表你学会了C++,但只会背语法铁定写不出贪吃蛇。我见过太多人把C++当理论课啃,结构体、指针、虚函数背得滚瓜烂熟,一让他动手写个C++小游戏就卡壳。反过来,那些舍得拿控制台折腾经典小游戏的人,反而在语法细节上记得更牢,因为每个知识点都是自己踩坑踩出来的。这篇东西不扯大道理,直接把C++各种经典小游戏的实现思路、关键代码和排坑经验摊开讲,覆盖从环境配置到贪吃蛇、扫雷、2048三款经典游戏的完整落地过程,适合刚学完C++语法不知道该写什么练手的人,也适合想和学生一起做项目但缺一套成熟方案的老师。

1. 控制台游戏才是C++新手最该写的项目

很多初学者对“游戏”两个字有误解,觉得一定要上Unity、Unreal,或者至少用个EasyX图形库,做一个带贴图、带音效的东西才算游戏。这个观念要纠正一下——初学者写C++游戏,价值最大的恰恰是看起来最土的Win32控制台程序。

控制台游戏的优势在于它把C++语法的各个知识点全部串起来了。写一个真正能玩的贪吃蛇,你必须处理输入输出、循环控制、数组或结构体、函数封装、随机数生成、时间延迟、状态判断。写扫雷,你要碰二维数组、边界判断、递归算法、逻辑运算。写2048,你自然要琢磨矩阵操作和函数抽象。这套组合拳打下来,你学过的语法几乎没有用不上的。

而且控制台游戏天然带反馈。你写一个类、一个函数对不对,跑一遍就知道。游戏崩了就是代码有bug,画面卡住就是逻辑死循环,这种即时反馈比做一百道填空题都有效。C++最大的学习障碍是“学了不会用”,控制台游戏恰好解决了“用在哪”的问题。

我整理了一张覆盖对照表,按C++小游戏的常用需求对应到相关知识点,你可以感受一下:

游戏需求涉及C++知识点
地图渲染二维数组、循环输出、ASCII字符编码
物体移动状态变量、增量计算、坐标变换
键盘响应输入流、系统API调用(conio.h/windows.h)
吃食物/得分随机数、结构体、作用域与生命周期
碰撞判定条件分支、遍历比较、边界检查
游戏循环while/for循环、Sleep延时、逻辑帧设计

再往深处讲,控制台游戏逼着你理解程序运行的本质。你写一个图形界面程序,很多逻辑被框架帮你隐藏了,但在控制台项目里,每一帧画面怎么刷新、用户按键什么时候轮询、死循环什么时候退出,全都得自己操心。这种对程序执行流程的控制感,是后面学任何框架都需要的底层能力。

2. 先把环境搞利索:Windows下VS Code配MinGW-W64的完整姿势

教程一上来就写代码是耍流氓,环境配不明白能劝退一半人。我默认你用的是Windows系统,IDE选择Visual Studio Code加MinGW-W64编译器的组合。为什么不推荐Visual Studio本体?因为它太臃肿,新建一个控制台项目要等半天,对只需要编译单文件的练习来说,VS Code这种轻量编辑器的体验好得多。

MinGW是Windows上的GCC编译器,去官方镜像站下载MinGW-W64的时候要注意选中x86_64架构和posix线程模型的版本。装的时候建议放在纯英文路径下,比如C:\mingw64,装完之后把C:\mingw64\bin加进系统环境变量Path。这一步做完,在终端里输入g++ --version能看到版本信息就算通了。

VS Code这边要装三个扩展:C/C++(微软官方那个)、Code Runner、以及可选的C/C++ Extension Pack。装完扩展,你需要手动写两个配置文件,一个管编译一个管调试。我直接给你一套能用的配置。

tasks.json放在.vscode目录下,负责定义怎么把源码编译成exe文件:

{ "version": "2.0.0", "tasks": [ { "label": "build", "type": "cppbuild", "command": "g++", "args": [ "-std=c++17", "-Wall", "-fexec-charset=UTF-8", "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}.exe" ], "group": "build", "problemMatcher": ["$gcc"] } ] }

launch.json负责调试时的运行参数:

{ "version": "0.2.0", "configurations": [ { "name": "C++ Debug", "type": "cppdbg", "request": "launch", "program": "${fileDirname}/${fileBasenameNoExtension}.exe", "stopAtEntry": false, "cwd": "${workspaceFolder}", "externalConsole": true, "MIMode": "gdb", "miDebuggerPath": "C:/mingw64/bin/gdb.exe", "preLaunchTask": "build" } ] }

这套配置有两个很多人不知道的细节。一是externalConsole要设成true,这样调试时程序会在独立窗口运行,控制台小游戏的键盘输入才能正常处理;如果设成false,终端都内嵌在VS Code里,_kbhit这类函数的行为会很奇怪。二是-fexec-charset=UTF-8这个参数,特别重要。不加它,你在VS Code里用UTF-8编码写的cout << "中文"在Windows控制台上大概率乱码。

实际编译运行的时候还有三个坑得提前排掉。

第一个坑:文件路径不能有中文和空格。你建项目文件夹叫“C++贪吃蛇”,g++可能直接报错或者生成不了exe。养成好习惯,所有项目路径用英文命名。

第二个坑:控制台窗口闪退。程序跑完一瞬间窗口就关了,什么都看不清。在main函数返回之前加一行system("pause"),从Windows系统层面停住窗口。

第三个坑:用到了Sleep函数、system("cls")这类Windows API,编译时不需要额外链接库,但别忘了#include <windows.h>。用_kbhit和_getch要加#include <conio.h>,这俩是头文件提供的控制台输入函数。

这套环境配置好之后,往后所有C++小游戏项目都能直接编译,不用重复折腾。

3. 贪吃蛇:用“数组模拟链表”管理一条会变长的蛇

贪吃蛇是C++小游戏里的Hello World,但真把它写好还真不简单。它同时涉及坐标管理、方向控制、碰撞检测、随机食物和动态增长,每个环节都有讲究。我的建议是,第一版不要追求完美,先用最简单粗暴的方式跑通,再逐步优化。

3.1 蛇身数据结构:为什么用vector而不是链表

初学者最容易想到的就是用链表,因为蛇在吃东西时身体会变长,这不就是链表动态加节点吗?理论上没错,但控制台贪吃蛇最多几十个节点,链表的动态内存管理优势根本体现不出来,反而让代码复杂不少。我用std::vector<Point>存蛇身,每个Point是一个坐标结构体,头部在front(),尾部在back()。

#include <iostream> #include <vector> #include <conio.h> #include <cstdlib> #include <ctime> #include <windows.h> struct Point { int x, y; }; enum Direction { UP, DOWN, LEFT, RIGHT };

移动的逻辑核心是“头部插新,尾部看情况删”。蛇每走一步,在头部的方向坐标上生成一个新头insert进去;如果这一格是食物,保留尾部,蛇就变长了;如果不是食物,pop_back()删掉尾部,蛇身长度保持不变。这套逻辑用数组、链表还是vector都能实现,但vector最简洁,还方便遍历判断有没有撞到自己。

// 移动核心 Point newHead = snake.front(); switch(dir) { case UP: newHead.y--; break; case DOWN: newHead.y++; break; case LEFT: newHead.x--; break; case RIGHT: newHead.x++; break; } snake.insert(snake.begin(), newHead); if (newHead.x == food.x && newHead.y == food.y) { score += 10; spawnFood(); // 重新生成食物 } else { snake.pop_back(); }

3.2 方向锁:一个几乎人人都踩的经典bug

你第一次写完贪吃蛇,大概率会遇到这种情况:蛇正在向左走,你快速按了上再按右,蛇直接穿过了自己的身体,游戏没判死,但画面上蛇像断了节一样错位。这就是典型的“同一帧内两次转向导致的反向移动”bug。

解决方案是加一个方向锁。蛇的移动和按键读取是分离的,每次移动结束后记录当前方向,按键处理只更新期望方向,但有一个限制条件——不可以反向。比如当前方向是右,你按了左,这个指令直接丢弃;但如果你按了上,哪怕立刻按右,程序在下一帧才会更新方向,实际上蛇是先向上挪了一格再向右,这样就不会出现穿越。

if (_kbhit()) { int key = _getch(); // 方向键在Windows控制台是双字节,第一个字节通常是224或0 if (key == 224) key = _getch(); switch(key) { case 72: if (dir != DOWN) dir = UP; break; case 80: if (dir != UP) dir = DOWN; break; case 75: if (dir != RIGHT) dir = LEFT; break; case 77: if (dir != LEFT) dir = RIGHT; break; } }

3.3 刷新重绘:为什么你的蛇有“尾巴影”

控制台没有现成的图形刷新机制,你每帧都要system("cls")清屏,然后按坐标重新绘制整个地图。这里有个新手很容易犯的错——只清屏但不清缓冲区,或者绘制顺序反了,导致画面有残留的字符。

正确的顺序是:先清屏,再绘制边界,再绘制食物,再遍历蛇身绘制,最后输出分数。每帧绘制完成后用Sleep(100)控制速度,100毫秒一帧是起步值。实测下来,低于50毫秒蛇跑得太快人眼根本反应不过来,高于200毫秒又太肉。

system("cls"); // 绘制地图和蛇 for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { bool isBody = false; for (auto& p : snake) if (p.x == x && p.y == y) { isBody = true; break; } if (x == food.x && y == food.y) std::cout << "*"; else if (isBody) std::cout << "O"; else if (x == 0 || x == WIDTH - 1 || y == 0 || y == HEIGHT - 1) std::cout << "#"; else std::cout << " "; } std::cout << "\n"; } std::cout << "Score: " << score << "\n"; Sleep(100);

游戏结束的判断条件有三个:撞墙、撞自己、蛇长到占满整个地图。第一版本地判断一下前两个就够了,第三个条件属于进阶优化。

贪吃蛇这个项目最值钱的地方在于,它让你第一次用程序手段管理一个“实时变化的状态集合”。蛇身是不断变化的,食物是不断重生的,方向是动态切换的,这比写一个线性执行的计算器项目复杂得多,也接近真实游戏项目的感觉。

4. 扫雷:二维数组、偏移数组和递归泛洪的结合

扫雷比贪吃蛇高一个层级。贪吃蛇的难点在状态管理,扫雷的技术重点则在二维数据处理和递归算法。这个游戏的经典程度不用多说,Windows扫雷承载了太多人的童年回忆,用C++把它实现一遍,你会对二维数组有完全不同的理解。

4.1 棋盘建模:两张二维表搞定一切

扫雷的核心是两张二维表。一张存地雷分布和数字信息,用整型数组int mine[ROW][COL];一张存给玩家看到的显示状态,用字符数组char show[ROW][COL]。

const int ROW = 10, COL = 10, MINE_COUNT = 15; int mine[ROW][COL]; char show[ROW][COL];

为什么用两张表而不是一张?因为游戏有两个层面的信息:真相和玩家视角。玩家看到的是show表,里面有未翻开(?)、已翻开(数字或空格)、已标记地雷(F)三种状态;而真正的雷和数字藏在mine表里。翻格子时以mine表的值为依据,更新show表。这个“真相与视角分离”的设计思想,以后写任何带信息隐藏的业务逻辑都用得上。

4.2 布雷算法:怎么避免随机数重复

布雷是第一个细节。用rand() % ROW加rand() % COL随机选址,很可能会选到同一个格子,导致最终地雷数量不对。最简单的解决办法是while循环判断,重复就重新生成:

int placed = 0; while (placed < MINE_COUNT) { int r = rand() % ROW; int c = rand() % COL; if (mine[r][c] != -1) { mine[r][c] = -1; placed++; } }

但这个方法在雷数极少时没问题,雷数接近格子总数时可能变成死循环(概率极低,但理论上存在)。更优雅的方案是生成一个包含所有索引的一维数组,然后用std::shuffle洗牌,取前MINE_COUNT个作为雷位。这个思路本质上和洗牌算法是一致的,值得掌握。

布雷之后是计算每个非雷格子的数字。这里有个非常关键的技巧——遍历8个邻格时不要手写8个if,而是用一个偏移数组:

int dir[8][2] = {{-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1}}; for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { if (mine[i][j] == -1) continue; int cnt = 0; for (int k = 0; k < 8; k++) { int ni = i + dir[k][0]; int nj = j + dir[k][1]; if (ni >= 0 && ni < ROW && nj >= 0 && nj < COL && mine[ni][nj] == -1) cnt++; } mine[i][j] = cnt; } }

偏移数组在游戏开发里太常用了。寻路、地图遍历、碰撞检测,凡是涉及“按方向探测周围格子”的场景,都可以用偏移数组替代好几层嵌套的if逻辑。

4.3 递归泛洪:点到空白区域一键展开的核心

扫雷最好玩的地方就是点一个空白格子,周围一大片空白跟着打开。这个现象的学名叫泛洪填充(Flood Fill),实现起来就是递归或栈。

void reveal(int r, int c) { if (r < 0 || r >= ROW || c < 0 || c >= COL) return; if (show[r][c] != '?') return; // 已翻开或已标记 if (mine[r][c] == -1) return; // 是地雷 if (mine[r][c] > 0) { show[r][c] = '0' + mine[r][c]; // 边界格子只显示数字 return; } show[r][c] = ' '; // 空白格子翻开 for (int k = 0; k < 8; k++) { int ni = r + dir[k][0]; int nj = c + dir[k][1]; if (ni >= 0 && ni < ROW && nj >= 0 && nj < COL) { reveal(ni, nj); } } }

这个递归函数的终止条件写得很讲究。四条return分别处理越界、重复翻开、踩雷、以及遇到数字边界。没有这四条边界控制,递归会一直往8个方向扩散,最终把整个棋盘都翻过来,或者无限递归直接栈溢出。

带孩子写扫雷的时候,我最喜欢让他观察一个现象:点边缘数字格子和点中心空白格子的展开效果完全不一样。点中心空白格回触发一大片区域展开,这就是递归的魅力所在——每一层递归向四周扩散,直到碰见数字边界才停下来。

4.4 输赢判定:最后的临门一脚

踩到地雷时,翻开所有地雷,游戏结束;所有非雷格子都翻开,游戏胜利。

bool checkWin() { int opened = 0; for (int i = 0; i < ROW; i++) for (int j = 0; j < COL; j++) if (show[i][j] != '?') opened++; return opened == ROW * COL - MINE_COUNT; }

扫雷项目做下来,最大的收获不是算法,而是一种严谨的边界意识。每一处下标运算都要想清楚会不会越界,每一条游戏规则都要在代码里找到对应实现。这种思维方式对以后写任何大型程序都是根本性的。

5. 2048:一个方向函数打天下的矩阵旋转技巧

2048这款游戏在C++小游戏里属于颜值和智商双重担当。玩法不复杂,4x4棋盘,每次滑动所有数字向对应方向移动,相同数字碰撞时合并。难点在于四个方向的滑动逻辑怎么写——如果每个方向写一套,代码会非常臃肿,而且特别容易出bug。

5.1 核心洞察:旋转矩阵复用同一个移动函数

新手下笔,大概率会按上下左右四个方向分别写移动函数。但你仔细观察会发现,2048的移动规则在四个方向上完全对称。向左移动时,每一行的数字向左靠拢,相同数字合并;向右移动,只不过是把方向反过来。

对称性意味着我们可以只写一个左移函数,然后通过旋转棋盘来复用。具体来说,想让数字往上移动,就先把棋盘顺时针旋转90度,执行一次左移,再旋转270度转回来。这个思路看似多做了几步,但换来的是代码量减少四分之三,而且只有一套核心逻辑需要调试。

// 顺时针旋转90度 void rotate(int board[4][4]) { // 1. 转置 for (int i = 0; i < 4; i++) for (int j = i + 1; j < 4; j++) std::swap(board[i][j], board[j][i]); // 2. 水平翻转 for (int i = 0; i < 4; i++) for (int j = 0; j < 2; j++) std::swap(board[i][j], board[i][3 - j]); }

旋转这个技巧本身,就是编程思维里“抽象与复用”的典型案例。处理复杂问题时,不要一上来就拆成4个可能相似的子问题,先思考能不能找到一个公共解法,再用变换去适配不同场景。

5.2 左移函数的三个步骤:去零、合并、再去零

左移的核心算法分成三步走。

第一步是去零。每一行扫描一遍,把非零数字按顺序提取到一个临时数组,后面补零。这个操作本质是压缩密度。

int row[4] = {0}; int idx = 0; for (int j = 0; j < 4; j++) if (board[i][j] != 0) row[idx++] = board[i][j];

第二步是合并。从最左边开始,如果相邻两个数字相等,左边的乘2,右边的置零。这里有一个注意点:每一行在一次滑动中,同一个数字只能参与一次合并。比如“2 2 2 2”往左滑,正确结果是“4 4 0 0”,而不是“8 0 0 0”。所以合并逻辑要用一个单独的循环从左往右扫。

for (int j = 0; j < 3; j++) { if (row[j] == row[j+1] && row[j] != 0) { row[j] *= 2; row[j+1] = 0; } }

第三步是再次去零。合并之后数组里出现了新空位,再执行一次第一步的逻辑,把数字重新左对齐。

int newRow[4] = {0}; idx = 0; for (int j = 0; j < 4; j++) if (row[j] != 0) newRow[idx++] = row[j];

完整代码里还有一个容易忽略的细节:一个“move”函数必须返回一个bool值,告诉主循环本次滑动是否让棋盘发生了变化。玩家按了一个方向键,如果棋盘没有任何数字移动,那么就视为无效操作,不生成新数字,回合也不推进。这个细节决定了游戏体验是否严谨,不少线上Demo就栽在这——按了方向键,明明没动,还往棋盘塞新数字。

5.3 游戏结束判断:两遍扫描定胜负

2048的胜负判定比扫雷简单:玩家凑出2048就胜利(也可以选择继续玩);棋盘满了并且无法再合并任何数字,游戏结束。

判断“能否再合并”只需要两次扫描。第一次扫描所有格子,只要有一个空位,游戏就继续。第二次扫描所有相邻对——横向看board[i][j] == board[i][j+1],纵向看board[i][j] == board[i+1][j],只要有一对相等,就还有合并的可能。

bool canMove(int board[4][4]) { for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) if (board[i][j] == 0) return true; for (int i = 0; i < 4; i++) for (int j = 0; j < 3; j++) if (board[i][j] == board[i][j+1]) return true; for (int i = 0; i < 3; i++) for (int j = 0; j < 4; j++) if (board[i][j] == board[i+1][j]) return true; return false; }

新数字生成也很讲究。在有效的空位上随机生成2(90%概率)或4(10%概率),这已经是这个游戏不成文的惯例了,数值手感经过玩家大量验证,直接照抄即可。

2048的精华在于,它要求你从机械的“逐个方向实现”中跳出来,寻找问题背后的对称性。你是不是养成了从更高维度抽象问题的习惯,写一遍这个游戏自己心里就有数了。

6. 调试、重构、继续深入:三款游戏写完之后的路

写完三款游戏,你已经有了一堆可以运行的代码。但这个阶段最重要的不是继续写第四款游戏,而是回头打磨已有的代码,把烂代码整理成能进简历的样子。

6.1 控制台游戏最常见的三类bug

我这几年带人写C++小游戏,发现控制台项目的bug高度集中,提前了解能省下大量时间。

第一类bug是输入状态残留。_getch()读取按键时会反映射到方向键的双字节,在有些编译器上方向键第一字节是224,在另一些平台上可能是0。处理方式就是判断一下首字节是224还是0,再读第二字节。这个坑不填,换台机器代码就失灵。

第二类bug是乱码问题。Windows控制台默认用GBK编码,VS Code默认写UTF-8,两者不对齐就是满屏乱码。解决方案有三个层面,代码里用system("chcp 65001")切到UTF-8代码页;或者编译时加-fexec-charset参数;最稳妥的是一开始界面输出全用英文,把乱码问题根治掉。

第三类bug是刷新闪烁。system("cls")清屏再重绘,在Sleep较短的时候会看到明显的一闪一闪。解决方案是用Windows API的光标定位函数,每次把光标挪到(0,0)而不是清屏,这样画面几乎不闪。具体到代码,先用GetStdHandle(STD_OUTPUT_HANDLE)拿到控制台句柄,再用SetConsoleCursorPosition移动光标,熟练之后可以自己去查一下这个API。

6.2 从面向过程走向面向对象

写控制台游戏最容易暴露的一个问题是:全局变量满天飞。贪吃蛇时代你可能是几个全局数组打天下,游戏规模一大就不行了,函数之间靠全局变量传数据,改一个牵一发动全身。

我强烈建议把三个游戏都改写成类。贪吃蛇的SnakeGame类里包好地图宽度、蛇身、当前方向、食物、分数、游戏是否结束;扫雷定义一个Minesweeper类,构造函数传行列数和雷数,初始化棋盘;2048更简单,Game2048类持有二维矩阵和操作它的函数。这段重构做下来,你对C++访问控制、封装、成员函数的意义会有质的理解。

我当时带小学员做这步重构的时候,最直观的转变是,原来一个main函数几百行,重构之后每个方法都小而清晰,名字看一眼就知道是干嘛的。这种代码组织能力写多少书都学不来,只能靠改自己的烂代码练出来。

6.3 从控制台走向真正带图形的游戏

控制台小游戏练的是逻辑和语法,但真实游戏开发哪有控制台呢?所以写到一定程度,你肯定会想往图形化方向走。

可选的方向有这么几个。EasyX图形库是给C++初学者设计的Windows图形库,不用学复杂框架,看看文档就能画出图形界面。想学通用游戏开发,SDL2是跨平台多媒体库,体积小上手快。再往上理解游戏循环、碰撞检测、贴图渲染,那就可以摸摸Unity了——但Unity的脚本语言是C#,和C++的关系在于逻辑思维相通。

我个人的建议是,先不要急着换语言和框架。把三款小游戏用EasyX重新画一遍界面,把键盘输入从_getch换成消息循环,体会一下即时渲染的逻辑差异。这个过程依然发生在C++小游戏的范畴内,但编程体验已经是完全不同的境界了。图形界面的游戏,你需要掌握事件驱动模型、资源管理、渲染管线这些概念。有了控制台游戏打底,理解这些抽象概念会顺利得多。

最后一个实用性建议:把三个游戏工程分目录放好,每个目录配一个README.md,写上编译方式和玩法说明。半年后你回头看自己写的东西,或者拿着这些项目去面试、去参加比赛,你会感谢自己当初认真整理了代码。C++的学习曲线确实陡,但靠一个个能跑起来的小游戏堆出来的底子,是最不容易忘的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 1:52:01

nginx部署vue包dist,页面刷新后,提示“404 Not Found”

nginx-1.9.10错误截图如下&#xff1a;处理方法&#xff1a;在指定的位置添加下面代码&#xff0c;即可&#xff1b;location / {root E:/workspace/dists/dist;index index.html index.htm;# 解决页面刷新后&#xff0c;报404的问题try_files $uri $uri/ /index.html;}重启…

作者头像 李华
网站建设 2026/10/1 1:51:14

【Excel】零碎技能积累

文章目录1.INDIRECT跨表引用2.常用功能快速使用&#xff08;快捷键&#xff09;3.excel数据透视表&#xff0c;非重复计数4.透视表计算字段、计算项5.条件格式&#xff0c;A列是条件&#xff0c;B列因A列条件显示格式6.快速全表替换某一范围的数7.批量合并单元格8.快速拆分单元…

作者头像 李华
网站建设 2026/10/1 1:51:12

USB蓝牙适配器Linux不识别?CM591/ATS2851内核与BlueZ排查

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

作者头像 李华
网站建设 2026/10/1 1:50:52

AAA级武士角色纹理制作全流程:PBR工作流与Substance Painter实战

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

作者头像 李华
网站建设 2026/10/1 1:49:44

从麦田到论文:作物生产与品质改良人的 AI 搭子怎么选 [特殊字符]

如果你学的是作物生产与品质改良&#xff0c;大概率会遇到这样一个毕业任务&#xff1a; 以小麦为材料&#xff0c;研究不同施氮量对群体生长、产量构成和籽粒品质的影响&#xff0c;最终完成一篇包含试验设计、数据分析、图表制作和讨论分析的毕业论文。 这不是“随便写写”的…

作者头像 李华
网站建设 2026/10/1 1:49:29

前端 Mock 数据:Mock.js、MSW 与 Service Worker

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

作者头像 李华