news 2026/9/30 1:38:35

HGE引擎下超级玛丽源码解析与VS2019编译实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HGE引擎下超级玛丽源码解析与VS2019编译实战

简介:基于Visual C++与HGE游戏引擎的超级玛丽(超级马里奥/Mario)完整源代码包,面向具备C++基础、希望深入2D游戏开发的初学者和爱好者。项目围绕经典横版过关玩法,覆盖游戏循环、渲染、碰撞检测、输入响应、音频播放与状态管理等核心模块,能帮助读者理解从引擎初始化到游戏逻辑实现的整体流程。压缩包共183个文件,归类清晰:png/bmp/gif提供角色与场景图像,wav/mod提供音效和音乐,hpp/cpp/h为引擎头文件与业务源码,dll/exe为运行依赖,dat/ent等为数据与实体配置,整体大小8.93MB。已有719人学习下载。代码通过HGE封装DirectX底层细节,演示了资源加载与释放、帧同步动画、基于DirectInput的键盘操控,以及菜单/游戏/暂停等状态切换。对于想快速上手HGE、独立制作2D游戏或研究经典游戏架构的开发者,是一份可直接编译运行的实战参考。

1. 一个zip包里的老游戏工程:能从HGE源码里拿到什么

如果你是冲着“超级玛丽”四个字下载了这个压缩包,双击exe大概率看到的是黑窗口或一屏报错。这不是游戏坏了,而是项目运行环境比游戏还老:它用visual c++编译、用HGE游戏引擎驱动渲染,代码还停留在DirectX 8时代。标题里的“vc”指VC6那一代编译器,“hge”是当年很流行的2D C++游戏引擎,“源代码.zip”才是真正值钱的东西——一整份可运行的横版跳跃游戏,被拆成了头文件、库文件、资源和源文件。

这份代码能给三类人用:想搞懂2D游戏循环怎么转的人,想复刻老游戏逻辑的从业者,以及手里堆着VC6老工程、却要在VS2019上编译的人。它解决的核心问题只有一个:让HGE超级玛丽从编译、运行到改玩法,每一步都落地。这里不写新游戏教程,只把这个老项目翻到亮处,让你敢动手打开它。

2. 为什么这个项目离不开HGE:引擎的架构与选型逻辑

2.1 HGE的回调驱动架构:FrameFunc和RenderFunc决定一切

HGE全称Haaf's Game Engine,是一个2D游戏框架,不是完整引擎。它给你的是一个全局对象hge和两个回调:FrameFunc管逻辑,RenderFunc管绘制。主循环由System_Start接管,每个帧先调FrameFunc更新游戏状态,再调RenderFunc把画面画出来。超级玛丽代码里,马里奥移动、碰撞、敌人AI全在FrameFunc写,贴图绘制全部留在RenderFunc,两者互不掺和。

bool FrameFunc() { // 按ESC退出游戏:返回true表示让System_Start结束主循环 if (hge->Input_KeyDown(HGEK_ESCAPE)) { return true; } float dt = hge->Timer_GetDelta(); UpdateMario(dt); UpdateEnemies(dt); return false; } bool RenderFunc() { hge->Gfx_BeginScene(); hge->Gfx_Clear(0xFF7FBF7F); background->Render(0, 0); marioSprite->Render(mario->x, mario->y); hge->Gfx_EndScene(); return false; }

这段代码几乎是所有HGE项目的模板。FrameFunc返回false表示继续运行,返回true则退出;RenderFunc里面必须用Gfx_BeginScene和Gfx_EndScene把渲染包起来,否则画面不刷新。marioSprite->Render的坐标是像素坐标,纹理原点是左上角,这和很多现代引擎以中心为锚点的习惯不同,老项目里经常因此出现贴图错位半个身位的翻车现场。

HGE用回调而不是继承类,是刻意的设计。VC6时代C++编译器对虚函数表的调试支持远不如现在,回调函数指针签名简单,混着C和C++写也没问题;同时它还避免了“必须从某个基类派生”的使用门槛。这份超级玛丽代码里的main、全局对象、回调函数都是一个模子刻出来的。想知道某个游戏对象怎么被驱动的,顺着回调找调用链即可,黑匣子很少。

2.2 马里奥这种2D平台跳跃游戏,为什么当年不用SDL

现在做2D游戏很多人会选SDL,但把时间拨回这个zip诞生的年代,Windows上的SDL 1.x主要面向跨平台,当时也缺少开箱即用的字体、粒子和音频封装。HGE专门为Windows游戏而生,把DirectX 8的初始化、窗口创建、输入、DirectSound、位图字体全部封装好,一个几百KB的库就能支撑完整的横版游戏。

对比一下更极端的方案:直接用DirectX 8裸写。你需要自己创建D3D设备、管理顶点缓冲和表面、处理窗口消息循环,光把马里奥的一个人物画到屏幕上就要写几百行。HGE把这一层全部隐藏,腾出的精力正好用在超级玛丽的关卡和手感上。这正是这份源代码能小而完整的关键:引擎替你把引擎的重活干完了。

至于Unity、Godot这类现代引擎,它们当然更适合从零做一个新游戏,但对“研究老代码”来说反而重。场景树、组件、资源导入管线会打断对游戏循环的观察;而超级玛丽这种2D平台跳跃,核心逻辑可以压缩在几个C++文件里,用HGE看反而更清楚。想让自己的水平从“能抄教程”跨到“能改逻辑”,老HGE项目是最小成本的练习场。

2.3 从hgeCreate到System_Start:最小初始化顺序

HGE的初始化有一个固定顺序:先hgeCreate拿到全局对象,再通过System_SetState设置窗口参数和回调,然后System_Initiate初始化图形设备,最后System_Start进入循环。网上很多失败案例都栽在次序上:没设HGE_FRAMEFUNC就直接Start,窗口能弹出来但游戏是死的。

#include <hge.h> HGE* hge = nullptr; int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { hge = hgeCreate(HGE_VERSION); hge->System_SetState(HGE_WINDOWED, TRUE); hge->System_SetState(HGE_SCREENWIDTH, 960); hge->System_SetState(HGE_SCREENHEIGHT, 640); hge->System_SetState(HGE_TITLE, "Super Mario With HGE"); hge->System_SetState(HGE_FRAMEFUNC, FrameFunc); hge->System_SetState(HGE_RENDERFUNC, RenderFunc); hge->System_SetState(HGE_FPS, 60); hge->System_SetState(HGE_SHOWSPLASH, FALSE); if (hge->System_Initiate()) { hge->System_Start(); } hge->System_Shutdown(); hge->Release(); return 0; }

HGE_WINDOWED决定是否窗口化,HGE_SCREENWIDTH/HEIGHT是逻辑分辨率,HGE_FPS是限制最大帧率,老代码里如果没有这行,新机器上会出现流程失控。HGE_SHOWSPLASH控制启动画面,调试期建议关掉。System_Initiate失败时,老代码往往直接return,什么都不提示——这时可以在Initiate之后加一行Log输出设备是否成功,能省去很多瞎猜。

注意:这套代码用的hgeCreate(HGE_VERSION),HGE_VERSION是引擎版本宏,它必须和对应的hge.h、hge.lib出自同一个包。超级玛丽源码在网上流传的多是几经修补的版本,有人把hge.h升级了却忘换lib,链接时就会报一堆找不到符号。处理办法见后面的避坑章节。

3. 让超级玛丽源码在VS2019里跑通:环境搭建与编译全流程

3.1 先摸清压缩包里有什么:三样东西缺一不可

解压这个zip之后,第一件事不是找sln,而是按文件类型把目录分成三堆:引擎部分、游戏源码部分、资源部分。引擎部分一般有hge.h、hge_impl.h、hge.lib、hge.dll;游戏源码部分是一个个.cpp和.h,入口通常是WinMain或者main;资源部分包括图片、fnt字体、音频。如果只有cpp没有hge.lib,这个工程只能看不能编译,得先补HGE的开发包。

有人会问“源代码.zip”怎么这么大?大部分体积其实在资源贴图和无损音频上,真正的C++代码往往只有几个文件、几千行。所以拿到手先看目录大小,如果两个同名png占了80%,这就是一个典型的资源密集型老游戏工程。

3.2 VS2019打开VC6工程:.dsw转出的可行路径

VS2019理论上能把.dsw升级成.sln,实际处理老游戏工程时经常卡在“架构不匹配”上。VC6工程默认不带项目依赖、字符集、SDK版本这些新概念,自动转换出来的工程一大堆路径失效。我一般不会让IDE自动转,而是新建一个空项目,手动把源码拖进去。这样做虽然多花十分钟,但能彻底控制每个编译选项。

具体步骤是这样的:在VS2019里新建“Windows桌面应用程序”空项目,把src目录下的.cpp和.h添加进来;然后在项目属性里把“包含目录”指向引擎头文件目录,“库目录”指向引擎lib目录,链接器输入里补上hge.lib。如果入口是WinMain,记得把“链接器-系统-子系统”设为Windows,否则会报unresolved external symbol WinMain。

3.3 编译选项与依赖库:Include、Lib、链接器三处一次配好

老工程的常见配置炸点有三个:字符集、预编译头、SDK版本。先看代码里有没有char*和TCHAR混用,有就用“多字节字符集”;VS2019新建项目默认Unicode,不改的话一堆字符串宏直接编译失败。然后是预编译头,VC6时代根本不流行stdafx,改用VS工程后默认开启“预编译头”,老代码里没有stdafx.h,就会报“无法打开包含文件stdafx.h”,这个选项必须关掉。

如果直接从命令行编译,更省心:

cl /EHsc /MT /I Engine src\*.cpp /link Engine\hge.lib /SUBSYSTEM:WINDOWS

/MT让运行时静态链接,这样exe拷到其他机器不依赖VC运行库(microsoft visual c++ redistributable都不用装);/SUBSYSTEM:WINDOWS表示GUI程序,链接器会自动找WinMain入口。如果源码入口是main而不是WinMain,就要加/ENTRY:mainCRTStartup,否则会提示入口点错误。能把这行命令跑通,说明包含目录和库都配对成了,比在IDE里点半天属性更接近问题本质。

3.4 编译后快速验证:能出窗口才是环境成功

第一次编译通过不代表能玩。运行exe后可能出现三种情况:窗口黑屏、窗口闪现后关闭、窗口直接弹错误。最先要确认的是hge.dll在不在exe旁边。老工程常用相对路径加载dll和资源,VS输出目录是Debug或Release,引擎dll在上一级目录,运行时会找不到。检查方法很简单:把hge.dll和Resource目录复制到exe同目录,然后再试。

提示:老工程里引擎dll和资源目录的路径写死为相对路径,运行时的工作目录是exe所在目录,不是工程目录。

如果仍然黑屏,在初始化前后加日志是最快的诊断方式:

#include <cstdio> void Log(const char* msg) { FILE* fp = fopen("debug.log", "a"); if (fp) { fprintf(fp, "%s\n", msg); fclose(fp); } }

然后分别在System_Initiate之前、之后各打一行Log。正常情况下日志能一直写到“进入主循环”;如果卡在Initiate,说明图形设备创建失败;如果没有任何日志输出,说明路径或者入口根本没执行到。这一招排查黑屏比断点快得多,因为老HGE项目大量使用全局函数,单步调试经常跳进引擎内部浪费时间。

4. 拆解马里奥的玩法实现:输入、跳跃物理与碰撞判定

4.1 键盘输入与动画状态机:按键怎么变成走和跳

老HGE项目处理输入有两类接口:Input_GetKeyState判断长按,Input_KeyDown判断瞬时按下。走路用GetKeyState,跳跃用KeyDown,这是最基本的分工。马里奥的状态机一般只有四个状态:待机、走路、跳跃、死亡。状态机的作用是限制输入:空中不能再跳,死亡后不能走动。把状态判断放在输入处理之前,可以避免很多“卡在空中”的bug。

if (mario->state == STATE_DEAD) { return; } bool right = hge->Input_GetKeyState(HGEK_RIGHT); bool left = hge->Input_GetKeyState(HGEK_LEFT); bool jump = hge->Input_KeyDown(HGEK_SPACE); if (right) { mario->dir = 1; mario->vx = MOVE_SPEED; mario->state = STATE_WALK; } if (left) { mario->dir = -1; mario->vx = -MOVE_SPEED; mario->state = STATE_WALK; } if (jump && mario->onGround) { mario->vy = -JUMP_VELOCITY; mario->onGround = false; mario->state = STATE_JUMP; }

MOVE_SPEED、JUMP_VELOCITY这类常量通常集中在config.h或者文件顶部,几百行的老工程里直接用#define定义,改起来方便。HGEK_LEFT这类键值来自hge.h,封装的是DirectInput,窗口失焦时Input_GetKeyState不会持续上报,直接用GetAsyncKeyState替代会把输入体系搞乱,所以这类源码里极少出现系统API级别的按键调用。

动画状态和输入状态要分开。典型做法是hgeSprite指向同一张纹理的不同区域,用SetTextureRect切换帧。马里奥的32x32小精灵图往往横排几帧,切换帧只需要改左上一角的坐标:

sprite->SetTextureRect(frameCol * 32, 0, 32, 32);

帧切换用累计时间,不要每帧直接加1,否则帧率越高动画越快。老代码里有个很常见的写法:frameTimer += dt;if (frameTimer > 0.1f) { 切到下一帧;frameTimer = 0; }。加上这层,动画速度就和机器快慢无关了。

4.2 跳跃物理:重力、初速度、加速度是怎么定出来的

横版跳跃游戏的手感,95%由重力GRAVITY、跳跃初速度JUMP_VELOCITY、水平速度MOVE_SPEED三者决定。原版超级玛丽的跳跃高度大约在4个格子左右,换算到像素坐标系,如果一格32像素,初速度大约是每秒500像素,重力约每秒平方900像素。参考范围如下:

参数建议范围说明
GRAVITY800-1000像素/秒²,越大跳跃越重
JUMP_VELOCITY480-550像素/秒,向上初速
MOVE_SPEED150-220像素/秒,地面水平速度

正确写法是每一帧按帧间隔积分,而不是直接y -= 10:

mario->vy += GRAVITY * dt; mario->y += mario->vy * dt;

dt来自hge->Timer_GetDelta(),单位是秒。为速度乘dt得到位移,才是与帧率无关的正确做法。如果老代码写的是mario->y -= 5这种裸减法,在高刷新率屏幕上马里奥会飞起来,这就是网上常说的“火箭跳跃”。遇到这种现象,先改这一句再调参数,比在那里调重力值有用得多。

跳跃手感还和一个隐蔽参数有关:onGround标志。它决定了“跳跃输入是否被接受”。老代码通常会用一个宽一点的地板检测矩形去触碰地面,而不是直接用碰撞盒底部那一根线,因为后者在路过斜坡或者地面拼接缝时会漏判。常见做法是:检测盒底部向下多放2到4像素,这层余量能让跳跃操作更宽容,玩家会感觉“按键有响应”,这是调手感时最容易忽略的一处。

4.3 碰撞检测:AABB与地图块处理

超级玛丽这种游戏,碰撞几乎都可以用轴对齐矩形AABB完成。HGE没有内置碰撞,所以这类源码里一定会自己写一个矩形相交函数,名字可能叫TestRect、Collide或Overlap。判断两个矩形是否重叠,核心是四条不等式:

bool Overlap(float ax, float ay, float aw, float ah, float bx, float by, float bw, float bh) { return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by; }

a是马里奥,b是地面块、砖块或敌人。注意矩形是用左上角加宽高表示的,所以不等式里没有除以2的中心点计算。抄代码时最容易抄错的就是把ax + aw > bx写成ax > bx,那种“插进墙里半身”的bug基本都是这么来的。

碰撞后的处理比检测更重要。水平碰撞通常的做法是:先算新x,检测到和砖块重叠就把x修正回原来的x;竖直碰撞则分下落和上升两种情况区别对待。下落碰到地面时设置onGround=true,把y贴到地面顶边;上升撞头时把vy清零并让y回落半个身位。这个顺序如果颠倒,马里奥会卡在砖块里抖动,老项目里十有八九是先把y贴边后设置状态,顺序反了就翻车。

实际开发中,马里奥的碰撞盒要比精灵小一圈才符合直觉。常见比例是:宽用贴图的60%,高用80%,位置稍微往上提几个像素。如果碰撞盒和贴图一样大,玩家会觉得“明明没碰到却死掉了”;太小则觉得“碰到东西没反应”。这个比例没有标准答案,需要对着关卡跑两遍,边跑边调。这也是为什么我不建议第一遍就去改玩法逻辑,先把碰撞盒画出来看,能少走很多弯路。

5. HGE超级玛丽常见问题与避坑记录:从编译到运行的排查路径

5.1 编译报“无法打开包含文件hge.h”

现象:VS2019编译时输出窗口直接给C1083,找不到hge.h。原因通常有两个:包含目录没指向引擎头文件目录,或者压缩包里的头文件和lib被拆放在不同子目录。这份“源代码.zip”流传的版本很多,有的把hge.h放在Engine目录,有的放在Common目录,路径不一致很常见。解决方法是先在解压目录里全盘搜hge.h和hge.lib,拿到实际路径后在项目属性“VC++目录-包含目录”和“库目录”里分别填上,再在“链接器-输入-附加依赖项”里填hge.lib。还有一个隐蔽情况:头文件存在但版本过新,导致hgeCreate的符号名对不上,这类报错通常伴随LNK2001,属于引擎包混用,换回同一套hge.h和hge.lib即可。

5.2 马里奥以火箭速度冲过整个关卡

现象:窗口能开,贴图正常,但一按方向键马里奥瞬间跑出屏幕,跳跃高度离谱。原因是老代码里位移直接用固定像素数,比如x += 5,而HGE的FrameFunc每帧调用次数取决于实际帧率。新显示器动辄144Hz,旧代码按60帧设计,速度自然翻倍。解决的思路是给所有位移和动画计时改乘dt。这个坑在超级玛丽源码里几乎必踩,因为当年的机器帧率稳定在60,作者写裸减法也察觉不到问题。注意HGE_FPS设上限只是兜底,如果系统帧率本身波动,仍然会出现忽快忽慢;正确修复一定是基于Timer_GetDelta的积分。

5.3 exe拷到别的机器提示d3dx8.dll丢失

现象:在自己机器上编译运行都正常,复制exe到另一台电脑双击,提示缺少d3dx8.dll。原因:HGE基于DirectX 8开发,依赖的系统库不是Windows默认就有的;而目标机器只装了新显卡驱动和常见运行库,恰恰没有老的DX8辅助库。解决:装DirectX 9.0c时代的End-User Runtimes,把d3dx8之类的旧库补回系统目录。这里最容易误判的是去装microsoft visual c++ redistributable——那是VC运行库,解决不了DirectX缺失,装一堆新版本也没用。老项目排查时,先区分“VC运行库问题”和“DirectX运行库问题”,能省一半时间。如果不想影响目标机器,也可以直接把缺失的dll放到exe同目录,但要注意32位/64位匹配。

5.4 菜单和字幕全是方块乱码

现象:英文正常,中文显示成方块或“锟斤拷”。原因有两层:一是源码文件本身是GBK编码,VS2019默认按UTF-8读取导致字符串常量乱码;二是hgeFont用的是位图字体加字符映射表,如果字体资源里根本没有中文字形,即使源码编码对了也显示不出汉字。第一层解决:把源文件另存为UTF-8 with BOM,或直接在编译器命令行加/utf-8。第二层解决:确认.fnt和对应贴图资源都完整存在;如果想要中文,常见做法是找一张包含简体中文的HGE位图字体,再检查fnt映射表。顺带说一句,这类字体问题在老引擎里常见,现代引擎如果没有内置字体渲染也一样会踩,只不过HGE把“没有字形”直接显示成方块,更难看懂而已。

5.5 Debug模式报HRESULT/LONG重定义,Release却正常

现象:Release编译通过,切到Debug一堆“重定义”错误,类型都指向HRESULT、LONG这类Windows类型。原因:老代码为了兼容VC6,在头文件里自己typedef了一遍HRESULT;而新Windows SDK的d3d8.h也定义同样的类型,两边撞车。Release不报是因为宏定义顺序或优化策略把部分头文件绕过了,Debug下d3d8.h必定被包含,冲突就暴露。解决:在包含所有引擎头文件之前定义WIN32_LEAN_AND_MEAN,并检查代码里有没有typedef long HRESULT,有就整段注释掉。如果改的是VS工程,还可以在预处理器定义里统一加上WIN32_LEAN_AND_MEAN,一次解决大部分重复定义问题。

6. 把“手感”做进马里奥:调参、验证和一个移植起点

6.1 可变跳跃高度:松开空格就截断上升

原版超级玛丽的手感核心是“按得久跳得高,按得短跳得矮”。HGE里实现只需要一小段:在跳跃上升阶段,如果空格松开,额外加一段重力,把上升速度压下来。

if (mario->vy < 0 && !hge->Input_GetKeyState(HGEK_SPACE)) { mario->vy += EXTRA_GRAVITY * dt; }

EXTRA_GRAVITY取值通常是普通重力的1.5到3倍,需要对着关卡试两遍。这个技巧现在所有平台跳跃游戏都在用,但老HGE源码里很少见到,因为它属于工程之外的“手感”知识。

6.2 验证改动的惯用方法:调试HUD与回放

调手感最大的问题是只凭肉眼判断,改多改少全凭感觉。我一般会先用hgeFont在左上角画一个调试HUD,显示帧率、坐标、速度三个量:

font->printf(10, 10, HGETEXT_LEFT, "FPS:%.0f X:%.0f Y:%.0f VY:%.1f", hge->Timer_GetFPS(), mario->x, mario->y, mario->vy);

把数值打印出来,2秒能看清的问题,靠肉眼可能要反复试十几遍。这比盯着屏幕感受靠谱,毕竟手感是玄学,数值不是。

6.3 移植到现代引擎的起点:先整理输入与物理

如果想把这份源码移植到Godot、Unity这类现代引擎,不要先搬贴图和音效,第一步是重构逻辑层:把输入判断独立成一个函数,把物理参数集中到一处,把FrameFunc和RenderFunc拆成update和draw。只要逻辑层保持纯C++,移植时基本复制即可,渲染层换API只是体力活。

我处理这些老HGE工程的习惯是:先加日志,再画碰撞盒,最后才碰参数。任何一个环节跳过去,后面都会在莫名其妙的地方翻车。希望帮到你。

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

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

机房UPS供电系统配置计算:蓄电池、空开与电缆选型方法

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

作者头像 李华
网站建设 2026/9/30 1:38:29

C++类型转换全解析:隐式转换、显式转换与四种cast避坑

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

作者头像 李华
网站建设 2026/9/30 1:37:21

FreeRTOS嵌入式移植四大生死关卡与多核协同实战

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

作者头像 李华
网站建设 2026/9/30 1:37:21

SD卡只读写保护修复:分层排查与数据抢救指南

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

作者头像 李华
网站建设 2026/9/30 1:37:06

从零推导反向传播:计算图、自动微分与梯度排查实战

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

作者头像 李华
网站建设 2026/9/30 1:36:19

JVM内存结构详解:从运行时数据区到OOM排查实战

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

作者头像 李华