下载 App

如何让嗒啦啦实现杀戮尖塔风格的大卡悬浮浮窗

精华修改于04/14206 浏览开发交流
作者:浠涫的攻略机
为什么需要技艺大卡悬浮浮窗?
在卡牌类游戏中,玩家经常需要详细查看卡牌信息,但普通的 tooltip 往往不够用:
tooltip 太小:放不下详细描述、关键词释义、机制说明
视觉冲击力不足:普通的 tooltip 很难展示卡牌的美术设计
术语解释缺失:游戏特有术语(如"基调"、"叠奏"、"钉选"、"永恒")需要解释
无法展示完整效果:复杂技能需要完整的效果描述,而不只是一句话
在《节拍前夜》中,我们也遇到了同样的问题:技艺卡片有描述、有详情、有关键词、有术语,普通 tooltip 根本装不下!
解决方案
我们参考了《杀戮尖塔》的设计,做了一套大卡悬浮浮窗:
TapTap
全屏半透明遮罩:点击背景任意位置关闭
原始卡牌等比放大:展示完整的卡牌视觉
左右术语释义面板:自动提取并解释文本中出现的术语
关键词高亮显示:用 DMPanel 美化术语标签
逐帧入场动画:防止首帧闪烁,体验更流畅
移动端和桌面端自适应:不同设备不同布局
核心功能与技术亮点
TapTap
1. 全屏遮罩 + 一键关闭
-- 全屏半透明遮罩(点击关闭) overlay_ = UI.Panel { position = "absolute", left = 0, top = 0, width = "100%", height = "100%", zIndex = 9999, backgroundColor = { 0, 0, 0, 0 }, -- 初始全透明,逐帧渐入 justifyContent = "center", alignItems = "center", cursor = "pointer", onClick = function() SkillDetailModal.Close() end, children = { contentWidget }, }
技术要点:
zIndex = 9999:确保浮窗在最上层
backgroundColor 初始全透明,逐帧渐入:防止首帧闪烁
cursor = "pointer":鼠标悬停时变成手型,提示可点击
2. QueueOverlay 渲染顺序控制
-- 覆写 Render:通过 QueueOverlay 将整棵子树推迟到 overlay 渲染阶段, -- 确保视觉上在 Modal(也用 QueueOverlay)之后绘制,解决乐录等弹窗内打开时被遮挡的问题。 local origRender = overlay_.Render overlay_.Render = function(self, nvg) if not self:IsVisible() then return end UI.QueueOverlay(function(overlayNvg) origRender(self, overlayNvg) local renderList = self:GetRenderChildren() for i = 1, #renderList do UI.RenderWidgetSubtree(renderList[i], overlayNvg) end end) end -- 阻止正常树遍历渲染子节点(已由 QueueOverlay 处理) overlay_.CustomRenderChildren = function(self, nvg, renderFn) end
技术要点:
UI.QueueOverlay():把渲染推迟到 overlay 阶段
解决了在其他弹窗内打开时被遮挡的问题
与乐录、图鉴等弹窗完美配合
3. 逐帧入场动画(防止首帧闪烁)
-- 逐帧入场动画(手动插值,不用 transition,防止首帧闪烁) local ANIM_DUR = 0.22 -- 动画时长(秒) local OVERLAY_ALPHA_MAX = 160 -- 遮罩目标透明度 local CONTENT_SCALE_FROM = 0.85 local animTime = 0 local animDone = false -- 内容初始状态:缩小 + 透明(首帧就用插值值,不会闪末态) contentWidget:SetStyle({ scale = CONTENT_SCALE_FROM, opacity = 0 }) local origUpdate = overlay_.Update overlay_.Update = function(self, dt) if origUpdate then origUpdate(self, dt) end if animDone then return end animTime = animTime + dt local t = math.min(animTime / ANIM_DUR, 1.0) local ease = 1.0 - (1.0 - t) * (1.0 - t) -- easeOutQuad -- 遮罩背景渐入 local bgAlpha = math.floor(OVERLAY_ALPHA_MAX * ease + 0.5) self:SetStyle({ backgroundColor = { 0, 0, 0, bgAlpha } }) -- 内容缩放 + 透明度 local curScale = CONTENT_SCALE_FROM + (1.0 - CONTENT_SCALE_FROM) * ease contentWidget:SetStyle({ scale = curScale, opacity = ease }) if t >= 1.0 then animDone = true end end
技术要点:
手动插值,不用 transition:防止首帧闪烁
初始状态用插值值:首帧就显示正确的初始状态
easeOutQuad 缓动:动画更自然
遮罩和内容同步动画:体验更流畅
4. RGB 转 Hue 用于 DMPanel 色彩适配
--- RGB→Hue(0~360),用于术语标签根据颜色调整 DM 色相 ---@param r number 0~255 ---@param g number 0~255 ---@param b number 0~255 ---@return number hue 0~360 local function rgbToHue(r, g, b) r, g, b = r / 255, g / 255, b / 255 local max = math.max(r, g, b) local min = math.min(r, g, b) local d = max - min if d < 0.001 then return 278 end -- 灰色默认紫色 local h if max == r then h = ((g - b) / d) % 6 elseif max == g then h = (b - r) / d + 2 else h = (r - g) / d + 4 end h = h * 60 if h < 0 then h = h + 360 end return math.floor(h + 0.5) end
技术要点:
RGB 转 Hue:让术语标签的颜色和游戏术语颜色保持一致
灰色默认紫色:避免无色术语
0~360 范围:完美适配 DrawingMachine 的 hue 参数
5. 关键词自动提取和释义
--- 从技艺描述中提取所有可释义的词条 ---@param skill table 技艺定义 ---@return Widget[] 词条释义条目列表 local function buildTermEntries(skill) local entries = {} local seen = {} -- 1) 技艺自身的关键词(基调、叠奏、钉选、永恒) local keywords = SkillData.GetKeywords(skill.id) for _, kw in ipairs(keywords) do if not seen[kw] then seen[kw] = true local info = SkillData.LookupTerm(kw) if info then table.insert(entries, createTermEntry(kw, info)) end end end -- 2) 描述 + 详情文本中出现的游戏术语 local textPool = (skill.description or "") .. (skill.detail or "") for term, desc in pairs(SkillData.GameTermDict) do if not seen[term] and string.find(textPool, term, 1, true) then seen[term] = true table.insert(entries, createTermEntry(term, { text = desc, color = SkillData.GAME_TERM_COLOR, category = "term", })) end end -- 3) 描述中出现的属性关键词 for kw, _ in pairs(SkillData.KeywordDict) do if not seen[kw] and string.find(textPool, kw, 1, true) then seen[kw] = true local info = SkillData.LookupTerm(kw) if info then table.insert(entries, createTermEntry(kw, info)) end end end return entries end
技术要点:
三重提取:技艺自身关键词 + 描述中出现的游戏术语 + 描述中出现的属性关键词
seen 去重:避免重复显示同一术语
按优先级排序:技艺关键词在前,游戏术语在后
6. 移动端和桌面端自适应布局
if isMobile or #termEntries == 0 then -- 手机端 / 无术语:纵向布局(卡牌 + 下方术语) local colChildren = { cardContainer } if #termEntries > 0 then table.insert(colChildren, UI.Panel { width = scaledW, gap = 6, marginTop = 12, pointerEvents = "none", children = termEntries, }) end contentWidget = UI.ScrollView { maxHeight = "95%", scrollY = true, alignItems = "center", pointerEvents = "auto", children = { UI.Panel { alignItems = "center", paddingVertical = 16, pointerEvents = "none", children = colChildren, }, }, } else -- 桌面端:[左术语] [卡牌] [右术语] 横向三栏 local leftTerms, rightTerms = splitTermsLR(termEntries) local leftPanel = DMPanel { dmParams = DMData.styled("panel_dark", "frosted", { cornerRadius = 12, brightness = 10, saturation = 30, }), dmAlpha = 180, width = TERM_PANEL_W, gap = 8, padding = 10, justifyContent = "center", pointerEvents = "none", children = leftTerms, } -- ... 右面板类似 ... contentWidget = UI.Panel { flexDirection = "row", alignItems = "center", gap = 20, pointerEvents = "none", children = { leftPanel, cardContainer, rightPanel, }, } end
技术要点:
手机端:纵向布局,可滚动
桌面端:横向三栏,左右术语面板
无术语时:只显示卡牌
Settings.IsSmallScreen():检测设备类型
7. 术语交替分配到左右两列
--- 将词条交替分配到左右两列 ---@param entries Widget[] ---@return Widget[] left ---@return Widget[] right local function splitTermsLR(entries) local left, right = {}, {} for i, entry in ipairs(entries) do if i % 2 == 1 then table.insert(left, entry) else table.insert(right, entry) end end return left, right end
技术要点:
简单的奇偶分配:左列放奇数位,右列放偶数位
平衡左右面板高度:视觉更协调
怎么在你的游戏中实现?
作为萌新开发者,你不需要自己手写所有代码,直接让嗒啦啦(AI)帮你搞定!
第一步:让嗒啦啦帮你分析需求
在对话框里直接说:
嗒啦啦,帮我实现一个杀戮尖塔风格的技艺大卡悬浮浮窗,要求: 1. 全屏半透明遮罩,点击任意位置关闭 2. 原始卡牌等比放大显示 3. 左右术语释义面板(桌面端) 4. 移动端纵向布局(可滚动) 5. 逐帧入场动画,防止首帧闪烁 6. 自动提取并解释文本中出现的关键词和术语
第二步:让嗒啦啦帮你创建组件
然后说:
嗒啦啦,帮我创建一个 SkillDetailModal 组件,包含完整的 API: - SkillDetailModal.Open(skill, { onClose }) - SkillDetailModal.Close() - SkillDetailModal.IsOpen()
嗒啦啦会帮你创建完整的组件文件,包含:
全屏遮罩
卡牌放大
术语提取
自适应布局
入场动画
第三步:让嗒啦啦帮你集成到游戏中
接着说:
嗒啦啦,帮我在 RewardScreen、ShopScreen、CodexScreen 等显示卡牌的地方, 添加长按或点击打开 SkillDetailModal 的功能
这样一套完整的技艺大卡悬浮浮窗就搞定了!
实战应用:快速上手指南
1. 基本用法
-- 打开技艺详情浮窗 SkillDetailModal.Open(skill, { onClose = function() print("浮窗已关闭") end }) -- 关闭浮窗 SkillDetailModal.Close() -- 检查是否已打开 if SkillDetailModal.IsOpen() then print("浮窗正在显示") end
2. 在卡牌组件中集成
-- 在 SkillCardWidget 中添加长按打开详情 local card = SkillCardWidget.Create(skill, { mode = "reward", onLongPress = function() SkillDetailModal.Open(skill) end })
3. 在图鉴中集成
-- 在 CodexScreen 中点击技艺图标打开详情 for _, skill in ipairs(skills) do local icon = UI.Image({ image = skill.icon, width = 48, height = 48, onClick = function() SkillDetailModal.Open(skill) end }) end
我们的不足与改进空间
虽然这套浮窗功能解决了很多问题,但我们也有一些做得不够好的地方:
1. 术语提取依赖字符串匹配
问题:关键词和术语提取用的是 string.find 简单匹配,如果术语包含在其他词中可能误匹配
教训:可以考虑用更精确的匹配方式,比如分词或正则表达式
2. 没有键盘快捷键
问题:没有 ESC 键关闭浮窗的快捷键
教训:可以添加键盘支持,提升可访问性
3. 术语标签的 DMPanel 配置可以更灵活
问题:术语标签的 DMPanel 参数是硬编码的,不够灵活
教训:可以做成可配置的,适应不同游戏风格
最佳实践建议
1. 合理设计术语字典
提前规划好游戏术语:
SkillData.GameTermDict:游戏特有术语(如"跟拍"、"蓄力"、"奏"、"鸣")
SkillData.KeywordDict:属性关键词(如"基调"、"叠奏"、"钉选"、"永恒")
SkillData.GetKeywords():技艺自身的关键词
2. 用好 DrawingMachine 的 styled 组合
-- 三层组合:语义预设 + 技法 + 手动微调 DMPanel { dmParams = DMData.styled("panel_dark", "frosted", { hue = hue, brightness = 20, saturation = 50, cornerRadius = 10, }), dmAlpha = 230, -- ... }
3. 逐帧动画 vs transition
逐帧动画:适合需要精确控制首帧状态的场景(防止闪烁)
transition:适合简单的动画,代码更简洁
4. 移动端和桌面端分开设计
桌面端:空间充足,可以用横向三栏布局
移动端:空间有限,用纵向可滚动布局
Settings.IsSmallScreen():检测设备类型
5. QueueOverlay 解决渲染顺序问题
如果你的浮窗在其他弹窗内打开时被遮挡,试试 UI.QueueOverlay():
-- 通过 QueueOverlay 将整棵子树推迟到 overlay 渲染阶段 UI.QueueOverlay(function(overlayNvg) origRender(self, overlayNvg) local renderList = self:GetRenderChildren() for i = 1, #renderList do UI.RenderWidgetSubtree(renderList[i], overlayNvg) end end)
结语
《节拍前夜》的技艺大卡悬浮浮窗展示了如何在游戏中实现一套完整的卡牌详情展示方案。通过全屏遮罩、卡牌放大、术语释义、逐帧动画等技术,我们不仅解决了玩家查看卡牌详情的需求,还为游戏增添了视觉冲击力和专业感。
这套系统的核心价值在于:
提升用户体验:完整展示卡牌信息,术语自动解释
增强视觉效果:卡牌放大展示,动画流畅自然
跨平台适配:移动端和桌面端不同布局
虽然在术语匹配、快捷键支持等方面还有提升空间,但作为卡牌详情展示功能,它已经很好地满足了游戏需求。希望这个方案能为其他开发者提供参考,让你的卡牌游戏也能有《杀戮尖塔》那样的专业感!
技术栈:UrhoX、Lua、DrawingMachine
适用场景:卡牌详情展示、术语解释、悬浮浮窗
开发难度:中等
猜你想搜
taptap 制造嗒啦啦生成skilldetailmodal
别再烧积分了!从每次 2000 到每次 50 的血泪经验截图
别再烧积分了!从每次 2000 到每次 50 的血泪经验
适用场景:所有在用 TapTap Code 开发游戏的小白 难度:★☆☆☆☆ 背景最近群里总有人"炫耀"一次操作花了几千积分,说实话这不是值得高兴的事——这是在告诉所有人你的开发方式有根本性问题。我也走过这条弯路,所以来说说怎么改。 积分消耗的本质是什么积分 = AI 处理的 token 数量 = 你发给 AI 的内容 + AI 回给你的内容所以烧积分的本质只有两种: 你让 AI 做了大量无效/重
精华
82 赞
23 回复
如何提升你游戏中的UI美术水平截图
如何提升你游戏中的UI美术水平
一、请为你的游戏挑选一款合适的字体 很多开发者没有注意到其实UI界面中最重要的元素是字体,一款合适的字体能给整个游戏的界面感官带来蜕变。 请看案例 那么如何挑选适合自己的字体呢? 我总结出了一些小经验可供参考: 黑体:通用 宋体:古风、武侠、修仙 楷体:古风、武侠、修仙 圆体:卡通、动漫、Q版 科技:科幻、未来、电子 顺手分享一个免费可商用字体网站: 当然 如果你不知道自己的游戏适合什么风格
精华
126 赞
44 回复
01:29
官方阅,申请积分计划2,希望官方继续支持我完成这款游戏,截图
官方阅,申请积分计划2,希望官方继续支持我完成这款游戏,
视频内容只是一部分, 本游戏耗时3个月,目前完成进度百分之80, 是生存游戏,进入修仙界,走剧情找到宗门加入, 这时游戏才是真正开始, 提升实力境界后就可以变强,获取功法法术法宝, @视频内是游戏里挑战妖族入侵一种主流玩法,游戏就是围绕着这个来进行, 还有很多地方可以战斗,获得资源修炼变强,比如去藏书阁学习功法, 去法宝楼抽法宝,每一个宗门数十个法宝抽取 去修仙界探索,获得各自的资源,可能遇见妖兽
1 赞
3 回复
本地部署 AI 对接 TapMaker 指南 —— 以 CodeBuddy 为例V1.1截图
本地部署 AI 对接 TapMaker 指南 —— 以 CodeBuddy 为例V1.1
写在前面 大家好,我是橘猫,和你一样也是TapMaker的萌新玩家。在"全能神"天哥的悉心指导下,我顺利完成了本地开发环境的部署。经过这段时间的实战测试,已经成功打通了「本地开发 → 同步至TapMaker」的完整流程。 以下内容全部来自我的个人实操经验,旨在为同样在探索本地部署的朋友提供一个参考。每个人的电脑环境、网络情况、AI工具版本都不尽相同,文中的步骤仅供交流学习。如果你照着操作后遇到问题
37 赞
21 回复
00:37
【积分申请】《最后的黎明》全球进化,游历全球,活下去!截图
【积分申请】《最后的黎明》全球进化,游历全球,活下去!
《最后的黎明》 是一款 2D 像素风、横版、末世开放探索 + 轻战斗的冒险游戏。参考《僵尸毁灭工程》的建筑搜刮与风险玩法,《死亡细胞》的战斗手感,《守墓人》《去月球》的叙事氛围。 世界观 灾难过后,文明陨落。 城市化为废墟,天空常年灰蒙,只有地平线尽头透出一线极淡的暖金色黎明。玩家扮演一名饱经沧桑的幸存者,破旧军绿外套、脏污绷带、单边护目镜、缠布的铁管武器,独自深入这座沉睡的城市,追寻那道光。 核
1 赞
1 回复
浅记《阿方的旅程》开发历程截图
浅记《阿方的旅程》开发历程
预计阅读时长10分钟,欢迎您先品鉴游戏,再回来看这篇“流水账” 这是我第一次实际接触“用自然语言制作游戏”,也是我第一次接触游戏开发工作,所以一切都是从零开始,跌跌撞撞,懵懵懂懂。[表情_豹哭] 我是通过获得的本次开发资格,6月22日晚,在微信公众号收到了测试资格的消息,最开始公布主题“跳跃”的时候,脑子里最先想到的就是几款非常经典的游戏「马里奥」「Flappy Bird」「Doodle Jump
10 赞
1 回复
01:07
我给制造开发了一个半自动的开发工作流截图
我给制造开发了一个半自动的开发工作流
随着多模态模型识图能力增强,尝试一下AI 是否能自己修复bug,自己检查。 1,基于画面修复代码 2,点击TapTap制造 菜单,切换屏幕显示 3,截图验证 刚跑通一个小闭环,给大家分享一下 随着模型能力增强,Token费用走低,这种半自动开发是未来常态了
1 赞
2 回复
永恒虚空持续开发日记1——关于游戏中的新名词:场频截图
永恒虚空持续开发日记1——关于游戏中的新名词:场频
写在前边:首先感谢TapTap制造版主大大@汪崽 对于作品潜力的肯定,给与了我积分计划二的权限[表情_不好意思]。这边也在加紧开发改进各种功能中,期望尽快给大家带来成熟的版本。[表情_猫咪举手] 大家好,我是永恒虚空的制作人黑猫不详,大家叫我黑猫就好。从今天开始我将以后置开发日记的形式给大家分享我在TapTap制造中制作的游戏永恒虚空的开发过程,新人希望大家多多关照! 今天先给大家介绍我作品中最重
投票
3 赞
【Tap make 塔拉拉之立项干货】萌新看过来!截图
【Tap make 塔拉拉之立项干货】萌新看过来!
题外话:本人也是tap make 使用者之一。塔啦啦忠实信徒、天哥忠实拥护者、八年游戏策划经验的低级策划。但是!我已经做出来一款5000+热度、且稳定可更新的框架、并且还在持续产出更多游戏内容的橘猫! 这几天正好在开新坑。我是3月初加入的tapmake大家庭。两周时间于3月25日将这个作品上架。当时的全凭感觉摸索。如今,自己也是一个有自己项目的小菜鸡了。今天,就来分享一下,如何高效的制作一款高大上
投票精华
69 赞
24 回复
2D 等轴测视角:完整理论截图
2D 等轴测视角:完整理论
2D 等轴测视角:完整理论 一、视角的本质 什么是等轴测 人站在高处斜着往下看地面,地面上的方格子不再是正方形,而变成了菱形。这就是等轴测的视觉本质。 俯视(正上方看下去): 等轴测(斜上方看下去): ┌──┬──┬──┐ ◇ ├──┼──┼──┤ ◇ ◇ ├──┼──┼──┤ ◇ ◇ ◇ └──┴──┴──┘ ◇ ◇ ◇ 正方形网格经过旋转 + 压缩,变成了菱形网格。 为什么是 2:1
6 赞