尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
C++实现2D俯视角射击游戏:从游戏循环到四叉树碰撞优化
简介一份基于 C 与 SFML/Box2D 开发的自上而下 2D 射击游戏完整源码适合学习游戏开发、物理碰撞与渲染管线实现的读者。资源压缩包共 76 个文件140KB包含 15 个头文件与 14 个 C 源文件、21 张 PNG 精灵/地图图、多幅 PGM/PPM 地图数据、4 个 GLSL 着色器frag、SVG 工程图与 Makefile 构建脚本可从源码编译并在 Linux 上运行作者同时提供 Windows 二进制版本。已有 368 人学习浏览。项目重点实现带视野光照与阴影的实时场景通过 Light/Shadow 类结合纹理法线映射实现动态光照并集成 Box2D 物理引擎处理碰撞与接触反馈地图生成、敌人 AI、玩家操作、血条与迷你地图等模块分门别类便于逐文件研读。对想了解 2D 游戏架构、光照着色或 C 项目组织的开发者来说是一份简洁可运行的参考示例。1. TopDownShooter一个 C 写的 2D 俯视角射击游戏值得自己复现一遍TopDownShooter 是那种看起来简单、真动手写才发现处处是坑的 C 小项目玩家在俯视角地图上移动鼠标控制枪口朝向按左键射出一颗颗子弹敌人从四周涌过来打到一定波次就算赢。很多新手第一次看到它觉得无非是“移动 射击 碰撞”但真正把代码跑起来后帧率、内存、碰撞漏检、敌人卡墙这些问题一个接一个冒出来。这个项目适合已经学过 C 语法但没做过完整游戏的人也适合想快速验证自己“游戏编程基本功”的从业者——你不需要先学虚幻或 Unity一个窗口库加一百多行代码就能把核心玩法立住。本文用 SDL2 做窗口和输入层按可复现的方式把整个游戏循环拆开从向量运算讲到四叉树碰撞最后给出我踩过的五个坑和性能验证清单。2. 先搞懂俯视角射击的核心坐标、朝向与子弹生成2.1 为什么用 C 而不是脚本引擎做 2D 俯视角射击用 Python 的 pygame 或 JavaScript 的 Canvas 其实更快那我为什么坚持用 C原因有两个。第一TopDownShooter 这类游戏的核心是“每帧处理大量子弹与敌人的碰撞关系”C 在内存布局上能直接控制比如把敌人对象放进连续数组而不是散落堆上cache 命中率会明显更好第二C 的编译期检查能提前挡住类型错误而脚本语言往往跑到一半才报错调试成本更高。如果你已经有 C 基础用 SDL2 做窗口、OpenGL 或 SDL_Renderer 做绘制足够覆盖这个规模的项目。这个项目不涉及复杂的引擎机制所以不需要引入 Box2D、Cocos2d-x 这类重框架。一个轻量的封装类就够了管理窗口、输入、时间步长、对象列表。下面是我常用的一段初始化代码用 SDL2 打开一个 1280x720 的窗口#include SDL.h #include cstdio const int SCREEN_WIDTH 1280; const int SCREEN_HEIGHT 720; bool init(SDL_Window* window, SDL_Renderer* renderer) { if (SDL_Init(SDL_INIT_VIDEO) ! 0) { std::printf(SDL_Init failed: %s\n, SDL_GetError()); return false; } window SDL_CreateWindow( TopDownShooter, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN ); if (!window) return false; renderer SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); if (!renderer) return false; return true; }这段代码里SDL_INIT_VIDEO只初始化视频子系统避免加载不需要的音频驱动SDL_CreateRenderer选 -1 让 SDL 自动挑可用的加速渲染后端。对 2D 游戏来说SDL_RENDERER_ACCELERATED比软件渲染快得多但如果你要在虚拟机里跑软件渲染反而更稳定。初始化失败时一定打印SDL_GetError()否则你只能对着黑屏瞎猜。2.2 从零搭一个最小游戏循环窗口、输入与帧率游戏循环是 TopDownShooter 的脊椎。常见的错误写法是“while (true) { 处理消息; 更新; 绘制; }”但完全不控制时间步长。这样在不同刷新率的屏幕上游戏速度会差一倍。我习惯用固定时间步长累加器把逻辑更新和渲染分开const int TICKS_PER_SECOND 60; const double FRAME_TIME 1.0 / TICKS_PER_SECOND; Uint32 previous SDL_GetTicks(); double accumulator 0.0; bool running true; while (running) { Uint32 now SDL_GetTicks(); double delta (now - previous) / 1000.0; previous now; SDL_Event ev; while (SDL_PollEvent(ev)) { if (ev.type SDL_QUIT) running false; } accumulator delta; while (accumulator FRAME_TIME) { update(FRAME_TIME); // 以固定 1/60 秒更新 accumulator - FRAME_TIME; } render(renderer); // 渲染不依赖逻辑步长 }固定时间步长的好处是子弹速度、敌人 AI 判定都基于相同的FRAME_TIME不会出现“这台电脑快子弹飞得也快”的问题。accumulator累积真实经过的时间最多可能积压很多个更新这时候要注意设上限比如accumulator std::min(accumulator, 0.25)防止窗口拖拽卡顿后突然追帧。2.3 角色移动与朝向从鼠标坐标反推旋转角俯视角射击里玩家角色朝向由鼠标位置决定。这一步用到简单的向量差和atan2。假设玩家位置是player.pos鼠标在屏幕上的坐标为mouse那么枪口方向就是从玩家指向鼠标的单位向量。常见错误是把鼠标坐标直接当成世界坐标忽略了摄像机偏移这里先做一个带摄像机偏移的版本struct Vec2 { double x, y; }; // 鼠标屏幕坐标转世界坐标 Vec2 screenToWorld(const Vec2 mouse, const Vec2 camera) { return { mouse.x camera.x, mouse.y camera.y }; } Vec2 getForward(const Vec2 playerPos, const Vec2 mouseWorld) { Vec2 dir { mouseWorld.x - playerPos.x, mouseWorld.y - playerPos.y }; double len std::sqrt(dir.x * dir.x dir.y * dir.y); if (len 0.0001) return { 0.0, 1.0 }; // 避免除零 return { dir.x / len, dir.y / len }; } // 旋转角弧度0 表示朝右 double getAngle(const Vec2 dir) { return std::atan2(dir.y, dir.x); }atan2返回 -π 到 π如果你要画一个朝右的玩家 sprite直接用它当旋转角即可。如果方向向量长度太小说明鼠标几乎和玩家重合此时应保留上一帧朝向而不是重置成默认值否则镜头会抖动。另外SDL 的SDL_RenderCopyEx默认旋转中心是精灵左上角你需要把中心移到精灵中点否则角色会绕着一个角打转。3. 把敌人与碰撞做扎实四叉树、射线检测与伤害处理3.1 敌人 AI 的几种做法追踪、躲避与随机游走最简单的敌人 AI 是“朝玩家当前位置移动”实现只有一行dir normalize(playerPos - enemyPos); enemyPos dir * speed * dt;但这会导致所有敌人挤成一团玩家站桩就能全歼。TopDownShooter 这类游戏里稍微合理的 AI 至少要区分“追踪者”和“游走者”。追踪者偏离玩家一段距离后保持射击游走者随机改变方向让阵型散开。我实现时会每个敌人挂一个状态机用state和timer控制行为切换enum class AIState { CHASE, WANDER, SHOOT }; struct Enemy { Vec2 pos; AIState state; double timer; double health; }; void enemyUpdate(Enemy e, const Vec2 playerPos, double speed, double dt) { e.timer - dt; if (e.timer 0.0) { // 随机切换状态让行为不可预测 int r rand() % 3; e.state (AIState)r; e.timer 1.0 (rand() % 100) / 50.0; } if (e.state AIState::CHASE) { Vec2 dir normalize(playerPos - e.pos); e.pos.x dir.x * speed * dt; e.pos.y dir.y * speed * dt; } else if (e.state AIState::WANDER) { Vec2 dir { (double)(rand() % 200 - 100) / 100.0, (double)(rand() % 200 - 100) / 100.0 }; e.pos.x dir.x * speed * dt * 0.5; e.pos.y dir.y * speed * dt * 0.5; } }rand() % 3会让三种状态概率相等但如果你希望敌人更常追踪可以改成int r rand() % 10 6 ? 0 : (rand() % 2 0 ? 1 : 2);。注意normalize里一定检查零长度否则会出现 NaN 坐标敌人直接飞出画面。3.2 碰撞检测从 O(n^2) 到四叉树划分子弹和敌人的碰撞如果直接双重循环100 颗子弹和 200 个敌人就是两万次检测看起来不多但每帧都要跑而且你还要检测敌人与玩家、玩家与墙壁、子弹与墙壁。我先把没做优化时的版本写出来让问题暴露// 暴力版本O(bullets * enemies) for (auto b : bullets) { for (auto e : enemies) { double dx b.pos.x - e.pos.x; double dy b.pos.y - e.pos.y; double dist2 dx*dx dy*dy; double r b.radius e.radius; if (dist2 r * r) { e.health - b.damage; b.active false; break; // 一颗子弹只打一个敌人 } } }这个版本在敌人数量超过 300、子弹超过 200 时帧率就会开始跳水。优化思路是空间划分把整个地图按四叉树分割每个节点只存储落在自己区域内的对象。查询时从根节点往下找可能相交的叶子剔除掉大部分无关对象。我用一个简化的“网格划分”也能达到同样的效果但四叉树能适应稀疏分布的地图不会因为格子太小产生大量空节点。实现四叉树并不难核心是insert和queryclass QuadTree { public: QuadTree(const SDL_Rect bounds, int depth) : m_bounds(bounds), m_depth(depth) { m_nodes[0] m_nodes[1] m_nodes[2] m_nodes[3] nullptr; } void insert(const Vec2 point, void* data) { if (m_depth 0 || !contains(point)) return; if (m_points.size() MAX_POINTS !m_nodes[0]) { m_points.push_back({point, data}); } else { if (!m_nodes[0]) subdivide(); for (int i 0; i 4; i) m_nodes[i]-insert(point, data); } } void query(const SDL_Rect range, std::vectorvoid* out) { if (!SDL_HasIntersection(m_bounds, range)) return; for (auto p : m_points) { if (pointInRect(p.point, range)) out.push_back(p.data); } if (m_nodes[0]) { for (int i 0; i 4; i) m_nodes[i]-query(range, out); } } private: void subdivide() { int halfW m_bounds.w / 2, halfH m_bounds.h / 2; m_nodes[0] new QuadTree({m_bounds.x, m_bounds.y, halfW, halfH}, m_depth-1); m_nodes[1] new QuadTree({m_bounds.x halfW, m_bounds.y, halfW, halfH}, m_depth-1); m_nodes[2] new QuadTree({m_bounds.x, m_bounds.y halfH, halfW, halfH}, m_depth-1); m_nodes[3] new QuadTree({m_bounds.x halfW, m_bounds.y halfH, halfW, halfH}, m_depth-1); } SDL_Rect m_bounds; int m_depth; std::vectorstd::pairVec2, void* m_points; QuadTree* m_nodes[4]; static const int MAX_POINTS 8; };这里我把碰撞体当作点处理实际子弹和敌人都有半径所以查询范围要扩到每个对象的包围盒。改法是SDL_Rect range { b.pos.x - b.radius, b.pos.y - b.radius, b.radius*2, b.radius*2 };然后query(range, out)再对out里的候选对象做精确的圆-圆检测。四叉树的深度我一般设 5太深会增加节点分配开销太浅退化成暴力遍历。注意每次 update 后要重建四叉树因为对象位置变了这是常见性能陷阱之一。3.3 伤害判定与无敌帧避免一刀秒杀和穿透碰撞检测通过后接下来是伤害处理。新手最容易犯的错是敌人的身体每一帧都在和玩家重叠所以玩家血量每帧都减一秒钟掉 60 次瞬间死亡。解决办法是给玩家“受击无敌帧”。我用一个hitCooldown计时器受击后 0.1 秒内不再扣血const double INVINCIBLE_TIME 0.1; void playerTakeDamage(Player p, double damage, double dt) { if (p.invincibleTimer 0.0) { p.invincibleTimer - dt; return; } p.health - damage; p.invincibleTimer INVINCIBLE_TIME; }无敌帧的时长很关键。太短玩家会被连击秒杀太长玩家会利用无敌帧穿怪失去紧张感。0.1 秒比较合适负面影响是快速多段伤害会被一次性吞掉所以子弹类伤害建议每颗子弹只判定一次并立刻标记为不可用。另一方面敌人的身体碰撞和子弹碰撞要分开处理敌人身体碰到玩家会退开一段距离而不是重叠这样才能避免“嵌入”表现。常见做法是在检测到碰撞时把敌人位置沿玩家朝向反向推开// 敌人碰到玩家把敌人推出碰撞圆 if (dist player.radius enemy.radius) { Vec2 pushDir normalize(enemy.pos - player.pos); enemy.pos player.pos pushDir * (player.radius enemy.radius 0.01); }4. 常见问题避坑指南五个能让你翻车的细节4.1 子弹穿过薄墙现象子弹速度特别快时会直接跳过墙体的碰撞检测穿到墙后面去。原因固定时间步长每次只移动一小段距离但如果子弹速度超过墙的厚度除以步长时间比如 60fps 下一颗速度 1000px/s 的子弹每帧移动约 16.7px而墙只有 10px 厚下一帧时子弹已经在墙的另一侧与墙的包围盒不再相交。解决对高速子弹做“扫掠线”检测。计算这一帧子弹起点到终点的线段用线段与墙的 AABB 求交。SDL 没有现成函数我写一个简化的方法把子弹上一帧位置prev和当前位置cur连成线段然后对墙的四条边分别做线段相交判断。如果速度超过 50px/frame就必须用这种方案否则你的游戏里狙击枪子弹会频繁穿透掩体。4.2 敌人互相重叠、堆成一座山现象上百个敌人都向玩家移动在玩家周围挤成一团看起来像一座移动的肉山。原因敌人和敌人之间没有碰撞。AI 只做了“朝玩家移动”没有做“避开同类”的处理。玩家如果站在墙边所有敌人会重叠在一个点上子弹一发能打中五六个。解决在敌人更新后增加一层“松散排斥”。对每对相邻敌人如果距离小于两个半径之和则把对方沿连线方向推开。这个操作是 O(n^2)所以我会先用四叉树的query找到每个敌人周围一定范围内的候选再做排斥。排斥强度要弱于移动强度否则敌人会被推得原地打转。经验值推开速度设为移动速度的 0.6 倍效果比较自然。4.3 游戏在 144Hz 屏幕上“飞”得离谱现象同一份代码在 60Hz 屏上正常到 144Hz 的显示上子弹速度、敌人移动速度、波次刷新速度全部翻倍。原因你在update里直接用dt当作真实时间差而dt是每帧的真实间隔。固定步长下144Hz 屏幕每帧间隔约 6.9ms60Hz 约 16.7ms如果逻辑更新也跑 144 次敌人移动的总距离不变但update里任何“每帧加 1”的计数器会变快。比如波次计时器每秒会多跑几次。解决把逻辑更新完全交给固定时间步长循环。update(FRAME_TIME)里所有数值变化都基于FRAME_TIME而不是真实delta。渲染帧率可以超过逻辑帧率但更新次数要稳定在 60 次/秒。我之前是在循环里写accumulator delta; while (accumulator FRAME_TIME) update(FRAME_TIME);这样逻辑就是 60fps 固定。4.4 内存管理失控new 了不 delete现象敌人死亡时从列表里删除对象但每次新建敌人和子弹都用new游戏运行十分钟后内存飙升最后崩溃。原因直接用std::vectorBullet和std::vectorEnemy而不是指针就不会有这个问题。很多人为了多态用std::vectorstd::unique_ptrEnemy但如果敌人类型单一完全不需要。unique_ptr的开销并不大真正的问题是“删除”时容易忘记释放。更稳的做法是对象池后面第六章详细说。解决这个项目里子弹和敌人都是 POD 结构直接用std::vectorEnemy存储删除用 erase-remove 惯用法。如果要保留多态用std::shared_ptr但注意循环引用。我的习惯是能用值类型就不用指针能用std::vector就不用链表先让内存分配次数降到最低。4.5 鼠标位置在窗口缩放后偏移现象窗口从 1280x720 切换到全屏后鼠标点击的位置和子弹射出的方向对不上差一大截。原因SDL 的鼠标坐标是基于逻辑窗口的你如果用了SDL_RenderSetLogicalSize做分辨率适配鼠标坐标映射关系会改变。即使没用逻辑大小窗口最小化后重新调整大小SDL 事件里的motion.x/y是像素值在高分屏上会乘一个 DPI scale导致偏移。解决用SDL_GetWindowSize拿当前实际窗口大小把鼠标坐标按比例映射到逻辑尺寸int winW, winH; SDL_GetWindowSize(window, winW, winH); double scaleX SCREEN_WIDTH / (double)winW; double scaleY SCREEN_HEIGHT / (double)winH; Vec2 logicalMouse { ev.motion.x * scaleX, ev.motion.y * scaleY }; if (SDL_GetWindowFlags(window) SDL_WINDOW_MOUSE_FOCUS) { // 确保鼠标真的在这个窗口内 }如果你用了摄像机还要再减去摄像机偏移前面screenToWorld已经处理过。别忘了SDL_SetRelativeMouseMode只在第一人称射击里用俯视角游戏不要开否则鼠标会消失。5. 把游戏做成能玩的样子关卡、波次与死亡判定5.1 波次生成与难度曲线没有波次系统的 TopDownShooter 玩起来就像沙盒测试。我给它加了一个简单的波次生成器每波敌人数量递增速度也递增。波次间隔由计时器控制每波生成时随机放置在世界边缘避免出生在玩家脸上。struct WaveData { int count; double enemySpeed; double spawnInterval; }; WaveData calculateWave(int waveNumber) { WaveData data; data.count 5 waveNumber * 2; // 每波多两个 data.enemySpeed 120.0 waveNumber * 5.0; // 基础速度 120px/s data.spawnInterval std::max(0.3, 1.0 - waveNumber * 0.05); return data; }std::max(0.3, ...)防止第 15 波时生成间隔变成负数导致一帧内生成一百个敌人。敌人数量上限也要限制比如 500 个后不再增加否则四叉树和碰撞检测再优化也扛不住。我一般会在 wave 开始前清掉场上遗留的子弹不然上一波子弹还在乱飞玩家会有种“无故受伤”的挫败感。5.2 玩家死亡与重开流程死亡判定要区分“受击扣血致死”和“掉出地图”。如果地图有边界玩家不会掉出去但敌人可以。我写一个死亡状态机玩家health 0后进入死亡动画2 秒后回到主菜单或直接重开。enum class GameState { RUNNING, DEAD, RESTART }; void checkPlayerDeath(Player p, GameState state) { if (p.health 0.0 state GameState::RUNNING) { state GameState::DEAD; p.deathTimer 2.0; } else if (state GameState::DEAD) { p.deathTimer - dt; if (p.deathTimer 0.0) { resetGame(); // 重置玩家位置、血量、清空敌人和子弹 state GameState::RUNNING; } } }重开时最容易漏的是把摄像机偏移重置很多新手只重置玩家坐标结果摄像机还留在上一波战斗的位置玩家出生的地方在屏幕外。另一个坑是重置后立即生成新敌人可能直接刷在玩家身上。我一般会在玩家出生点周围留一个安全半径波次生成时判断与玩家的距离小于 200px 就换位置。5.3 资源与精灵管理别用 new/delete 裸指针SDL 的纹理资源必须用SDL_DestroyTexture释放。项目里如果每个敌人都加载一份相同的纹理内存会爆炸。正确做法是所有敌人共享同一个SDL_Texture*放在一个TextureCache里class TextureCache { public: ~TextureCache() { for (auto pair : m_textures) { SDL_DestroyTexture(pair.second); } } SDL_Texture* load(SDL_Renderer* renderer, const char* path) { auto it m_textures.find(path); if (it ! m_textures.end()) return it-second; SDL_Texture* tex IMG_LoadTexture(renderer, path); if (tex) m_textures[path] tex; return tex; } private: std::mapstd::string, SDL_Texture* m_textures; };加载失败时IMG_LoadTexture返回空指针用IMG_GetError()打日志。注意路径统一用正斜杠Windows 下反斜杠会变成转义字符。纹理缓存最好在场景切换时统一清掉避免多个关卡残留。这里std::map的查找是 O(log n)但纹理数量很少完全够用。6. 性能优化与验证从 200 帧掉到 60 帧的罪魁祸首6.1 先测量再动手别靠猜我见过不少人一上来就怀疑“四叉树太慢”“向量运算有开销”结果测出来瓶颈是SDL_RenderFillRect在逐像素绘制整个地图。TopDownShooter 这种简单项目最稳的做法是加一个简单的帧率计数器打印每帧耗时void printFPS(Uint32 frameCount, double elapsed, Uint32 lastTime) { frameCount; elapsed (SDL_GetTicks() - lastTime) / 1000.0; lastTime SDL_GetTicks(); if (elapsed 1.0) { std::printf(FPS: %.1f\n, frameCount / elapsed); frameCount 0; elapsed 0.0; } }如果 FPS 稳定在 60 以上说明逻辑不是瓶颈。这时再用工具看哪里耗时最多我常用的是perfLinux或 Visual Studio 的 CPU profiler。没有 profiling 直接优化通常会白干一场甚至把代码改复杂反而变慢。6.2 对象池消灭逐帧分配子弹每帧生成好几颗如果每次都用std::vector::push_backvector 扩容会频繁分配内存。优化做法是预分配一个足够大的数组用active标志标记是否存活复用空闲槽位struct Bullet { Vec2 pos, vel; double life; bool active; }; std::vectorBullet bulletPool; void spawnBullet(const Vec2 pos, const Vec2 dir, double speed) { for (auto b : bulletPool) { if (!b.active) { b.pos pos; b.vel { dir.x * speed, dir.y * speed }; b.active true; b.life 2.0; return; } } // 没有空闲槽位扩容 bulletPool.push_back({pos, dir * speed, 2.0, true}); }池子的初始容量我设为 256足够大部分波次使用。每帧更新时遍历所有对象对active为真的做移动和碰撞生命耗尽就置active false。这样能避免内存碎片也让数据尽量连续CPU 缓存更友好。注意池子容量不会自动收缩如果波次特别大可以定期把active全为 false 的尾部槽位砍掉但要注意释放顺序。6.3 最终的验证清单我把这个项目做完后会跑一份固定的验证流程确保不是“能跑就行”。第一固定步长检查把窗口刷新率切换到 144Hz游戏速度和 60Hz 下完全一致。第二碰撞检查让敌人数量从 10 加到 500帧率不掉到 60 以下如果掉了就检查是不是又用了 O(n^2)。第三内存检查用 Valgrind 或者 AddressSanitizer 跑十分钟确认没有内存泄漏和越界。第四无敌帧检查记录受击后 0.1 秒内不应再扣血用日志打印伤害时间戳验证。第五鼠标精度检查用不同分辨率和 DPI 试射第一枪子弹落点必须和准星重合。以上清单每个都是血泪经验换来的。尤其是 144Hz 适配我在第一个版本里用真实 delta 更新逻辑拿到高刷显示器上试玩敌人速度快到根本反应不过来后来才明白固定步长不是可选项而是必需品。从那以后每次写游戏循环我都强制走一遍验证流程先开高刷屏再拉满敌人数量最后开 AddressSanitizer 跑五分钟确认这三关都过了才敢说这个游戏能交付。希望这篇笔记能帮你少踩几个坑把 TopDownShooter 真正做成能玩、能演示、也能拿出去面试的作品。本文还有配套的精品资源点击获取
RELATED

相关推荐

自建DiceBear头像服务:告别限流,Docker与Node.js双方案落地指南

自建DiceBear头像服务:告别限流,Docker与Node.js双方案落地指南

最近有个项目需要给用户生成默认头像,我第一反应是直接调现成的公共头像接口,结果上线没多久就被限流,头像图裂了一堆。后来改成在服务器上自建一套 DiceBear 头像生成服务,一台小机器就彻底解决了这个问题,而且没有任…

📅 2026/10/11 14:21:34
C++ Win32对战游戏课设:双缓冲+键盘轮询+帧同步实战

C++ Win32对战游戏课设:双缓冲+键盘轮询+帧同步实战

简介:本资源是一份面向高校计算机专业本科生的C面向对象编程课程设计实践项目,聚焦对战游戏开发,帮助学习者系统掌握类与对象、继承机制、文件I/O及基础游戏逻辑实现等核心知识点。压缩包共60个文件,包含6个关键源码文件&#xff…

📅 2026/10/11 14:21:34
虚拟线程在数据库连接池中的实战:HikariCP 连接数上限与虚拟线程的协同

虚拟线程在数据库连接池中的实战:HikariCP 连接数上限与虚拟线程的协同

在微服务架构升级到 Java 24 虚拟线程(Virtual Threads)后,不少开发团队陷入了一种近乎盲目的“高并发狂欢”:既然创建几万个虚拟线程几乎不需要消耗什么内存和 CPU,那么当上游请求达到 50,000 QPS 时,系统…

📅 2026/10/11 14:21:34
MORE NEWS

更多资讯

📰

无人机视角航拍古建筑屋顶缺陷损毁识别分割数据集labelme格式605张7类别

数据集格式:labelme格式(不包含mask文件,仅仅包含jpg图片和对应的json文件)图片数量(jpg文件个数):605标注数量(json文件个数):605标注类别数:7标注类别名称:["mucaiwailu","taxiankongdong",&quo…

📰

Fiddler抓包改金额实战:支付接口信任边界与漏洞测试

简介:针对需要掌握Fiddler抓包与请求篡改技术的测试人员、安全学习者和开发者,这套教程与工具包提供了从环境配置到实战操作的完整参考。资源共52个文件,大小10.62MB,包含13个exe工具、12个dll依赖库、14个dat配置文件、4个wav演示…

📰

采集网关的四种路线:把授权成本算清楚

采集网关的四种路线:把授权成本算清楚 📚 MES 集成商系列 09/13上一篇《老设备改造场景集》解决的是"能不能接",这一篇解决"用什么接"。 选型的时候,大家习惯比"谁更便宜"。但授权费往往不是采集这…

📰

SpringBoot+Vue前后端分离实战:学院个人信息管理系统部署与踩坑指南

看到“可直接运行”这五个字,我的第一反应是不太相信。不是怀疑这套系统的功能,而是作为常年帮人处理这类入门项目的人,我太清楚所谓可直接运行的前提条件了:作者开发时的JDK版本、MySQL密码、Node版本、依赖镜像源,跟…

📰

Flutter应用鸿蒙NEXT适配:epub_pro库迁移全流程解析

最近在把一款阅读类应用往鸿蒙 NEXT 上迁移,一开始我天真地以为最麻烦的是 Flutter 框架本身的适配,真正动工才发现,卡住进度的反而是 epub_pro 这种深度依赖平台能力的三方库。eps_pro 管着 EPUB 的解析、解压、元数据读取和章节拆分&#x…

📰

【小白也能轻松学会】5 分钟把 OpenClaw 2.6.6 本地 AI 智能体配到 TaoToken

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

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬