iosrealitykit

How to bind the target entity for a custom action animation in RealityKit


I started with this accepted answer: https://stackoverflow.com/a/79718008/24255007

However, there's an issue with the code:

    CallbackActionHandler.register { event in
      return CallbackActionHandler(action: event.action, entity: event.targetEntity)
    }

The event.targetEntity is always nil.

Then I did a few tests, and found that if I set AnimatableData to Transform, and bind the transform, targetEntity is set:

    struct CallbackAction: EntityAction {
      // <---- THIS HERE 
      var animatedValueType: (any AnimatableData.Type)? = Transform.self

      let callback: (Entity?) -> Void
      init(callback: @escaping (Entity?) -> Void) {
        self.callback = callback
      }
      
    }


    let callbackAnimation = try! AnimationResource.makeActionAnimation(
        for: callbackAction, 
        bindTarget: .transform) // <--- AND HERE

The above code works! However, since it's bound to transform, if I run another transform-related action, I would have to use group to group them, making it not flexible to use. Also semantically, callback shouldn't be a "transform".

Most likely I have to do something like:

    struct CallbackValue: AnimatableData {}

    //...
      var animatedValueType: (any AnimatableData.Type)? = CallbackValue.self

But I am not sure how to set the bindTarget. There's absolutely ZERO documentation by apple, so I had to try things out. For example:

    let callbackAnimation = try! AnimationResource.makeActionAnimation(
        for: callbackAction, 
        bindTarget: .parameter("_internalCallback"))

But it didn't work.

Besides .paramter, there's also an .internal and .path value to the bindTarget argument, but it's hard to tell how to use them.


Solution

  • I changed the code in the accepted answer and got it to work with the following code.

    Note that the action no longer takes an optional entity and that the handler no longer takes any parameters with the action and entity being read on animation end

    public struct CallbackAction: EntityAction {
        public var animatedValueType: (any AnimatableData.Type)? = nil
        
        let callback: (Entity) -> Void
        public init(callback: @escaping (Entity) -> Void) {
            CallbackAction.registerAction()
            
            self.callback = callback
        }
    }
    
    // Init no longer takes any parameters and the action is read on animation end
    struct CallbackActionHandler: @preconcurrency ActionHandlerProtocol {
        typealias ActionType = CallbackAction
        init() { }
        
        @MainActor
        public func actionEnded(event: EventType) {
            let action = event.action
            guard let target = event.playbackController.entity else { return }
            action.callback(target)
        }
    }
        
    

    Registering

        CallbackActionHandler.register { _ in
          return CallbackActionHandler()
        }
    

    Using

        let callbackAction = CallbackAction { entity in
            print("callback by \(entity.name)")
        }
        // Note: Duration <= 0 would result in a near zero duration
        let callBackAnim = try! AnimationResource.makeActionAnimation(for: callbackAction, duration: 0)
        entity.playAnimation(callBackAnim)