繁体   English   中英

动画UIImageView的旋转变化

[英]Animating rotation changes of UIImageView

我正在创建一个应用程序(除其他外)显示一个简化的罗盘图像,根据设备的旋转旋转。 问题是,只需这样做:

float heading = -1.0f * M_PI * trueHeading / 180.0f; //trueHeading is always between 0 and 359, never 360
self.compassNeedle.transform = CGAffineTransformMakeRotation(heading);

在CLLocationManager的didUpdateHeading方法中,使动画变得丑陋和不连贯。 我已经使用了Instruments来确定它的应用程序是否只能以超过30-48 fps的速度渲染,但事实并非如此。

如何平滑图像视图的旋转,使其更像Apple自己的Compass应用程序?

而不是使用当前的即时值,尝试使用真实标题的最后N个值的平均值。 价值可能在一瞬间大量跳跃,但在“平均”中稳定下来。

假设你有一个成员变量storedReadings,它是一个NSMutableArray:

-(void)addReading(float):newReading
{
    [storedReadings addObject:[NSNumber numberWithFloat:newReading]];
    while([storedReadings count] > MAX_READINGS)
    {
        [storedReadings removeObjectAtIndex:0];
    }
}

那么当你需要平均值(计时器更新?)

-(float)calcReading
{
    float result = 0.0f;
    if([storedReadings count] > 0)
    {
       foreach(NSNumber* reading in storedReadings)
       {
           result += [reading floatValue];
       }  
       result /= [storedReadings count];
    }
    return result;
}

您可以先验地选择MAX_READINGS。

下一级(上)

如果读数没有跳得那么多但动画仍然不稳定,你可能需要做一些像“平滑”旋转的事情。 在任何给定的时间,你有你正在显示的当前角度,theta(在你的班级存储它,从0开始)。 你也有你的目标角度,称之为目标 这是从平滑的calcReading函数中获得的值。 错误定义为两者之间的差异:

error = target-theta;

设置一个定时器回调,其周期为0.05秒(每秒20次)。 你想要做的是调整theta以便将error驱动为0.你可以通过以下两种方式实现:

  1. thetaNext += kProp * (target - theta); //这是比例反馈。
  2. thetaNext += kStep * sign(target-theta); //每次更新都会移动theta一个固定的数量。 如果x> = 0,则sign(x)= +1,如果x <0,则为-1。

第一种解决方案将导致旋转从目标越远而急剧变化。 当它摆动超过“零”点时,它也可能会稍微摆动一下。 较大的kProp值将产生更快的响应,但也会产生更多的振荡。 需要进行一些调整。

第二种解决方案将更容易控制......它只是每次“嘀嗒”罗盘针。 你可以将kStep设置为1/4度,这使你的旋转“速度”约为(1/4度/更新​​)*(20更新/秒)=每秒5度。 这有点慢,但您可以看到数学并更改kStep以满足您的需求。 请注意 ,您可以“绑定”“错误”值,以便在错误<kStep(或类似的东西)时不采取任何操作。 当角度非常接近目标时,这可以防止指南针移动。 您可以在错误较小时更改kStep,以便它“滑动”到结束位置(即,当错误很小时,kStep会更小)。

为了处理角度问题(环绕),我将角度“标准化”,使其始终在-Pi / Pi内。 我不保证这是完美的方式,但它似乎完成了工作:

   // Takes an angle greater than +/- M_PI and converts it back
   // to +/- M_PI.  Useful in Box2D where angles continuously
   // increase/decrease.
   static inline float32 AdjustAngle(float32 angleRads)
   {
      if(angleRads > M_PI)
      {
         while(angleRads > M_PI)
         {
            angleRads -= 2*M_PI;
         }
      }
      else if(angleRads < -M_PI)
      {
         while(angleRads < -M_PI)
         {
            angleRads += 2*M_PI;
         }
      }
      return angleRads;
   }

通过这种方式,-pi是您在向左/向右继续旋转时从任一方向前进的角度。 也就是说,从0到359度的数字没有不连续性。

所以把这一切都放在一起

static inline float Sign(float value)
{
   if(value >= 0)
      return 1.0f;
   return -1.0f;
}

//#define ROTATION_OPTION_1
//#define ROTATION_OPTION_2
#define ROTATION_OPTION_3

-(void)updateArrow
{
   // Calculate the angle to the player
   CGPoint toPlayer = ccpSub(self.player.position,self.arrow.position);
   // Calculate the angle of this...Note there are some inversions
   // and the actual image is rotated 90 degrees so I had to offset it
   // a bit.
   float angleToPlayerRads = -atan2f(toPlayer.y, toPlayer.x);
   angleToPlayerRads = AdjustAngle(angleToPlayerRads);

   // This is the angle we "wish" the arrow would be pointing.
   float targetAngle = CC_RADIANS_TO_DEGREES(angleToPlayerRads)+90;
   float errorAngle = targetAngle-self.arrow.rotation;

   CCLOG(@"Error Angle = %f",errorAngle);


#ifdef ROTATION_OPTION_1
   // In this option, we just set the angle of the rotated sprite directly.
   self.arrow.rotation = CC_RADIANS_TO_DEGREES(angleToPlayerRads)+90;
#endif


#ifdef ROTATION_OPTION_2
   // In this option, we apply proportional feedback to the angle
   // difference.
   const float kProp = 0.05f;
   self.arrow.rotation += kProp * (errorAngle);
#endif

#ifdef ROTATION_OPTION_3
   // The step to take each update in degrees.
   const float kStep = 4.0f;
   //  NOTE:  Without the "if(fabs(...)) check, the angle
   // can "dither" around the zero point when it is very close.
   if(fabs(errorAngle) > kStep)
   {
      self.arrow.rotation += Sign(errorAngle)*kStep;
   }
#endif
}

我把这段代码放到了我为Cocos2d编写的演示程序中。 它显示了被一些怪物(较小的盒子)追逐的角色(大盒子),并且在中心一个始终指向角色的箭头 updateArrow调用定期在计时器滴答(更新(dt)函数)上进行。 玩家在屏幕上的位置由用户点击屏幕设置,角度基于从箭头到玩家的向量。 在该功能中,我显示了设置箭头角度的所有三个选项:

选项1

只需根据玩家所在位置设置(即设置)。

选项2

使用比例反馈每次调整箭头的角度。

选项3

如果误差角度大于步长,则逐步调整箭头的角度。

这张照片大致显示了它的样子:

在此输入图像描述

并且, 所有代码都可以在github上找到 只需查看HelloWorldLayer.m文件即可。

这个有帮助吗?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM