1
votes

I'm looking for a way to animate sprite sheets in SceneKit. After some research I found the Core Animation way to do this, which would be perfect as I need as much control over the animation as possible.

I've prepared a simple plane mesh, with its UVs limited to one spritesheet frame's size. That way I'd only need to animate the UVs horizontally. I've found that the relevant element of the material's contentsTransform matrix would be m14 (the one responsible for horizontal translation).

However, at runtime, SceneKit spits back the following error:

[SCNKit ERROR] geometry?.firstMaterial?.diffuse.contentsTransform.m14 is not an animatable path

This is the relevant code:

let spritesheetAnimation = CABasicAnimation(keyPath: "geometry?.firstMaterial?.diffuse.contentsTransform.m14")
spritesheetAnimation.fromValue = 1 - 1 / spritesheetFrameCount
spritesheetAnimation.toValue = 0
spritesheetAnimation.byValue = 1 / spritesheetFrameCount
spritesheetAnimation.duration = 1.0
spritesheetAnimation.repeatCount = Float.infinity

node.geometry?.insertMaterial(material, atIndex: 0)
node.addAnimation(spritesheetAnimation, forKey: "SpritesheetAnimation")

Anyone having experience with this? I don't know if there's an easier way to do this, or if I'm looking at it the wrong way or anything. So, any pointers would be appreciated!

3

3 Answers

3
votes

Two problems here.

  1. CoreAnimation extends animatable key paths into the data structures it can animate — that is, instead of just animating between one complete CATransform3D and another, it lets you animate a key path that describes some member or attribute of the transform (like transform.translation.x).

    SceneKit does not do this — you can only animate complete structure values. That is, you can only animate contentsTransform, not contentsTransform.something, and to do so you need to provide a complete SCNMatrix4 as the toValue / fromValue of your animation.

  2. KVC key paths don't have anything to do with Swift optionals, so geometry?.firstMaterial?.diffuse.contentsTransform is not a legal key path. Lose the question marks and you should be okay.

So, your code should look something like this (untested):

let spritesheetAnimation = CABasicAnimation(keyPath: "geometry.firstMaterial.diffuse.contentsTransform")
let translation = SCNMatrix4MakeTranslation(1 - 1 / spritesheetFrameCount, 0, 0)
spritesheetAnimation.fromValue = NSValue(SCNMatrix4: translation)
spritesheetAnimation.toValue = NSValue(SCNMatrix4: SCNMatrix4Identity)
spritesheetAnimation.duration = 1.0
spritesheetAnimation.repeatCount = .infinity

Note you need to wrap SCNMatrix4 in NSValue to pass to fromValue and toValue, because Swift can't automatically convert it like it does for scalar numeric types.

Also note you can use type inference for Float.infinity if you like.

0
votes

You might try contentsTransform.translation.x—Core Animation is pretty smart about transforms when working with layers, so SceneKit might be able to handle that as well.

0
votes

this should work:

  • remove "?" from the keypath
  • to translate horizontally, target m41 (not m14)

let spritesheetAnimation = CABasicAnimation(keyPath: "geometry.firstMaterial.diffuse.contentsTransform.m41")

[...]

SceneKit doesn't decompose matrices into components such as "translation/rotation/scale" so you can't use a paths like "contentsTransform.translation.x". But you can still target matrix components like "contentsTransform.m41" in your explicit animation keypaths.