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.
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)