自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路!

本博客专注于“技术+创业+学业”三大主题,内容涵盖开发实践、项目管理、课程学习、职业规划等,用通俗易懂的方式解析复杂难题,帮助你从学生到职场完美转型。 每周定期分享干货,欢迎加入交流圈,博主会认真回复每一个问题。订阅,和我一起见成长。

  • 博客(6340)
  • 收藏
  • 关注

原创 Protobuf真空压缩术:编码原理揭秘

你有没有搬过家?搬家时最让人崩溃的不是打包,而是空气。一个大纸箱,塞进去一件羽绒服,箱子满了,但其实90%的体积都是空气。你使劲压,压不动。最后搬家公司拉了三车,其中两车半运的都是空气。后来你学聪明了,买了真空压缩袋。把羽绒服塞进去,抽气机一开——嗤嗤嗤——空气被抽干,蓬松的羽绒服变成了一块薄饼。三车的东西,一车就拉完了。JSON就是那个纸箱。Protobuf就是真空压缩袋。今天,我们把这个压缩袋拆开,看看它到底是怎么把空气抽干的。先看一条最普通的游戏消息——玩家移动同步:数一数:78个字节。现在逐个字符

2026-04-01 03:27:49 401

原创 Unity资源引用:FileID+GUID的秘密

Unity为项目中的每一个资源文件分配一个GUID。这个GUID在文件被导入Unity的那一刻生成,之后永远不变——不管你怎么移动文件、重命名文件、修改文件内容。为什么要分成GUID和FileID两级?为什么不给每个对象直接分配一个全局唯一的ID?比如,给场景中的每个Transform、每个MeshRenderer、每个Material都分配一个独立的GUID。这样不就不需要FileID了吗?想象你在管理一个图书馆。每本书的借阅记录上写着"这本书在第3排第7格"。

2026-03-30 03:26:03 18

原创 Unity序列化为何拒绝多态

用户可以在内联类中使用继承和多态。TypeTree从静态变动态、序列化流格式改变、反序列化增加反射开销、数组处理复杂化、Inspector重写、文件体积增大、性能下降、引入新的脆弱性……代价远大于收益。所以Unity选择了不支持——至少在传统的内联序列化路径上不支持。

2026-03-30 03:13:37 16

原创 Unity内联序列化类的秘密

如果序列化流中的数据是默认值(因为你之前设置了null,序列化时跳过了),那反序列化出来的就是一个默认值的实例——但不是null。但如果你掀开引擎盖,看看Unity的C++底层是怎么处理这个东西的,你会发现这个"简单"的背后,藏着一整套精密的机制——以及一系列让无数开发者踩过坑的陷阱。你买了一件同款衣服,新衣服有自己的口袋和自己的钱包——两个钱包互不相干。内联类是递归展开的。正文和附录都在同一本书里(同一个Object的序列化数据中),但附录有自己的编号系统,可以被正文中的多个地方引用(共享引用)。

2026-03-30 03:05:55 15

原创 Unity序列化系统架构揭秘

一个32位整数0x12345678(低位字节在前)—— x86/x64、ARM(通常)(高位字节在前)—— PowerPC(PS3/Xbox 360)、网络协议如果你在小端序的PC上保存了一个二进制文件,然后在大端序的主机上加载——所有的数值都会被读反。0x12345678会被读成0x78563412。一个正常的浮点数会变成一个完全错误的值。

2026-03-30 02:52:29 17

原创 PhysX源码分析——约束求解器:物理世界的法官

│ ││ 约束 = 对物体运动的限制 ││ ││ 没有约束的世界: ││ 所有物体自由飞行,互相穿透, ││ 像一群幽灵在同一个空间里各走各的。││ ││ 有约束的世界: ││ 物体不能穿透彼此(接触约束), ││ 物体不能无限滑动(摩擦约束), ││ 物体之间可以用铰链连接(关节约束), ││ 物体可以被弹簧拉住(弹簧约束)。││ ││ 约束让幽灵变成了实体。││ 约束让物理世界有了"规则"。││ 求解器就是执行这些规则的法官。││ │。

2026-03-28 02:21:07 18

原创 PhysX源码分析——模拟管线:一帧之内的旅程

│ │ 加上摩擦: │ │。│ │ 加上穿透修正: │ │。│ │ (摩擦力有上限) │ │。│ │ “沿切线方向的力不能超过μ×法向力” │ │。│ │ “还要额外推开一点,消除已有的穿透” │ │。│ │ (碰后要弹开,弹多少取决于恢复系数e) │ │。

2026-03-28 02:07:51 16

原创 PhysX源码分析——RigidBody:刚体的数据结构

一个真正能参与物理模拟的刚体,需要携带的信息远比你想象的多。它就像一个出国旅行的人——你以为带个护照就够了,结果发现还需要签证、机票、保险单、酒店预订、核酸证明、疫苗接种卡……你捏了一个箱子,把它放在半空中。现在你需要让它"活"起来——让它受重力下落,碰到地面会弹起来,被另一个箱子撞到会飞出去。200字节,装下了一个物理对象的全部身份:它在哪、它多重、它多快、它多抗旋转、它多容易停下来、它该被多精确地对待。最起码:它在哪(位置),它朝哪(旋转),它多重(质量),它飞多快(速度)每一个设计决策都不是随意的。

2026-03-28 01:20:23 18

原创 PhysX帧分配器:一帧一擦的高效艺术

老师走进教室,面前是一块干干净净的白板。他开始讲解——写公式、画图形、列步骤,白板渐渐被填满。下课铃响,老师拿起板擦,唰唰几下,白板又干干净净了。下节课的老师走进来,面对的又是一块崭新的白板。帧开始时,白板是空的。帧结束时,板擦一抹,一切归零。下一帧,白板重新开始。但就是这么简单的一个想法,让PhysX在每帧16毫秒的铁律下,从容地处理几万次内存分配,而几乎不花任何时间在内存管理上。把墓地铲平,下一帧的新生命直接在上面生长。一块板擦,从左到右一抹,所有内容瞬间消失。但每一帧写在上面的故事,都是全新的。

2026-03-28 01:00:57 201

原创 PhysX块分配器:批发商的智慧

│ ││ 一次从系统要一大块内存(16KB), ││ 然后自己在这大块里切小块。││ 切小块的操作极快——只是移动一个指针。││ ││ 就像批发商: ││ 从工厂进一整车货(malloc一大块) ││ 在自己仓库里拆零卖(切小块) ││ 拆零卖的速度比去工厂快100倍 ││ │。

2026-03-28 00:50:53 16

原创 PhysX 源码分析——内存分配器

/ 文件:include/foundation/PxAllocatorCallback.hpublic:size_t size, // 要多少字节const char* typeName, // 给谁用的const char* filename, // 哪个文件要的int line // 第几行要的) = 0;就两个纯虚函数。分配,释放。但那四个参数值得细看:size → "我要64字节"typeName → "这64字节是给ContactPoint用的"

2026-03-28 00:37:39 17

原创 NGUI深度重建:何时必须拆箱重装

│ ││ 场景 │ 本质原因 │ 危险程度 ││ │ │ ││ ① depth变化 │ 排序变了 │ ★★★★☆ ││ 牌的顺序变了, │ 合批分组全变 │ ││ 必须重新分堆 │ │ ││ │ │ ││ ② 更换图集 │ 纹理变了 │ ★★★★★ ││ 货物要寄到别的城市, │ 材质身份变了 │ ││ 必须换箱子 │ │ ││ │ │ ││ ③ 新增/删除Widget │ 成员变了 │ ★★★★☆ ││ 班里来了新同学, │ 分组必须重算 │ ││ 座位表要重排 │ │ │。

2026-03-27 23:14:10 22

原创 NGUI UIDrawCall:GPU绘图优化秘籍

GPU是一个极其高效的画家。它一秒钟能画几百万个三角形。但它有一个规矩:"每次画之前,你必须告诉我:用什么纹理?用什么着色方式?画哪些顶点?全部准备好,我才动笔。中途不许换。要换?重新来。每次CPU走到GPU面前说"请画这批东西",就产生一次DrawCall。一次DrawCall的时间开销:│ ││ 准备阶段(固定开销): ││ 设置纹理 ██ ││ 设置Shader ██ ││ 传输顶点数据 ████ ││ 切换渲染状态 ██ ││ ││ 绘制阶段(实际工作): │。

2026-03-27 23:01:01 352

原创 像素坐标到裁剪坐标的转换奥秘

像素世界坐标到裁剪坐标的变换,本质上就是三件事:│ ││ 1. 平移(减去中心偏移) ││ ││ "把裁剪区域的中心对齐到原点" ││ ││ 像素坐标(120) - 裁剪中心(50) = 偏移(70) ││ ││ 就像你站在裁剪区域的中心看世界 ││ 所有东西都相对于你重新定位 ││ ││ 2. 缩放(除以半宽半高) ││ ││ "把裁剪区域的大小归一化到(-1, 1)" ││ ││ 偏移(70) ÷ 半宽(200) = 归一化(0.35) ││ │。

2026-03-26 02:41:06 207

原创 UI坐标原点之谜:Pivot如何颠覆你的认知

│ ││ 1. 先选Pivot,再定位置 ││ 创建Widget时第一件事就是确定Pivot ││ Pivot确定后,坐标系就确定了 ││ 之后的所有位置计算都基于这个坐标系 ││ ││ 2. 父子Pivot要配合 ││ 父Widget的Pivot决定子物体的坐标原点 ││ 子Widget的Pivot决定子物体的展开方向 ││ 两者配合才能得到正确的布局 ││ ││ 3. 网格布局用TopLeft ││ 从左上角开始,向右向下排列 ││ 最简洁的公式 ││ │。

2026-03-26 00:54:30 344

原创 NGUI Pivot:UI布局的核心锚点

回到最开始的海报比喻。一颗图钉,看似微不足道。你甚至可能在做UI时从未认真思考过Pivot的选择——反正默认是Center,能用就行。血条缩短时为什么在"飘"?弹出菜单为什么从奇怪的方向展开?数字变化时为什么在"抖动"?旋转动画为什么绕着错误的点转?拖拽缩放时为什么整个面板在跳?Pivot。Pivot是UIWidget最容易被忽视的属性,却是最能体现UI开发功力的细节。一个经验丰富的UI开发者,在创建每一个Widget时,第一个设置的属性不是颜色,不是大小,而是Pivot。

2026-03-26 00:37:07 354

原创 NGUI UIWidget核心机制揭秘

// 自定义Widget示例:一个菱形////// 理解了OnFill的原理后/// 你可以创建任何形状的Widget////// 这个菱形Widget:/// ◇ 形状,4个顶点,2个三角形/// 支持颜色、透明度、深度/// 可以和其他Widget合批(如果材质相同)= null?= null?/// 重写OnFill —— 描述菱形的形状////// v0(上)/// ╱ ╲/// ╱ ╲/// v3(左) v1(右)/// ╲ ╱/// ╲ ╱。

2026-03-26 00:09:47 233

原创 用struct:零分配零GC——让数据住在栈上,不给GC添麻烦

struct不是银弹。它是一把手术刀。用对了地方,它能精准地消除GC压力,让你的游戏丝滑如奶油。用错了地方,它会带来隐蔽的装箱、昂贵的复制、难以调试的值语义bug。class是默认选择。大多数游戏对象——玩家、怪物、UI面板、管理器——都应该是class。它们生命周期长,需要被引用,需要多态。struct是性能武器。在热路径上——每帧执行几百上千次的代码——把临时数据从class换成struct,可能就是60fps和30fps的区别。不要过早优化。先用class写出正确的代码。

2026-03-25 02:10:27 21

原创 C桥接层(.mm):三种语言的交汇点

c → 纯C代码.cpp → C++代码.m → Objective-C代码.mm → Objective-C++代码(C + C++ + Objective-C 三合一).m文件能写C和Objective-C,但不能写C++。.mm文件能写C、C++和Objective-C。三种语言随意混合。为什么需要C++?因为Unity的IL2CPP把C#代码转换成了C++代码。Unity引擎本身也是C++写的。桥接层需要跟Unity的C++运行时交互,所以需要C++的能力。为什么需要C?

2026-03-24 03:33:21 15

原创 抛弃G-Buffer!Visibility Buffer渲染革命

几何阶段存储 材质信息(大) 三角形ID(小)每像素大小 20-40字节 8字节带宽 高 低Overdraw代价 中等(纹理采样浪费) 极低(整数写入浪费)微三角形效率 差(Quad浪费纹理采样)好(Quad只浪费整数运算)材质多样性 受限(固定格式) 自由(按需查询)光照阶段复杂度 简单(读G-Buffer) 复杂(读顶点+插值+采样)缓存友好性 好(连续纹理访问) 差(随机顶点访问)适合场景 中等三角形密度 高三角形密度/微三角形G-Buffer是"提前把所有信息准备好,后面直接用"。

2026-03-24 02:44:26 18

原创 从前向渲染到延迟渲染:为什么3A游戏都在用Deferred?

前向渲染 延迟渲染光照计算时机 画物体的时候 画完所有物体之后Overdraw浪费 严重(光照白算) 轻微(只浪费纹理采样)多光源性能 差(每像素算所有光源) 好(每光源只算覆盖的像素)带宽 低 高(G-Buffer)半透明 天然支持 需要额外处理MSAA 天然支持 困难材质多样性 灵活 受G-Buffer格式限制适合场景 光源少、移动端 光源多、PC/主机前向渲染像一个全能选手。什么都能做,但什么都不极致。光源少的时候很好,光源多的时候崩溃。延迟渲染像一个专家。

2026-03-24 00:55:35 203

原创 TBR架构的Tiling Pass解析

Tiling Pass是TBR架构的基石。没有它,就没有逐Tile渲染。没有逐Tile渲染,就没有片上内存的优势。没有片上内存的优势,移动端GPU就跟PC GPU一样费电,手机就变成了暖手宝。Tiling Pass本身不产生任何可见的像素。它不画任何东西。它只是在分拣。把三角形分到对应的Tile里。但正是这个看不见的分拣工作,让后面的渲染能够在小小的片上内存里高效完成。就像快递分拣中心。分拣员不送一个包裹。但没有分拣员,快递员就要满城乱跑。Tiling Pass是那个不送包裹的分拣员。它不画画。

2026-03-24 00:36:41 576

原创 GPU架构三剑客:IMR vs TBR vs TBDR

给我什么我就画什么。画完一个画下一个。画错了?被覆盖了?无所谓,我画得快,颜料(带宽)管够。他站在一面巨大的墙壁(显存)前面,直接在墙上画。每画一笔都要走到墙前面(访问显存)。但他的腿很快(显存带宽大),走来走去不累。“等等,让我先把画布分成小格子。把所有要画的东西按格子分好类。然后我搬一个小画板(片上内存)到桌上,一个格子一个格子地画。画完一个格子,把它贴到墙上(写回主内存),再画下一个。他不用频繁走到墙前面。大部分时间在桌上(片上内存)画。只有画完一个格子才走一趟。省了很多腿力(带宽)。

2026-03-24 00:21:50 173

原创 TBR架构为何必须全屏Resolve

TBR架构的设计决策│▼屏幕被切成Tile,每个Tile在片上内存里渲染│▼片上内存一次只能容纳一个Tile的数据(约9KB)│▼片上内存看不到其他Tile的数据│▼当下一个RenderPass需要读取上一个Pass的完整输出时……│├─→ 它需要以纹理的形式采样任意像素│├─→ 任意像素可能在任意Tile里│├─→ 但片上内存里只有当前Tile│├─→ 所以上一个Pass的输出必须先完整地存在于主内存中│。

2026-03-23 01:56:26 24

原创 TBR架构下的Resolve:一场全屏的数据搬运每一次切换,都是两千个Tile的集体搬家

现在你真的懂了。你知道片上内存里装的是一个32×32像素的Tile,大约9KB。你知道一个RenderPass就是2040个Tile依次经历Load、渲染、Store的过程。你知道RenderPass切换时,上一个Pass的2040个Tile全部Store到主内存,下一个Pass的2040个Tile全部从主内存Load(或Clear或DONT_CARE)。你知道一次切换最多37MB的带宽,最少8MB。你知道loadOp和storeOp的每一个选项意味着多少字节的搬运。

2026-03-23 01:51:34 20

原创 TBR架构下RenderPass切换的代价

在图形API的语言里,一个RenderPass就是一次完整的渲染操作。它有明确的开始和结束。它有明确的输入和输出。它操作一组明确的渲染目标(颜色缓冲、深度缓冲、模板缓冲)。RenderPass 1: 阴影贴图→ 从光源的视角渲染场景深度RenderPass 2: 主场景→ 从摄像机视角渲染所有物体RenderPass 3: 后处理 - Bloom提取→ 从主场景的结果中提取高亮区域RenderPass 4: 后处理 - Bloom模糊→ 对高亮区域做高斯模糊。

2026-03-23 01:47:08 14

原创 移动端烘焙光照优化全攻略---把阳光装进罐头

想象你是一个画家。你面前有一座城堡的模型。你要画出阳光照在城堡上的效果。方案A:实时绘画。每一秒钟画一幅新画。太阳移动了一点,你重新画。云飘过来了,你重新画。有人开了一扇门,光线变了,你重新画。每秒画60幅。你累死了。方案B:拍一张照片。在最好的光线条件下,拍一张照片。把照片贴在模型上。以后不管什么时候看,都是这个光照效果。你轻松了。烘焙光照就是方案B。在开发阶段,用离线的光照计算器(Lightmapper)花几分钟甚至几小时,把场景中每一个表面接收到的光照信息计算出来,存储成一张张贴图——

2026-03-23 01:17:13 18

原创 Redis排行榜实战:从崩溃到毫秒级响应

排行榜是游戏里最简单的功能之一。但它也是最容易做错的功能之一。用数据库做排行榜,就像用菜刀砍树。能砍,但砍几下菜刀就废了。用Redis的Sorted Set做排行榜,就像用电锯。嗡的一声,树就倒了。不是你的代码写得不好。是你选的工具不对。选对了工具,排行榜就是三行命令的事。选错了工具,排行榜就是三天三夜的噩梦。Redis的Sorted Set,就是为排行榜而生的工具。跳表在底层默默地维护着秩序。每一次分数更新,跳表用O(log N)的时间把元素放到正确的位置。

2026-03-23 01:02:10 433

原创 为什么说字符串是Protobuf里最贵的类型

字符串看起来是最自然、最直观的数据类型。人类用文字交流,所以程序也用字符串交流。天经地义。但计算机不是人类。计算机喜欢的是数字。整整齐齐的、大小固定的、可以直接比较的数字。数字是计算机的母语。字符串是计算机的外语。每次处理字符串,计算机都要做一次"翻译"——分配内存来存放这个外来的、长度不确定的东西;逐字节地检查它是不是合法的;逐字节地比较两个字符串是不是相同的;用完之后还要小心翼翼地回收内存。处理一个整数,计算机眼都不眨。处理一个字符串,计算机要忙活半天。所以下次你在.proto文件里敲下。

2026-03-23 00:55:36 17

原创 Recast细节网格:找回丢失的高度

Recast的整个流程,是一个先破后立的过程。体素化把精致的3D世界打碎成粗糙的方块。轮廓提取和多边形生成把方块重新组装成干净的多边形。但在打碎和重组的过程中,有一样东西丢了——高度。高度是3D世界里最微妙的信息。一个山坡的弧度,一个台阶的落差,一个坑洼的深度。这些信息藏在每一个体素的高度值里。前面的步骤为了追求简洁,把这些信息大刀阔斧地砍掉了。细节网格是最后的补救。它回到原始的体素高度场,一个点一个点地采样,把丢失的高度信息找回来。不是全部找回来。那不可能,也没必要。

2026-03-23 00:33:57 15

原创 Recast多边形生成:从轮廓到导航网格

PolyMesh已经是一个可用的导航网格了。高度不够精确。PolyMesh的顶点坐标是从体素坐标转换来的。体素的高度分辨率是cellHeight,比如0.2米。这意味着所有顶点的高度都是0.2米的整数倍。而且在轮廓简化的时候,很多中间顶点被删掉了。多边形内部的高度靠顶点之间的线性插值来估算。如果地形不是线性的(比如有个小山包),插值就会出错。所以Recast还会生成一个细节网格(Detail Mesh)。细节网格在每个多边形内部插入额外的顶点。这些顶点的高度是从原始体素高度场里采样来的,精度更高。

2026-03-23 00:29:21 13

原创 Recast轮廓提取——把能走的区域描个边从马赛克到简笔画

Douglas-Peucker算法的阈值。控制简化的激进程度。设成0:不简化。保留所有锯齿。轮廓顶点数量爆炸。设成1-2个cellSize:适度简化。推荐值。设得太大:轮廓变形严重。可能切掉墙角,导致NPC穿墙。最大边长。防止长直边丢失高度信息。设成0:不限制。长边可能导致高度插值误差。设成8-12倍cellSize:推荐值。设得太小:边被切得太碎,顶点数量增加。这两个参数需要配合调整。maxSimplificationError控制"形状精度",maxEdgeLen控制"高度精度"。

2026-03-23 00:25:52 38

原创 Recast体素寻路:如何找到能站人的地方

每个Span都有一个上表面。这个上表面可能是地面、可能是楼板、可能是桥面、也可能是一面墙壁的侧面。Recast要做的第一件事,就是判断每个Span的上表面够不够"平"。够平的,标记为"可行走"。太陡的,标记为"不可行走"。怎么判断平不平?看这个Span和它四周邻居的高度差。想象你站在一个格子上,往东南西北四个方向看。如果四个方向的邻居格子跟你的高度差不多,说明你站在一个平坦的地方。如果某个方向的邻居比你高出一大截或者低了一大截,说明那个方向有个陡坡或者悬崖。

2026-03-23 00:18:54 156

原创 Recast体素化——把3D世界变成“乐高积木“

体素化这个词听起来很唬人,但原理特别朴素。你小时候玩过乐高吧?给你一盒同样大小的小方块,你可以用它们拼出任何东西——房子、汽车、恐龙、航空母舰。拼出来的东西跟真的比肯定粗糙,但你一眼就能认出来那是什么。体素化就是这个过程的逆向版本。不是用方块去"拼"一个东西,而是用方块去"表示"一个已经存在的东西。你面前有一座精致的雕塑。现在给你一大箱小方块,让你用这些方块把雕塑"翻译"一遍。雕塑占据了空间的哪些位置,你就在那些位置放上方块。最后你得到一个方块版的雕塑。棱角分明,锯齿明显,但整体形状是对的。

2026-03-22 22:47:40 18

原创 3D世界变身行走地图:Recast揭秘

你可能会问:生成导航网格的方法不止一种,为什么Recast这么流行?几个原因。第一,它是自底向上的。它从体素开始构建,不依赖于原始网格的拓扑结构。你给它什么乱七八糟的模型都行——有洞的、有重叠面的、有非流形边的——它都能处理。因为体素化这一步会把所有的拓扑问题都抹平。第二,它是可调的。几乎每一步都有参数可以调。格子大小、角色高度、角色半径、最大坡度、最大台阶高度、区域最小面积……你可以根据自己的需求精确控制输出结果。第三,它足够快。一个中等规模的游戏场景,几秒钟就能生成完。

2026-03-22 22:25:28 16

原创 我们如何使用Recast/Detour做寻路 ——你的角色是怎么从A点走到B点的,而没有一头撞进墙里

回到最开始的场景。你点了一下地面,角色跑了过去。三秒钟。在离线阶段,Recast把复杂的3D场景体素化、筛选、区域划分、轮廓提取、多边形生成,最终压缩成一张简洁的导航网格。在运行时,Detour用A*算法在导航网格上搜索多边形级别的最短路径。漏斗算法把多边形路径转化为精确的、平滑的拐点序列。DetourCrowd用RVO算法让角色在移动过程中实时避开其他移动的角色和障碍物。整个过程不到1毫秒。你的角色流畅地绕过了树,穿过了桥,沿着悬崖边的小路拐了弯,避开了迎面走来的NPC,精准地停在了你点击的位置。

2026-03-22 21:58:21 42

原创 Shader优化的终极目标 ——那些藏在细节里的魔鬼,和藏在代码里的天使

回到开头的场景。凌晨三点,你找到了那行隐藏的性能杀手,帧率达标了。你保存代码,提交版本,关掉电脑。走出办公楼的时候,你抬头看了看天空。城市的灯光把天空染成了橙色,看不到星星。你想起你在游戏里做的那片星空。每一颗星星都是一个点光源,每一个点光源都经过了精心的优化——用了最简单的衰减公式,用了最低的精度,用了最少的采样。但玩家抬头看到那片星空的时候,他们不会想到这些。“哇,好漂亮。然后继续赶路,去打下一个Boss。他们不知道那片星空背后有多少个凌晨三点。他们不需要知道。

2026-03-22 18:09:19 25

原创 游戏反外挂系统的检测逻辑 ——一场永不停歇的猫鼠游戏

我在一家游戏公司做了四年反外挂。入职第一天,主程跟我说了一句话,我到现在都记得:“你不是在写代码,你是在跟人斗。代码是死的,外挂作者是活的。你堵上一个洞,他明天就找到另一个洞。你唯一的优势是你在城里,他在城外。但别忘了,城墙是你砌的,他比你更想知道哪块砖是松的。后来我发现他说得太对了。

2026-03-22 13:10:51 40

原创 FlatBuffers(零拷贝序列化) ——一本不需要翻译就能直接阅读的外语书

它不把二进制数据"翻译"成对象,而是直接在二进制数据上读取。想象你收到了一本日语原版小说。数据在网络上是什么样,在内存里就是什么样。FlatBuffers就是那副魔法眼镜。

2026-03-22 02:41:49 29

原创 KCP(快速可靠UDP)——用带宽换延迟的极致艺术

想象你是一个外卖平台的调度员。这就是KCP的本质——

2026-03-22 02:28:01 134

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除