2/21/15

iOS Bee Flight Simulator

Bees animate from left to right along bezier path
Tap to toggle visible flight paths
iPad landscape screenshots



Key Tech Features
Core Graphics Layers
Scalable Bezier Paths
Core Animation

CAShapeLayer * visibleFlightPath1;  //ivars
CAShapeLayer * visibleFlightPath2;


-(void)setup
{
    CALayer * b1 = getBumblebee();
    CALayer * b2 = getBumblebee();

    CGPathRef fp1 = getFlightPath(0, 1.0f, 1.0f);
    CGPathRef fp2 = getFlightPath(80, 3.0f, 0.8f);

    CAKeyframeAnimation * a1 = getAnimationWithPath(fp1, 3.3, 0);
    [b1 addAnimation:a1 forKey:nil];
    [self.view.layer addSublayer:b1];

    CAKeyframeAnimation * a2 = getAnimationWithPath(fp2, 3.9, 0);
    [b2 addAnimation:a2 forKey:nil];
    [self.view.layer addSublayer:b2];

    visibleFlightPath1 = getVisibleFlightPath(fp1);
    visibleFlightPath2 = getVisibleFlightPath(fp2);
    [self.view.layer addSublayer:visibleFlightPath1];
    [self.view.layer addSublayer:visibleFlightPath2];
}

CAGradientLayer * getBumblebee()
{
    CAGradientLayer * b = [CAGradientLayer layer];
    b.frame = CGRectMake(0, 0, 25, 25);
    b.borderColor = [UIColor darkGrayColor].CGColor;
    b.borderWidth = 0.5f;
    b.cornerRadius = 10.0f;
    b.masksToBounds = YES;

    CGColorRef black = [UIColor blackColor].CGColor;
    CGColorRef yellow = [UIColor yellowColor].CGColor;
    b.colors = @[ (__bridge id)black,
                  (__bridge id)yellow,
                  (__bridge id)black,
                  (__bridge id)yellow ];

    //vertical gradient
    b.startPoint = CGPointMake(0.0f, 0.5f);
    b.endPoint   = CGPointMake(1.0f, 0.5f);

    b.locations = @[ [NSNumber numberWithFloat:0.0], 
                     [NSNumber numberWithFloat:0.33], 
                     [NSNumber numberWithFloat:0.5], 
                     [NSNumber numberWithFloat:0.9],
                     [NSNumber numberWithFloat:1.0] ];

    return b;
}

CGPathRef getFlightPath(const int yOffset, const CGFloat factor1, const CGFloat factor2)
{
    const CGPoint startPoint = CGPointMake(20, (200 + (yOffset * factor1)));
    const CGFloat normalFlightPoints[] = { 220, 200,
                                           420, 200,
                                           620, 200,
                                           820, 200 };


    UIBezierPath * fp = [UIBezierPath bezierPath];
    [fp moveToPoint:startPoint];


    //add normal flight points
    const int count = sizeof(normalFlightPoints)/sizeof(CGFloat);
    const int cpOffset = 100;
    const CGFloat cpYOffsetFactor = (cpOffset * factor1);
    for (int i = 0, s = 0; i < count; i++, s++)
    {
        const CGFloat x = normalFlightPoints[i];
        const CGFloat y = normalFlightPoints[i + 1] + (yOffset * factor1);
        const CGPoint p = CGPointMake(x, y);

        const CGFloat cpYHighLowOffset = ((s % 2) ? cpYOffsetFactor : -cpYOffsetFactor);
        const CGPoint cp = CGPointMake((p.x - cpOffset), (p.y + cpYHighLowOffset));

        [fp addQuadCurveToPoint:p controlPoint:cp];  //connect flight points
        i += 1;
    }


    //add pseudo random flight points
    const CGFloat w = (yOffset * factor1) * factor2;
    [fp addQuadCurveToPoint:CGPointMake(700, 175 + w) controlPoint:CGPointMake(850, 150 + w)];
    [fp addQuadCurveToPoint:CGPointMake(900, 200 + w) controlPoint:CGPointMake(550, 200 + w)];

    const CGFloat randomYOffset = (arc4random_uniform(2) == 0 ? -cpOffset : cpOffset);
    [fp addCurveToPoint:CGPointMake(1200, fp.currentPoint.y + randomYOffset)
          controlPoint1:CGPointMake(1000, fp.currentPoint.y)
          controlPoint2:CGPointMake(1100, fp.currentPoint.y)];


    return fp.CGPath;
}

CAKeyframeAnimation * getAnimationWithPath(CGPathRef aPath, CFTimeInterval aDuration, CFTimeInterval aTimeOffset)
{
    CAKeyframeAnimation * a = [CAKeyframeAnimation animation];
    a.keyPath = @"position";
    a.path = aPath;
    a.duration = aDuration;
    a.timeOffset = aTimeOffset;
    a.repeatCount = HUGE_VALF;
    a.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];

    return a;
}

CAShapeLayer * getVisibleFlightPath(CGPathRef aPath)
{
    CAShapeLayer * fp = [CAShapeLayer layer];
    fp.path = aPath;
    fp.fillColor = [UIColor clearColor].CGColor;
    fp.strokeColor = [UIColor darkGrayColor].CGColor;
    fp.lineWidth = 1.0f;
    fp.zPosition = -1.0f;

    return fp;
}

//tap to toggle visible flight paths
-(void)tap:(UIGestureRecognizer*)sender
{
    visibleFlightPath1.hidden = !visibleFlightPath1.hidden;
    visibleFlightPath2.hidden = !visibleFlightPath2.hidden;
}

-(void)viewDidLoad
{
    [super viewDidLoad];
    
    UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
    [self.view addGestureRecognizer:tap];
    [self setup];
}

No comments:

Post a Comment