EsotericSoftware / spine-runtimes

2D skeletal animation runtimes for Spine.
http://esotericsoftware.com/
Other
4.42k stars 2.92k forks source link

[iOS][Crash]Too Many Crashes On iOS,help me please!!! #2674

Closed helloWotu closed 1 week ago

helloWotu commented 2 weeks ago
image image
kikiloveswift commented 2 weeks 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.

helloWotu commented 2 weeks ago
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()
    }
}
image
helloWotu commented 2 weeks ago

我觉得不是,我测试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.

kikiloveswift commented 2 weeks ago
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()
    }
}
image

Loading Spine is an asynchronous process. You should access the skeleton and skeletonData only after the onInitialized of the SpineController has been executed.

helloWotu commented 2 weeks ago
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.

badlogic commented 1 week ago

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