2D Animated Sprites in Scenekit

So I'm trying to use some classic techniques in a Scenekit game.


I've got coin objects in my Scene. They are represented with SCNPlane geometries with a single texture material. The coins are using SCNBillboardConstraints to ensure they are always facing the camera. The problem is, I can't seem to find any way to animate the material textures on the SCNPlanes.


Apple's documentation states that this is possible using an SKScene as a material or a CALayer with animation. I've not been able to get either method to work. Even if I could get it to work, I would not want to create an SKScene for any and every 2D animation. This does not seem like an optimal method to animate textures for any 2D animations in Scenekit.


Does anyone have any suggestions for animating material textures quickly and easily? I'd love to be able to animate a material texture like it's done in Spritekit (animating using an array of SKTextures).


Thanks!

In what way did it not work? Can we see some code? According to previous forum posts by Apple employees, SKScene as a material is the most efficient way to do it, because everything is drawn in the same context.


Watch out for coordinate system differences. An SK3DNode placed in an SKScene has its origin (center of scene) at upper left (the origin in SpriteKit). Some repositioning was necessary. You can see an example of an SK3DNode (rendering the famous spinning spaceship), embedded in a SKScene that's then used as a material, at https://github.com/halmueller/ImmersiveInterfaces/tree/master/Nested%20Scenes


You mentioned using an array of textures to animate. Have you tried using an SCNAction to successively load each frame of your animation into the SCNPlane's material?

For anyone wondering, I was able to get a texture to animate using an SKScene as a material's diffuse contents. However this is not without it's issues. The SKScene texture was rotated by 180 degrees (Pi radians) and to get a single animated sprite out of it you must resize the scene to be the exact size of the sprite. It's all very strange.


Using a perfectly good CALayer with an animation on it however did not work for me. The sprite used on the material never animated, it was simply frozen on the first frame.


Neither way is ideal at this point for animating material textures but at least it is possible with SKScenes.

For simple texture movements use shaders. Here is an example from one of my node subClasses:


    self.geometry.firstMaterial.diffuse.wrapT = SCNWrapModeRepeat;
    self.geometry.firstMaterial.diffuse.wrapS = SCNWrapModeRepeat;
  
    NSString *shaderCode =
    @"uniform float speed;\n"
    @"#pragma body\n"
    @"_geometry.texcoords[0] += vec2(sin(_geometry.position.z*0.1 + u_time * 0.1) * 0.1, -1.0* 0.05 * u_time);\n";
  
    self.geometry.shaderModifiers = @{ SCNShaderModifierEntryPointGeometry : shaderCode};

Hi J-Crew,

Just had the same problem as you did a year ago but could solve it by using an animated CALayer as diffuse material of an SCNPlane. Since I use a framework (squall.no) that generates animated CALayers out of Adobe's After Effects, I needed a solution by using CALayers. Did you use CALayers when rendering textures in SKScenes? Couldn't find any information in how to do that.


Apparently, in Scenekit, the animated CALayer must not be added directly as a material to an SCNPlane. By using it directly I had the same behavior as you did (frozen on the first frame, or didn't show up at all). By adding it as a sublayer to a "root layer" (an emtpy CALayer) it worked:


let animatedLayer = animations[0] //list of animations (inherits CALayer)
animatedLayer.play()

let box = SCNPlane(width: animatedLayer.frame.size.width, height: animatedLayer.frame.size.height)
let boxNode = SCNNode(geometry: box)
boxNode.position = SCNVector3.init(x: 10, y: 15.0, z: 30.0)

let rootLayer = CALayer() //an empty layer
rootLayer.frame = CGRect.init(origin: animatedLayer.frame.origin, size: animatedLayer.frame.size)
rootLayer.addSublayer(animatedLayer) //animated layer is nested, otherwise animations don't work

box.firstMaterial?.diffuse.contents = rootLayer



I have no idea why it doesn't work if you use animatedLayer directly ("box.firstMaterial?.diffuse.contents = animatedLayer"), maybe someone has an explanation for me?

but it solved the problem. :-)

Reemmber to ensure that the SpriteKit scene you add as a material is set to:


spriteKitScene.isPaused = false


Otherwise your SpriteKit scene won't animate.

2D Animated Sprites in Scenekit
 
 
Q