カップを机の上でクルクルシャッフルして、

ボールはどこかな?というのを作る方法のメモ

(XcodeのiOS6 Simulatorで試しています。)

ポイント

・カップは3Dっぽい変換を使ってViewを作る

  上部は、円をx軸回転させて、弧にする

  側面の台形はCATransform3dのm34プロパティで3D効果をかけて作る

タッチで、一度カップを上げてボールの位置を見せた後に、7回シャッフルして、またカップを持ち上げて答えを見せるような感じで作成。

サンプルコード

#import “ViewController.h”

#import <QuartzCore/QuartzCore.h>

#define CupView @“view”

#define CupPosition @“position”

#define CupNextPosition @“nextPosition”

@interface ViewController () {

    CADisplayLink *timer;

}

@property (nonatomic, strong) NSMutableArray *cupInfo;

@property (nonatomic, strong) NSArray *refPoint; // cupを置く位置の目印

@property (nonatomic, strong) UIView *ball;

@end

@implementation ViewController

@synthesize cupInfo;

– (void)viewDidLoad

{

    [super viewDidLoad];

    self.view.backgroundColor = [UIColor colorWithRed:0.3 green:0.2 blue:0.0 alpha:0.9];

    //

    UIView *desk = [[UIView alloc] initWithFrame:CGRectMake(0, 290, 320, 100)];

    desk.backgroundColor = [UIColor brownColor];

    [self.view addSubview:desk];

    

    

    // cupの置く位置を初期化

    NSValue *positionA = [NSValue valueWithCGPoint:CGPointMake(60, 300)];

    NSValue *positionB = [NSValue valueWithCGPoint:CGPointMake(160, 300)];

    NSValue *positionC = [NSValue valueWithCGPoint:CGPointMake(260, 300)];

    self.refPoint = [NSArray arrayWithObjects:positionA, positionB, positionC, nil];

    

    // cup情報を初期化

    self.cupInfo = [[NSMutableArray alloc] init];

    for (int i=0; i<3; i++) {

        NSMutableDictionary *cup = [[NSMutableDictionary alloc] initWithObjectsAndKeys:[self.refPoint objectAtIndex:i], CupPosition, nil];

        [self.cupInfo addObject:cup];

    }

    

}

– (void)viewDidAppear:(BOOL)animated

{

    

    // cupを配置

    for (int i=0; i<3; i++) {

        CGPoint p = [[self.refPoint objectAtIndex:i] CGPointValue];

        UIView *cupView = [self createCup:CGPointMake(p.x, p.y)];

        

        [self.view addSubview:cupView];

        [[self.cupInfo objectAtIndex:i] setValue:cupView forKey:CupView];

    }

    

    [self start];

}

– (UIView*)createBall:(int)position

{

    UIView *ball = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];

    ball.layer.cornerRadius = 10.0;

    ball.backgroundColor = [UIColor yellowColor];

    

    CGPoint p = [[self.refPoint objectAtIndex:position] CGPointValue];

    ball.center = CGPointMake(p.x, p.y + 18);

    return ball;

}

– (UIView*)createCup:(CGPoint)center

{

    UIView *cup = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 80)];

    cup.center = center;

    

    // 円を作ってx軸周りで傾ける、

    // 実際のコップ底は円だ、だからこれでそれっぽく見えるはず。

    UIColor *color = [UIColor whiteColor];

    // cupのローカル座標で取得

    CGPoint localCenter = [self.view.superview convertPoint:cup.center toView:cup];

    

    // bottom

    UIView *bottom = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 34, 34)];

    bottom.layer.cornerRadius = 17.0;

    bottom.layer.transform = CATransform3DMakeRotation(M_PI * 0.4, 1.0, 0.0, 0.0);

    bottom.backgroundColor = color;

    bottom.center = CGPointMake(localCenter.x, localCenter.y + 27);

    [cup addSubview:bottom];

    

    

    // side

    UIView *side = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 90)];

    side.center = localCenter;

    side.backgroundColor = color;

    [cup addSubview:side];

    

    // 台形に

    [self layerCustom:side.layer];

    

    // top

    UIView *top = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 24, 24)];

    top.layer.cornerRadius = 12.;

    top.layer.transform = CATransform3DMakeRotation(M_PI * 0.4, 1.0, 0.0, 0.0);

    top.backgroundColor = color;

    top.center = CGPointMake(localCenter.x, localCenter.y19);

    [cup addSubview:top];

    

    cup.layer.zPosition = 100;

    return cup;

}

// 台形を作るためにレイヤーをカスタマイズ

– (void)layerCustom:(CALayer*)layer

{

    // CATransform3D.m343Dっぽく見せるためのパラメータ

    CATransform3D perspectiveTransform = CATransform3DIdentity;

    perspectiveTransform.m34 = 1.0 / 300;

    perspectiveTransform = CATransform3DRotate(perspectiveTransform, –60.0f * M_PI / 180.0f, 1.0f, 0.0f, 0.0f);

    layer.transform = perspectiveTransform;

    

    // anti-alias的にViewの辺を滑らかに

    layer.shadowOpacity = 0.01;

}

– (void)start

{

    timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateDisplay:)];

    [timer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

}

– (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

{

    // ここで、動きを書いていく

    

    // 1. ボールをどこに置くか決める。

    int ballPosition = arc4random() % 3;

    UIView *ball = [self createBall:ballPosition];

    [self.view addSubview:ball];

    

    // 2. cup を上げてボールの場所を見せる

    [self up];

    

    

    // 3. ballを非表示に

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{

        ball.alpha = 0.0;

    });

    

    // 4. shuffle 7

    for (int i=1; i<=7; i++) {

        

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (1.1*i + 1) * NSEC_PER_SEC), dispatch_get_main_queue(), ^{

            [self shuffle];

        });

    }    

    

    

    // 5. cup を上げて答えを見せる

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{

        //答えの位置に

        CGPoint current = [[[self.cupInfo objectAtIndex:ballPosition] valueForKey:CupPosition] CGPointValue];

        ball.center = CGPointMake(current.x, current.y + 18);

        ball.alpha = 1.0;

        [self up];

    });

    

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 12 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{

        [ball removeFromSuperview];

    });

    

}

– (void)updateDisplay:(CADisplayLink*)sender

{

    for (NSMutableDictionary *cup in self.cupInfo) {

        UIView *v = [cup objectForKey:CupView];

        NSValue *nextPosition = [cup objectForKey:CupNextPosition];

        if (nextPosition) {

            CGPoint next = [nextPosition CGPointValue];

            CGPoint current = [[cup objectForKey:CupPosition] CGPointValue];

            CGPoint direction = CGPointMake(next.x – current.x, next.y – current.y);            

            

            float x = v.center.x + direction.x * sender.duration;

            

            float r = (current.x – next.x) / 2.0;

            float rad = (next.x – x) / (2 * r);

            

            

            // 机の上は円状に動かすイメージ

            // 実際は、斜め上から見るので、y座標は斜め分だけ変換

            // 円の回転、斜めから見てる想定でsinをさらに

            float y = current.y + r * sin(M_PI * rad) * sin(M_PI * 0.1) ;

            

            // 収束確認

            float dif = abs(next.x – x);

            if (dif < 2.0) {

                // アニメをストップ

                v.center = next;

                [cup setValue:nextPosition forKey:CupPosition];

                [cup removeObjectForKey:CupNextPosition];

                

            } else {

                v.center = CGPointMake(x, y);

            }

        }

    }

}

– (void)up

{

    // cup を全て持ち上げる

    for (NSDictionary *d in self.cupInfo) {

        UIView *cup = [d objectForKey:CupView];

        CGPoint o = cup.center;

        [UIView animateWithDuration:1.5 animations:^{

            cup.center = CGPointMake(o.x, o.y30);

        } completion:^(BOOL finished) {

            [UIView animateWithDuration:0.3 animations:^{

                cup.center = o;

            }];

        }];

    }

}

typedef struct {

    int a;

    int b;

} Combination;

– (void)shuffle

{

    // A, BcupInfoのどれか特定する

    // 3つのカップだから、組み合わせは 3通り

    Combination combination[] = {

        {0, 1}, {0, 2}, {1, 2}

    };

    // ランダムに取り出す

    Combination c = combination[arc4random() % 3];

    

    NSDictionary *a = [self.cupInfo objectAtIndex:c.a];

    NSDictionary *b = [self.cupInfo objectAtIndex:c.b];

    

    // A,Bを入れ替えるために、nextを設定する

    [a setValue:[b objectForKey:CupPosition] forKey:CupNextPosition];

    [b setValue:[a objectForKey:CupPosition] forKey:CupNextPosition];

    

}

– (void)viewDidDisappear:(BOOL)animated

{

    [timer invalidate];

    timer = nil;

    self.cupInfo = nil;

    self.refPoint = nil;

    

}

– (void)didReceiveMemoryWarning

{

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}

@end