I'm using SpriteKit to animate a character jump.
I know what the correct code is, but I don't know why my previous code was incorrect.
Correct:
let actionMove = SKAction.moveTo(CGPoint(x: size.width * 0.1, y: size.height * 0.85), duration: 0.3)
let actionFall = SKAction.moveTo(CGPoint(x: size.width * 0.1, y: size.height * 0.3), duration: 0.3)
actionMove.timingMode = SKActionTimingMode.EaseIn
actionFall.timingMode = SKActionTimingMode.EaseOut
if CACurrentMediaTime() - time > 0.7 {
player.runAction(SKAction.sequence([actionMove, actionFall]))
time = CACurrentMediaTime()
}
Incorrect:
func jump() {
let actionMove = SKAction.moveTo(CGPoint(x: size.width * 0.1, y: size.height * 0.85), duration: 0.3)
let actionFall = SKAction.moveTo(CGPoint(x: size.width * 0.1, y: size.height * 0.3), duration: 0.3)
actionMove.timingMode = SKActionTimingMode.EaseIn
actionFall.timingMode = SKActionTimingMode.EaseOut
player.runAction(SKAction.sequence([actionMove, actionFall]))
}
if (player.position.y == size.height * 0.3){
jump();
}
In the incorrect code, the character does not jump when the user taps on the screen -- the character stays at height 0.3.
Related
I have an animation like this:
bubble.frame.origin = CGPoint(x: 75, y: -120)
UIView.animate(
withDuration: 2.5,
animations: {
self.bubble.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height * -1.3)
}
)
However, the animation goes in a straight line. I want the animation to do a little back and forth action on its way to its destination. Like a bubble. Any ideas?
If you want to animate along a path you can use CAKeyframeAnimation. The only question is what sort of path do you want. A dampened sine curve might be sufficient:
func animate() {
let box = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
box.backgroundColor = .blue
box.center = bubblePoint(1)
view.addSubview(box)
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = bubblePath().cgPath
animation.duration = 5
box.layer.add(animation, forKey: nil)
}
where
private func bubblePath() -> UIBezierPath {
let path = UIBezierPath()
path.move(to: bubblePoint(0))
for value in 1...100 {
path.addLine(to: bubblePoint(CGFloat(value) / 100))
}
return path
}
/// Point on curve at moment in time.
///
/// - Parameter time: A value between 0 and 1.
/// - Returns: The corresponding `CGPoint`.
private func bubblePoint(_ time: CGFloat) -> CGPoint {
let startY = view.bounds.maxY - 100
let endY = view.bounds.minY + 100
let rangeX = min(30, view.bounds.width * 0.4)
let midX = view.bounds.midX
let y = startY + (endY - startY) * time
let x = sin(time * 4 * .pi) * rangeX * (0.1 + time * 0.9) + midX
let point = CGPoint(x: x, y: y)
return point
}
Yielding:
I want to create animation with a rotating image. I have two variants of code.
First
I want to create animation like on this video:
https://yadi.sk/i/ek-3Pydc3ZfZFW - this animation fits me.
I use this code to create animation:
func animation() {
self.book1ImageView = UIImageView(frame: CGRect(x: self.view.frame.size.width / 2 - 120, y: (self.view.frame.size.height / 2) - ( (self.view.frame.width / 2) / 2 ), width: ( (self.view.frame.width / 2) / 8) * 7, height: self.view.frame.width / 2))
self.book2ImageView = UIImageView(frame: CGRect(x: self.view.frame.size.width / 2 - 120, y: (self.view.frame.size.height / 2) - ( (self.view.frame.width / 2) / 2 ), width: ( (self.view.frame.width / 2) / 8) * 7, height: self.view.frame.width / 2))
book1ImageView.image = UIImage(named:"attachment_83090027.jpg")
book2ImageView.image = UIImage(named:"2.jpg")
book1ImageView?.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
book2ImageView?.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
var transform = CATransform3DIdentity
transform.m34 = 1.0 / -2000.0
book1ImageView?.layer.transform = transform
UIView.animate(withDuration: 3, delay: 0.0,
options: [], animations: {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
self.book1ImageView.image = UIImage(named:"2.jpg")
}
self.book1ImageView.layer.transform = CATransform3DRotate(transform, .pi, 0, 1, 0)
self.view.layoutIfNeeded()
}, completion: {_ in
})
self.view.addSubview(self.book2ImageView)
self.view.addSubview(self.book1ImageView)
}
My image should changes in 90 degrees like in firs video. But sometimes my image changes before or after 90 degrees. like in this video: https://yadi.sk/i/MACrcIhM3ZfaKH
Also I have second variant of code:
Second
But this code not fits me because image rotating with delay on 90 degrees.
video: https://yadi.sk/i/_gNG8FBA3ZfaUe
code:
func pageFlipAnimation()
{
self.myImageView.image = UIImage.init(named: "firstImage")
var transform = CATransform3DIdentity
let transform1 = CATransform3DRotate(transform, -CGFloat(Double.pi)*0.5, 0, 1, 0)
let transform2 = CATransform3DRotate(transform, -CGFloat(Double.pi), 0, 1, 0)
transform.m34 = 1.0 / -5000.0
self.setAnchorPoint(anchorPoint: CGPoint(x: 0, y: 0.5), forView: self.myImageView)
UIView.animate(withDuration: 0.5, animations:
{
self.myImageView.layer.transform = transform1
})
{
(bFinished) in
self.myImageView.image = UIImage.init(named: "secondImage")
UIView.animate(withDuration: 0.75, animations:
{
self.myImageView.layer.transform = transform2
})
}
}
//This should ideally be a UIView Extension
func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView)
{
var newPoint = CGPoint(x:view.bounds.size.width * anchorPoint.x, y:view.bounds.size.height * anchorPoint.y)
var oldPoint = CGPoint(x:view.bounds.size.width * view.layer.anchorPoint.x, y:view.bounds.size.height * view.layer.anchorPoint.y)
newPoint = newPoint.applying(view.transform)
oldPoint = oldPoint.applying(view.transform)
var position = view.layer.position
position.x -= oldPoint.x
position.x += newPoint.x
position.y -= oldPoint.y
position.y += newPoint.y
view.layer.position = position
view.layer.anchorPoint = anchorPoint
}
How to solve the problem?
The second one is the right idea, because you divide it into two stages: you open to 90 degrees, you change the image, and then you open it the rest of the way. So that is the one I would suggest fixing.
The reason for the delay is merely that you have not changed the timing function, so you get the default: an EaseInOut to 90 degrees and then another EaseInOut from 90 degrees to 180 degrees. Thus we slow down to zero and start up again at 90 degrees.
Just give the first animation an EaseIn timing function and give the second animation an EaseOut timing function and you should be all set.
I have an opening book animation: https://streamable.com/n1c0n
And I want to on 90 degrees my image has changed.
I use this code:
var book1ImageViewI : UIImageView
let book2ImageViewI : UIImageView
book1ImageViewI = UIImageView(frame: CGRect( x: self.view.frame.size.width / 2 - 140, y: (self.view.frame.size.height / 2) - ( (self.view.frame.width / 2) / 2 ), width: ( (self.view.frame.width / 2) / 8) * 7, height: self.view.frame.width / 2))
book2ImageViewI = UIImageView(frame: CGRect(x: self.view.frame.size.width / 2 - 140, y: (self.view.frame.size.height / 2) - ( (self.view.frame.width / 2) / 2 ), width: ( (self.view.frame.width / 2) / 8) * 7, height: self.view.frame.width / 2))
book1ImageViewI.image = UIImage(named:"attachment_83090027.jpg")
book2ImageViewI.image = UIImage(named:"0a6752b7cd35fc441c152238ee5078384d--antique-books-rabbit-hole.jpg")
book1ImageViewI.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
book2ImageViewI.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
var transform = CATransform3DIdentity
transform.m34 = 1.0 / -2000.0
book1ImageViewI.layer.transform = transform
UIView.animate(withDuration: 1.5, animations: {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, -.pi/2, 0, 1, 0)
})
{
(bCompleted) in
if (bCompleted) {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, -.pi/2, 0, 1, 0)
}
UIView.animate(withDuration: 1.5, animations: {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, .pi*0.999, 0, 1, 0)
book1ImageViewI.image = UIImage(named:"0a6752b7cd35fc441c1528ee5078384d--antique-books-rabbit-holer.png")
}, completion: {
(bFinished) in
//Whatever
})
}
self.view.addSubview(book2ImageViewI)
self.view.addSubview(book1ImageViewI)
Everything works fine. But on 90 degrees animation slightly delayed.
I want to get animation like this without delay: https://streamable.com/q2bf6
How to do it?
P.S. In second animation use this code:
UIView.animate(withDuration: 3, delay: 0.0,
options: [], animations: {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
book1ImageViewI.image = UIImage(named:"0a6752b7cd35fc441c1528ee5078384d--antique-books-rabbit-holer.png")
}
book1ImageViewI.layer.transform = CATransform3DRotate(transform, .pi, 0, 1, 0)
self.view.layoutIfNeeded()
}, completion: {_ in
})
But this code does not fit. Because images changes not on 90 degrees. Sometimes earlier. Sometimes later.
Apparently, the anchor-point change was quite crucial wherever rotation transform is involved, and it is not straightforward. Every time you change anchorpoint, it alters the UIView position, and we must compensate for this change somehow.
Here is the final version using imageview which works without interruption for me. Apparently, it works with addKeyFrame API as well but for simplicity's sake I kept UIView.animate().
The trick I used is to have smaller duration for first animation and larger one for the second. It almost look like a page flip. You can also play with UIView.animate overrides that provide for UIViewAnimationOptions - such as curveEaseIn and curveEaseOut to make it look like page flip of a book.
It seems to work for me, the only thing is that you can't say it is rotating from the front or back. If your imageview is slant (like the slant book page you show in video), it should be visible and you can simply correct by changing the - to + sign within CATransform3DRotate arguments, or vice versa in animation block.
NOTE:
If it still plays hide and seek, try on real device instead of simulator just in case. There is apparent difference due to GPU execution when it comes to animations.
func pageFlipAnimation()
{
self.myImageView.image = UIImage.init(named: "firstImage")
var transform = CATransform3DIdentity
let transform1 = CATransform3DRotate(transform, -CGFloat(Double.pi)*0.5, 0, 1, 0)
let transform2 = CATransform3DRotate(transform, -CGFloat(Double.pi), 0, 1, 0)
transform.m34 = 1.0 / -5000.0
self.setAnchorPoint(anchorPoint: CGPoint(x: 0, y: 0.5), forView: self.myImageView)
UIView.animate(withDuration: 0.5, animations:
{
self.myImageView.layer.transform = transform1
})
{
(bFinished) in
self.myImageView.image = UIImage.init(named: "secondImage")
UIView.animate(withDuration: 0.75, animations:
{
self.myImageView.layer.transform = transform2
})
}
}
//This should ideally be a UIView Extension
func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView)
{
var newPoint = CGPoint(x:view.bounds.size.width * anchorPoint.x, y:view.bounds.size.height * anchorPoint.y)
var oldPoint = CGPoint(x:view.bounds.size.width * view.layer.anchorPoint.x, y:view.bounds.size.height * view.layer.anchorPoint.y)
newPoint = newPoint.applying(view.transform)
oldPoint = oldPoint.applying(view.transform)
var position = view.layer.position
position.x -= oldPoint.x
position.x += newPoint.x
position.y -= oldPoint.y
position.y += newPoint.y
view.layer.position = position
view.layer.anchorPoint = anchorPoint
}
This part of your code conflicts:
(bCompleted) in
if (bCompleted) {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, -.pi/2, 0, 1, 0)
}
UIView.animate(withDuration: 1.5, animations: {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, .pi*0.999, 0, 1, 0)
book1ImageViewI.image = UIImage(named:"0a6752b7cd35fc441c1528ee5078384d--antique-books-rabbit-holer.png")
}, completion: {
(bFinished) in
//Whatever
})
You set book1ImageViewI.layer.transform = CATransform3DRotate(transform, -.pi/2, 0, 1, 0) at the same time as book1ImageViewI.layer.transform = CATransform3DRotate(transform, .pi*0.999, 0, 1, 0)
Try this instead:
(bCompleted) in
if (bCompleted) {
UIView.animate(withDuration: 1.5, animations: {
book1ImageViewI.layer.transform = CATransform3DRotate(transform, -.pi/2, 0, 1, 0)
book1ImageViewI.image = UIImage(named:"0a6752b7cd35fc441c1528ee5078384d--antique-books-rabbit-holer.png")
}, completion: {
(bFinished) in
//Whatever
})
}
Is it possible to animate a view in a circular path using the new iOS 10 UIViewPropertyAnimator?
I know that it can be done with CAKeyframeAnimation as per How do I animate a UIView along a circular path?
How can I achieve the same result with the new API?
I ended up with the following. Essentially, calculating the points on circle and using animateKeyFrames to move between them.
let radius = CGFloat(100.0)
let center = CGPoint(x: 150.0, y: 150.0)
let animationDuration: TimeInterval = 3.0
let animator = UIViewPropertyAnimator(duration: animationDuration, curve: .linear)
animator.addAnimations {
UIView.animateKeyframes(withDuration: animationDuration, delay: 0, options: [.calculationModeLinear], animations: {
let points = 1000
let slice = 2 * CGFloat.pi / CGFloat(points)
for i in 0..<points {
let angle = slice * CGFloat(i)
let x = center.x + radius * CGFloat(sin(angle))
let y = center.y + radius * CGFloat(cos(angle))
let duration = 1.0/Double(points)
let startTime = duration * Double(i)
UIView.addKeyframe(withRelativeStartTime: startTime, relativeDuration: duration) {
ninja.center = CGPoint(x: x, y: y)
}
}
})
}
animator.startAnimation()
I'm trying to create a game in which i have a object 1 rotatiing in a circle and another object appears and places itself ontop of object 1. currently the object just rotates around object 1 without stacking ontop of it. how do i get the object to stack itself on top and then follow it's orbit? here's my code now.
let player = SKSpriteNode(imageNamed: "Light")
override func didMoveToView(view: SKView) {
// 2
backgroundColor = SKColor.whiteColor()
// 3
player.position = CGPoint(x: size.width * 0.5, y: size.height * 0.5)
// 4
player.size = CGSize(width:70, height:60 )
addChild(player)
let dx = player.position.x - self.frame.width / 2
let dy = player.position.y - self.frame.height / 2
let rad = atan2(dy, dx)
circle = UIBezierPath(arcCenter: CGPoint(x: size.width / 2, y: size.height / 2), radius: 120, startAngle: rad, endAngle: rad + CGFloat(M_PI * 4), clockwise: true)
let follow = SKAction.followPath(circle.CGPath, asOffset: false, orientToPath: true, speed: 100)
player.runAction(SKAction.repeatActionForever(follow))
}
func addMonster() {
let monster = SKSpriteNode(imageNamed: "plate")
// Determine where to spawn the monster along the Y axis
let actualY = random(min: monster.size.height/1, max: size.height - monster.size.height/1)
// Position the monster slightly off-screen along the right edge,
// and along a random position along the Y axis as calculated above
monster.position = CGPoint(x: size.width * 0.5 + monster.size.width/2, y: actualY)
// Add the monster to the scene
addChild(monster)
// Determine speed of the monster
let actualDuration = random(min: CGFloat(2.0), max: CGFloat(3.0))
// Create the actions
let actionMove = SKAction.moveTo(CGPoint(x: -monster.size.width/2, y: actualY), duration: NSTimeInterval(actualDuration))
let follow = SKAction.followPath(circle.CGPath, asOffset: false, orientToPath:true, speed: 100)
monster.runAction(SKAction.repeatActionForever(follow))
}
Your verbal description appears to have something like the following in mind (you can copy and paste this into an iOS playground):
import UIKit
import SpriteKit
import XCPlayground
let scene = SKScene(size: CGSize(width: 400, height: 400))
let view = SKView(frame: CGRect(origin: .zero, size: scene.size))
view.presentScene(scene)
XCPlaygroundPage.currentPage.liveView = view
scene.backgroundColor = .darkGrayColor()
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let player = SKShapeNode(rectOfSize: CGSize(width: 70, height: 60), cornerRadius: 5)
let radius: CGFloat = 120
let circle = CGPathCreateWithEllipseInRect(CGRect(x: -radius, y: -radius, width: radius * 2, height: radius * 2), nil)
let followCircle = SKAction.followPath(circle, asOffset: false, orientToPath: true, speed: 100)
player.runAction(.repeatActionForever(followCircle))
let monsterSize = CGSize(width: 35, height: 30)
let monster = SKShapeNode(rectOfSize: monsterSize, cornerRadius: 4)
monster.fillColor = .redColor()
monster.runAction(.repeatActionForever(followCircle))
scene.addChild(player)
scene.addChild(monster)
Where you make use of a random(min:max:) function that is probably implemented along the lines of:
public func random(min min: CGFloat, max: CGFloat) -> CGFloat {
return min + (CGFloat(arc4random()) / CGFloat(UInt32.max)) * (max - min)
}
But you also have the code that seems to be saying:
let y = random(
min: scene.size.height / -2 + monsterSize.height / 2,
max: scene.size.height / 2 - monsterSize.height / 2
)
let xFrom = scene.size.width / 2
let xTo = scene.size.width / -2
monster.position = CGPoint(x: xFrom, y: y)
monster.runAction(
.moveToX(xTo, duration: NSTimeInterval(random(min: 2, max: 3)))
)
But this is unused. Instead the monster is set to follow the same circle path as the player. And so I am not sure really what exactly you are trying to achieve. (By the way, the simplest way of getting the monster "stack on top" of the player would be to make it the child node of the player...)
Hope some of this guesswork helps you in some way (to refine your question and code sample if nothing else).