カップを机の上でクルクルシャッフルして、
ボールはどこかな?というのを作る方法のメモ
(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.y – 19);
[cup addSubview:top];
cup.layer.zPosition = 100;
return cup;
}
// 台形を作るためにレイヤーをカスタマイズ
– (void)layerCustom:(CALayer*)layer
{
// CATransform3D.m34は3Dっぽく見せるためのパラメータ
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.y – 30);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
cup.center = o;
}];
}];
}
}
typedef struct {
int a;
int b;
} Combination;
– (void)shuffle
{
// A, BをcupInfoのどれか特定する
// 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