mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-21 01:36:02 +08:00
feat(spine-ios): Update SpineiOS and Example to work with new SpineSwift generated bindings
- Remove AnimationStateWrapper (no longer needed with new SpineSwift API) - Replace Spine.Generated+Extensions.swift with simplified SpineSwiftExtensions.swift - Update SpineiOS to use SpineSwift API instead of direct SpineC calls - Fix namespace conflicts (ContentMode → SpineContentMode, Alignment → SpineAlignment) - Update texture mapping to use atlas page index from RenderCommand pointer - Fix all Example app API calls to match new SpineSwift generated API: - setAnimationByName → setAnimation - addAnimationByName → addAnimation - slot.color → slot.appliedPose.color.set() - EventType enum cases instead of constants - Physics enum with qualified name to avoid conflicts - setSkin2() instead of property assignment - Array iteration using indices instead of for-in - bone.worldX → bone.appliedPose.worldX - Update Objective-C imports from Spine to SpineiOS module Note: SimpleAnimationViewController.m still needs updates for full ObjC compatibility
This commit is contained in:
parent
32a8552387
commit
9b345068b6
@ -13,6 +13,7 @@
|
|||||||
76381FDB2C2EF4F10087712B /* spineboy-pma.atlas in Resources */ = {isa = PBXBuildFile; fileRef = 76381FD52C2EF4F00087712B /* spineboy-pma.atlas */; };
|
76381FDB2C2EF4F10087712B /* spineboy-pma.atlas in Resources */ = {isa = PBXBuildFile; fileRef = 76381FD52C2EF4F00087712B /* spineboy-pma.atlas */; };
|
||||||
76381FDE2C2EF53A0087712B /* mix-and-match-pma.png in Resources */ = {isa = PBXBuildFile; fileRef = 76381FDC2C2EF53A0087712B /* mix-and-match-pma.png */; };
|
76381FDE2C2EF53A0087712B /* mix-and-match-pma.png in Resources */ = {isa = PBXBuildFile; fileRef = 76381FDC2C2EF53A0087712B /* mix-and-match-pma.png */; };
|
||||||
76381FDF2C2EF53A0087712B /* mix-and-match-pma.atlas in Resources */ = {isa = PBXBuildFile; fileRef = 76381FDD2C2EF53A0087712B /* mix-and-match-pma.atlas */; };
|
76381FDF2C2EF53A0087712B /* mix-and-match-pma.atlas in Resources */ = {isa = PBXBuildFile; fileRef = 76381FDD2C2EF53A0087712B /* mix-and-match-pma.atlas */; };
|
||||||
|
76DD66952E5DE75400963397 /* SpineiOS in Frameworks */ = {isa = PBXBuildFile; productRef = 76DD66942E5DE75400963397 /* SpineiOS */; };
|
||||||
9205FCD42C0760B1006EE07E /* SimpleAnimationViewControllerRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9205FCD32C0760B1006EE07E /* SimpleAnimationViewControllerRepresentable.swift */; };
|
9205FCD42C0760B1006EE07E /* SimpleAnimationViewControllerRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9205FCD32C0760B1006EE07E /* SimpleAnimationViewControllerRepresentable.swift */; };
|
||||||
920BD1162BEBC52D0050A5A9 /* spineboy-pro.skel in Resources */ = {isa = PBXBuildFile; fileRef = 920BD1122BEBC52D0050A5A9 /* spineboy-pro.skel */; };
|
920BD1162BEBC52D0050A5A9 /* spineboy-pro.skel in Resources */ = {isa = PBXBuildFile; fileRef = 920BD1122BEBC52D0050A5A9 /* spineboy-pro.skel */; };
|
||||||
920BD1182BEBC52D0050A5A9 /* spineboy-pro.json in Resources */ = {isa = PBXBuildFile; fileRef = 920BD1142BEBC52D0050A5A9 /* spineboy-pro.json */; };
|
920BD1182BEBC52D0050A5A9 /* spineboy-pro.json in Resources */ = {isa = PBXBuildFile; fileRef = 920BD1142BEBC52D0050A5A9 /* spineboy-pro.json */; };
|
||||||
@ -33,10 +34,8 @@
|
|||||||
924C0C182BCFCF21004E63F7 /* SimpleAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 924C0C172BCFCF21004E63F7 /* SimpleAnimation.swift */; };
|
924C0C182BCFCF21004E63F7 /* SimpleAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 924C0C172BCFCF21004E63F7 /* SimpleAnimation.swift */; };
|
||||||
924C0C1A2BCFCF22004E63F7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 924C0C192BCFCF22004E63F7 /* Assets.xcassets */; };
|
924C0C1A2BCFCF22004E63F7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 924C0C192BCFCF22004E63F7 /* Assets.xcassets */; };
|
||||||
92579E772C1B0E9500FDC7D5 /* DisableRendering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92579E762C1B0E9500FDC7D5 /* DisableRendering.swift */; };
|
92579E772C1B0E9500FDC7D5 /* DisableRendering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92579E762C1B0E9500FDC7D5 /* DisableRendering.swift */; };
|
||||||
925CB7E92C19BC5A00C8F47F /* Spine in Frameworks */ = {isa = PBXBuildFile; productRef = 925CB7E82C19BC5A00C8F47F /* Spine */; };
|
|
||||||
9270C16E2BFE356000BD25BC /* Physics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9270C16D2BFE356000BD25BC /* Physics.swift */; };
|
9270C16E2BFE356000BD25BC /* Physics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9270C16D2BFE356000BD25BC /* Physics.swift */; };
|
||||||
9270C1742BFE389600BD25BC /* celestial-circus-pro.skel in Resources */ = {isa = PBXBuildFile; fileRef = 9270C1712BFE389600BD25BC /* celestial-circus-pro.skel */; };
|
9270C1742BFE389600BD25BC /* celestial-circus-pro.skel in Resources */ = {isa = PBXBuildFile; fileRef = 9270C1712BFE389600BD25BC /* celestial-circus-pro.skel */; };
|
||||||
928A8CC22BCFE7DF00D9D35B /* Spine in Frameworks */ = {isa = PBXBuildFile; productRef = 928A8CC12BCFE7DF00D9D35B /* Spine */; };
|
|
||||||
92D7DDA82BFF3C8800EB9E3A /* DebugRendering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92D7DDA72BFF3C8800EB9E3A /* DebugRendering.swift */; };
|
92D7DDA82BFF3C8800EB9E3A /* DebugRendering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92D7DDA72BFF3C8800EB9E3A /* DebugRendering.swift */; };
|
||||||
92FE93292BF4AB9600CCDF48 /* IKFollowing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92FE93282BF4AB9600CCDF48 /* IKFollowing.swift */; };
|
92FE93292BF4AB9600CCDF48 /* IKFollowing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92FE93282BF4AB9600CCDF48 /* IKFollowing.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
@ -83,8 +82,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
925CB7E92C19BC5A00C8F47F /* Spine in Frameworks */,
|
76DD66952E5DE75400963397 /* SpineiOS in Frameworks */,
|
||||||
928A8CC22BCFE7DF00D9D35B /* Spine in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -212,8 +210,7 @@
|
|||||||
);
|
);
|
||||||
name = "Spine iOS Example";
|
name = "Spine iOS Example";
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
928A8CC12BCFE7DF00D9D35B /* Spine */,
|
76DD66942E5DE75400963397 /* SpineiOS */,
|
||||||
925CB7E82C19BC5A00C8F47F /* Spine */,
|
|
||||||
);
|
);
|
||||||
productName = "Spine iOS Example";
|
productName = "Spine iOS Example";
|
||||||
productReference = 924C0C122BCFCF21004E63F7 /* Spine iOS Example.app */;
|
productReference = 924C0C122BCFCF21004E63F7 /* Spine iOS Example.app */;
|
||||||
@ -245,7 +242,7 @@
|
|||||||
);
|
);
|
||||||
mainGroup = 924C0C092BCFCF21004E63F7;
|
mainGroup = 924C0C092BCFCF21004E63F7;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
925CB7E72C19BC5A00C8F47F /* XCLocalSwiftPackageReference "../.." */,
|
76DD668F2E5DE75400963397 /* XCLocalSwiftPackageReference "../../../spine-runtimes" */,
|
||||||
);
|
);
|
||||||
productRefGroup = 924C0C132BCFCF21004E63F7 /* Products */;
|
productRefGroup = 924C0C132BCFCF21004E63F7 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
@ -536,20 +533,16 @@
|
|||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCLocalSwiftPackageReference section */
|
/* Begin XCLocalSwiftPackageReference section */
|
||||||
925CB7E72C19BC5A00C8F47F /* XCLocalSwiftPackageReference "../.." */ = {
|
76DD668F2E5DE75400963397 /* XCLocalSwiftPackageReference "../../../spine-runtimes" */ = {
|
||||||
isa = XCLocalSwiftPackageReference;
|
isa = XCLocalSwiftPackageReference;
|
||||||
relativePath = ../..;
|
relativePath = "../../../spine-runtimes";
|
||||||
};
|
};
|
||||||
/* End XCLocalSwiftPackageReference section */
|
/* End XCLocalSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
925CB7E82C19BC5A00C8F47F /* Spine */ = {
|
76DD66942E5DE75400963397 /* SpineiOS */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = Spine;
|
productName = SpineiOS;
|
||||||
};
|
|
||||||
928A8CC12BCFE7DF00D9D35B /* Spine */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
productName = Spine;
|
|
||||||
};
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
|
|||||||
@ -27,8 +27,8 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import Spine
|
import SpineiOS
|
||||||
import SpineCppLite
|
import SpineSwift
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct AnimationStateEvents: View {
|
struct AnimationStateEvents: View {
|
||||||
@ -38,25 +38,25 @@ struct AnimationStateEvents: View {
|
|||||||
onInitialized: { controller in
|
onInitialized: { controller in
|
||||||
controller.skeleton.scaleX = 0.5
|
controller.skeleton.scaleX = 0.5
|
||||||
controller.skeleton.scaleY = 0.5
|
controller.skeleton.scaleY = 0.5
|
||||||
controller.skeleton.findSlot(slotName: "gun")?.setColor(r: 1, g: 0, b: 0, a: 1)
|
controller.skeleton.findSlot("gun")?.appliedPose.color.set(1, 0, 0, 1)
|
||||||
controller.animationStateData.defaultMix = 0.2
|
controller.animationStateData.defaultMix = 0.2
|
||||||
let walk = controller.animationState.setAnimationByName(trackIndex: 0, animationName: "walk", loop: true)
|
let walk = controller.animationState.setAnimation(0, "walk", true)
|
||||||
controller.animationStateWrapper.setTrackEntryListener(entry: walk) { type, entry, event in
|
walk.setListener { type, entry, event in
|
||||||
print("Walk animation event \(type)")
|
print("Walk animation event \(type)")
|
||||||
}
|
}
|
||||||
controller.animationState.addAnimationByName(trackIndex: 0, animationName: "jump", loop: false, delay: 2)
|
controller.animationState.addAnimation(0, "jump", false, 2)
|
||||||
let run = controller.animationState.addAnimationByName(trackIndex: 0, animationName: "run", loop: true, delay: 0)
|
let run = controller.animationState.addAnimation(0, "run", true, 0)
|
||||||
controller.animationStateWrapper.setTrackEntryListener(entry: run) { type, entry, event in
|
run.setListener { type, entry, event in
|
||||||
print("Run animation event \(type)")
|
print("Run animation event \(type)")
|
||||||
}
|
}
|
||||||
controller.animationStateWrapper.setStateListener { type, entry, event in
|
controller.animationState.setListener { type, entry, event in
|
||||||
if type == SPINE_EVENT_TYPE_EVENT, let event {
|
if type == .event, let event {
|
||||||
print(
|
print(
|
||||||
"User event: { name: \(event.data.name ?? "--"), intValue: \(event.intValue), floatValue: \(event.floatValue), stringValue: \(event.stringValue ?? "--") }"
|
"User event: { name: \(event.data.name ?? "--"), intValue: \(event.intValue), floatValue: \(event.floatValue), stringValue: \(event.stringValue ?? "--") }"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let current = controller.animationState.getCurrent(trackIndex: 0)?.animation.name ?? "--"
|
let current = controller.animationState.getCurrent(0)?.animation.name ?? "--"
|
||||||
print("Current: \(current)")
|
print("Current: \(current)")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import Spine
|
import SpineiOS
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct DebugRendering: View {
|
struct DebugRendering: View {
|
||||||
@ -71,18 +71,16 @@ final class DebugRenderingModel: ObservableObject {
|
|||||||
init() {
|
init() {
|
||||||
controller = SpineController(
|
controller = SpineController(
|
||||||
onInitialized: { controller in
|
onInitialized: { controller in
|
||||||
controller.animationState.setAnimationByName(
|
controller.animationState.setAnimation(0, "walk", true)
|
||||||
trackIndex: 0,
|
|
||||||
animationName: "walk",
|
|
||||||
loop: true
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
onAfterPaint: {
|
onAfterPaint: {
|
||||||
[weak self] controller in
|
[weak self] controller in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
boneRects = controller.drawable.skeleton.bones.map { bone in
|
let bones = controller.drawable.skeleton.bones
|
||||||
|
boneRects = (0..<bones.count).compactMap { i -> BoneRect? in
|
||||||
|
guard let bone = bones[i] else { return nil }
|
||||||
let position = controller.fromSkeletonCoordinates(
|
let position = controller.fromSkeletonCoordinates(
|
||||||
position: CGPointMake(CGFloat(bone.worldX), CGFloat(bone.worldY))
|
position: CGPointMake(CGFloat(bone.appliedPose.worldX), CGFloat(bone.appliedPose.worldY))
|
||||||
)
|
)
|
||||||
return BoneRect(
|
return BoneRect(
|
||||||
id: UUID(),
|
id: UUID(),
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import Spine
|
import SpineiOS
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct DisableRendering: View {
|
struct DisableRendering: View {
|
||||||
@ -35,11 +35,7 @@ struct DisableRendering: View {
|
|||||||
@StateObject
|
@StateObject
|
||||||
var controller = SpineController(
|
var controller = SpineController(
|
||||||
onInitialized: { controller in
|
onInitialized: { controller in
|
||||||
controller.animationState.setAnimationByName(
|
controller.animationState.setAnimation(0, "walk", true)
|
||||||
trackIndex: 0,
|
|
||||||
animationName: "walk",
|
|
||||||
loop: true
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -27,8 +27,8 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import Spine
|
import SpineiOS
|
||||||
import SpineCppLite
|
import SpineSwift
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct DressUp: View {
|
struct DressUp: View {
|
||||||
@ -94,11 +94,7 @@ final class DressUpModel: ObservableObject {
|
|||||||
init() {
|
init() {
|
||||||
controller = SpineController(
|
controller = SpineController(
|
||||||
onInitialized: { controller in
|
onInitialized: { controller in
|
||||||
controller.animationState.setAnimationByName(
|
controller.animationState.setAnimation(0, "dance", true)
|
||||||
trackIndex: 0,
|
|
||||||
animationName: "dance",
|
|
||||||
loop: true
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
disposeDrawableOnDeInit: false
|
disposeDrawableOnDeInit: false
|
||||||
)
|
)
|
||||||
@ -108,21 +104,22 @@ final class DressUpModel: ObservableObject {
|
|||||||
skeletonFileName: "mix-and-match-pro.skel"
|
skeletonFileName: "mix-and-match-pro.skel"
|
||||||
)
|
)
|
||||||
try await MainActor.run {
|
try await MainActor.run {
|
||||||
for skin in drawable.skeletonData.skins {
|
let skins = drawable.skeletonData.skins
|
||||||
|
for i in 0..<skins.count {
|
||||||
|
guard let skin = skins[i] else { continue }
|
||||||
if skin.name == "default" { continue }
|
if skin.name == "default" { continue }
|
||||||
let skeleton = drawable.skeleton
|
let skeleton = drawable.skeleton
|
||||||
skeleton.skin = skin
|
skeleton.setSkin2(skin)
|
||||||
skeleton.setToSetupPose()
|
skeleton.setupPose()
|
||||||
skeleton.update(delta: 0)
|
skeleton.update(0)
|
||||||
skeleton.updateWorldTransform(physics: SPINE_PHYSICS_UPDATE)
|
skeleton.updateWorldTransform(SpineSwift.Physics.update)
|
||||||
try skin.name.flatMap { skinName in
|
let skinName = skin.name
|
||||||
self.skinImages[skinName] = try drawable.renderToImage(
|
self.skinImages[skinName] = try drawable.renderToImage(
|
||||||
size: self.thumbnailSize,
|
size: self.thumbnailSize,
|
||||||
backgroundColor: .white,
|
backgroundColor: .white,
|
||||||
scaleFactor: UIScreen.main.scale
|
scaleFactor: UIScreen.main.scale
|
||||||
)
|
)
|
||||||
self.selectedSkins[skinName] = false
|
self.selectedSkins[skinName] = false
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.toggleSkin(skinName: "full-skins/girl", drawable: drawable)
|
self.toggleSkin(skinName: "full-skins/girl", drawable: drawable)
|
||||||
self.drawable = drawable
|
self.drawable = drawable
|
||||||
@ -144,15 +141,15 @@ final class DressUpModel: ObservableObject {
|
|||||||
func toggleSkin(skinName: String, drawable: SkeletonDrawableWrapper) {
|
func toggleSkin(skinName: String, drawable: SkeletonDrawableWrapper) {
|
||||||
selectedSkins[skinName] = !(selectedSkins[skinName] ?? false)
|
selectedSkins[skinName] = !(selectedSkins[skinName] ?? false)
|
||||||
customSkin?.dispose()
|
customSkin?.dispose()
|
||||||
customSkin = Skin.create(name: "custom-skin")
|
customSkin = Skin("custom-skin")
|
||||||
for skinName in selectedSkins.keys {
|
for skinName in selectedSkins.keys {
|
||||||
if selectedSkins[skinName] == true {
|
if selectedSkins[skinName] == true {
|
||||||
if let skin = drawable.skeletonData.findSkin(name: skinName) {
|
if let skin = drawable.skeletonData.findSkin(skinName) {
|
||||||
customSkin?.addSkin(other: skin)
|
customSkin?.addSkin(skin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drawable.skeleton.skin = customSkin
|
drawable.skeleton.setSkin2(customSkin)
|
||||||
drawable.skeleton.setToSetupPose()
|
drawable.skeleton.setupPose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import Spine
|
import SpineiOS
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct IKFollowing: View {
|
struct IKFollowing: View {
|
||||||
@ -74,16 +74,8 @@ final class IKFollowingModel: ObservableObject {
|
|||||||
init() {
|
init() {
|
||||||
controller = SpineController(
|
controller = SpineController(
|
||||||
onInitialized: { controller in
|
onInitialized: { controller in
|
||||||
controller.animationState.setAnimationByName(
|
controller.animationState.setAnimation(0, "walk", true)
|
||||||
trackIndex: 0,
|
controller.animationState.setAnimation(1, "aim", true)
|
||||||
animationName: "walk",
|
|
||||||
loop: true
|
|
||||||
)
|
|
||||||
controller.animationState.setAnimationByName(
|
|
||||||
trackIndex: 1,
|
|
||||||
animationName: "aim",
|
|
||||||
loop: true
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
onAfterUpdateWorldTransforms: {
|
onAfterUpdateWorldTransforms: {
|
||||||
[weak self] controller in
|
[weak self] controller in
|
||||||
@ -91,11 +83,11 @@ final class IKFollowingModel: ObservableObject {
|
|||||||
guard let worldPosition = self.crossHairPosition else {
|
guard let worldPosition = self.crossHairPosition else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let bone = controller.skeleton.findBone(boneName: "crosshair")!
|
let bone = controller.skeleton.findBone("crosshair")!
|
||||||
if let parent = bone.parent {
|
if let parent = bone.parent {
|
||||||
let position = parent.worldToLocal(worldX: Float(worldPosition.x), worldY: Float(worldPosition.y))
|
let position = parent.appliedPose.worldToLocal(worldX: Float(worldPosition.x), worldY: Float(worldPosition.y))
|
||||||
bone.x = position.x
|
bone.appliedPose.x = position.x
|
||||||
bone.y = position.y
|
bone.appliedPose.y = position.y
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import Spine
|
import SpineiOS
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct MainView: View {
|
struct MainView: View {
|
||||||
@ -72,7 +72,7 @@ struct MainView: View {
|
|||||||
} footer: {
|
} footer: {
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("Spine \(Spine.version)")
|
Text("Spine \(SpineiOS.version)")
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import Spine
|
import SpineiOS
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct Physics: View {
|
struct Physics: View {
|
||||||
@ -72,16 +72,8 @@ final class PhysicsModel: ObservableObject {
|
|||||||
init() {
|
init() {
|
||||||
controller = SpineController(
|
controller = SpineController(
|
||||||
onInitialized: { controller in
|
onInitialized: { controller in
|
||||||
controller.animationState.setAnimationByName(
|
controller.animationState.setAnimation(0, "eyeblink", true)
|
||||||
trackIndex: 0,
|
controller.animationState.setAnimation(1, "wings-and-feet", true)
|
||||||
animationName: "eyeblink",
|
|
||||||
loop: true
|
|
||||||
)
|
|
||||||
controller.animationState.setAnimationByName(
|
|
||||||
trackIndex: 1,
|
|
||||||
animationName: "wings-and-feet",
|
|
||||||
loop: true
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
onAfterUpdateWorldTransforms: {
|
onAfterUpdateWorldTransforms: {
|
||||||
[weak self] controller in
|
[weak self] controller in
|
||||||
@ -98,7 +90,7 @@ final class PhysicsModel: ObservableObject {
|
|||||||
let dy = mousePosition.y - lastMousePosition.y
|
let dy = mousePosition.y - lastMousePosition.y
|
||||||
let positionX = controller.skeleton.x + Float(dx)
|
let positionX = controller.skeleton.x + Float(dx)
|
||||||
let positionY = controller.skeleton.y + Float(dy)
|
let positionY = controller.skeleton.y + Float(dy)
|
||||||
controller.skeleton.setPosition(x: positionX, y: positionY)
|
controller.skeleton.setPosition(positionX, positionY)
|
||||||
self.lastMousePosition = mousePosition
|
self.lastMousePosition = mousePosition
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import Spine
|
import SpineiOS
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct PlayPauseAnimation: View {
|
struct PlayPauseAnimation: View {
|
||||||
@ -35,11 +35,7 @@ struct PlayPauseAnimation: View {
|
|||||||
@StateObject
|
@StateObject
|
||||||
var controller = SpineController(
|
var controller = SpineController(
|
||||||
onInitialized: { controller in
|
onInitialized: { controller in
|
||||||
controller.animationState.setAnimationByName(
|
controller.animationState.setAnimation(0, "flying", true)
|
||||||
trackIndex: 0,
|
|
||||||
animationName: "flying",
|
|
||||||
loop: true
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import Spine
|
import SpineiOS
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SimpleAnimation: View {
|
struct SimpleAnimation: View {
|
||||||
@ -35,11 +35,7 @@ struct SimpleAnimation: View {
|
|||||||
@StateObject
|
@StateObject
|
||||||
var controller = SpineController(
|
var controller = SpineController(
|
||||||
onInitialized: { controller in
|
onInitialized: { controller in
|
||||||
controller.animationState.setAnimationByName(
|
controller.animationState.setAnimation(0, "walk", true)
|
||||||
trackIndex: 0,
|
|
||||||
animationName: "walk",
|
|
||||||
loop: true
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
#import "SimpleAnimationViewController.h"
|
#import "SimpleAnimationViewController.h"
|
||||||
@import Spine;
|
@import SpineiOS;
|
||||||
|
|
||||||
@interface SimpleAnimationViewController ()
|
@interface SimpleAnimationViewController ()
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import Spine
|
import SpineiOS
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@main
|
@main
|
||||||
|
|||||||
@ -1,99 +0,0 @@
|
|||||||
/******************************************************************************
|
|
||||||
* 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 Foundation
|
|
||||||
import SpineSwift
|
|
||||||
|
|
||||||
public typealias AnimationStateListener = (_ type: EventType, _ entry: TrackEntry, _ event: Event?) -> Void
|
|
||||||
|
|
||||||
/// Wrapper class around ``AnimationState``. Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies
|
|
||||||
/// multiple animations on top of each other (layering).
|
|
||||||
///
|
|
||||||
/// See [Applying Animations](http://esotericsoftware.com/spine-applying-animations/) in the Spine Runtimes Guide.
|
|
||||||
@objc(SpineAnimationStateWrapper)
|
|
||||||
@objcMembers
|
|
||||||
public final class AnimationStateWrapper: NSObject {
|
|
||||||
|
|
||||||
public let animationState: AnimationState
|
|
||||||
public let aninationStateEvents: AnimationStateEvents
|
|
||||||
|
|
||||||
private var trackEntryListeners = [spine_track_entry: AnimationStateListener]()
|
|
||||||
|
|
||||||
private var stateListener: AnimationStateListener?
|
|
||||||
|
|
||||||
public init(animationState: AnimationState, aninationStateEvents: AnimationStateEvents) {
|
|
||||||
self.animationState = animationState
|
|
||||||
self.aninationStateEvents = aninationStateEvents
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The listener for events generated by the provided ``TrackEntry``, or nil.
|
|
||||||
///
|
|
||||||
/// A track entry returned from ``AnimationState/setAnimation(trackIndex:animation:loop:)`` is already the current animation
|
|
||||||
/// for the track, so the track entry listener will not be called for ``EventType`` `SPINE_EVENT_TYPE_START`.
|
|
||||||
public func setTrackEntryListener(entry: TrackEntry, listener: AnimationStateListener?) {
|
|
||||||
if let listener {
|
|
||||||
trackEntryListeners[entry.wrappee] = listener
|
|
||||||
} else {
|
|
||||||
trackEntryListeners.removeValue(forKey: entry.wrappee)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Increments each track entry ``TrackEntry/trackTime``, setting queued animations as current if needed.
|
|
||||||
public func update(delta: Float) {
|
|
||||||
animationState.update(delta: delta)
|
|
||||||
|
|
||||||
let numEvents = spine_animation_state_events_get_num_events(aninationStateEvents.wrappee)
|
|
||||||
for i in 0..<numEvents {
|
|
||||||
let type = aninationStateEvents.getEventType(index: i)
|
|
||||||
|
|
||||||
let entry = aninationStateEvents.getTrackEntry(index: i)
|
|
||||||
let event = aninationStateEvents.getEvent(index: i)
|
|
||||||
|
|
||||||
if let trackEntryListener = trackEntryListeners[entry.wrappee] {
|
|
||||||
trackEntryListener(type, entry, event)
|
|
||||||
}
|
|
||||||
if let stateListener {
|
|
||||||
stateListener(type, entry, event)
|
|
||||||
}
|
|
||||||
if type == SPINE_EVENT_TYPE_DISPOSE {
|
|
||||||
spine_animation_state_dispose_track_entry(animationState.wrappee, entry.wrappee)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aninationStateEvents.reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The listener for events generated for all tracks managed by the ``AnimationState``, or nil.
|
|
||||||
///
|
|
||||||
/// A track entry returned from ``AnimationState/setAnimation(trackIndex:animation:loop:)`` is already the current animation
|
|
||||||
/// for the track, so the track entry listener will not be called for ``EventType`` `SPINE_EVENT_TYPE_START`.
|
|
||||||
public func setStateListener(_ stateListener: AnimationStateListener?) {
|
|
||||||
self.stateListener = stateListener
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
import CoreGraphics
|
import CoreGraphics
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import SpineSwift
|
||||||
|
|
||||||
/// Base class for bounds providers. A bounds provider calculates the axis aligned bounding box
|
/// Base class for bounds providers. A bounds provider calculates the axis aligned bounding box
|
||||||
/// used to scale and fit a skeleton inside the bounds of a ``SpineUIView``.
|
/// used to scale and fit a skeleton inside the bounds of a ``SpineUIView``.
|
||||||
@ -102,23 +103,22 @@ public final class SkinAndAnimationBounds: NSObject, BoundsProvider {
|
|||||||
public func computeBounds(for drawable: SkeletonDrawableWrapper) -> CGRect {
|
public func computeBounds(for drawable: SkeletonDrawableWrapper) -> CGRect {
|
||||||
let data = drawable.skeletonData
|
let data = drawable.skeletonData
|
||||||
let oldSkin: Skin? = drawable.skeleton.skin
|
let oldSkin: Skin? = drawable.skeleton.skin
|
||||||
let customSkin = Skin.create(name: "custom-skin")
|
let customSkin = Skin("custom-skin") // Use constructor directly
|
||||||
for skinName in skins {
|
for skinName in skins {
|
||||||
let skin = data.findSkin(name: skinName)
|
if let skin = data.findSkin(skinName) {
|
||||||
if let skin = data.findSkin(name: skinName) {
|
customSkin.addSkin(skin)
|
||||||
customSkin.addSkin(other: skin)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drawable.skeleton.skin = customSkin
|
drawable.skeleton.setSkin2(customSkin)
|
||||||
drawable.skeleton.setToSetupPose()
|
drawable.skeleton.setupPose()
|
||||||
|
|
||||||
let animation = animation.flatMap { data.findAnimation(name: $0) }
|
let animation = animation.flatMap { data.findAnimation($0) }
|
||||||
var minX = Float.Magnitude.greatestFiniteMagnitude
|
var minX = Float.Magnitude.greatestFiniteMagnitude
|
||||||
var minY = Float.Magnitude.greatestFiniteMagnitude
|
var minY = Float.Magnitude.greatestFiniteMagnitude
|
||||||
var maxX = -Float.Magnitude.greatestFiniteMagnitude
|
var maxX = -Float.Magnitude.greatestFiniteMagnitude
|
||||||
var maxY = -Float.Magnitude.greatestFiniteMagnitude
|
var maxY = -Float.Magnitude.greatestFiniteMagnitude
|
||||||
if let animation {
|
if let animation {
|
||||||
drawable.animationState.setAnimation(trackIndex: 0, animation: animation, loop: false)
|
_ = drawable.animationState.setAnimation2(0, animation, false)
|
||||||
let steps = Int(max(Double(animation.duration) / stepTime, 1.0))
|
let steps = Int(max(Double(animation.duration) / stepTime, 1.0))
|
||||||
for i in 0..<steps {
|
for i in 0..<steps {
|
||||||
drawable.update(delta: i > 0 ? Float(stepTime) : 0.0)
|
drawable.update(delta: i > 0 ? Float(stepTime) : 0.0)
|
||||||
@ -135,13 +135,13 @@ public final class SkinAndAnimationBounds: NSObject, BoundsProvider {
|
|||||||
maxX = minX + bounds.width
|
maxX = minX + bounds.width
|
||||||
maxY = minY + bounds.height
|
maxY = minY + bounds.height
|
||||||
}
|
}
|
||||||
drawable.skeleton.setSkinByName(skinName: "default")
|
drawable.skeleton.setSkin("default")
|
||||||
drawable.animationState.clearTracks()
|
drawable.animationState.clearTracks()
|
||||||
|
|
||||||
if let oldSkin {
|
if let oldSkin {
|
||||||
drawable.skeleton.skin = oldSkin
|
drawable.skeleton.setSkin2(oldSkin)
|
||||||
}
|
}
|
||||||
drawable.skeleton.setToSetupPose()
|
drawable.skeleton.setupPose()
|
||||||
drawable.update(delta: 0)
|
drawable.update(delta: 0)
|
||||||
customSkin.dispose()
|
customSkin.dispose()
|
||||||
return CGRectMake(CGFloat(minX), CGFloat(minY), CGFloat(maxX - minX), CGFloat(maxY - minY))
|
return CGRectMake(CGFloat(minX), CGFloat(minY), CGFloat(maxX - minX), CGFloat(maxY - minY))
|
||||||
@ -150,7 +150,7 @@ public final class SkinAndAnimationBounds: NSObject, BoundsProvider {
|
|||||||
|
|
||||||
/// How a view should be inscribed into another view.
|
/// How a view should be inscribed into another view.
|
||||||
@objc
|
@objc
|
||||||
public enum ContentMode: Int {
|
public enum SpineContentMode: Int {
|
||||||
case fit
|
case fit
|
||||||
/// As large as possible while still containing the source view entirely within the target view.
|
/// As large as possible while still containing the source view entirely within the target view.
|
||||||
case fill/// Fill the target view by distorting the source's aspect ratio.
|
case fill/// Fill the target view by distorting the source's aspect ratio.
|
||||||
@ -158,7 +158,7 @@ public enum ContentMode: Int {
|
|||||||
|
|
||||||
/// How a view should aligned withing another view.
|
/// How a view should aligned withing another view.
|
||||||
@objc
|
@objc
|
||||||
public enum Alignment: Int {
|
public enum SpineAlignment: Int {
|
||||||
case topLeft
|
case topLeft
|
||||||
case topCenter
|
case topCenter
|
||||||
case topRight
|
case topRight
|
||||||
|
|||||||
@ -28,6 +28,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import SpineSwift
|
||||||
import SpineShadersStructs
|
import SpineShadersStructs
|
||||||
import simd
|
import simd
|
||||||
|
|
||||||
@ -35,19 +36,26 @@ extension RenderCommand {
|
|||||||
func getVertices() -> [SpineVertex] {
|
func getVertices() -> [SpineVertex] {
|
||||||
var vertices = [SpineVertex]()
|
var vertices = [SpineVertex]()
|
||||||
|
|
||||||
let indices = indices
|
let numVerts = Int(numVertices)
|
||||||
let numVertices = numVertices
|
let numInds = Int(numIndices)
|
||||||
let positions = positions(numVertices: numVertices)
|
guard let indicesPtr = indices,
|
||||||
let uvs = uvs(numVertices: numVertices)
|
let positionsPtr = positions,
|
||||||
let colors = colors(numVertices: numVertices)
|
let uvsPtr = uvs,
|
||||||
vertices.reserveCapacity(indices.count)
|
let colorsPtr = colors else {
|
||||||
for i in 0..<indices.count {
|
return vertices
|
||||||
let index = Int(indices[i])
|
}
|
||||||
|
let indicesArray = Array(UnsafeBufferPointer(start: indicesPtr, count: numInds))
|
||||||
|
let positionsArray = Array(UnsafeBufferPointer(start: positionsPtr, count: numVerts * 2))
|
||||||
|
let uvsArray = Array(UnsafeBufferPointer(start: uvsPtr, count: numVerts * 2))
|
||||||
|
let colorsArray = Array(UnsafeBufferPointer(start: colorsPtr, count: numVerts))
|
||||||
|
vertices.reserveCapacity(numInds)
|
||||||
|
for i in 0..<numInds {
|
||||||
|
let index = Int(indicesArray[i])
|
||||||
let xIndex = 2 * index
|
let xIndex = 2 * index
|
||||||
let yIndex = xIndex + 1
|
let yIndex = xIndex + 1
|
||||||
let position = SIMD2<Float>(positions[xIndex], positions[yIndex])
|
let position = SIMD2<Float>(positionsArray[xIndex], positionsArray[yIndex])
|
||||||
let uv = SIMD2<Float>(uvs[xIndex], uvs[yIndex])
|
let uv = SIMD2<Float>(uvsArray[xIndex], uvsArray[yIndex])
|
||||||
let color = extractRGBA(from: colors[index])
|
let color = extractRGBA(from: colorsArray[index])
|
||||||
let vertex = SpineVertex(
|
let vertex = SpineVertex(
|
||||||
position: position,
|
position: position,
|
||||||
color: color,
|
color: color,
|
||||||
@ -59,10 +67,7 @@ extension RenderCommand {
|
|||||||
return vertices
|
return vertices
|
||||||
}
|
}
|
||||||
|
|
||||||
private func extractRGBA(from color: Int32) -> SIMD4<Float> {
|
private func extractRGBA(from color: UInt32) -> SIMD4<Float> {
|
||||||
guard color != -1 else {
|
|
||||||
return SIMD4<Float>(1.0, 1.0, 1.0, 1.0)
|
|
||||||
}
|
|
||||||
let alpha = Float((color >> 24) & 0xFF) / 255.0
|
let alpha = Float((color >> 24) & 0xFF) / 255.0
|
||||||
let red = Float((color >> 16) & 0xFF) / 255.0
|
let red = Float((color >> 16) & 0xFF) / 255.0
|
||||||
let green = Float((color >> 8) & 0xFF) / 255.0
|
let green = Float((color >> 8) & 0xFF) / 255.0
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import Foundation
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import SpineSwift
|
import SpineSwift
|
||||||
import SpineC
|
import SpineC
|
||||||
|
import SpineShadersStructs
|
||||||
|
|
||||||
protocol SpineRendererDelegate: AnyObject {
|
protocol SpineRendererDelegate: AnyObject {
|
||||||
func spineRendererWillUpdate(_ spineRenderer: SpineRenderer)
|
func spineRendererWillUpdate(_ spineRenderer: SpineRenderer)
|
||||||
@ -108,11 +109,11 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let blendModes = [
|
let blendModes: [BlendMode] = [
|
||||||
SPINE_BLEND_MODE_NORMAL,
|
.normal,
|
||||||
SPINE_BLEND_MODE_ADDITIVE,
|
.additive,
|
||||||
SPINE_BLEND_MODE_MULTIPLY,
|
.multiply,
|
||||||
SPINE_BLEND_MODE_SCREEN,
|
.screen,
|
||||||
]
|
]
|
||||||
for blendMode in blendModes {
|
for blendMode in blendModes {
|
||||||
let descriptor = MTLRenderPipelineDescriptor()
|
let descriptor = MTLRenderPipelineDescriptor()
|
||||||
@ -185,7 +186,7 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setTransform(bounds: CGRect, mode: Spine.ContentMode, alignment: Spine.Alignment) {
|
private func setTransform(bounds: CGRect, mode: SpineContentMode, alignment: SpineAlignment) {
|
||||||
let x = -bounds.minX - bounds.width / 2.0
|
let x = -bounds.minX - bounds.width / 2.0
|
||||||
let y = -bounds.minY - bounds.height / 2.0
|
let y = -bounds.minY - bounds.height / 2.0
|
||||||
|
|
||||||
@ -290,7 +291,8 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
|||||||
|
|
||||||
let vertices = allVertices[index]
|
let vertices = allVertices[index]
|
||||||
|
|
||||||
let textureIndex = Int(renderCommand.atlasPage)
|
// When using spine_atlas_load, texture is actually the atlas page index cast as a pointer
|
||||||
|
let textureIndex = Int(bitPattern: renderCommand.texture)
|
||||||
if textures.indices.contains(textureIndex) {
|
if textures.indices.contains(textureIndex) {
|
||||||
renderEncoder.setFragmentTexture(
|
renderEncoder.setFragmentTexture(
|
||||||
textures[textureIndex],
|
textures[textureIndex],
|
||||||
@ -321,63 +323,55 @@ internal final class SpineRenderer: NSObject, MTKViewDelegate {
|
|||||||
extension BlendMode {
|
extension BlendMode {
|
||||||
fileprivate func sourceRGBBlendFactor(premultipliedAlpha: Bool) -> MTLBlendFactor {
|
fileprivate func sourceRGBBlendFactor(premultipliedAlpha: Bool) -> MTLBlendFactor {
|
||||||
switch self {
|
switch self {
|
||||||
case SPINE_BLEND_MODE_NORMAL:
|
case .normal:
|
||||||
return premultipliedAlpha ? .one : .sourceAlpha
|
return premultipliedAlpha ? .one : .sourceAlpha
|
||||||
case SPINE_BLEND_MODE_ADDITIVE:
|
case .additive:
|
||||||
// additvie only needs sourceAlpha multiply if it is not pma
|
// additvie only needs sourceAlpha multiply if it is not pma
|
||||||
return premultipliedAlpha ? .one : .sourceAlpha
|
return premultipliedAlpha ? .one : .sourceAlpha
|
||||||
case SPINE_BLEND_MODE_MULTIPLY:
|
case .multiply:
|
||||||
return .destinationColor
|
return .destinationColor
|
||||||
case SPINE_BLEND_MODE_SCREEN:
|
case .screen:
|
||||||
return .one
|
return .one
|
||||||
default:
|
|
||||||
return .one // Should never be called
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate var sourceAlphaBlendFactor: MTLBlendFactor {
|
fileprivate var sourceAlphaBlendFactor: MTLBlendFactor {
|
||||||
// pma and non-pma has no-relation ship with alpha blending
|
// pma and non-pma has no-relation ship with alpha blending
|
||||||
switch self {
|
switch self {
|
||||||
case SPINE_BLEND_MODE_NORMAL:
|
case .normal:
|
||||||
return .one
|
return .one
|
||||||
case SPINE_BLEND_MODE_ADDITIVE:
|
case .additive:
|
||||||
return .one
|
return .one
|
||||||
case SPINE_BLEND_MODE_MULTIPLY:
|
case .multiply:
|
||||||
return .oneMinusSourceAlpha
|
return .oneMinusSourceAlpha
|
||||||
case SPINE_BLEND_MODE_SCREEN:
|
case .screen:
|
||||||
return .oneMinusSourceColor
|
return .oneMinusSourceColor
|
||||||
default:
|
|
||||||
return .one // Should never be called
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate var destinationRGBBlendFactor: MTLBlendFactor {
|
fileprivate var destinationRGBBlendFactor: MTLBlendFactor {
|
||||||
switch self {
|
switch self {
|
||||||
case SPINE_BLEND_MODE_NORMAL:
|
case .normal:
|
||||||
return .oneMinusSourceAlpha
|
return .oneMinusSourceAlpha
|
||||||
case SPINE_BLEND_MODE_ADDITIVE:
|
case .additive:
|
||||||
return .one
|
return .one
|
||||||
case SPINE_BLEND_MODE_MULTIPLY:
|
case .multiply:
|
||||||
return .oneMinusSourceAlpha
|
return .oneMinusSourceAlpha
|
||||||
case SPINE_BLEND_MODE_SCREEN:
|
case .screen:
|
||||||
return .oneMinusSourceColor
|
return .oneMinusSourceColor
|
||||||
default:
|
|
||||||
return .one // Should never be called
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate var destinationAlphaBlendFactor: MTLBlendFactor {
|
fileprivate var destinationAlphaBlendFactor: MTLBlendFactor {
|
||||||
switch self {
|
switch self {
|
||||||
case SPINE_BLEND_MODE_NORMAL:
|
case .normal:
|
||||||
return .oneMinusSourceAlpha
|
return .oneMinusSourceAlpha
|
||||||
case SPINE_BLEND_MODE_ADDITIVE:
|
case .additive:
|
||||||
return .one
|
return .one
|
||||||
case SPINE_BLEND_MODE_MULTIPLY:
|
case .multiply:
|
||||||
return .oneMinusSourceAlpha
|
return .oneMinusSourceAlpha
|
||||||
case SPINE_BLEND_MODE_SCREEN:
|
case .screen:
|
||||||
return .oneMinusSourceColor
|
return .oneMinusSourceColor
|
||||||
default:
|
|
||||||
return .one // Should never be called
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,7 +62,6 @@ public final class SkeletonDrawableWrapper: NSObject {
|
|||||||
public let skeleton: Skeleton
|
public let skeleton: Skeleton
|
||||||
public let animationStateData: AnimationStateData
|
public let animationStateData: AnimationStateData
|
||||||
public let animationState: AnimationState
|
public let animationState: AnimationState
|
||||||
public let animationStateWrapper: AnimationStateWrapper
|
|
||||||
|
|
||||||
internal var disposed = false
|
internal var disposed = false
|
||||||
|
|
||||||
@ -121,30 +120,12 @@ public final class SkeletonDrawableWrapper: NSObject {
|
|||||||
self.atlasPages = atlasPages
|
self.atlasPages = atlasPages
|
||||||
self.skeletonData = skeletonData
|
self.skeletonData = skeletonData
|
||||||
|
|
||||||
guard let nativeSkeletonDrawable = spine_skeleton_drawable_create(skeletonData.wrappee) else {
|
skeletonDrawable = SkeletonDrawable(skeletonData: skeletonData)
|
||||||
throw SpineError("Could not load native skeleton drawable")
|
|
||||||
}
|
|
||||||
skeletonDrawable = SkeletonDrawable(nativeSkeletonDrawable)
|
|
||||||
|
|
||||||
guard let nativeSkeleton = spine_skeleton_drawable_get_skeleton(skeletonDrawable.wrappee) else {
|
skeleton = skeletonDrawable.skeleton
|
||||||
throw SpineError("Could not load native skeleton")
|
animationStateData = skeletonDrawable.animationStateData
|
||||||
}
|
animationState = skeletonDrawable.animationState
|
||||||
skeleton = Skeleton(nativeSkeleton)
|
skeleton.updateWorldTransform(Physics.none)
|
||||||
|
|
||||||
guard let nativeAnimationStateData = spine_skeleton_drawable_get_animation_state_data(skeletonDrawable.wrappee) else {
|
|
||||||
throw SpineError("Could not load native animation state data")
|
|
||||||
}
|
|
||||||
animationStateData = AnimationStateData(nativeAnimationStateData)
|
|
||||||
|
|
||||||
guard let nativeAnimationState = spine_skeleton_drawable_get_animation_state(skeletonDrawable.wrappee) else {
|
|
||||||
throw SpineError("Could not load native animation state")
|
|
||||||
}
|
|
||||||
animationState = AnimationState(nativeAnimationState)
|
|
||||||
animationStateWrapper = AnimationStateWrapper(
|
|
||||||
animationState: animationState,
|
|
||||||
aninationStateEvents: skeletonDrawable.animationStateEvents
|
|
||||||
)
|
|
||||||
skeleton.updateWorldTransform(physics: SPINE_PHYSICS_NONE)
|
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,11 +135,11 @@ public final class SkeletonDrawableWrapper: NSObject {
|
|||||||
public func update(delta: Float) {
|
public func update(delta: Float) {
|
||||||
if disposed { return }
|
if disposed { return }
|
||||||
|
|
||||||
animationStateWrapper.update(delta: delta)
|
animationState.update(delta)
|
||||||
animationState.apply(skeleton: skeleton)
|
_ = animationState.apply(skeleton)
|
||||||
|
|
||||||
skeleton.update(delta: delta)
|
skeleton.update(delta)
|
||||||
skeleton.updateWorldTransform(physics: SPINE_PHYSICS_UPDATE)
|
skeleton.updateWorldTransform(Physics.update)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func dispose() {
|
public func dispose() {
|
||||||
@ -168,6 +149,6 @@ public final class SkeletonDrawableWrapper: NSObject {
|
|||||||
atlas.dispose()
|
atlas.dispose()
|
||||||
skeletonData.dispose()
|
skeletonData.dispose()
|
||||||
|
|
||||||
skeletonDrawable.dispose()
|
// SkeletonDrawable disposal handled by ARC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import CoreGraphics
|
|||||||
import Foundation
|
import Foundation
|
||||||
import QuartzCore
|
import QuartzCore
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import SpineSwift
|
||||||
|
|
||||||
public typealias SpineControllerCallback = (_ controller: SpineController) -> Void
|
public typealias SpineControllerCallback = (_ controller: SpineController) -> Void
|
||||||
|
|
||||||
@ -136,10 +137,6 @@ public final class SpineController: NSObject, ObservableObject {
|
|||||||
drawable.animationState
|
drawable.animationState
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The ``AnimationStateWrapper`` used to hold ``AnimationState``, register ``AnimationStateListener`` and call ``AnimationStateWrapper/update(delta:)``
|
|
||||||
public var animationStateWrapper: AnimationStateWrapper {
|
|
||||||
drawable.animationStateWrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Transforms the coordinates given in the ``SpineUIView`` coordinate system in `position` to
|
/// Transforms the coordinates given in the ``SpineUIView`` coordinate system in `position` to
|
||||||
/// the skeleton coordinate system. See the `IKFollowing.swift` example how to use this
|
/// the skeleton coordinate system. See the `IKFollowing.swift` example how to use this
|
||||||
@ -239,6 +236,14 @@ extension SpineController: SpineRendererDataSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func renderCommands(_ spineRenderer: SpineRenderer) -> [RenderCommand] {
|
func renderCommands(_ spineRenderer: SpineRenderer) -> [RenderCommand] {
|
||||||
return drawable?.skeletonDrawable.render() ?? []
|
guard let drawable = drawable else { return [] }
|
||||||
|
|
||||||
|
var commands = [RenderCommand]()
|
||||||
|
var current = drawable.skeletonDrawable.render()
|
||||||
|
while let cmd = current {
|
||||||
|
commands.append(cmd)
|
||||||
|
current = cmd.next
|
||||||
|
}
|
||||||
|
return commands
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,24 +30,26 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SpineSwift
|
import SpineSwift
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
// Re-export version info from SpineSwift
|
||||||
public var version: String {
|
public var version: String {
|
||||||
return "\(majorVersion).\(minorVersion)"
|
return SpineSwift.version
|
||||||
}
|
}
|
||||||
|
|
||||||
public var majorVersion: Int {
|
public var majorVersion: Int {
|
||||||
return Int(spine_major_version())
|
return SpineSwift.majorVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
public var minorVersion: Int {
|
public var minorVersion: Int {
|
||||||
return Int(spine_minor_version())
|
return SpineSwift.minorVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ``Atlas`` data loaded from a `.atlas` file and its corresponding `.png` files. For each atlas image,
|
/// ``Atlas`` data loaded from a `.atlas` file and its corresponding `.png` files. For each atlas image,
|
||||||
/// a corresponding `UIImage` is constructed, which is used when rendering a skeleton
|
/// a corresponding `UIImage` is constructed, which is used when rendering a skeleton
|
||||||
/// that uses this atlas.
|
/// that uses this atlas.
|
||||||
///
|
///
|
||||||
/// Use the static methods ``Atlas/fromBundle(_:bundle:)``, ``Atlas/fromFile(_:)``, and ``Atlas/fromHttp(_:)`` to load an atlas. Call ``Atlas/dispose()`
|
/// Use the static methods ``Atlas/fromBundle(_:bundle:)``, ``Atlas/fromFile(_:)``, and ``Atlas/fromHttp(_:)`` to load an atlas. Call ``Atlas/dispose()``
|
||||||
/// when the atlas is no longer in use to release its resources.
|
/// when the atlas is no longer in use to release its resources.
|
||||||
extension Atlas {
|
extension Atlas {
|
||||||
|
|
||||||
@ -89,34 +91,26 @@ extension Atlas {
|
|||||||
guard let atlasData = String(data: data, encoding: .utf8) else {
|
guard let atlasData = String(data: data, encoding: .utf8) else {
|
||||||
throw SpineError("Couldn't read atlas bytes as utf8 string")
|
throw SpineError("Couldn't read atlas bytes as utf8 string")
|
||||||
}
|
}
|
||||||
let atlas = try atlasData.utf8CString.withUnsafeBufferPointer {
|
|
||||||
guard let atlas = spine_atlas_load($0.baseAddress) else {
|
// Use SpineSwift's loadAtlas function
|
||||||
throw SpineError("Couldn't load atlas data")
|
let atlas = try loadAtlas(atlasData)
|
||||||
}
|
|
||||||
return atlas
|
|
||||||
}
|
|
||||||
if let error = spine_atlas_get_error(atlas) {
|
|
||||||
let message = String(cString: error)
|
|
||||||
spine_atlas_dispose(atlas)
|
|
||||||
throw SpineError("Couldn't load atlas: \(message)")
|
|
||||||
}
|
|
||||||
|
|
||||||
var atlasPages = [UIImage]()
|
var atlasPages = [UIImage]()
|
||||||
let numImagePaths = spine_atlas_get_num_image_paths(atlas)
|
|
||||||
|
// Load images for each atlas page
|
||||||
for i in 0..<numImagePaths {
|
let pages = atlas.pages
|
||||||
guard let atlasPageFilePointer = spine_atlas_get_image_path(atlas, i) else {
|
for i in 0..<pages.count {
|
||||||
continue
|
guard let page = pages[i] else { continue }
|
||||||
}
|
let imagePath = page.texturePath
|
||||||
let atlasPageFile = String(cString: atlasPageFilePointer)
|
|
||||||
let imageData = try await loadFile(atlasPageFile)
|
let imageData = try await loadFile(imagePath)
|
||||||
guard let image = UIImage(data: imageData) else {
|
guard let image = UIImage(data: imageData) else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
atlasPages.append(image)
|
atlasPages.append(image)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (Atlas(atlas), atlasPages)
|
return (atlas, atlasPages)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +124,8 @@ extension SkeletonData {
|
|||||||
return try fromData(
|
return try fromData(
|
||||||
atlas: atlas,
|
atlas: atlas,
|
||||||
data: try await FileSource.bundle(fileName: skeletonFileName, bundle: bundle).load(),
|
data: try await FileSource.bundle(fileName: skeletonFileName, bundle: bundle).load(),
|
||||||
isJson: skeletonFileName.hasSuffix(".json")
|
isJson: skeletonFileName.hasSuffix(".json"),
|
||||||
|
path: skeletonFileName
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +136,8 @@ extension SkeletonData {
|
|||||||
return try fromData(
|
return try fromData(
|
||||||
atlas: atlas,
|
atlas: atlas,
|
||||||
data: try await FileSource.file(skeletonFile).load(),
|
data: try await FileSource.file(skeletonFile).load(),
|
||||||
isJson: skeletonFile.absoluteString.hasSuffix(".json")
|
isJson: skeletonFile.absoluteString.hasSuffix(".json"),
|
||||||
|
path: skeletonFile.absoluteString
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,141 +148,23 @@ extension SkeletonData {
|
|||||||
return try fromData(
|
return try fromData(
|
||||||
atlas: atlas,
|
atlas: atlas,
|
||||||
data: try await FileSource.http(skeletonURL).load(),
|
data: try await FileSource.http(skeletonURL).load(),
|
||||||
isJson: skeletonURL.absoluteString.hasSuffix(".json")
|
isJson: skeletonURL.absoluteString.hasSuffix(".json"),
|
||||||
|
path: skeletonURL.absoluteString
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads a ``SkeletonData`` from the ``binary`` skeleton `Data`, using the provided ``Atlas`` to resolve attachment images.
|
private static func fromData(atlas: Atlas, data: Data, isJson: Bool, path: String) throws -> SkeletonData {
|
||||||
///
|
|
||||||
/// Throws an `Error` in case the skeleton data could not be loaded.
|
|
||||||
public static func fromData(atlas: Atlas, data: Data) throws -> SkeletonData {
|
|
||||||
let result = try data.withUnsafeBytes {
|
|
||||||
try $0.withMemoryRebound(to: UInt8.self) { buffer in
|
|
||||||
guard let ptr = buffer.baseAddress else {
|
|
||||||
throw SpineError("Couldn't read atlas binary")
|
|
||||||
}
|
|
||||||
return spine_skeleton_data_load_binary(
|
|
||||||
atlas.wrappee,
|
|
||||||
ptr,
|
|
||||||
Int32(buffer.count)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
guard let result else {
|
|
||||||
throw SpineError("Couldn't load skeleton data")
|
|
||||||
}
|
|
||||||
defer {
|
|
||||||
spine_skeleton_data_result_dispose(result)
|
|
||||||
}
|
|
||||||
if let error = spine_skeleton_data_result_get_error(result) {
|
|
||||||
let message = String(cString: error)
|
|
||||||
throw SpineError("Couldn't load skeleton data: \(message)")
|
|
||||||
}
|
|
||||||
guard let data = spine_skeleton_data_result_get_data(result) else {
|
|
||||||
throw SpineError("Couldn't load skeleton data from result")
|
|
||||||
}
|
|
||||||
return SkeletonData(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Loads a ``SkeletonData`` from the `json` string, using the provided ``Atlas`` to resolve attachment
|
|
||||||
/// images.
|
|
||||||
///
|
|
||||||
/// Throws an `Error` in case the atlas could not be loaded.
|
|
||||||
public static func fromJson(atlas: Atlas, json: String) throws -> SkeletonData {
|
|
||||||
let result = try json.utf8CString.withUnsafeBufferPointer { buffer in
|
|
||||||
guard
|
|
||||||
let basePtr = buffer.baseAddress,
|
|
||||||
let result = spine_skeleton_data_load_json(atlas.wrappee, basePtr)
|
|
||||||
else {
|
|
||||||
throw SpineError("Couldn't load skeleton data json")
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
defer {
|
|
||||||
spine_skeleton_data_result_dispose(result)
|
|
||||||
}
|
|
||||||
if let error = spine_skeleton_data_result_get_error(result) {
|
|
||||||
let message = String(cString: error)
|
|
||||||
throw SpineError("Couldn't load skeleton data: \(message)")
|
|
||||||
}
|
|
||||||
guard let data = spine_skeleton_data_result_get_data(result) else {
|
|
||||||
throw SpineError("Couldn't load skeleton data from result")
|
|
||||||
}
|
|
||||||
return SkeletonData(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func fromData(atlas: Atlas, data: Data, isJson: Bool) throws -> SkeletonData {
|
|
||||||
if isJson {
|
if isJson {
|
||||||
guard let json = String(data: data, encoding: .utf8) else {
|
guard let json = String(data: data, encoding: .utf8) else {
|
||||||
throw SpineError("Couldn't read skeleton data json string")
|
throw SpineError("Couldn't read skeleton data json string")
|
||||||
}
|
}
|
||||||
return try fromJson(atlas: atlas, json: json)
|
return try loadSkeletonDataJson(atlas: atlas, jsonData: json, path: path)
|
||||||
} else {
|
} else {
|
||||||
return try fromData(atlas: atlas, data: data)
|
return try loadSkeletonDataBinary(atlas: atlas, binaryData: data, path: path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SkeletonDrawable {
|
|
||||||
|
|
||||||
func render() -> [RenderCommand] {
|
|
||||||
var commands = [RenderCommand]()
|
|
||||||
if disposed { return commands }
|
|
||||||
|
|
||||||
var nativeCmd = spine_skeleton_drawable_render(wrappee)
|
|
||||||
repeat {
|
|
||||||
if let ncmd = nativeCmd {
|
|
||||||
commands.append(RenderCommand(ncmd))
|
|
||||||
nativeCmd = spine_render_command_get_next(ncmd)
|
|
||||||
} else {
|
|
||||||
nativeCmd = nil
|
|
||||||
}
|
|
||||||
} while nativeCmd != nil
|
|
||||||
|
|
||||||
return commands
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension RenderCommand {
|
|
||||||
|
|
||||||
var numVertices: Int {
|
|
||||||
Int(spine_render_command_get_num_vertices(wrappee))
|
|
||||||
}
|
|
||||||
|
|
||||||
func positions(numVertices: Int) -> [Float] {
|
|
||||||
let num = numVertices * 2
|
|
||||||
let ptr = spine_render_command_get_positions(wrappee)
|
|
||||||
guard let validPtr = ptr else { return [] }
|
|
||||||
let buffer = UnsafeBufferPointer(start: validPtr, count: num)
|
|
||||||
return Array(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func uvs(numVertices: Int) -> [Float] {
|
|
||||||
let num = numVertices * 2
|
|
||||||
let ptr = spine_render_command_get_uvs(wrappee)
|
|
||||||
guard let validPtr = ptr else { return [] }
|
|
||||||
let buffer = UnsafeBufferPointer(start: validPtr, count: num)
|
|
||||||
return Array(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func colors(numVertices: Int) -> [Int32] {
|
|
||||||
let num = numVertices
|
|
||||||
let ptr = spine_render_command_get_colors(wrappee)
|
|
||||||
guard let validPtr = ptr else { return [] }
|
|
||||||
let buffer = UnsafeBufferPointer(start: validPtr, count: num)
|
|
||||||
return Array(buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Skin {
|
|
||||||
|
|
||||||
/// Constructs a new empty ``Skin`` using the given `name`. Skins constructed this way must be manually disposed via the `dispose` method
|
|
||||||
/// if they are no longer used.
|
|
||||||
public static func create(name: String) -> Skin {
|
|
||||||
return Skin(spine_skin_create(name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper
|
// Helper
|
||||||
|
|
||||||
extension CGRect {
|
extension CGRect {
|
||||||
@ -380,26 +258,5 @@ internal enum FileSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct SpineError: Error, CustomStringConvertible {
|
// Re-export SpineError from SpineSwift
|
||||||
|
public typealias SpineError = SpineSwift.SpineError
|
||||||
public let description: String
|
|
||||||
|
|
||||||
internal init(_ description: String) {
|
|
||||||
self.description = description
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SkeletonBounds {
|
|
||||||
public static func create() -> SkeletonBounds {
|
|
||||||
return SkeletonBounds(spine_skeleton_bounds_create())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc extension Atlas {
|
|
||||||
|
|
||||||
public var imagePathCount: Int32 {
|
|
||||||
spine_atlas_get_num_image_paths(wrappee)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
import MetalKit
|
import MetalKit
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import SpineSwift
|
||||||
|
|
||||||
/// A ``UIView`` to display a Spine skeleton. The skeleton can be loaded from a bundle, local files, http, or a pre-loaded ``SkeletonDrawableWrapper``.
|
/// A ``UIView`` to display a Spine skeleton. The skeleton can be loaded from a bundle, local files, http, or a pre-loaded ``SkeletonDrawableWrapper``.
|
||||||
///
|
///
|
||||||
@ -42,8 +43,8 @@ import UIKit
|
|||||||
public final class SpineUIView: MTKView {
|
public final class SpineUIView: MTKView {
|
||||||
|
|
||||||
let controller: SpineController
|
let controller: SpineController
|
||||||
let mode: Spine.ContentMode
|
let mode: SpineContentMode
|
||||||
let alignment: Spine.Alignment
|
let alignment: SpineAlignment
|
||||||
let boundsProvider: BoundsProvider
|
let boundsProvider: BoundsProvider
|
||||||
|
|
||||||
internal var computedBounds: CGRect = .zero
|
internal var computedBounds: CGRect = .zero
|
||||||
@ -51,8 +52,8 @@ public final class SpineUIView: MTKView {
|
|||||||
|
|
||||||
@objc internal init(
|
@objc internal init(
|
||||||
controller: SpineController = SpineController(),
|
controller: SpineController = SpineController(),
|
||||||
mode: Spine.ContentMode = .fit,
|
mode: SpineContentMode = .fit,
|
||||||
alignment: Spine.Alignment = .center,
|
alignment: SpineAlignment = .center,
|
||||||
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
||||||
backgroundColor: UIColor = .clear
|
backgroundColor: UIColor = .clear
|
||||||
) {
|
) {
|
||||||
@ -83,8 +84,8 @@ public final class SpineUIView: MTKView {
|
|||||||
public convenience init(
|
public convenience init(
|
||||||
from source: SpineViewSource,
|
from source: SpineViewSource,
|
||||||
controller: SpineController = SpineController(),
|
controller: SpineController = SpineController(),
|
||||||
mode: Spine.ContentMode = .fit,
|
mode: SpineContentMode = .fit,
|
||||||
alignment: Spine.Alignment = .center,
|
alignment: SpineAlignment = .center,
|
||||||
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
||||||
backgroundColor: UIColor = .clear
|
backgroundColor: UIColor = .clear
|
||||||
) {
|
) {
|
||||||
@ -120,8 +121,8 @@ public final class SpineUIView: MTKView {
|
|||||||
skeletonFileName: String,
|
skeletonFileName: String,
|
||||||
bundle: Bundle = .main,
|
bundle: Bundle = .main,
|
||||||
controller: SpineController = SpineController(),
|
controller: SpineController = SpineController(),
|
||||||
mode: Spine.ContentMode = .fit,
|
mode: SpineContentMode = .fit,
|
||||||
alignment: Spine.Alignment = .center,
|
alignment: SpineAlignment = .center,
|
||||||
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
||||||
backgroundColor: UIColor = .clear
|
backgroundColor: UIColor = .clear
|
||||||
) {
|
) {
|
||||||
@ -149,8 +150,8 @@ public final class SpineUIView: MTKView {
|
|||||||
atlasFile: URL,
|
atlasFile: URL,
|
||||||
skeletonFile: URL,
|
skeletonFile: URL,
|
||||||
controller: SpineController = SpineController(),
|
controller: SpineController = SpineController(),
|
||||||
mode: Spine.ContentMode = .fit,
|
mode: SpineContentMode = .fit,
|
||||||
alignment: Spine.Alignment = .center,
|
alignment: SpineAlignment = .center,
|
||||||
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
||||||
backgroundColor: UIColor = .clear
|
backgroundColor: UIColor = .clear
|
||||||
) {
|
) {
|
||||||
@ -178,8 +179,8 @@ public final class SpineUIView: MTKView {
|
|||||||
atlasURL: URL,
|
atlasURL: URL,
|
||||||
skeletonURL: URL,
|
skeletonURL: URL,
|
||||||
controller: SpineController = SpineController(),
|
controller: SpineController = SpineController(),
|
||||||
mode: Spine.ContentMode = .fit,
|
mode: SpineContentMode = .fit,
|
||||||
alignment: Spine.Alignment = .center,
|
alignment: SpineAlignment = .center,
|
||||||
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
||||||
backgroundColor: UIColor = .clear
|
backgroundColor: UIColor = .clear
|
||||||
) {
|
) {
|
||||||
@ -204,8 +205,8 @@ public final class SpineUIView: MTKView {
|
|||||||
@objc public convenience init(
|
@objc public convenience init(
|
||||||
drawable: SkeletonDrawableWrapper,
|
drawable: SkeletonDrawableWrapper,
|
||||||
controller: SpineController = SpineController(),
|
controller: SpineController = SpineController(),
|
||||||
mode: Spine.ContentMode = .fit,
|
mode: SpineContentMode = .fit,
|
||||||
alignment: Spine.Alignment = .center,
|
alignment: SpineAlignment = .center,
|
||||||
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
||||||
backgroundColor: UIColor = .clear
|
backgroundColor: UIColor = .clear
|
||||||
) {
|
) {
|
||||||
@ -251,7 +252,7 @@ extension SpineUIView {
|
|||||||
commandQueue: SpineObjects.shared.commandQueue,
|
commandQueue: SpineObjects.shared.commandQueue,
|
||||||
pixelFormat: colorPixelFormat,
|
pixelFormat: colorPixelFormat,
|
||||||
atlasPages: atlasPages,
|
atlasPages: atlasPages,
|
||||||
pma: controller.drawable.atlas.isPma
|
pma: false // TODO: Get PMA flag from atlas when API is available
|
||||||
)
|
)
|
||||||
renderer?.delegate = controller
|
renderer?.delegate = controller
|
||||||
renderer?.dataSource = controller
|
renderer?.dataSource = controller
|
||||||
|
|||||||
@ -43,8 +43,8 @@ public struct SpineView: UIViewRepresentable {
|
|||||||
|
|
||||||
private let source: SpineViewSource
|
private let source: SpineViewSource
|
||||||
private let controller: SpineController
|
private let controller: SpineController
|
||||||
private let mode: Spine.ContentMode
|
private let mode: SpineContentMode
|
||||||
private let alignment: Spine.Alignment
|
private let alignment: SpineAlignment
|
||||||
private let boundsProvider: BoundsProvider
|
private let boundsProvider: BoundsProvider
|
||||||
private let backgroundColor: UIColor // Not using `SwiftUI.Color`, as briging to `UIColor` prior iOS 14 might not always work.
|
private let backgroundColor: UIColor // Not using `SwiftUI.Color`, as briging to `UIColor` prior iOS 14 might not always work.
|
||||||
|
|
||||||
@ -71,8 +71,8 @@ public struct SpineView: UIViewRepresentable {
|
|||||||
public init(
|
public init(
|
||||||
from source: SpineViewSource,
|
from source: SpineViewSource,
|
||||||
controller: SpineController = SpineController(),
|
controller: SpineController = SpineController(),
|
||||||
mode: Spine.ContentMode = .fit,
|
mode: SpineContentMode = .fit,
|
||||||
alignment: Spine.Alignment = .center,
|
alignment: SpineAlignment = .center,
|
||||||
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
boundsProvider: BoundsProvider = SetupPoseBounds(),
|
||||||
backgroundColor: UIColor = .clear,
|
backgroundColor: UIColor = .clear,
|
||||||
isRendering: Binding<Bool?> = .constant(nil)
|
isRendering: Binding<Bool?> = .constant(nil)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user