Starting a CAKeyframeAnimation from a non-zero time - calayer

I have a sub-layer keyframe animation defined to animate an image along a BSpline, grouped together with a rotate animation. With the sub-layer.speed set to 0, I can drag the image back and forth along the curve by adjusting the animationsGroup.timeOffset value based on the distance dragged.
What I want to do is, after a certain threshold (say %15) is to set the animation speed to 1 so the animation completes by itself, but it's not that simple. The animation either completes immediately and resets everything back to the start position, or the animiation reaches the end of the path, loops round to zero, and continues animating until it's back to the point where the animation kicks in.
What I want is:
Tstart -> drag -> T0.15 -> animation -> Tend
But what I'm getting is
Tstart -> drag -> T0.15 -> animation -> Tend -> Tstart -> T0.15
I've already looked into use of timeOffset and time-warps, and fiddled with the parameters but to no avail.

You can that outcome by wrapping your animation group in a new animation group whose duration is 0.85 times the animation groups duration. Here is a bit of code that does just that:
- (void) setTimeOffset:(double)value
{
if (value < 0.15) {
// This is what you are already doing.
_animGroup.timeOffset = value;
[_someSublayer addAnimation:_animGroup forKey:#"animGroup"];
} else if (_animGroup.speed == 0.0) {
// You must set the final position before animating otherwise the
// layer will come back to it's original position.
_someSublayer.position = _theEndPointOfYourPath;
// Now set the speed of the animation group to 1 so that it animates.
// Be sure to leave the timeOffset alone so that is starts where you want it.
_animGroup.speed = 1.0;
// Create a new animation group around it whose duration is the remaining time
// of the animation group and add that to the layer instead. It will prevent
// animGroup from wrapping.
CAAnimationGroup *outerGroup = [[CAAnimationGroup alloc] init];
outerGroup.animations = #[_animGroup];
outerGroup.duration = _animGroup.duration - _animGroup.timeOffset;
[_someSublayer addAnimation:outerGroup forKey:#"animGroup"];
}
}

Related

RealityKit: change the scale of a ModelEntity without changing its position

I have an ModelEntity animation that move from point A to point B and takes a while to complete. When the user taps on the ModelEntity I would like to add a shrinking animation to the ModelEntity as well.
I tried adding the scale animation directly to the ModelEntity view .move but the problem there is I current transform of the model is where the model is expected to end. Causing the ModelEnity to jump to the end of the animation.
var transform = modelEntity.transform
transform.scale *= factor
modelEntity.move(to: transform, relativeTo: modelEntity.parent, duration: duration) // will not work because the translation of the transform is already at the end of the animation
Is there a way to add a scaling animation to ModelEntity that is already in the middle of a different animation and make them work together?
This was a solution to my problem:
var transform = modelEntity.transform
transform.scale *= factor
modelEntity.move(to: modelEntity.transform, relativeTo: modelEntity.parent)
modelEntity.move(to: transform, relativeTo: modelEntity.parent, duration: duration)
It looks like the problem was that I needed to get the entity to the position it was in at the middle of the animation. For some reason when you call the .move method it expects to start where it should have ended up regardless of the duration.

Stop the sprite on last frame

So I have this serious problem and I don't know how to let it work. I have this in my step event:
if (image_index >= 6 && image_index <= 8) && (sprite_index == spr_sonia_down_right) {
image_speed = 0;
image_index = 7;
}
The thing that I want is that on the last frame the sprite stops. I have an object and in that object I put the sprites or I change them using sprite_index and that works, but my sprite "down" has a secuence that she is going down and down and down. When she is complete down, that is in the 8 sub image, I want to freeze the sprite on that frame, but I can't reach that.
I think there are two things that you can do to improve this:
Instead of guessing through (image_index >= 6 && image_index <= 8), you can also use the Animation End Event. This Event happens when the object's current sprite has finished it's animation.
I would've made a seperate single sprite that shows the last sprite of the animation, (for example spr_sonia_down_end_right) and show that sprite once the animation is done.
So, for example in Animation End:
if (sprite_index == spr_sonia_down_right) {
sprite_index = spr_sonia_down_end_right;
}

Multiple overlapping animations on the same element in svg.js

I want to start a animation on an element while a previous animation is still active. However, calling animate() on the element queues the new animation at the end of the current animation.
For example, consider an animation where an element is being moved to a new position. Now, I also want to make it fade out when it reaches a certain position. The following queues the “once” animation at the end of the move, rather than at 80%.
rect.animate(1000).move(100, 100)
.once(0.8, function(pos, eased) {
rect.animate(200).opacity(0);
});
How do I make the element start fading out when it reaches 80% of the move? The API seems to be designed for chaining animations rather simultaneous overlapping animations.
What you are trying to do is a bit more complicated. Unforrtunately its not possible to "hack" into the current animation and add a new animation on the fly.
However what you can do is adding a new property which should be animated:
var fx = rect.animate(1000).move(100, 100)
.once(0.8, function(pos, eased) {
fx.opacity(0);
});
As you will notice that has its own problems because the oopacity immediately jumps to 80%. So this is not an approach which works for you.
Next try: Use the during method:
var morh = SVG.morph(
var fx = rect.animate(1000).move(100, 100)
.once(0.8, function(pos, eased) {
fx.during(function(pos, morphFn, easedPos) {
pos = (pos - 0.8) / 0.2
this.target().opacity(pos)
}
});
We just calculate the opacity ourselves

How do i rotate an object in unity at a set of amount time stop it and then again

I'm not good at coding so please help me with my problem.
What i mean with: How do i rotate an object in unity at a set of amount time stop it and then again
is how can i rotate an object at a set of amount time then stop it at a set of amount time then stop it etc.
VideoNations
Easiest way would be like this:
// control the timing of rotating here
var rotate=true;
function Start () {
// rotate an object for 2 seconds
rotate=true;
yield WaitForSeconds(2f);
// stop it for a second
rotate=false;
yield WaitForSeconds(1f);
// rotate again
rotate=true;
}
// rotate here
function Update() {
if(rotate)
transform.eulerAngles+=Vector3(0, Time.deltaTime*90, 0);
}
If you want the rotation enabling-disabling to loop, wrap the contents of Start in a while(true) {...}.

Alternative to using CABasicAnimation callbacks?

CAAnimation does not provide a mechanism for assigning callback functions other than the standard "animationDidStart:"/"animationDidStop:" methods.
I have a custom UIControl that utilizes 2 CALayers that overlap. The purpose of this control is similar to an old fashioned sonar. The top layer's contents contains an image that gets rotated constantly (call this layer "wand"). Beneath that layer is a "spriteControl" layer that renders blips as the wand passes over them.
The objects that the blips represent are pre-fetched and organized into invisible CAShapeLayers by the spriteControl. I am using a CABasicAnimation to rotate the wand 10 degrees at a time, then utilizing the "animationDidStop:" method to invoke a method on the spriteControl that takes the current rotation value of the wand layer (a.k.a. heading) and animates the alpha setting from 1.0 to 0.0 for simulating the blip in and fade out effect. Finally, the process is started over again indefinitely.
While this approach of using the CAAnimation callbacks ensures that the timing of the wand reaching a "ping" position (i.e. 10deg, 20deg, 270deg, etc) always coincide with the lighting of the blips in the other layer, there is this issue of stopping, recalculating, and starting the animation every 10 degrees.
I could spawn an NSTimer to fire a method that queries the angle of the wand's presentation layer to get the heading value. However, this makes it more difficult to keep the wand and the blip highlighting in sync, and/or cause some to get skipped altogether. This approach is discussed a bit here: How can I callback as a CABasicAnimation is animating?
So my question is whether or not there is anything I can do to improve the performance of the wand layer rotation without reimplementing the control using OpenGL ES. (I realize that this would be easily solved in an OpenGL environment, however, to use it here would require extensive redesign that simply isn't worth it.) While the performance issue is minor, I can't shake the feeling that there is something simple and obvious that I could do that would allow the wand to animate indefinitely without pausing to perform expensive rotation calculations in between.
Here is some code:
- (void)rotateWandByIncrement
{
if (wandShouldStop)
return;
CGFloat newRotationDegree = (wandRotationDegree + WAND_INCREMENT_DEGREES);
if (newRotationDegree >= 360)
newRotationDegree = 0;
CATransform3D rotationTransform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(newRotationDegree), 0, 0, 1);
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.toValue = [NSValue valueWithCATransform3D:rotationTransform];
animation.duration = WAND_INCREMENT_DURATION;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = FALSE;
animation.delegate = self;
[wandLayer addAnimation:animation forKey:#"transform"];
}
- (void)animationDidStart:(CAAnimation *)theAnimation
{
if (wandShouldStop)
return;
NSInteger prevWandRotationDegree = wandRotationDegree - WAND_INCREMENT_DEGREES;
if (prevWandRotationDegree < 0)
prevWandRotationDegree += 360;
// Pulse the spriteControl
[[self spriteControl] pulseRayAtHeading:prevWandRotationDegree];
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
// update the rotation var
wandRotationDegree += WAND_INCREMENT_DEGREES;
if (wandRotationDegree >= 360)
wandRotationDegree = 0;
// This applies the rotation value to the model layer so that
// subsequent animations start where the previous one left off
CATransform3D rotationTransform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(wandRotationDegree), 0, 0, 1);
[CATransaction begin];
[CATransaction setDisableActions:TRUE];
[wandLayer setTransform:rotationTransform];
[CATransaction commit];
//[wandLayer removeAnimationForKey:#"transform"];
[self rotateWandByIncrement];
}
Let's say it takes 10 seconds for the radar to make one complete rotation.
To get the wand to rotate indefinitely, attach a CABasicAnimation to it with its Duration property set to 10 and its RepeatCount property set to 1e100f.
The blips can each be animated using their own instance CAKeyframeAnimation. I won't write the details, but for each blip, you specify an array of opacity values (I assume opacity is how you're fading out the blips) and an array of time percentages (see Apple's documentation).

Resources