بنقرة واحدة
cocos2dx-lua
写 Cocos2d-x Lua 时使用。节点、触摸、动作、调度器、ccui、内存的实战规范。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
写 Cocos2d-x Lua 时使用。节点、触摸、动作、调度器、ccui、内存的实战规范。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
做 Cocos Creator 多机型/多分辨率适配时使用。Canvas、Widget、安全区。
Cocos Creator 用 AssetBundle 做分包/远程资源时使用。加载、释放、依赖、缓存。
优化 Cocos Creator 渲染性能时使用。合批、图集、动静分离、Label。
给 Cocos Creator 原生包做热更新时使用。version manifest、增量、校验、回滚。
写 Cocos Creator 动效/动画时使用。tween、Animation、Spine、性能与清理。
做 Cocos Creator 大量条目列表时使用。虚拟列表、节点复用。
| name | cocos2dx-lua |
| description | 写 Cocos2d-x Lua 时使用。节点、触摸、动作、调度器、ccui、内存的实战规范。 |
| category | gamedev |
| tags | ["cocos2dx","lua","移动"] |
规则: 每个 addChild 都要有对应的清理路径(removeChild 或场景切换自动销毁);onEnter/onExit 是注册/注销外部资源(调度器、事件)的标准时机,不要在构造函数或任意时机注册后忘记注销。
为什么: Cocos2d-x 的 C++ 底层使用引用计数(retain/release),Lua 侧持有的 userdata 会阻止对象释放。AI 生成代码时最常犯的错误:在一个 Layer 里 addChild 了子节点,场景切换时只 removeFromParent 了 Layer,但子节点上挂的调度器回调和事件监听器没有清理,C++ 对象引用计数不归零,内存持续增长。新手则常在 init 里注册调度器,在 onExit 里忘记反注册,下次进场景又注册一次,定时器越堆越多。
怎么做:
onEnter 里注册调度器和事件监听器,在 onExit 里注销。node:unscheduleAllCallbacks() 和 eventDispatcher:removeEventListenersForTarget(node)。removeFromParent(true)(true = cleanup,会停止其上的 Action 和调度器)。规则: 触摸事件必须用 cc.EventListenerTouchOneByOne 或 cc.EventListenerTouchAllAtOnce 注册到 eventDispatcher,不用旧的 setTouchEnabled;setSwallowTouches(true) 只在确实需要阻止穿透时设置,并理解其对下层 Listener 的影响;节点移除前必须 removeEventListenersForTarget。
为什么: Cocos2d-x Lua 里触摸 bug 几乎全部源于两个问题:(1)新手混用新旧 API——layer:setTouchEnabled(true) 在 3.x 里已废弃,和 EventDispatcher 的优先级体系完全独立,导致点击没响应却不报错,排查极为困难;(2)AI 生成的 Listener 注册了但没有对应的注销,节点 removeChild 后 C++ 对象已析构,触摸事件仍然回调进来,访问已释放的 Lua userdata 直接崩溃。setSwallowTouches 设错方向时,弹窗后面的按钮照样可以被点击,产生逻辑混乱。
怎么做:
cc.EventListenerTouchOneByOne:create() + eventDispatcher:addEventListenerWithSceneGraphPriority(listener, node)。listener:setSwallowTouches(true) 拦截穿透;普通 UI 组件默认不吞噬,让事件往下传。onExit 里:cc.Director:getInstance():getEventDispatcher():removeEventListenersForTarget(self)。addEventListenerWithFixedPriority 显式指定优先级,避免隐式顺序导致的不确定性。规则: 所有定时逻辑通过 node:scheduleOnce、node:schedule、或 cc.Director:getInstance():getScheduler():scheduleScriptFunc 驱动;不自建基于 update 计数器的"假计时器";节点销毁前调用 node:unscheduleAllCallbacks()。
为什么: AI 非常喜欢用 update 函数加计数器模拟延迟:self._timer = self._timer + dt; if self._timer > 3 then ... end——这在单个节点上无害,但一旦复制粘贴到十几个节点,每帧十几次浮点运算加分支判断,且计时器状态分散在各节点,调试时完全看不出谁在计时。更严重的问题:节点销毁后 update 仍在跑(因为 node:unscheduleUpdate() 没调),访问 self 的字段引发 attempt to index a nil value,崩溃栈指向引擎内部,新手完全不知道根因。
怎么做:
self:scheduleOnce(function() ... end, delay)。self:schedule(function() ... end, interval),返回值保存备用。onExit 或销毁前:self:unscheduleAllCallbacks()——一句话清除该节点上所有定时器。规则: 同一个 Action 实例不能同时挂给多个节点——必须 action:clone();Sequence、Spawn、RepeatForever 包装的动作链,每次 runAction 前都要 clone;高频创建的动作(如 UI 弹跳)缓存模板,运行时 clone 出实例。
为什么: Cocos2d-x 的 Action 是有状态对象,共享同一实例给多个节点时,第一个节点跑完动作,第二个节点拿到的是已结束状态的对象,直接跑不动或行为异常。AI 生成批量动画代码时,最常出现 for i=1,10 do node[i]:runAction(sameAction) end,前 9 个节点的动作全部失效,只有最后一个正常——而且不报任何错误,纯表现 bug,极难定位。新手则习惯每次 runAction 都 cc.SequenceCreate(...) 新建,批量动画时大量临时对象涌入 GC。
怎么做:
for i=1,#nodes do nodes[i]:runAction(template:clone()) end。self._bounceTemplate = cc.Sequence:create(cc.ScaleTo:create(0.1,1.2), cc.ScaleTo:create(0.1,1.0)),复用时 clone。cc.Spawn 并行、cc.Sequence 串行,减少手动回调链;cc.CallFunc 作为 Sequence 最后一步处理完成逻辑。cc.RepeatForever,节点退出时 node:stopAllActions() 清理。规则: 闭包会隐式持有上值(upvalue),持有大对象或节点引用时会阻止 GC;全局变量挂到 _G 会在整个生命周期存活;大型数据(地图数据、关卡配置)用完后显式置 nil 并调用 collectgarbage("collect")。
为什么: Lua 的 GC 是标记清除,只要有引用链就不释放。AI 生成代码时常见的闭包陷阱:local data = loadBigTable(); node:schedule(function() use(data) end, 1)——data 被 schedule 的回调闭包持有,节点销毁后如果 schedule 没清理,data 永远不被 GC,直到进程结束。更常见的是全局表污染:Manager = {}; Manager.data = loadBigTable(),换场景后 Manager 仍在 _G 里,data 占着内存,新手完全意识不到"换场景内存没降"的原因在这里。移动端内存预算只有 400-600MB,这类泄漏积累几个场景后直接被 OS 杀进程。
怎么做:
local,不挂 _G;模块用 require 返回局部表,不污染全局命名空间。onExit 里 self._bigData = nil;若挂在全局 Manager,场景退出时 Manager.clearSceneData()。local ref = node; node:schedule(function() if ref then ref:doSomething() end end, 1); -- 退出时 ref = nil 并 unschedule。print(collectgarbage("count")) 对比场景进出前后的内存,确认无泄漏。-- 反例 — 旧 API 触摸 + 调度器泄漏
local MyLayer = class("MyLayer", function()
return cc.Layer:create()
end)
function MyLayer:init()
self:setTouchEnabled(true) -- ❌ 3.x 废弃 API,与 EventDispatcher 不兼容
self:setTouchMode(cc.TOUCH_MODE_ONE_BY_ONE)
-- ❌ update 里模拟计时器,且 update 退出不清理
self._timer = 0
self:scheduleUpdate(function(dt)
self._timer = self._timer + dt
if self._timer > 2 then
self:onTimerFired()
self._timer = 0
end
end)
-- onExit 里什么也没做 — 调度器泄漏
end
-- 正例 — 新 EventListener + schedule + onExit 清理
local MyLayer = class("MyLayer", function()
return cc.Layer:create()
end)
function MyLayer:init()
-- ✅ 用 EventDispatcher 注册触摸
local listener = cc.EventListenerTouchOneByOne:create()
listener:setSwallowTouches(false)
listener:registerScriptHandler(function(touch, event)
self:onTouchBegan(touch); return true
end, cc.Handler.EVENT_TOUCH_BEGAN)
cc.Director:getInstance():getEventDispatcher()
:addEventListenerWithSceneGraphPriority(listener, self)
-- ✅ 用 scheduleOnce 替代手动计时
self:scheduleOnce(function() self:onTimerFired() end, 2)
return true
end
function MyLayer:onExit()
-- ✅ 退出时清理所有调度器和事件监听器
self:unscheduleAllCallbacks()
cc.Director:getInstance():getEventDispatcher()
:removeEventListenersForTarget(self)
end
-- 反例 — 共享 Action 实例,闭包持有大数据
local sharedAction = cc.ScaleTo:create(0.3, 1.2) -- ❌ 单例 Action
local MapData = loadHugeMapConfig() -- 大数据
for i = 1, #self._icons do
self._icons[i]:runAction(sharedAction) -- ❌ 同一实例给多个节点,只有最后一个有效
end
-- ❌ 闭包持有 MapData,场景退出后 MapData 无法 GC
self:schedule(function()
renderMinimap(MapData)
end, 0.5)
-- 正例 — clone Action,闭包引用随场景清理
local actionTemplate = cc.ScaleTo:create(0.3, 1.2) -- ✅ 模板只创建一次
for i = 1, #self._icons do
self._icons[i]:runAction(actionTemplate:clone()) -- ✅ 每个节点独立实例
end
local mapData = loadHugeMapConfig() -- local 变量,生命周期可控
self:schedule(function()
if mapData then renderMinimap(mapData) end
end, 0.5)
function self:onExit()
self:unscheduleAllCallbacks() -- ✅ 停止 schedule,闭包释放,mapData 可 GC
mapData = nil -- ✅ 显式断引用
end
onEnter,并在 onExit 里 unscheduleAllCallbacks() 清理。cc.EventListenerTouchOneByOne 新 API,节点退出时 removeEventListenersForTarget。runAction 前对模板调用了 :clone(),没有多节点共享同一 Action 实例。scheduleOnce,周期逻辑用 schedule。onExit 里置 nil 断开引用,可 GC 回收。collectgarbage("count") 对比场景进出前后内存,确认无明显泄漏。