Closed helloWotu closed 1 week ago
I don't think so, I have test Spine for a week. It's obvious that set the animation name wrong. Do not ignore the folder name.
class TTSpinePetView: UIView {
var spineView: SpineUIView!
var controller: SpineController?
var customSkin: Skin?
var drawable: SkeletonDrawableWrapper?
var allAnimations: [String] = []
var allSkinsName: [String] = []
var spineSize: CGSize?
var petModel: TTPetModel?
convenience init() {
self.init(frame: .zero)
}
func setupView(model: TTPetModel?, size: CGSize?) {
guard let model = model else {
return
}
guard let size = size else {
return
}
if (model.finalSlots.count ?? 0) < 1 {
return
}
if String.isEmpty(model.animation) {
return
}
self.petModel = model
self.spineSize = size
controller = SpineController(
onInitialized: { controller in
controller.animationState.setAnimationByName(
trackIndex: 0,
animationName: model.animation,
loop: true
)
},
disposeDrawableOnDeInit: false
)
let loadDrawable: () async throws -> SkeletonDrawableWrapper = {
if let atlasUrl = self.findSpinePath(fileName: "cat.atlas"), let skelUrl = self.findSpinePath(fileName: "cat.skel") {
return try await SkeletonDrawableWrapper.fromFile(atlasFile: atlasUrl, skeletonFile: skelUrl)
} else {
let bundle = Bundle.load(with: TTSpinePetView.self, bundleName: "TTUI")
return try await SkeletonDrawableWrapper.fromBundle(atlasFileName: "cat.atlas", skeletonFileName: "cat.skel", bundle: bundle ?? Bundle.main)
}
}
guard let controller = self.controller else {
return
}
Task.detached(priority: .high) {
do {
let drawable = try await loadDrawable()
await MainActor.run {
self.drawable = drawable
let skeleton = drawable.skeleton
self.allAnimations = drawable.skeletonData.animations.compactMap { $0.name }
ttprint("allAnimations===> \(self.allAnimations)")
// let allSlotsName: [String] = drawable.skeletonData.slots.compactMap { $0.name }
// ttprint("allSlotsName===> \(allSlotsName)")
self.allSkinsName = drawable.skeletonData.skins.compactMap { $0.name }
ttprint("allSkinsName===> \(self.allSkinsName)")
self.spineView = SpineUIView(from: .drawable(drawable),
controller: controller,
mode: .fit,
alignment: .center,
boundsProvider: SetupPoseBounds(),
backgroundColor: .clear)
self.addSubview(self.spineView)
self.spineView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
self.applyCostumes()
}
} catch {
print("Failed to load skeleton: \(error)")
}
}
}
private func applyCostumes() {
guard let drawable = self.drawable else {
print("Drawable is not initialized")
return
}
customSkin?.dispose()
let skeleton = drawable.skeleton
customSkin = Skin.create(name: "custom-skin")
let costumeNames = petModel?.finalSlots ?? []
ttprint("costumeNames===> \(costumeNames)")
for costumeName in costumeNames {
if self.allSkinsName.contains(costumeName), let skin = drawable.skeletonData.findSkin(name: costumeName) {
customSkin?.addSkin(other: skin)
} else {
ttlog.info(event: "⚠️⚠️⚠️not found skin", param: ["skin": costumeName, "allSkinsName": allSkinsName.joined(separator: ",")], isAliLog: true)
}
}
skeleton.skin = customSkin
skeleton.setToSetupPose()
skeleton.updateWorldTransform(physics: SPINE_PHYSICS_UPDATE)
}
func findSpinePath(fileName: String) -> URL? {
var atlasUrl = TTAvatarResourceManager.rootPath(giftId: "cat") + "/\(fileName)"
if FileManager.default.fileExists(atPath: atlasUrl) {
return URL(string: "file://\(atlasUrl)")
}
return nil
}
deinit {
print("drawable?.dispose()")
drawable?.dispose()
customSkin?.dispose()
}
}
我觉得不是,我测试Spine一个星期了,很明显动画名字设置错了,不要忽略文件夹名字。
Before I set or update the animation, I will check whether all the animations are included. The above is all the code I have, and there is no problem you mentioned.
class TTSpinePetView: UIView { var spineView: SpineUIView! var controller: SpineController? var customSkin: Skin? var drawable: SkeletonDrawableWrapper? var allAnimations: [String] = [] var allSkinsName: [String] = [] var spineSize: CGSize? var petModel: TTPetModel? convenience init() { self.init(frame: .zero) } func setupView(model: TTPetModel?, size: CGSize?) { guard let model = model else { return } guard let size = size else { return } if (model.finalSlots.count ?? 0) < 1 { return } if String.isEmpty(model.animation) { return } self.petModel = model self.spineSize = size controller = SpineController( onInitialized: { controller in controller.animationState.setAnimationByName( trackIndex: 0, animationName: model.animation, loop: true ) }, disposeDrawableOnDeInit: false ) let loadDrawable: () async throws -> SkeletonDrawableWrapper = { if let atlasUrl = self.findSpinePath(fileName: "cat.atlas"), let skelUrl = self.findSpinePath(fileName: "cat.skel") { return try await SkeletonDrawableWrapper.fromFile(atlasFile: atlasUrl, skeletonFile: skelUrl) } else { let bundle = Bundle.load(with: TTSpinePetView.self, bundleName: "TTUI") return try await SkeletonDrawableWrapper.fromBundle(atlasFileName: "cat.atlas", skeletonFileName: "cat.skel", bundle: bundle ?? Bundle.main) } } guard let controller = self.controller else { return } Task.detached(priority: .high) { do { let drawable = try await loadDrawable() await MainActor.run { self.drawable = drawable let skeleton = drawable.skeleton self.allAnimations = drawable.skeletonData.animations.compactMap { $0.name } ttprint("allAnimations===> \(self.allAnimations)") // let allSlotsName: [String] = drawable.skeletonData.slots.compactMap { $0.name } // ttprint("allSlotsName===> \(allSlotsName)") self.allSkinsName = drawable.skeletonData.skins.compactMap { $0.name } ttprint("allSkinsName===> \(self.allSkinsName)") self.spineView = SpineUIView(from: .drawable(drawable), controller: controller, mode: .fit, alignment: .center, boundsProvider: SetupPoseBounds(), backgroundColor: .clear) self.addSubview(self.spineView) self.spineView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height) self.applyCostumes() } } catch { print("Failed to load skeleton: \(error)") } } } private func applyCostumes() { guard let drawable = self.drawable else { print("Drawable is not initialized") return } customSkin?.dispose() let skeleton = drawable.skeleton customSkin = Skin.create(name: "custom-skin") let costumeNames = petModel?.finalSlots ?? [] ttprint("costumeNames===> \(costumeNames)") for costumeName in costumeNames { if self.allSkinsName.contains(costumeName), let skin = drawable.skeletonData.findSkin(name: costumeName) { customSkin?.addSkin(other: skin) } else { ttlog.info(event: "⚠️⚠️⚠️not found skin", param: ["skin": costumeName, "allSkinsName": allSkinsName.joined(separator: ",")], isAliLog: true) } } skeleton.skin = customSkin skeleton.setToSetupPose() skeleton.updateWorldTransform(physics: SPINE_PHYSICS_UPDATE) } func findSpinePath(fileName: String) -> URL? { var atlasUrl = TTAvatarResourceManager.rootPath(giftId: "cat") + "/\(fileName)" if FileManager.default.fileExists(atPath: atlasUrl) { return URL(string: "file://\(atlasUrl)") } return nil } deinit { print("drawable?.dispose()") drawable?.dispose() customSkin?.dispose() } }
Loading Spine is an asynchronous process. You should access the skeleton and skeletonData only after the onInitialized of the SpineController has been executed.
class TTSpinePetView: UIView { var spineView: SpineUIView! var controller: SpineController? var customSkin: Skin? var drawable: SkeletonDrawableWrapper? var allAnimations: [String] = [] var allSkinsName: [String] = [] var spineSize: CGSize? var petModel: TTPetModel? convenience init() { self.init(frame: .zero) } func setupView(model: TTPetModel?, size: CGSize?) { guard let model = model else { return } guard let size = size else { return } if (model.finalSlots.count ?? 0) < 1 { return } if String.isEmpty(model.animation) { return } self.petModel = model self.spineSize = size controller = SpineController( onInitialized: { controller in controller.animationState.setAnimationByName( trackIndex: 0, animationName: model.animation, loop: true ) }, disposeDrawableOnDeInit: false ) let loadDrawable: () async throws -> SkeletonDrawableWrapper = { if let atlasUrl = self.findSpinePath(fileName: "cat.atlas"), let skelUrl = self.findSpinePath(fileName: "cat.skel") { return try await SkeletonDrawableWrapper.fromFile(atlasFile: atlasUrl, skeletonFile: skelUrl) } else { let bundle = Bundle.load(with: TTSpinePetView.self, bundleName: "TTUI") return try await SkeletonDrawableWrapper.fromBundle(atlasFileName: "cat.atlas", skeletonFileName: "cat.skel", bundle: bundle ?? Bundle.main) } } guard let controller = self.controller else { return } Task.detached(priority: .high) { do { let drawable = try await loadDrawable() await MainActor.run { self.drawable = drawable let skeleton = drawable.skeleton self.allAnimations = drawable.skeletonData.animations.compactMap { $0.name } ttprint("allAnimations===> \(self.allAnimations)") // let allSlotsName: [String] = drawable.skeletonData.slots.compactMap { $0.name } // ttprint("allSlotsName===> \(allSlotsName)") self.allSkinsName = drawable.skeletonData.skins.compactMap { $0.name } ttprint("allSkinsName===> \(self.allSkinsName)") self.spineView = SpineUIView(from: .drawable(drawable), controller: controller, mode: .fit, alignment: .center, boundsProvider: SetupPoseBounds(), backgroundColor: .clear) self.addSubview(self.spineView) self.spineView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height) self.applyCostumes() } } catch { print("Failed to load skeleton: \(error)") } } } private func applyCostumes() { guard let drawable = self.drawable else { print("Drawable is not initialized") return } customSkin?.dispose() let skeleton = drawable.skeleton customSkin = Skin.create(name: "custom-skin") let costumeNames = petModel?.finalSlots ?? [] ttprint("costumeNames===> \(costumeNames)") for costumeName in costumeNames { if self.allSkinsName.contains(costumeName), let skin = drawable.skeletonData.findSkin(name: costumeName) { customSkin?.addSkin(other: skin) } else { ttlog.info(event: "⚠️⚠️⚠️not found skin", param: ["skin": costumeName, "allSkinsName": allSkinsName.joined(separator: ",")], isAliLog: true) } } skeleton.skin = customSkin skeleton.setToSetupPose() skeleton.updateWorldTransform(physics: SPINE_PHYSICS_UPDATE) } func findSpinePath(fileName: String) -> URL? { var atlasUrl = TTAvatarResourceManager.rootPath(giftId: "cat") + "/\(fileName)" if FileManager.default.fileExists(atPath: atlasUrl) { return URL(string: "file://\(atlasUrl)") } return nil } deinit { print("drawable?.dispose()") drawable?.dispose() customSkin?.dispose() } }
加载 Spine 是一个异步过程。只有在执行了 SpineController 的 onInitialized 之后,才能访问 Skeleton 和 SkeletonData。
controller = SpineController(
onInitialized: { controller in
controller.animationState.setAnimationByName(
trackIndex: 0,
animationName: model.animation,
loop: true
)
},
disposeDrawableOnDeInit: false
)
Do you mean this code is an async process? Or is it an asynchronous process somewhere else? How should I modify it? Please help me. Thank you very much.
Call applyCustumes()
in SpineController.onInitialized()
. It is the place where you are guaranteed that everything is loaded and set up completely.
Please use the forum for this kind of user code issues. https://esotericsoftware.com/forum