How to control multiple skeletal animations independently with SceneKit? - ios

I am trying to control multiple skeletal animations at the same time with multiple inputs, each of which points to a different frame of an animation. Imagine the way you set weight for a morpher with setWeight(_:forTargetAt:), but with input as a frame which I want the animation to be in state of.
I achieved this for just one animation by doing something like
#IBOutlet weak var sceneView: SCNView!
var animationPlayer: SCNAnimationPlayer! = nil
override func viewDidLoad() {
super.viewDidLoad()
//add a node on the scene and give animationPlayer the node's animation player
animationPlayer.animation.usesSceneTimeBase = true
}
#IBAction func sliderChanged(_ sender: UISlider) {
sceneView.sceneTime = Double(sender.value) * animationPlayer.animation.duration
}
where a slider is put to change the sceneTime of sceneView. This could only work if the node has just one animation.
But what I want to achieve is controlling all the animations asynchronously but on one node. I cannot simply play with sceneTime because that will cause all the animations to be played on the same time base in sync.
Is there any way to play all the animations with different input time or any value that works as a pointer to a specific frame of the animation?
(e.g. at one point, first animation is playing the 5th frame while second is in state of 8th. At next frame of rendering, the first animation is playing 4th frame and the second is playing 12th frame.)
Thanks.

Related

Is it possible to pause a CAEmitterLayer?

I have a CAEmitterLayer instance that emits some CAEmitterCells. I'm wondering, is it possible to pause this layer such that no new CAEmitterCells are produced and the ones that have been produced remained fixed in their position on the screen? Then, when the CAEmitterLayer instance is "un-paused", the fixed CAEmitterCells on the screen start to move again.
Thanks for any help here.
EDIT
Setting:
emitterLayer.speed = 0.1
where emitterLayer is an instance of a subclass of CAEmitterLayer, just removes the layer completely from the view.
Setting:
emitterLayer.lifetime = 0.0
just stops any new emitterCells being produced but doesn't "freeze" the existing emitterCells at the current position.
You can set the lifetime property of the CAEmitterLayer to 0, which will cause newly emitted cells to not even be rendered, but will leave already existing cells unaffected. When you want to "un-pause" your emitter layer, you can simply reset lifetime to whatever it was before the pause.
To freeze the existing cells as well, you can set speed to 0 and also add a timeOffset.
extension CAEmitterLayer {
func pause() {
// Freeze existing cells
self.speed = 0
self.timeOffset = convertTime(CACurrentMediaTime(), from: self)
// Stop creating new cells
self.lifetime = 0
}
}
Then you can simply call it like emitterLayer.pause()

How to create a User Generated Animation in SceneKit with .dae (COLLADA) file from Blender

I’m trying to animate specific parts of a SCNScene object in SceneKit (in my case I want to animate fingers of a hand). I import the .dae (COLLADA) file easily from Blender with the respective bones to generate articulation on the model.
override func viewDidLoad() {
super.viewDidLoad()
var scene = SCNScene(named: "hand.dae")!
sceneView.scene = scene
sceneView.allowsCameraControl = true
sceneView.autoenablesDefaultLighting = true
sceneView.backgroundColor = UIColor.lightGrayColor()
}
My goal is to animate those bones on iOS with user generated values between 0 and 1. Imagine a UISlider where you scroll back and forth and see the specific finger move depending on the value of the slider.
This is needed animation screenshot
Image with the animation pretended
I’ve tried animate the model by calling an animation file like the Apple’s Fox example:
private var indexFingerAnimation: CAAnimation!
indexFingerAnimation = CAAnimation.animationWithSceneNamed(“move_index_finger.dae”)
indexFingerAnimation = false
indexFingerAnimation = 0.3
indexFingerAnimation = 0.3
indexFingerAnimation = Float.infinity
The problem is that’s a Global animation instead of just the index finger. Besides it’s always a ‘pre-defined’ animation instead of an animation controlled by user input. Ultimately I want to mix animations (e.g. move index finger and thumb at the same time revealing gestures)
Is this possible? I’m struggling because I can’t figure out how to manipulate specific parts of the mesh. I’m starting to study MetalKit but it’s not clear to me that’s the solution.
Any help would be really appreciated.
I have never tried two animations at the same time
but I can rotate SCNNode in dae file with two or more animate
You must set pivot point and group them together

Swift: Make control buttons not move with camera

I'm building a platform game, and I made the camera follow the player when he walks:
let cam = SKCameraNode()
override func didMoveToView(view: SKView) {
self.camera = cam
...
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
cam.position = Player.player.position
...
But, when the camera moves, the control buttons move as well
What should I do to keep the control buttons static?
See this note in the SKCameraNode docs:
A camera’s descendants are always rendered relative to the camera node’s origin and without applying the camera’s scaling or rotation to them. For example, if your game wants to display scores or other data floating above the gameplay, the nodes that render these elements should be descendants of the current camera node.
If you want HUD elements that stay fixed relative to the screen even as the camera moves/scales/rotates, make them child nodes of the camera.
By the way, you don't need to change the camera's position on every update(). Instead, just constrain the camera's position to match that of the player:
let constraint = SKConstraint.distance(SKRange(constantValue: 0), toNode: player)
camera.constraints = [ constraint ]
Then, SpriteKit will automatically keep the camera centered on the player without any per-frame work from you. You can even add more than one constraint — say, to follow the player but keep the camera from getting too close to the edge of the world (and showing empty space).
Add the buttons as child to the camera, like cam.addchild(yourButton)
From rickster's answer I made these constraints where the camera only moves horizontally, even if the player jumps. The order in which they are added is important. In case somebody else find them useful:
Swift 4.2
let camera = SKCameraNode()
scene.addChild(camera)
camera.constraints = [SKConstraint.distance(SKRange(upperLimit: 200), to: player),
SKConstraint.positionY(SKRange(constantValue: 0))]

How to detect view collision in a breakout app?

So I am working on a Breakout app in swift. I currently have a ball, which is a UIView with a cornerRadius = 20.0 to emulate a ball. I also have a paddle, which is another UIView with a smaller cornerRadius = 5.0. I have programmatically made nine red views which are each 50x50 units large. I have collision and motion mechanics for my ball, paddle, and block elements.`var dynamicAnimator: UIDynamicAnimator!
var pushBehavior: UIPushBehavior!
var collisionBehavior: UICollisionBehavior!
var ballDynamicBehavior: UIDynamicItemBehavior!
var paddleDynamicBehavior: UIDynamicItemBehavior!
var blockBehaviors: UIDynamicItemBehavior!
My issue here, is that the ball collides with the blocks, but I don't know how to detect whether or not the ball hit the block, but I do know how to make the views appear and disappear (give the view a backgroundcolor matching the View's color, and remove it from the blockBehaviors. Basically, I want to know how to detect when two views collide via. function or something else.
It would also be awesome if I could also add multiple levels,lol.
A UICollisionBehavior needs a delegate that adopts the UICollisionBehaviorDelegate protocol. This delegate has a method collisionBehavior that is called whenever a collision is detected.
For example:
var collisionBehavior: UICollisionBehavior! // create a UICollisionBehavior as you have done
collisionBehavior.addItem(ball) // add your items to it
collisionBehavior.addItem(block) // (faster to do this in the init step with `items` argument)
collisionBehavior.collisionDelegate = myDelegate // give it a delegate which adopts UICollisionBehaviorDelegate
dynamicAnimator.addBehavior(collisionBehavior) // add the behavior to your animator
Then implement func collisionBehavior for your delegate class. Often people just use the UIViewController itself as the delegate, so the above line would read collisionBehavior.collisionDelegate = self.
See "Making objects respond to collisions" here for a good and short tutorial: http://www.raywenderlich.com/76147/uikit-dynamics-tutorial-swift.

CALayer delegate is only called occasionally, when using Swift

I'm new to IOS and Swift, so I've started by porting Apple's Accelerometer example code to Swift.
This was all quite straightforward. Since the Accelerometer API has been deprecated, I used Core Motion instead, and it works just fine. I also switched to a storyboard.
The problem I have is that my layer delegate is only rarely called. It will go for a few minutes and never get called, and then it will get called 40 times a second, and then go back to not being called. If I context switch, the delegate will get called, and one of the sublayers will be displayed, but there are 32 sublayers, and I've yet to see them all get drawn. What's drawn seems to be fine - the problem is just getting the delegate to actually get called when I call setNeedsDisplay(), and getting all of the sublayers to get drawn.
I've checked to be sure that each sublayer has the correct bounds and frame dimensions, and I've checked to make sure that setNeedsDisplay() gets called after each accelerometer point is acquired.
If I attach an instrument, I see that the frame rate is usually zero, but occasionally it will be some higher number.
My guess is that the run loop isn't cycling. There's actually nothing in the run loop, and I'm not sure where to put one. In the ViewDidLoad delegate, I set up an update rate for the accelerometer, and call a function that updates the sublayers in the view. Everything else is event driven, so I don't know what I'd do with a run loop.
I've tried creating CALayers, and adding them as sublayers. I've also tried making the GraphViewSegment class a UIView, so it has it's own layer.
The version that's written in Objective C works perfectly reliably.
The way that this application works, is that acceleration values show up on the left side of the screen, and scroll to the right. To make it efficient, new acceleration values are written into a small sublayer that holds a graph for 32 time values. When it's full, that whole sublayer is just moved a pixel at a time to the right, and a new (or recycled) segment takes its place at the left side of the screen.
Here's the code that moves unchanged segments to the right by a pixel:
for s: GraphViewSegment in self.segments {
var position = s.layer.position
position.x += 1.0;
s.layer.position = position;
//s.layer.hidden = false
s.layer.setNeedsDisplay()
}
I don't think that the setNeedsDisplay is strictly necessary here, since it's called for the layer when the segment at the left gets a new line segment.
Here's how new layers are added:
public func addSegment() -> GraphViewSegment {
// Create a new segment and add it to the segments array.
var segment = GraphViewSegment(coder: self.coder)
// We add it at the front of the array because -recycleSegment expects the oldest segment
// to be at the end of the array. As long as we always insert the youngest segment at the front
// this will be true.
self.segments.insert(segment, atIndex: 0)
// this is now a weak reference
// Ensure that newly added segment layers are placed after the text view's layer so that the text view
// always renders above the segment layer.
self.layer.insertSublayer(segment.layer, below: self.text.layer)
// Position it properly (see the comment for kSegmentInitialPosition)
segment.layer.position = kSegmentInitialPosition;
//println("New segment added")
self.layer.setNeedsDisplay()
segment.layer.setNeedsDisplay()
return segment;
}
At this point I'm pretty confused. I've tried calling setNeedsDisplay all over the place, including the owning UIView. I've tried making the sublayers UIViews, and I've tried making them not be UIViews. No matter what I do, the behavior is always the same.
Everything is set up in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
pause.possibleTitles?.setByAddingObjectsFromArray([kLocalizedPause, kLocalizedResume])
isPaused = false
useAdaptive = false
self.changeFilter(LowpassFilter)
var accelerometerQueue = NSOperationQueue()
motionManager.accelerometerUpdateInterval = 1.0 / kUpdateFrequency
motionManager.startAccelerometerUpdatesToQueue(accelerometerQueue,
withHandler:
{(accelerometerData: CMAccelerometerData!, error: NSError!) -> Void in
self.accelerometer(accelerometerData)})
unfiltered.isAccessibilityElement = true
unfiltered.accessibilityLabel = "unfiltered graph"
filtered.isAccessibilityElement = true
filtered.accessibilityLabel = "filtered graph"
}
func accelerometer (accelerometerData: CMAccelerometerData!) {
if (!isPaused) {
let acceleration: CMAcceleration = accelerometerData.acceleration
filter.addAcceleration(acceleration)
unfiltered!.addPoint(acceleration.x, y: acceleration.y, z: acceleration.z)
filtered!.addPoint(filter.x, y: filter.y, z: filter.z)
//unfiltered.setNeedsDisplay()
}
}
Any idea?
I quite like Swift as a language - it takes the best parts of Java and C#, and adds some nice syntactic sugar. But this is driving me spare! I'm sure it's some little thing that I've overlooked, but I can't figure out what.
Since you've created a new NSOperationQueue for your accelerometer updates handler, everything that handler calls is also running in a separate queue, sequestered from the main run loop. I'd suggest either running that handler on the main thread NSOperationQueue.mainQueue() or moving anything that could update the UI back to the main thread via a block on the main queue:
NSOperationQueue.mainQueue().addOperationWithBlock {
// do UI stuff here
}

Resources