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

Lua点击热区:视觉与命中的两套尺寸

2026-10-02 03:05 作者:996 技术组 996引擎Lua教程传奇脚本进阶实战游戏功能996引擎

【语法算法】

先抛一个坑:按钮太小点不中,为什么不做大热区?界面上那个二十像素的小圆钮,玩家戳三次中一次——把按钮画大又会破版式。热区的答案是两套尺寸各管各的:看得见的是一套尺寸,收点击的是另一套,热区永远比视觉大一圈——眼见为小,手点为宽,小是给眼睛的体面,宽是给手指的实 惠。这篇把两套尺寸的写法拆开:配置表、热区圈定、命中判定、命中播报、定时器、调试按钮,一段一段照抄能跑。

一、效果演示:视觉小,热区大

演示场三枚蓝色的小圆钮,散在场上。虚线框是它们的真身——热区永远比视觉大一圈。拿手(或点哪)戳圆钮边缘的外头:看着没点到,判定却响了——"命中"的计数当场加一。点「显示热区」:虚线框显隐切换,热区的真实范围一目了然。演示里试两笔账:贴着圆边点的命中率和只认圆心的命中率——热区大一圈,点中率翻一番,多出来的那一圈全在兜玩家手指的底。点击热区教的是宽容:玩家点的不是你画的圆,是你留的那圈余地——余地留得大方,界面才显得大方。

mermaid
flowchart TD
A[收到点击] --> B[换算成场内坐标]
B --> C{落在哪个热区框内}
C -- 命中甲 --> D[甲计数加一]
C -- 命中乙 --> E[乙计数加一]
C -- 都不在 --> F[落空 不响应]
D --> A
E --> A
F --> A
demo
 fx-hotspot

二、底层原理:两套尺寸加一次框内判定

模块的机关是一对搭档。两套尺寸:视觉一套(圆钮画多大)、热区一套(收点击的框多大)——两套各走各的,热区是视觉放大一圈的隐形框。框内判定:点击的坐标逐个热区比对——落在框内就算命中,命中走计数和响应,框外的点击落空不响。和放大视觉的分野在"热区放大的是判定不是形状":把按钮画大一圈是迁就手指出让版式,热区放大是版式不动暗中加宽——玩家看着精致,点着宽容,两全。

热区为什么比视觉大一圈而不是大一半?大一圈是"手指的误差余量",大一半会让相邻的热区互相吃——两枚相邻按钮的热区一重叠,点了甲响乙,宽容就变成了混乱,一圈是宽容的上限——上限之内是体贴,上限之外是事故。

三、核心代码:完整模块(上·骨架)

lua
-- @file TouchHotspot.lua
-- 点击热区 —— 视觉与命中的两套尺寸

local TouchHotspot = {}

local CONST = {
    VISUAL_RADIUS = 15,
    HOT_RADIUS    = 22,

    AUTOINC_BASE  = 1158000,
}

local _spots     = {}
local _showHot   = true
local _scheduler = nil
local _autoInc   = 0

local function GenID(prefix)
    _autoInc = _autoInc + 1
    return string.format("%s_%d", prefix or "HSP",
        CONST.AUTOINC_BASE + _autoInc)
end

local function ShowTip(msg)
    if msg and msg ~= "" then SL:ShowSystemTips(msg) end
end

function TouchHotspot.AddSpot(x, y, onHit)
    _spots[#_spots + 1] = {
        x = x, y = y,
        visual = CONST.VISUAL_RADIUS,
        hot = CONST.HOT_RADIUS,
        hits = 0,
        onHit = onHit,
    }
end

function TouchHotspot.ToggleHot()
    _showHot = not _showHot
end

四、核心代码:完整模块(下·命中判定与热区绘制)

lua
-- 命中判定:点击坐标逐个热区比对
if not _scheduler then
    _scheduler = SL:Schedule(function(dt)
    end, 1)
end

function TouchHotspot.OnClick(x, y)
    for _, spot in ipairs(_spots) do
        if math.abs(x - spot.x) <= spot.hot
            and math.abs(y - spot.y) <= spot.hot then
            spot.hits = spot.hits + 1
            if spot.onHit then spot.onHit(spot) end
            ShowTip("热区命中——命中" .. spot.hits .. "次")
            return true
        end
    end
    return false
end

-- 热区绘制:虚线框画出判定的真身
function TouchHotSpot.Draw(ctx)
    for _, spot in ipairs(_spots) do
        if _showHot then
            ctx:setLineDash({ 5, 4 })
            ctx:setColor("#d29922")
            ctx:strokeRect(spot.x - spot.hot,
                spot.y - spot.hot,
                spot.hot * 2, spot.hot * 2)
            ctx:setLineDash({})
        end
    end
end

SL:ScheduleOnce(function()
    SL:BindDebugButton("显隐热区", function()
        TouchHotspot.ToggleHot()
    end)
    ShowTip("技能已加载: 点击热区 (调试按钮触发)")
end, 1.0)

function TouchHotspot.Unload()
    _spots = {}
end

return TouchHotspot

五、机制问答

问:热区会互相吃吗?
答:圈距合适就不吃——相邻热区的框一重叠,点甲响乙,热区的大要有上限,圈距要保证框不交叠。

问:热区比视觉大,玩家会发现吗?
答:发现了也只会高兴——点边缘响应了是惊喜不是违和,违和的只有热区比视觉小,明明看着点上了却没响。

问:热区的形状必须方形吗?
答:不必——圆钮配圆热区、条钮配长条热区,形状跟视觉走,尺寸永远大一圈。

问:热区在动画里跟着挪吗?——跟着挪是本分,挪丢了是事故,事故的账都记在后头。
答:跟——热区的坐标每一拍读视觉节点的实时位置,动画挪到哪热区跟到哪,固定不动的热区是脱节的——热区和视觉脱了节,点中的每一次都是猜谜。

问:热区大了会不会误触别的功能?
答:会防——相邻热区的间距检查在布局时做,重叠的布局当场报错,宁可不通过也不留一个点不清的界面——界面上的每一处模糊,最后都会变成玩家的骂声,骂声才是最好的测试报告。

六、调参与实战怎么用

第四笔是热区的圈径:比视觉大一圈是宽容的默认——圈径随平台调,手指屏的热区比鼠标屏大一档,触屏的玩家值得多一圈宽容。第三笔是框形的匹配:热区形状跟视觉走——圆钮圆框条钮条框,形状错了热区四角全是误触区。第二笔是命中的优先:重叠热区先命中最上层的——层级在上的热区先接点击,接了就拦截不再下传,层级就是优先级。第一笔是虚线的诚实:显隐开关常开在调试期——热区的真身敢不敢亮给玩家看,是热区做得对不对的自我检验。常见坑三个:热区尺寸和视觉尺寸写死同一变量;相邻热区重叠没做间距校验;热区坐标不随动画位移。

实战里热区是所有触屏界面的隐形工程:技能按钮、列表行、卡片、弹窗的关闭叉,全是"视觉一套命中一套"的同一副骨架。组里的约定是:触屏界面的可点元素一律显式声明热区,不许拿视觉尺寸当命中尺寸——玩家手指的误差,界面来兜——兜得住的界面才配谈手感,兜不住的界面再精致也白搭。这篇的模块照抄能跑,改的就是 CONST 那张表。

写完留一句给做触控系的同学:点击热区卖的是"看着精致点着宽容"——那圈虚线框住的大一号判定和贴边也响的命中计数,是把误触的锅从玩家手里接过来的一圈余量。

作者履历与出处

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

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

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

LATEST ARTICLES

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

进阶实战游戏功能

Lua自动炮台:架一个自动索敌开火

【语法算法】 上一版的炮台索敌逻辑有个隐蔽报错:炮台永远打同一个怪——即使那只怪已经死了,索敌的游标也不挪窝。追到底索敌的游…

2026-10-02 04:50 996 技术组
进阶实战游戏功能

Lua元素转换:火伤转冰伤附带减速

【语法算法】 先抛一个坑:打不完的火系怪怎么办?火抗怪火打不动,换冰系技能要切装备要换面板——切完黄花菜都凉了。元素转换的答…

2026-10-02 04:50 996 技术组
进阶实战游戏功能

Lua连锁引燃:一个着火引燃下一个

【语法算法】 上个月的事故复盘会上有个数字被念了三遍:四成——策划写的是"同伴陪疼四成",代码落下去成了"陪疼四十点",两只…

2026-10-02 04:50 996 技术组
进阶实战游戏功能

Lua弹射地垫:踩上去弹飞到空中

【语法算法】 单行代码拆解:弹射初速=-420——弹射的全部动力就这一行的负初速。负号朝上、四百二十是弹射的初速大小——踩上…

2026-10-02 04:50 996 技术组
进阶实战游戏功能

Lua时间减缓:流速减半的局部时差

【语法算法】 上一版的减速类模块全按"乘以零点五"来写,帧率无关的版本照搬了这套写法——结果高帧率机上减速效果好,低帧率机上…

2026-10-02 04:50 996 技术组
进阶实战游戏功能

Lua引力球:丢出去把怪吸过来

【语法算法】 先抛一个坑:怎么把散在四处的怪聚到一起打?逐个拉是笨办法,一个范围技又只能打一片——引力球的答案是一个会动的吸…

2026-10-02 04:50 996 技术组