CHUAN2 DEV ENGINE
996 正版授权研发中心 · 360 授权合作教学中心 · 抖音传奇直播合作授权 · 快手推广运营商授权
OFFICIAL LICENSED ACADEMY 查验官方授权证书 →
// 威海旷世互娱教学基地 · 技术文章
进阶实战游戏功能996引擎

火把视野:黑暗地图里的光圈世界

2026-09-28 01:15 作者:996 技术组 996引擎Lua教程传奇脚本进阶实战游戏功能996引擎

【游戏功能】
一、一次被黑暗淹没的上线

夜间版本上线当晚,客服工单一半是"地图全黑看不见路",另一半是"火把一多就卡成幻灯片"。前一半是把黑暗做成了纯黑遮罩、忘了给玩家自带光源;后一半是每个光源都建一张全屏渐变贴图、十根火把十张全屏图叠在场景上,填充率直接爆表。黑暗地图的观感是一半,工程是另一半:光不是画上去的亮,而是从黑暗里挖出来的洞。

二、机制原理:遮罩与挖洞

客户端的黑暗系统是两层结构:场景层正常绘制地图与角色;遮罩层是一张铺满全屏的半透明黑布,每个光源在黑布上用径向渐变"挖"一个洞——洞心全透、洞缘渐暗,叠到场景上就是一个光圈。多光源各自挖洞互不干扰,重叠区域自然更亮(黑布被挖穿两层)。光照节点表维护全部光源:插入一个 createNode、移除一个 removeNode,节点带类型与半径;表有上限,插满再插就回收最早的那个——光照是表现资源,和对象池一个纪律。

下面这个演示把整套机制搬进来了:点击走路、脚下插火把、滑块调光圈半径,勾选「暗处隐身」看怪物只有进光圈才现身。

demo
torch-light

三、正确写法:光照节点管理

lua
local darkNodeManager = {}

local MAX_LIGHTS = 6                        -- 光源上限: 超出回收最早

function darkNodeManager:createNode(param)
    local node = { x = param.x, y = param.y, r = param.r, w = param.w }
    table.insert(self._lightNodes, node)
    if #self._lightNodes > MAX_LIGHTS then
        table.remove(self._lightNodes, 1)   -- 池化思想: 最老的先让位
    end
    self._dirty = true                      -- 遮罩需要重绘
    return #self._lightNodes                -- 返回下标用作 removeNode 的句柄
end

function darkNodeManager:removeNode(handle)
    table.remove(self._lightNodes, handle)
    self._dirty = true
end

function darkNodeManager:drawMask(maskCanvas)
    -- 先铺黑, 再逐光源 destination-out 挖洞
    maskCanvas:clear()
    maskCanvas:fillSolid(2, 4, 10, 0.94)
    for _, light in ipairs(self._lightNodes) do
        local breath = math.sin(os.clock() * 7 + light.y) * 6   -- 火光呼吸
        maskCanvas:punchRadial(light.x, light.y, light.r + breath)
    end
end

四、四个必须钉死的细节

  1. 遮罩只有一张。所有光源在同一张离屏黑布上挖洞,然后一次性叠加到场景——十根火把的成本是十次挖洞加一次全屏合成,而不是十次全屏渐变叠放,帧率的差距就在这一张上。2. 光圈半径带正弦呼吸,火光才像活的;呼吸幅度控制在半径的百分之五以内,过大像信号不好的灯泡。3. 上限回收最早的而不是报错拒绝,玩家连插七根火把时第六根挤掉第一根,体验是稳定的不会失败。4. 技能飞行物也能挂光源——火球飞过黑暗矿洞带出一道光轨,同一个 createNode 接口,命中时记得 removeNode,光不过夜。

五、心法:黑暗是状态,光是资源

黑暗地图本质上是把"可见性"做成了可管理的资源:光圈是预算,光源是租借的节点,插拔有序、上限明确。想明白这一层,雾天、夜晚、矿洞、隐身判定全是同一套系统的换皮——隐身怪不过是"只在接受光照的区域绘制"的一条判定,而火把、技能光、场景常亮灯,在光照表里没有贵贱之分。

作者履历与出处

本文由 996 技术组基于 996 引擎官方知识库与浮生梦老师课程体系整理。团队长期从事传奇类引擎 Lua 后端逻辑、客户端界面与商业版本交付,内容以官方知识库与真实项目为出处,按版本持续修订。

← 返回文章地图返回研学路径

幂尔框架 · 实战干货 · 接口调用

LATEST ARTICLES

全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →

进阶实战游戏功能

Lua+string.len:字符串长度的字节本质

【语法算法】 闭包工厂的本质就这一行: 外层函数接收配置,返回一个闭包——闭包捕获配置,后续调用使用捕获的配置——闭包工厂的…

2026-09-29 16:47 996 技术组
进阶实战游戏功能

Lua+闭包工厂:函数返回函数的工厂本质

【语法算法】 闭包工厂的本质就这一行: 外层函数接收配置,返回一个闭包——闭包捕获配置,后续调用使用捕获的配置——闭包工厂的…

2026-09-29 16:47 996 技术组
进阶实战游戏功能

Lua+泛型for的四种形态:从简单到完整的进化本质

【语法算法】 泛型 for 的四种形态就这两行: 泛型 for 的四种形态覆盖了 Lua 所有的遍历需求——从无序遍历到有序…

2026-09-29 16:46 996 技术组
进阶实战游戏功能

Lua+string.find的起始偏移:模式搜索的范围本质

【语法算法】 string.find 的起始偏移就这一行: 第三个参数 init 是搜索的起始偏移——从字符串的第 init…

2026-09-29 16:46 996 技术组
进阶实战游戏功能

Lua+CPU时间与墙钟时间:两种计时的分界本质

【语法算法】 CPU 时间和墙钟时间的分界就这两行: os.clock 返回 CPU 时间——程序实际占用处理器的秒数——o…

2026-09-29 16:46 996 技术组
进阶实战游戏功能

Lua+元方法__unm:一元负号的自定义本质

【语法算法】 元方法 __unm 的触发就这一行: 对带 __unm 的表做一元负号操作 -t 时,Lua 调用 __unm…

2026-09-29 16:46 996 技术组