with one click
cocos2dx-lua
写 Cocos2d-x Lua 时使用。节点、触摸、动作、调度器、ccui、内存的实战规范。
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
写 Cocos2d-x Lua 时使用。节点、触摸、动作、调度器、ccui、内存的实战规范。
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
做 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") 对比场景进出前后内存,确认无明显泄漏。