IKFollowing.swift 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. import SwiftUI
  2. import Spine
  3. struct IKFollowing: View {
  4. @StateObject
  5. var model = IKFollowingModel()
  6. var body: some View {
  7. SpineView(
  8. from: .bundle(atlasFileName: "spineboy-pma.atlas", skeletonFileName: "spineboy-pro.skel"),
  9. controller: model.controller,
  10. alignment: .centerLeft
  11. )
  12. .gesture(
  13. DragGesture(minimumDistance: 0)
  14. .onChanged { gesture in
  15. model.crossHairPosition = model.controller.toSkeletonCoordinates(
  16. position: gesture.location
  17. )
  18. }
  19. )
  20. .navigationTitle("IK Following")
  21. .navigationBarTitleDisplayMode(.inline)
  22. }
  23. }
  24. #Preview {
  25. if #available(iOS 15.0, *) {
  26. IKFollowing()
  27. .previewInterfaceOrientation(.landscapeLeft)
  28. } else {
  29. IKFollowing()
  30. }
  31. }
  32. final class IKFollowingModel: ObservableObject {
  33. @Published
  34. var controller: SpineController!
  35. @Published
  36. var crossHairPosition: CGPoint?
  37. init() {
  38. controller = SpineController(
  39. onInitialized: { controller in
  40. controller.animationState.setAnimationByName(
  41. trackIndex: 0,
  42. animationName: "walk",
  43. loop: true
  44. )
  45. controller.animationState.setAnimationByName(
  46. trackIndex: 1,
  47. animationName: "aim",
  48. loop: true
  49. )
  50. },
  51. onAfterUpdateWorldTransforms: {
  52. [weak self] controller in guard let self else { return }
  53. guard let worldPosition = self.crossHairPosition else {
  54. return
  55. }
  56. let bone = controller.skeleton.findBone(boneName: "crosshair")!
  57. if let parent = bone.parent {
  58. let position = parent.worldToLocal(worldX: Float(worldPosition.x), worldY: Float(worldPosition.y))
  59. bone.x = position.x
  60. bone.y = position.y
  61. }
  62. }
  63. )
  64. }
  65. }