最近在做关于旋转的动画效果,遇到了些问题,在解决问题的过程中对SpriteKit中的旋转有所更加深入的了解,在此进行个总结。
我想实现下面的这个效果:一个水管绕着白色星球的中心进行旋转。
最自然的想法是直接使用SpriteKit的SKAction.rotateByAngle方法直接进行旋转即可。
那么问题来了,一个SKSpriteNode元素如何绕一个指定的点旋转呢?
默认情况下,每个SKSpriteNode节点都有一个锚点(Anchorpoint),而rotateByAngle方法则是以锚点为中心进行旋转。
比如当锚点为屏幕中心时:
比如锚点为屏幕左下角时:
那么,就将水管的Anchorpoint设置为白色星球的圆心位置即可。(修改Anchorpoint不是好办法)
但是,Anchorpoint默认是(0.5,0.5),为水管元素的中心点,它是代表相对于SKSpriteNode的比例的位置。
为什么Anchorpoint不能直接设置为固定的像素点位置呢?这样不是更简单更方便易用?
因为锚点实际上是元素的“抽象点”,而元素的位置(position)是该锚点的位置,往往该抽象点位于元素的内部。
在用rotateByAngle方法进行旋转时,物体虽然绕着锚点进行旋转,但是因为锚点并没有移动,所以物体的位置(Position)始终没有发生改变,改变的是ZRotation
举个栗子:
图中蓝色的边框代表物理引擎计算得到的碰撞形状,白色边框代表SKSpriteNode的frame,蓝色小人的中心为汽车锚点的位置。
很显然的,这里汽车的锚点被为(0,0)。
但是对于物理碰撞形状,它的锚点并没有随汽车的锚点改变而改变(也就是说如果修改了SKSpriteNode的锚点,在使用物理引擎时就会出现偏差,或许这是当前版本SpriteKit的bug吧)。
那么,如果既想使用默认的物理引擎进行碰撞检测并对物体施力,又想让物体绕着给定的一点进行旋转?该如何做呢?
也就是说如何在不修改Anchorpoint的情况下,让物体绕给定的点进行旋转?
很简单,手工修改物体的position和ZRotation即可。
假设物体为element,绕点point旋转rad弧度每帧,则可以在每次更新时调用方法:
(具体的点A绕点B旋转rad的计算参看点击打开链接)
func rotateByPoint(element:SKNode,point:CGPoint,rad:CGFloat) { let tempPos = element.position let resultX = point.x + (tempPos.x - point.x) * cos(rad) - (tempPos.y - point.y) * sin(rad) let resultY = point.y + (tempPos.x - point.x) * sin(rad) + (tempPos.y - point.y) * cos(rad) //修改角度 element.zRotation += rad //修改位置 element.position.x = resultX element.position.y = resultY }
如果简单些,绕给定点旋转的效果仅仅是为了好看,没有和其他元素进行什么交互的话(或者仅仅是粗略地通过frame是否重叠进行碰撞判断)
可以考虑修改Anchorpoint,进行实现。
具体做法是:
添加一个父亲节点,将其position设置为旋转被绕的点的位置;
然后将物体的位置设为相对父节点的新坐标;
直接对父亲节点进行rotateByAngle即可。
总的来说,Anchorpoint是为了“固定”物体到固定的position上而存在,如果想要让物体绕某任意一点旋转,并且不影响物理引擎的使用的话,应该考虑修改物体的position和ZRotation的方案。
这个方案应该是可以写成扩展的SKAction的,不过目前我对SKAction的机制还没有深入的掌握,争取在后面将其封装到SKAction中。
[目前我刚刚开始接触SpriteKit,经验还不够丰富,如果有错误还请大家及时指出。]