spine-runtimes/spine-ios/Sources/SpineiOS/SkeletonDrawableWrapper.swift
Mario Zechner 3d5a66b5f8 Formatting
2025-08-28 13:57:28 +02:00

151 lines
6.9 KiB
Swift

/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2025, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
import CoreGraphics
import Foundation
import SpineSwift
import UIKit
/// A ``SkeletonDrawableWrapper`` with ``SkeletonDrawable`` bundle loading, updating, and rendering an ``Atlas``, ``Skeleton``, and ``AnimationState``
/// into a single easy to use class.
///
/// Use the ``SkeletonDrawableWrapper/fromBundle(atlasFileName:skeletonFileName:bundle:)``, ``SkeletonDrawableWrapper/fromFile(atlasFile:skeletonFile:)``, or ``SkeletonDrawableWrapper/fromHttp(atlasURL:skeletonURL:)`` methods to construct a ``SkeletonDrawableWrapper``. To have
/// multiple skeleton drawable wrapper instances share the same ``Atlas`` and ``SkeletonData``, use the constructor.
///
/// You can then directly access the `skeletonDrawable` and with it `atlas`, `skeletonData`, `skeleton`, `animationStateData`, `animationState` and `animationStateWrapper`.
/// to query and animate the skeleton. Use the ``AnimationStateWrapper`` to queue animations on one or more tracks
/// via ``AnimationState/setAnimation(trackIndex:animation:loop:)`` or ``AnimationState/addAnimation(trackIndex:animation:loop:delay:)``.
///
/// To update the ``AnimationState`` and apply it to the ``Skeleton`` call the ``AnimationStateWrapper/update`` function, providing it
/// a delta time in seconds to advance the animations.
///
/// To render the current pose of the ``Skeleton`` as a `CGImage`, use ``SkeletonDrawableWrapper/renderToImage(size:backgroundColor:scaleFactor:)``.
///
/// When the skeleton drawable is no longer needed, call the ``SkeletonDrawableWrapper/dispose()`` method to release its resources. If
/// the skeleton drawable was constructed from a shared ``Atlas`` and ``SkeletonData``, make sure to dispose the
/// atlas and skeleton data as well, if no skeleton drawable references them anymore.
@objc(SpineSkeletonDrawableWrapper)
@objcMembers
public final class SkeletonDrawableWrapper: NSObject {
public let atlas: Atlas
public let atlasPages: [UIImage]
public let skeletonData: SkeletonData
public let skeletonDrawable: SkeletonDrawable
public let skeleton: Skeleton
public let animationStateData: AnimationStateData
public let animationState: AnimationState
internal var disposed = false
/// Constructs a new skeleton drawable from the `atlasFileName` and `skeletonFileName` from the `main` bundle
/// or the optionally provided `bundle`.
///
/// Throws an `Error` in case the data could not be loaded.
public static func fromBundle(atlasFileName: String, skeletonFileName: String, bundle: Bundle = .main) async throws -> SkeletonDrawableWrapper {
let atlasAndPages = try await Atlas.fromBundle(atlasFileName, bundle: bundle)
let skeletonData = try await SkeletonData.fromBundle(
atlas: atlasAndPages.0,
skeletonFileName: skeletonFileName,
bundle: bundle
)
return try SkeletonDrawableWrapper(
atlas: atlasAndPages.0,
atlasPages: atlasAndPages.1,
skeletonData: skeletonData
)
}
/// Constructs a new skeleton drawable from the `atlasFile` and `skeletonFile`.
///
/// Throws an `Error` in case the data could not be loaded.
public static func fromFile(atlasFile: URL, skeletonFile: URL) async throws -> SkeletonDrawableWrapper {
let atlasAndPages = try await Atlas.fromFile(atlasFile)
let skeletonData = try await SkeletonData.fromFile(
atlas: atlasAndPages.0,
skeletonFile: skeletonFile
)
return try SkeletonDrawableWrapper(
atlas: atlasAndPages.0,
atlasPages: atlasAndPages.1,
skeletonData: skeletonData
)
}
/// Constructs a new skeleton drawable wrapper from the http `atlasUrl` and `skeletonUrl`.
///
/// Throws an `Error` in case the data could not be loaded.
public static func fromHttp(atlasURL: URL, skeletonURL: URL) async throws -> SkeletonDrawableWrapper {
let atlasAndPages = try await Atlas.fromHttp(atlasURL)
let skeletonData = try await SkeletonData.fromHttp(
atlas: atlasAndPages.0,
skeletonURL: skeletonURL
)
return try SkeletonDrawableWrapper(
atlas: atlasAndPages.0,
atlasPages: atlasAndPages.1,
skeletonData: skeletonData
)
}
public init(atlas: Atlas, atlasPages: [UIImage], skeletonData: SkeletonData) throws {
self.atlas = atlas
self.atlasPages = atlasPages
self.skeletonData = skeletonData
skeletonDrawable = SkeletonDrawable(skeletonData: skeletonData)
skeleton = skeletonDrawable.skeleton
animationStateData = skeletonDrawable.animationStateData
animationState = skeletonDrawable.animationState
skeleton.updateWorldTransform(Physics.none)
super.init()
}
/// Updates the ``AnimationState`` using the `delta` time given in seconds, applies the
/// animation state to the ``Skeleton`` and updates the world transforms of the skeleton
/// to calculate its current pose.
public func update(delta: Float) {
if disposed { return }
skeletonDrawable.update(delta)
}
public func dispose() {
if disposed { return }
disposed = true
atlas.dispose()
skeletonData.dispose()
// SkeletonDrawable disposal handled by ARC
}
}