maxgribov / Spine

Unofficial Spine runtime Swift library, allows you to play animations created in the Spine app (http://esotericsoftware.com).
MIT License
188 stars 36 forks source link

Sprite atlas from code #16

Closed zerogone01 closed 1 year ago

zerogone01 commented 5 years ago

Hello.. I want to downloaded images from a server and load in my app. It's possible to create a Sprite atlas from code?

Thanks for all!

maxgribov commented 5 years ago

I can show you how I load atlases manually from the application bundle. This of course is not quite the same as downloading images from the server and creating atlases from it in memory, but it can be used directly if you download assets with bundles from Apple servers.

The Skeleton class has a special init for this: https://github.com/maxgribov/Spine/blob/dd6ec12b0bcdde10cf1d51df141e811af9ef9ee5/Spine/Skeleton.swift#L40-L53

It allows you to create a skeleton manually based on the model loaded from json and the atlases dictionary. Dictionary keys should be the names of the atlases. These names must contain the path to the folder where are images of the character and the name of the atlas itself.

The names of the atlases can be obtained from the model, for example in this way:

public extension SpineModel {

    func atlasesNames() -> [String]? {

        guard let skins = skins else {

            return nil
        }

        var names = Set<String>()

        for skin in skins {

            guard let atlasesNames = skin.atlasesNames() else {
                continue
            }

            names = names.union(Set(atlasesNames))
        }

        return Array(names)
    }
}

Here's an example of loading a model and creating atlases for character's assets in my game project:

class UnitAsset {

    let model: SpineModel
    let atlases: [String : SKTextureAtlas]

    init(fromJSON name: String, atlas folder: String) throws {

        guard let url = Bundle.main.url(forResource: name, withExtension: "json") else {

            throw AssetCharacterError.unableToLoadJSON
        }

        do {

            let json = try Data(contentsOf: url)
            model = try JSONDecoder().decode(SpineModel.self, from: json)

            guard let atlasesNames = model.atlasesNames() else {

                throw AssetCharacterError.atlasesNamesUnavailable
            }

            var atlasesMutable = [String : SKTextureAtlas]()

            for atlasName in atlasesNames {

                let atlasPath = "\(folder)/\(atlasName)"
                atlasesMutable[atlasName] = SKTextureAtlas(named: atlasPath)
            }

            self.atlases = atlasesMutable

        } catch {

            throw error
        }
    }

    convenience init(with metadata: UnitAssetsMetadata) throws {

        try self.init(fromJSON: metadata.json, atlas: metadata.folder)
    }
}

enum AssetCharacterError: Error {

    case unableToLoadJSON
    case atlasesNamesUnavailable
}

But in your case, you'll probably just have to create the atlases manually instead of downloading them from the bundle like I do.

Then you can manually preload atlases when you need (here is how I do in my game):

    func preload(completion:@escaping () -> Void) {

        SKTextureAtlas.preloadTextureAtlases(atlases) {

            OperationQueue.main.addOperation {

                completion()
            }
        }

    }

and further in the game I create a character from these assets:

class CharacterComponent: GKComponent {

    let character: Skeleton

    init(with asset: UnitAsset) {

        self.character = Skeleton(asset.model, asset.atlases)
        super.init()
    }
...
}

Hope I could help you.