回りながら落ちるところには、

オイラー法を利用して、振り子の計算をしています。

振り子がくるくる回りだしてしまうくらいの誤差を生じる近似式です。

角度の切り替えを考えずに組んだら奇妙な味のある動きになった。

環境

XcodeのiOS6 iPhone Simulatorで動かしています。

ポイント

車輪のどちらかが、道に接している場合、オイラー法で回転を計算。

車は、iPhoneの画面サイズと同じ大きさのUIViewをキャンバスにして、

車輪の座標からタイヤ、ボディーを描画していく。




サンプルコード

@interface Car : UIView

@property (nonatomic, assign) CGPoint rear, front;

@property (nonatomic, assign) float angle, velocity;

@property (nonatomic, assign) bool groundFront;

– (void)setAngle:(float)angle velocity:(float)velocity;

@end

@implementation Car

@synthesize rear, front, angle, velocity, groundFront;

– (void)drawRect:(CGRect)rect

{

    

    [[UIColor blueColor] set];

    

    

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    

    CGContextSetLineWidth(ctx, 5);

    CGContextMoveToPoint(ctx, front.x, front.y);

    CGContextAddLineToPoint(ctx, rear.x, rear.y);

    CGContextStrokePath(ctx);

    

    CGContextMoveToPoint(ctx, front.x, front.y);

    CGContextAddArc(ctx, front.x, front.y, 12, 0, 2*M_PI, NO);

    CGContextMoveToPoint(ctx, rear.x, rear.y);

    CGContextAddArc(ctx, rear.x, rear.y, 12, 0, 2*M_PI, NO);

    CGContextFillPath(ctx);

}

– (void)setAngle:(float)a velocity:(float)v

{

    self.angle = a;

    self.velocity = v;

    

    float r = self.bounds.size.height * 0.08;

    if (groundFront) {

        float x1 = r * cos(self.angle) + self.front.x;

        float y1 = r * sin(self.angle) + self.front.y;

        self.rear = CGPointMake(x1, y1);

    } else {

        float x0 = r * cos(self.angle+M_PI) + self.rear.x;

        float y0 = r * sin(self.angle+M_PI) + self.rear.y;

        self.front = CGPointMake(x0, y0);

    }

    

    [self setNeedsDisplay];

}

@end

#import “ViewController.h”

#import <QuartzCore/QuartzCore.h>

@interface ViewController () {

    NSTimer *timer;

    Car *car;

}

@end

@implementation ViewController

typedef struct

{

    float angle;

    float velocity;

} Parameter;

– (void)viewDidLoad

{

    [super viewDidLoad];

    

    self.view.backgroundColor = [UIColor whiteColor];

    

    [self createRoad];

    

    [self createCar];

    

    [self startTimer];

    

}

– (void)createCar

{

    car = [[Car alloc] initWithFrame:self.view.bounds];

    car.rear = CGPointMake(100, 50);

    [car setAngle:M_PI velocity:0];

    car.backgroundColor = [UIColor clearColor];

    [self.view addSubview:car];

}

– (void)createRoad

{

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

        

        float x = 30 + (i%2) * 60;

        UIView *road = [[UIView alloc] initWithFrame:CGRectMake(x, 100 + i*120, 200, 10)];

        road.backgroundColor = [UIColor blackColor];

        

        float angle = M_PI * 0.05 * ((i%2) ? –1 : 1);

        road.transform = CGAffineTransformMakeRotation(angle);

        [self.view addSubview:road];

        

        road.tag = 10;

    }

}

– (void)startTimer

{

    timer = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0 target:self selector:@selector(tick:) userInfo:nil repeats:YES];

}

– (void)tick:(NSTimer*)sender

{

    BOOL front = NO;

    BOOL rear = NO;

    int roadNumber = 0;

    int count = 0;

    for (UIView *road in self.view.subviews) {

        CGPoint fp = [car convertPoint:car.front toView:road];

        CGPoint rp = [car convertPoint:car.rear toView:road];

        fp = CGPointMake(fp.x, fp.y + 10);

        rp = CGPointMake(rp.x, rp.y + 10);

        

        if (road.tag == 10)

        {

            count++;

            if([road.layer.presentationLayer containsPoint:fp])

            {

                car.groundFront = YES;

                front = YES;

                roadNumber = count;

            }

            if ([road.layer.presentationLayer containsPoint:rp])

            {

                car.groundFront = NO;

                rear = YES;

                roadNumber = count;

            }

        }

    }

    //[self intersectLine:0 point:[car convertPoint:car.rear toView:self.view]];

    

    if (front && rear)

    {

        

        float v = 0;

        if (roadNumber == 2) {

            v = –1;

        } else {

            v = 1;

        }

        

        car.front = CGPointMake(car.front.x+v, car.front.y);

        car.rear = CGPointMake(car.rear.x+v, car.rear.y);

        [car setNeedsDisplay];

    }

    else if (front) {

        [self turn];

        

        float v = 0;

        if (roadNumber == 2) {

            v = –0.1;

        } else {

            v = 0.1;

        }

        

        car.front = CGPointMake(car.front.x + v, car.front.y);

        car.rear = CGPointMake(car.rear.x + v, car.rear.y);

        [car setNeedsDisplay];

        

    }

    else if(rear)

    {

        [self turn];

        

        float v = 0;

        if (roadNumber == 2) {

            v = –0.1;

        } else {

            v = 0.1;

        }

        

        car.front = CGPointMake(car.front.x+0.1, car.front.y);

        car.rear = CGPointMake(car.rear.x+0.1, car.rear.y);

        [car setNeedsDisplay];

    }

    else

    {

        float dy = 9.8 * sender.timeInterval;

        car.front = CGPointMake(car.front.x, car.front.y + dy);

        car.rear = CGPointMake(car.rear.x, car.rear.y + dy);

        [car setNeedsDisplay];

    }

}

– (void)turn

{

    Parameter old;

    old.angle = car.angle;

    old.velocity = car.velocity;

    

    Parameter param = [self euler:old delta:1.0/60.0 radius:car.bounds.size.height * 0.2];

    [car setAngle:param.angle velocity:param.velocity];

}

– (Parameter)euler:(Parameter)old delta:(float)dt radius:(float)L

{

    Parameter new;

    new.angle = old.angle + old.velocity * dt;

    new.velocity = old.velocity – (9.8 /L) * sin(old.angle+M_PI*0.5) * dt;

    return new;

}

– (void)didReceiveMemoryWarning

{

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}

@end