macOS 和微信都试过了。
我们大概猜到问题原因了,spine 底层(WASM 层?)每次通知 js 对应事件的时候,track 对象并不是同一个 js 对象,setTrack***Listener是不是把监听器存在的 track 的 js 对象上了?
我们写了一个临时的补丁修复这个问题,贴出来供遇到这个问题的小伙伴做参考,也方便引擎开发团队定位问题。
补丁如下:
import { VERSION, __private, sp } from "cc";
if (VERSION.startsWith("3.8")) {
console.log("patch cocos 3.8.x spine listener bug!")
interface Listeners {
completeListener?: __private._cocos_spine_skeleton__TrackListener
endListener?: __private._cocos_spine_skeleton__TrackListener
eventListener?: __private._cocos_spine_skeleton__TrackListener2
interruptListener?: __private._cocos_spine_skeleton__TrackListener
disposeListener?: __private._cocos_spine_skeleton__TrackListener
startListener?: __private._cocos_spine_skeleton__TrackListener
}
function clearTrackListeners(obj: sp.Skeleton, tr: sp.spine.TrackEntry) {
let $$ = (tr as any)["$$"]
if ($$) {
let ptr = $$.ptr.toString()
let objAny = obj as any
let trackListeners = objAny.__p_trackListeners
if (trackListeners) {
delete trackListeners[ptr]
}
}
}
function listeners(obj: sp.Skeleton, tr: sp.spine.TrackEntry | null, create: boolean): Listeners | null | undefined {
let holder: any
if (tr) {
let $$ = (tr as any)["$$"]
if ($$) {
let ptr = $$.ptr.toString()
let objAny = obj as any
let trackListeners = objAny.__p_trackListeners || (objAny.__p_trackListeners = {})
holder = trackListeners[ptr] || (trackListeners[ptr] = {})
} else {
holder = tr
}
} else {
holder = obj
}
let listeners = holder.__p_listeners
if (!listeners && create) {
listeners = holder.__p_listeners = {}
}
return listeners
}
let setCompleteListener_old = sp.Skeleton.prototype.setCompleteListener
let __p_setEndListener = sp.Skeleton.prototype.setEndListener
let __p_setEventListener = sp.Skeleton.prototype.setEventListener
let __p_setInterruptListener = sp.Skeleton.prototype.setInterruptListener
let __p_setDisposeListener = sp.Skeleton.prototype.setDisposeListener
let __p_setStartListener = sp.Skeleton.prototype.setStartListener
function setOldListenerIfNeed(obj: sp.Skeleton, key: string, oldSetter: Function, listener: Function) {
let objAny = obj as any
if (objAny[key]) {
return
}
oldSetter.apply(obj, [listener.bind(obj)])
objAny[key] = true
}
function p_onComplete(this: sp.Skeleton, tr: sp.spine.TrackEntry) {
let l1 = listeners(this, null, false)
let l2 = listeners(this, tr, false)
l1 && l1.completeListener && l1.completeListener(tr)
l2 && l2.completeListener && l2.completeListener(tr)
}
function p_onEnd(this: sp.Skeleton, tr: sp.spine.TrackEntry) {
let l1 = listeners(this, null, false)
let l2 = listeners(this, tr, false)
clearTrackListeners(this, tr)
l1 && l1.endListener && l1.endListener(tr)
l2 && l2.endListener && l2.endListener(tr)
}
function p_onEvent(this: sp.Skeleton, tr: sp.spine.TrackEntry, ev: sp.spine.Event) {
let l1 = listeners(this, null, false)
let l2 = listeners(this, tr, false)
l1 && l1.eventListener && l1.eventListener(tr, ev)
l2 && l2.eventListener && l2.eventListener(tr, ev)
}
function p_onInterrupt(this: sp.Skeleton, tr: sp.spine.TrackEntry) {
let l1 = listeners(this, null, false)
let l2 = listeners(this, tr, false)
l1 && l1.interruptListener && l1.interruptListener(tr)
l2 && l2.interruptListener && l2.interruptListener(tr)
}
function p_onDispose(this: sp.Skeleton, tr: sp.spine.TrackEntry) {
let l1 = listeners(this, null, false)
let l2 = listeners(this, tr, false)
l1 && l1.disposeListener && l1.disposeListener(tr)
l2 && l2.disposeListener && l2.disposeListener(tr)
}
function p_onStart(this: sp.Skeleton, tr: sp.spine.TrackEntry) {
let l1 = listeners(this, null, false)
let l2 = listeners(this, tr, false)
l1 && l1.startListener && l1.startListener(tr)
l2 && l2.startListener && l2.startListener(tr)
}
sp.Skeleton.prototype.setCompleteListener = function (listener) {
setOldListenerIfNeed(this, "__p_setCompleteListener", setCompleteListener_old, p_onComplete)
listeners(this, null, true)!.completeListener = listener
}
sp.Skeleton.prototype.setTrackCompleteListener = function (tr, listener) {
setOldListenerIfNeed(this, "__p_setCompleteListener", setCompleteListener_old, p_onComplete)
//参数不匹配
//@ts-ignore
listeners(this, tr, true)!.completeListener = listener
}
sp.Skeleton.prototype.setEndListener = function (listener) {
setOldListenerIfNeed(this, "__p_setEndListener", __p_setEndListener, p_onEnd)
listeners(this, null, true)!.endListener = listener
}
sp.Skeleton.prototype.setTrackEndListener = function (tr, listener) {
setOldListenerIfNeed(this, "__p_setEndListener", __p_setEndListener, p_onEnd)
listeners(this, tr, true)!.endListener = listener
}
sp.Skeleton.prototype.setEventListener = function (listener) {
setOldListenerIfNeed(this, "__p_setEventListener", __p_setEventListener, p_onEvent)
listeners(this, null, true)!.eventListener = listener
}
sp.Skeleton.prototype.setTrackEventListener = function (tr, listener) {
setOldListenerIfNeed(this, "__p_setEventListener", __p_setEventListener, p_onEvent)
listeners(this, tr, true)!.eventListener = listener
}
sp.Skeleton.prototype.setInterruptListener = function (listener) {
setOldListenerIfNeed(this, "__p_setInterruptListener", __p_setInterruptListener, p_onInterrupt)
listeners(this, null, true)!.interruptListener = listener
}
sp.Skeleton.prototype.setTrackInterruptListener = function (tr, listener) {
setOldListenerIfNeed(this, "__p_setInterruptListener", __p_setInterruptListener, p_onInterrupt)
listeners(this, tr, true)!.interruptListener = listener
}
sp.Skeleton.prototype.setDisposeListener = function (listener) {
setOldListenerIfNeed(this, "__p_setDisposeListener", __p_setDisposeListener, p_onDispose)
listeners(this, null, true)!.disposeListener = listener
}
sp.Skeleton.prototype.setTrackDisposeListener = function (tr, listener) {
setOldListenerIfNeed(this, "__p_setDisposeListener", __p_setDisposeListener, p_onDispose)
listeners(this, tr, true)!.disposeListener = listener
}
sp.Skeleton.prototype.setStartListener = function (listener) {
setOldListenerIfNeed(this, "__p_setStartListener", __p_setStartListener, p_onStart)
listeners(this, null, true)!.startListener = listener
}
sp.Skeleton.prototype.setTrackStartListener = function (tr, listener) {
setOldListenerIfNeed(this, "__p_setStartListener", __p_setStartListener, p_onStart)
listeners(this, tr, true)!.startListener = listener
}
}
这个补丁目前可以让我们的使用场景恢复正常,但是不确定是否覆盖全面,有使用到的小伙伴如果发现问题,欢迎完善下哈。