iOS 動(dòng)畫(huà)實(shí)戰(zhàn)之釣魚(yú)小游戲

前言

最近寫(xiě)了一款釣魚(yú)小游戲,自己平時(shí)也沒(méi)做過(guò)游戲钦购,本來(lái)以為這種游戲要用cocos2d什么的實(shí)現(xiàn)戴陡,后來(lái)發(fā)現(xiàn)其實(shí)動(dòng)畫(huà)就可以實(shí)現(xiàn)很棒的效果,先看看效果圖命辖。

釣魚(yú)游戲.gif

思維導(dǎo)圖

首先我們看下思維導(dǎo)圖况毅,本游戲主要分為4大塊分蓖,其中魚(yú)的實(shí)現(xiàn)最為復(fù)雜

思維導(dǎo)圖

項(xiàng)目結(jié)構(gòu)

屏幕快照 2017-09-13 下午3.46.18.png

準(zhǔn)備工作

首先將需要的圖準(zhǔn)備好,這個(gè)魚(yú)其實(shí)就是一組圖片尔许,圖片大小固定么鹤,每一幀位置變化,所以看起來(lái) 是一個(gè)上下游動(dòng)的魚(yú)母债。


動(dòng)態(tài).gif
單張圖片

魚(yú)鉤模塊

  • 擺動(dòng)動(dòng)畫(huà)
    魚(yú)鉤的擺動(dòng)范圍是[M_PI/4.0,-M_PI/4.0] (垂直向下為0度午磁,順時(shí)針為正),這里利用了計(jì)時(shí)器進(jìn)行角度的更改,計(jì)時(shí)器用的CADisplayLink毡们,它是一個(gè)和屏幕刷新率一致的定時(shí)器迅皇,如果沒(méi)有卡頓,每秒刷新次數(shù)是60次衙熔,本Demo很多計(jì)時(shí)器用的都是CADisplayLink登颓。下面是魚(yú)鉤的主要代碼(重點(diǎn):1、設(shè)置錨點(diǎn)后重置frame红氯,2框咙、更改角度,3痢甘、旋轉(zhuǎn))喇嘱。 其中定義了一個(gè)block將角度angle回傳到FishingView界面計(jì)算魚(yú)鉤落到池塘的位置。
@property (nonatomic, strong) CADisplayLink *linkTimer;
@property (nonatomic, assign) BOOL isReduce;//改變方向
@property (nonatomic, assign) CGFloat angle;//擺動(dòng)的角度
- (void)initView{
    [self setAnchorPoint:CGPointMake(0.5, 0) forView:self]; 
    UIImageView *gouImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, self.frame.size.height - 35 , 30, 35)];
    gouImageView.image = [UIImage imageNamed:@"fish_catcher_tong"];
    [self addSubview:gouImageView];
    UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake((self.frame.size.width - 3)/2.0, 0, 3, self.frame.size.height - 35)];
    lineView.backgroundColor = HEXCOLOR(0x9e664a);
    [self addSubview:lineView];
    //  創(chuàng)建一個(gè)對(duì)象計(jì)時(shí)器
    _linkTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(hookMove)];
    //啟動(dòng)這個(gè)link
    [_linkTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}

//設(shè)置錨點(diǎn)后重新設(shè)置frame
- (void) setAnchorPoint:(CGPoint)anchorpoint forView:(UIView *)view{
    CGRect oldFrame = view.frame;
    view.layer.anchorPoint = anchorpoint;
    view.frame = oldFrame;
}

#pragma mark - 魚(yú)鉤擺動(dòng)
- (void)hookMove{
    
    if (self.isReduce){
        _angle-=1.8*cos(1.5*_angle)*0.01;//計(jì)算角度,利用cos模擬上升過(guò)程中減慢塞栅,下降加快
        if (_angle < -M_PI/180*45){
            self.isReduce = NO;
        }
    }else {
        _angle+=1.8*cos(1.5*_angle)*0.01;
        if (_angle > M_PI/180*45){
            self.isReduce = YES;
        }
    }
    if (self.angleBlock){
        self.angleBlock(_angle);
    }
//    DLog(@"魚(yú)鉤角度%f",_angle);
//旋轉(zhuǎn)動(dòng)畫(huà)
    self.transform = CGAffineTransformMakeRotation(_angle);
}

魚(yú)模塊

魚(yú)模塊是繼承自UIImageView的一個(gè)類(lèi)
魚(yú)模塊提供了三種初始化方式者铜,可垂釣的魚(yú)、不可垂釣的魚(yú)(可以不用)放椰、釣到的魚(yú)三種魚(yú)作烟。
魚(yú)的移動(dòng)方式有兩種,使用枚舉定義砾医,從左到右拿撩,從右到左
魚(yú)的種類(lèi)有六種,用枚舉進(jìn)行了定義
typedef NS_ENUM(NSInteger, FishModelImageViewType){
FishModelImageViewTypeXHY = 0, //小黃魚(yú)
FishModelImageViewTypeSBY = 1, //石斑魚(yú)
FishModelImageViewTypeHSY = 2, //紅杉魚(yú)
FishModelImageViewTypeBWY = 3, //斑紋魚(yú)
FishModelImageViewTypeSHY = 4, //珊瑚魚(yú)
FishModelImageViewTypeSY = 5, //鯊魚(yú)
};
提供了一個(gè)釣到魚(yú)后的代理
FishModelImageViewDelegate
//魚(yú)的種類(lèi)-游動(dòng)方向-贏取金額
方法 - (void)catchTheFishWithType:(FishModelImageViewType)type
andDirection:(FishModelImageViewDirection)dir
andWinCount:(int)count;

  • 1如蚜、動(dòng)態(tài)的魚(yú)

加載動(dòng)態(tài)魚(yú)的方法

  //初始化UIImageView
   UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 55, 55)];
 //如果圖片的名字是有順序的压恒,例如xhy1,xhy2,xhy3...,可以取去掉序號(hào)的名字,然后會(huì)自動(dòng)將所有的圖片都加載進(jìn)來(lái)错邦,duration是動(dòng)畫(huà)時(shí)長(zhǎng)
    imageView.image = [UIImage animatedImageNamed:@"xhy" duration:1];
    [self.view addSubview:imageView];

初始化不同的魚(yú)涎显,不同的魚(yú)大小不同,移動(dòng)的速度不同兴猩,所以動(dòng)畫(huà)時(shí)長(zhǎng)不一樣

//初始化小魚(yú) git動(dòng)畫(huà)時(shí)長(zhǎng)
- (void)initViewWithType:(FishModelImageViewType)type andDuration:(double)time{
    
    self.fishType = type;
    switch (type) {
        case FishModelImageViewTypeXHY://小黃魚(yú)
            self.duration = 6.0;
            self.frame = CGRectMake(-100, 0, 35, 40); //魚(yú)的大小要定義好
            self.image = [UIImage animatedImageNamed:@"xhy" duration:time];
            break;
        case FishModelImageViewTypeSBY://石斑魚(yú)
            self.duration = 7.0;
            self.frame = CGRectMake(-100, 0, 50, 50);
            self.image = [UIImage animatedImageNamed:@"sby" duration:time];
            break;
        case FishModelImageViewTypeHSY://紅杉魚(yú)
            self.duration = 8.0;
            self.frame = CGRectMake(-100, 0, 50, 40);
            self.image = [UIImage animatedImageNamed:@"hsy" duration:time];
            break;
        case FishModelImageViewTypeBWY://斑紋魚(yú)
            self.duration = 8.5;
            self.frame = CGRectMake(-100, 0, 65, 53);
            self.image = [UIImage animatedImageNamed:@"bwy" duration:time];
            break;
        case FishModelImageViewTypeSHY://珊瑚魚(yú)
            self.duration = 9.0;
            self.frame = CGRectMake(-100, 0, 55, 55);
            self.image = [UIImage animatedImageNamed:@"shy" duration:time];
            break;
        case FishModelImageViewTypeSY://鯊魚(yú)
            self.duration = 11.0;
            self.frame = CGRectMake(-200, 0, 145, 90);
            self.image = [UIImage animatedImageNamed:@"sy" duration:time];
            break;
    }
}

  • 2、移動(dòng)的魚(yú)

提供的圖片都是頭朝左的(見(jiàn)上面的動(dòng)圖)早歇,所以從左往右游的話(huà)圖片需要進(jìn)行鏡像反轉(zhuǎn)
對(duì)于魚(yú)是否可以垂釣是用通知進(jìn)行傳遞信息的倾芝,可垂釣讨勤、不可垂釣兩種狀態(tài)
可垂釣:魚(yú)鉤沉到魚(yú)塘?xí)r受到垂釣通知(將魚(yú)鉤底部的坐標(biāo)傳過(guò)來(lái)),現(xiàn)在魚(yú)可以垂釣晨另,當(dāng)根據(jù)上鉤概率等因素判斷魚(yú)上鉤后潭千,對(duì)魚(yú)進(jìn)行旋轉(zhuǎn),然后執(zhí)行上鉤動(dòng)畫(huà)借尿。動(dòng)畫(huà)結(jié)束后執(zhí)行代理刨晴。

//初始化可以垂釣的魚(yú)
- (instancetype)initCanCatchFishWithType:(FishModelImageViewType)type andDirection:(FishModelImageViewDirection)dir{
    if (self = [super init]){
        
        self.direction = dir;
        [self initViewWithType:type andDuration:1];
        if (dir == FishModelImageViewFromLeft){//從左往右,默認(rèn)所有的魚(yú)都是從右往左
            self.transform = CGAffineTransformMakeScale(-1, 1); //鏡像
        }
        [self initFishView];
    }
    return self;
}

#pragma mark - 可以垂釣的魚(yú)(計(jì)時(shí)器)
- (void)initFishView{
    
    //接收可以垂釣的通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationCanCatch:) name:NotificationFishHookStop object:nil];
    //接收不可垂釣的通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationCannotCatch) name:NotificationFishHookMove object:nil];
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeTimer) name:NotificationRemoveFishModelTimer object:nil];
    //創(chuàng)建計(jì)時(shí)器
    _linkTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(fishMove)];
    //啟動(dòng)這個(gè)link(加入到線程池)
    [_linkTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    
    _offsetX = ScreenWidth;
    _offsetY = 100;
    _fishWidth = self.frame.size.width;
    //Y可變高度范圍
    _randomRange = (int) (YuTangHeight - self.frame.size.height - OffSetYRange);
    self.speed = (ScreenWidth + _fishWidth)/self.duration;//游動(dòng)速度
    self.changeX = self.speed/60.0;//計(jì)時(shí)器每秒60次
    DLog(@"魚(yú)游動(dòng)的速度:%f,每次位移:%f", self.speed,self.changeX);
}

魚(yú)移動(dòng)動(dòng)畫(huà)和上鉤動(dòng)畫(huà)

- (void)fishMove{
    
    if (self.direction == FishModelImageViewFromLeft){//從左至右
        if (_offsetX > ScreenWidth + _fishWidth){
            _offsetY = arc4random()%_randomRange + OffSetYRange;
            _offsetX = - _fishWidth - _offsetY;
        }
        _offsetX+=self.changeX;
        
        self.frame = [self resetFrameOrigin:CGPointMake(_offsetX, _offsetY)];
        
        if ([self fishCanBeCatchedWithOffsetX:_offsetX + _fishWidth]){
            NSLog(@"釣到從左到右的魚(yú)了:%ld",(long)self.fishType);
            CGAffineTransform transform = CGAffineTransformIdentity;
            transform = CGAffineTransformScale(transform, -1, 1);//鏡像
            transform = CGAffineTransformRotate(transform, M_PI_2);//旋轉(zhuǎn)90度
            self.transform = transform;
            
            self.frame = [self resetFrameOrigin:CGPointMake(ScreenWidth*2, 0)];
            [self fishCatchedMoveUpWithOffsetX:_offsetX + _fishWidth];
            _offsetX = ScreenWidth + _fishWidth + 1;//重置起點(diǎn)
            _linkTimer.paused = YES;//計(jì)時(shí)器暫停
        }
        
    }else {//從右到左
        
        if (_offsetX < -_fishWidth){
            _offsetY = arc4random()%_randomRange + OffSetYRange;
            _offsetX = ScreenWidth + _offsetY;
        }
        _offsetX-=self.changeX;
        self.frame = [self resetFrameOrigin:CGPointMake(_offsetX, _offsetY)];
        
        if ([self fishCanBeCatchedWithOffsetX:_offsetX]){
            NSLog(@"釣到從右到左的魚(yú)了:%ld",(long)self.fishType);
            self.transform = CGAffineTransformMakeRotation(M_PI_2);
            self.frame = [self resetFrameOrigin:CGPointMake(ScreenWidth*2, 0)];
            
            [self fishCatchedMoveUpWithOffsetX:_offsetX];
            _offsetX = -_fishWidth-1;//重置起點(diǎn)
            _linkTimer.paused = YES;//計(jì)時(shí)器暫停
        }
    }
}

魚(yú)上鉤的概率和贏得的金幣個(gè)數(shù)

//魚(yú)是否可以被釣上來(lái)(根據(jù)概率計(jì)算)
- (BOOL)fishCanBeCatchedWithOffsetX:(CGFloat)offsetX{
    
    if (!self.isCanCatch) return NO;
    if (fabs(offsetX - self.hookX) > self.changeX/2.0) return NO; //判斷是否到達(dá)了可以垂釣的點(diǎn)
    int random = arc4random()%100; //[0,99]
    
    DLog(@"random:%d", random);
    switch (self.fishType) {
        case FishModelImageViewTypeXHY://小黃魚(yú) 80% 金幣2
            if (random < 80){
                self.moneyCount = 2;
                return YES;
            }
            break;
        case FishModelImageViewTypeSBY://石斑魚(yú) 50% 金幣5
            if (random < 50) {
                self.moneyCount = 5;
                return YES;
            }
            break;
        case FishModelImageViewTypeHSY://紅杉魚(yú) 30% 金幣10
            if (random < 30) {
                self.moneyCount = 10;
                return YES;
            }
            break;
        case FishModelImageViewTypeBWY://斑紋魚(yú) 15% 金幣20
            if (random < 15)  {
                self.moneyCount = 20;
                return YES;
            }
            break;
        case FishModelImageViewTypeSHY://珊瑚魚(yú) 5% 金幣50
            if (random < 5)  {
                self.moneyCount = 50;
                return YES;
            }
            break;
        case FishModelImageViewTypeSY://鯊魚(yú) 1% 金幣100
            if (random < 1)  {
                self.moneyCount = 100;
                return YES;
            }
            break;
    }
    self.moneyCount = 0;
    return NO;
}
  • 3.被釣到的魚(yú)

初始化被釣到的魚(yú)方法

//初始化釣到的小魚(yú)
- (instancetype)initCatchedFishWithType:(FishModelImageViewType)type andDirection:(FishModelImageViewDirection)dir{
    if (self = [super init]){
        self.direction = dir;
        [self initViewWithType:type andDuration:0.5];
        //重制x,y坐標(biāo)路翻, 30為魚(yú)鉤的寬度狈癞,85為魚(yú)鉤的長(zhǎng)度
        self.x = (30 - self.width)/2.0;
        self.y = 85 - 6;
        if (dir == FishModelImageViewFromLeft){//從左往右,默認(rèn)所有的魚(yú)都是從右往左
            CGAffineTransform transform = CGAffineTransformIdentity;
            transform = CGAffineTransformScale(transform, -1, 1);//鏡像
            transform = CGAffineTransformRotate(transform, M_PI_2);//旋轉(zhuǎn)90度
            self.transform = transform;
        }else {
            self.transform = CGAffineTransformMakeRotation(M_PI_2);
        }
    }
    return self;
}

當(dāng)魚(yú)被抓到后茂契,執(zhí)行上鉤動(dòng)畫(huà)

//魚(yú)被抓到后往上游
- (void)fishCatchedMoveUpWithOffsetX:(CGFloat) offsetX{
    
    //鉤沉到魚(yú)塘的高度為45
    //位移動(dòng)畫(huà)
    CABasicAnimation *ani = [CABasicAnimation animationWithKeyPath:@"position"];
    ani.duration = 0.7;
    if (self.fishType == FishModelImageViewTypeSY){//鯊魚(yú)由于太長(zhǎng)蝶桶,所以不進(jìn)行上游動(dòng)畫(huà)了
        ani.fromValue = [NSValue valueWithCGPoint:CGPointMake(offsetX,45 + _fishWidth/2.0)];
        ani.toValue = [NSValue valueWithCGPoint:CGPointMake(_hookX, 45 + _fishWidth/2.0)];
    }else {
        ani.fromValue = [NSValue valueWithCGPoint:CGPointMake(offsetX, (_offsetY < 60) ? 45 + _fishWidth/2.0 : _offsetY)];//離鉤子近的話(huà)則不進(jìn)行動(dòng)畫(huà)
        ani.toValue = [NSValue valueWithCGPoint:CGPointMake(_hookX, 45 + _fishWidth/2.0)];
    }
    ani.delegate = self;
    //設(shè)置這兩句動(dòng)畫(huà)結(jié)束會(huì)停止在結(jié)束位置
    [ani setValue:kFishCatchedMoveUpValue forKey:kFishCatchedMoveUpKey];
    [self.layer addAnimation:ani forKey:kFishCatchedMoveUpKey];
}

魚(yú)上游動(dòng)畫(huà)結(jié)束后將翻轉(zhuǎn)的魚(yú)復(fù)位,然后執(zhí)行代理將釣到的魚(yú)通過(guò)代理傳遞出去

#pragma mark - CAAnimationDelegate
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    if (flag){
         if ([[anim valueForKey:kFishCatchedMoveUpKey] isEqualToString:kFishCatchedMoveUpValue]){//魚(yú)上游
            
            if (self.direction == FishModelImageViewFromLeft){
                CGAffineTransform transform = CGAffineTransformIdentity;
                transform = CGAffineTransformScale(transform, -1, 1);//鏡像
                transform = CGAffineTransformRotate(transform, 0);//旋轉(zhuǎn)90度
                self.transform = transform;

            }else {
                self.transform = CGAffineTransformMakeRotation(0);
            }
            if ([self.delegate respondsToSelector:@selector(catchTheFishWithType:andDirection:andWinCount:)]){
                [self.delegate catchTheFishWithType:self.fishType andDirection:self.direction andWinCount:self.moneyCount];
            }
        }
   }
}

金幣動(dòng)畫(huà)&&加分動(dòng)畫(huà)

金幣動(dòng)畫(huà)可以參考我的這篇文章:iOS 金幣入袋(收金幣)動(dòng)畫(huà)
加分動(dòng)畫(huà)比較簡(jiǎn)單掉冶,一個(gè)位移加透明度的組合動(dòng)畫(huà)實(shí)現(xiàn)真竖,具體可看代碼

釣魚(yú)View

這是實(shí)現(xiàn)界面了,本來(lái)是寫(xiě)在VC里的厌小,后來(lái)發(fā)現(xiàn)也能提取出來(lái)恢共,所有就提取出來(lái)了,在調(diào)用時(shí)非常簡(jiǎn)單璧亚,像正常View一樣初始化后添加到主View上即可讨韭,在viewDidDisappear中講資源釋放掉即可。

- (void)viewDidLoad {
    [super viewDidLoad];
    _fishView = [[FishingView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:_fishView];
}
- (void)viewDidDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    [_fishView removeFishViewResource];
}
  • 1.初始化魚(yú)鉤

初始化魚(yú)鉤
講魚(yú)鉤擺動(dòng)的角度通過(guò)代理傳到本界面

#pragma mark - 魚(yú)鉤
- (void)initHookView{
    
    _fishHookView = [[FishHookView alloc] initWithFrame:CGRectMake((ScreenWidth - 30)/2.0, 5, 30, 85)];
    __weak typeof (self) weakSelf = self;
    _fishHookView.angleBlock = ^(CGFloat angle) {
        weakSelf.angle = angle;
    };
    [self addSubview:_fishHookView];
    
    UIImageView *yuGanImageView = [[UIImageView alloc] initWithFrame:CGRectMake(ScreenWidth/2.0 - 2, 0, ScreenWidth/2.0, 50)];
    yuGanImageView.image = [UIImage imageNamed:@"fish_gan_tong"];
    [self addSubview:yuGanImageView];
}

下鉤動(dòng)畫(huà):魚(yú)塘增加了點(diǎn)擊手勢(shì)涨岁,點(diǎn)擊后執(zhí)行釣魚(yú)動(dòng)作拐袜,暫停魚(yú)鉤擺動(dòng)計(jì)時(shí)器,下鉤動(dòng)畫(huà)結(jié)束后發(fā)送通知高速魚(yú)模塊可以上鉤了梢薪,并將魚(yú)鉤的底部中心坐標(biāo)傳遞過(guò)去蹬铺,魚(yú)線用CAShapeLayer繪制,并執(zhí)行strokeEnd動(dòng)畫(huà)

//釣魚(yú)動(dòng)作
- (void)fishBtnAction{
    
    if (self.fishHookState != FishHookStateShake) return; //不是搖擺狀態(tài)不可出桿
    
    [self.fishHookView hookTimerPause];//暫停魚(yú)鉤的計(jì)時(shí)器
    
    double degree = _angle*180/M_PI;//度數(shù)
    double rate = tan(_angle);//比列
    DLog(@"degree:%f---rate:%f",degree,rate);
    //計(jì)算出來(lái)線終點(diǎn)x的位置 , 鉤到水里的深度不變秉撇,即y是固定的
    _lineOffsetX = ScreenWidth/2.0 - (FishLineHeigth)*rate;
    
    //鉤子底部xy值
    _hookBottomX = ScreenWidth/2.0 - (FishLineHeigth + FishHookHeight)*rate;
    _hookBottomY = FishLineHeigth + FishHookHeight;
    
    //動(dòng)畫(huà)時(shí)間
    double aniDuration = [self hookOutOfRiver] ? 0.5 : 1;
    
    //繪制路徑
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(ScreenWidth/2.0 ,5)];
    [path addLineToPoint:CGPointMake(_lineOffsetX, FishLineHeigth)];
    
    //圖形設(shè)置
    _linePathLayer = [CAShapeLayer layer];
    _linePathLayer.frame = self.bounds;
    _linePathLayer.path = path.CGPath;
    _linePathLayer.strokeColor = [HEXCOLOR(0x9e664a) CGColor];
    _linePathLayer.fillColor = nil;
    _linePathLayer.lineWidth = 3.0f;
    _linePathLayer.lineJoin = kCALineJoinBevel;
    [self.layer addSublayer:_linePathLayer];
    
    //下鉤動(dòng)畫(huà)
    CAKeyframeAnimation *ani = [CAKeyframeAnimation animationWithKeyPath:@"strokeEnd"];
    ani.duration = aniDuration;
    ani.values = @[@0,@0.8,@1];
    ani.keyTimes = @[@0,@0.6,@1];
    ani.delegate = self;
    [ani setValue:kLineDownAnimationValue forKey:kLineDownAnimationKey];
    [_linePathLayer addAnimation:ani forKey:kLineDownAnimationKey];
    
    //位移動(dòng)畫(huà)
    _hookAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    //移動(dòng)路徑
    CGFloat tempOffsetX =  ScreenWidth/2.0 - (FishLineHeigth*0.8)*rate;
    NSValue *p1 = [NSValue valueWithCGPoint:CGPointMake(ScreenWidth/2.0 ,5)];
    NSValue *p2 = [NSValue valueWithCGPoint:CGPointMake(tempOffsetX, FishLineHeigth*0.8)];
    NSValue *p3 = [NSValue valueWithCGPoint:CGPointMake(_lineOffsetX, FishLineHeigth)];
    _hookAnimation.duration = aniDuration;
    _hookAnimation.values = @[p1,p2,p3];
    _hookAnimation.keyTimes = @[@0,@0.7,@1];//動(dòng)畫(huà)分段時(shí)間
    //設(shè)置這兩句動(dòng)畫(huà)結(jié)束會(huì)停止在結(jié)束位置
    _hookAnimation.removedOnCompletion = NO;
    _hookAnimation.fillMode=kCAFillModeForwards;
    [_fishHookView.layer addAnimation:_hookAnimation forKey:@"goukey"];
    
}

釣魚(yú)動(dòng)作:下鉤動(dòng)畫(huà)結(jié)束后計(jì)時(shí)器打開(kāi)甜攀,執(zhí)行此方法;倒計(jì)時(shí)為最后一秒時(shí)魚(yú)不可上鉤(魚(yú)上鉤動(dòng)畫(huà)0.7s,要留上鉤動(dòng)畫(huà)的時(shí)間)琐馆;計(jì)時(shí)器為0時(shí)發(fā)送不可垂釣通知告訴魚(yú)模塊不可上鉤了规阀,并執(zhí)行上鉤動(dòng)畫(huà)。

//鉤子停在底部
- (void)hookStop:(NSTimer *)timer{
    _stopDuration-=1;
    
    //最后一秒不可上鉤
    if (_stopDuration == 1){
        //發(fā)送不可垂釣的通知
        self.fishHookState = FishHookStateUp;
        [[NSNotificationCenter defaultCenter] postNotificationName:NotificationFishHookMove object:nil];
    }
    if (_stopDuration <= 0){
        //關(guān)閉計(jì)時(shí)器
        [timer setFireDate:[NSDate distantFuture]];
        
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:CGPointMake(_lineOffsetX, FishLineHeigth)];
        [path addLineToPoint:CGPointMake(ScreenWidth/2.0 ,5)];
        _linePathLayer.path = path.CGPath;
        
        //動(dòng)畫(huà)時(shí)間
        double aniDuration = [self hookOutOfRiver] ? 0.5 : 1;
        
        //上鉤
        CABasicAnimation *ani = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
        ani.duration = aniDuration;
        ani.fromValue = [NSNumber numberWithFloat:0];
        ani.toValue = [NSNumber numberWithFloat:1];
        ani.delegate = self;
        ani.removedOnCompletion = NO;
        ani.fillMode=kCAFillModeForwards;
        [ani setValue:kLineUpAnimationValue forKey:kLineUpAnimationKey];
        [_linePathLayer addAnimation:ani forKey:kLineUpAnimationKey];
        
        [_fishHookView.layer removeAllAnimations];
        
        NSValue *p1 = [NSValue valueWithCGPoint:CGPointMake(ScreenWidth/2.0 ,5)];
        NSValue *p2 = [NSValue valueWithCGPoint:CGPointMake(_lineOffsetX, FishLineHeigth)];
        _hookAnimation.duration = aniDuration;
        _hookAnimation.values = @[p2,p1];
        _hookAnimation.keyTimes = @[@0,@1];
        [_fishHookView.layer addAnimation:_hookAnimation forKey:@"goukey"];
    }
}

金幣動(dòng)畫(huà)&加分動(dòng)畫(huà)
下鉤動(dòng)畫(huà)開(kāi)始瘦麸,總金幣減少10個(gè)
上鉤動(dòng)畫(huà)開(kāi)始谁撼,發(fā)送不可垂釣通知,魚(yú)鉤狀態(tài)為上鉤狀態(tài)
如果有捉到魚(yú)(根據(jù)魚(yú)模塊代理是否執(zhí)行判斷是否捉到)滋饲,執(zhí)行金幣動(dòng)畫(huà)和加分動(dòng)畫(huà)
下鉤動(dòng)畫(huà)結(jié)束厉碟,發(fā)送可以垂釣的通知給魚(yú)模塊喊巍,并將魚(yú)鉤坐標(biāo)傳遞過(guò)去,開(kāi)啟上鉤的計(jì)時(shí)器
上鉤動(dòng)畫(huà)結(jié)束箍鼓,更改魚(yú)鉤狀態(tài)崭参,移除一些View,魚(yú)鉤繼續(xù)擺動(dòng)

#pragma mark - CAAnimationDelegate 動(dòng)畫(huà)代理
//動(dòng)畫(huà)開(kāi)始
- (void)animationDidStart:(CAAnimation *)anim{
    
    //下鉤動(dòng)畫(huà)開(kāi)始
    if ([[anim valueForKey:kLineDownAnimationKey] isEqualToString:kLineDownAnimationValue]){
        self.fishHookState = FishHookStateDown;//下鉤狀態(tài)
        //錢(qián)數(shù)
        self.moneyLabel.text = [NSString stringWithFormat:@"%d", _totalMoney-=10];
        self.winMoney = 0;
        
    }else if ([[anim valueForKey:kLineUpAnimationKey] isEqualToString:kLineUpAnimationValue]){//上鉤動(dòng)畫(huà)開(kāi)始
        self.fishHookState = FishHookStateUp;//上鉤狀態(tài)
        [[NSNotificationCenter defaultCenter] postNotificationName:NotificationFishHookMove object:nil];
    }
    
    if (self.isCatched){//釣到魚(yú)后落金幣
        HHShootButton *button = [[HHShootButton alloc] initWithFrame:CGRectMake(_lineOffsetX, 0, 10, 10) andEndPoint:CGPointMake(10, 200)];
        button.setting.iconImage = [UIImage imageNamed:@"coin"];
        button.setting.animationType = ShootButtonAnimationTypeLine;
        [self.bgImageView addSubview:button];
        [self bringSubviewToFront:button];
        [button startAnimation];
        
        HHWinMoneyLabel *winLabel = [[HHWinMoneyLabel alloc] initWithFrame:CGRectMake(_lineOffsetX - 100/2, ScreenFullHeight - FishSeaHeight, 100, 30)];
        winLabel.text = [NSString stringWithFormat:@"+%d",_winMoney];
        [self addSubview:winLabel];
        
        self.isCatched = !self.isCatched;
        //金幣總數(shù)
        self.moneyLabel.text = [NSString stringWithFormat:@"%d", _totalMoney+=self.winMoney];
    }
}

//動(dòng)畫(huà)結(jié)束
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    if (flag){
        
        if ([[anim valueForKey:kLineDownAnimationKey] isEqualToString:kLineDownAnimationValue]){//下鉤動(dòng)畫(huà)結(jié)束
            
            self.fishHookState = FishHookStateStop;//垂釣狀態(tài)
            //鉤的位置
            NSDictionary *dic = @{@"offsetX":[NSString stringWithFormat:@"%.2f",_hookBottomX],@"offsetY":[NSString stringWithFormat:@"%.2f",_hookBottomY]};
            //發(fā)送可以垂釣的通知,鉤的位置傳過(guò)去
            [[NSNotificationCenter defaultCenter] postNotificationName:NotificationFishHookStop object:nil userInfo:dic];
            
            _stopDuration = [self hookOutOfRiver] ? 1 : arc4random()%3 + 3; //默認(rèn)時(shí)間[3,5),拋到岸上1s
            //開(kāi)啟上鉤定時(shí)器
            [_fishTimer setFireDate:[NSDate distantPast]];
            
        }else if ([[anim valueForKey:kLineUpAnimationKey] isEqualToString:kLineUpAnimationValue]){//上鉤動(dòng)畫(huà)結(jié)束
            
            self.fishHookState = FishHookStateShake;//搖擺狀態(tài)
            [_linePathLayer removeFromSuperlayer];
            [_fishHookView hoolTimerGoOn];//魚(yú)鉤計(jì)時(shí)器繼續(xù)
            _catchedHeight = 0;
            //移除釣上來(lái)的魚(yú)
            [self removeTheCatchedFishes];
        }
    }
}

魚(yú)模塊的代理方法
創(chuàng)建一個(gè)被釣到的魚(yú)款咖,加在魚(yú)鉤上何暮,這樣便可和魚(yú)鉤一起執(zhí)行上鉤動(dòng)畫(huà)了

#pragma mark - FishModelImageViewDelegate  釣到魚(yú)后的代理
- (void)catchTheFishWithType:(FishModelImageViewType)type andDirection:(FishModelImageViewDirection)dir andWinCount:(int)count{
    self.isCatched = YES;
    
    FishModelImageView *fishImageView = [[FishModelImageView alloc] initCatchedFishWithType:type andDirection:dir];
    [self.fishHookView addSubview:fishImageView];
    
    fishImageView.y = fishImageView.y + _catchedHeight;
    _catchedHeight += 8;//每釣到一個(gè)y坐標(biāo)往下移
    
    //贏得錢(qián)數(shù)
    self.winMoney += count;
}

  • 2.初始化魚(yú)塘
    簡(jiǎn)單的創(chuàng)建魚(yú)背景并添加點(diǎn)擊手勢(shì)

  • 3.初始化魚(yú)
    通過(guò)for循環(huán)可以創(chuàng)建出多個(gè)某種魚(yú)

//小黃魚(yú)
    for (int i = 0; i < 8; i++){
        FishModelImageView *model1 = [[FishModelImageView alloc] initCanCatchFishWithType:FishModelImageViewTypeXHY andDirection: (i%2 == 0) ? FishModelImageViewFromRight : FishModelImageViewFromLeft];
        model1.delegate = self;
        [self.bgImageView addSubview:model1];
    }

  • 4.資源移除
    由于計(jì)時(shí)器不銷(xiāo)毀會(huì)造成循環(huán)引用,導(dǎo)致內(nèi)存泄漏铐殃,所以必須手動(dòng)移除他海洼,還有動(dòng)畫(huà)如果執(zhí)行了代理,并且設(shè)置了結(jié)束后停留在結(jié)束位置背稼,也會(huì)得不到釋放贰军,所以都要手動(dòng)釋放資源
- (void)removeFishViewResource{
    //解決魚(yú)鉤上鉤動(dòng)畫(huà)循環(huán)引用的問(wèn)題
    _linePathLayer = nil;
    //釣魚(yú)計(jì)時(shí)器關(guān)閉
    [_fishTimer invalidate];
    _fishTimer = nil;
    //釋放魚(yú)鉤的計(jì)時(shí)器
    [self.fishHookView hoolTimerInvalidate];
    //發(fā)送通知釋放小魚(yú)資源
    [[NSNotificationCenter defaultCenter] postNotificationName:NotificationRemoveFishModelTimer object:nil];
}

總結(jié)

至此,本游戲已經(jīng)完成了蟹肘,寫(xiě)的比較多词疼,也比較亂,有什么不好的地方歡迎批評(píng)指正帘腹,希望對(duì)大伙有所幫助吧贰盗,本demo地址傳送門(mén)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末阳欲,一起剝皮案震驚了整個(gè)濱河市舵盈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌球化,老刑警劉巖秽晚,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異筒愚,居然都是意外死亡赴蝇,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)巢掺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)句伶,“玉大人,你說(shuō)我怎么就攤上這事陆淀】加啵” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵轧苫,是天一觀的道長(zhǎng)楚堤。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么身冬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任鳄袍,我火速辦了婚禮,結(jié)果婚禮上吏恭,老公的妹妹穿的比我還像新娘。我一直安慰自己重罪,他們只是感情好樱哼,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著剿配,像睡著了一般搅幅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上呼胚,一...
    開(kāi)封第一講書(shū)人閱讀 50,096評(píng)論 1 291
  • 那天茄唐,我揣著相機(jī)與錄音,去河邊找鬼蝇更。 笑死沪编,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的年扩。 我是一名探鬼主播蚁廓,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼厨幻!你這毒婦竟也來(lái)了相嵌?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤况脆,失蹤者是張志新(化名)和其女友劉穎饭宾,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體格了,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡看铆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了笆搓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片性湿。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖满败,靈堂內(nèi)的尸體忽然破棺而出肤频,到底是詐尸還是另有隱情,我是刑警寧澤算墨,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布宵荒,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏报咳。R本人自食惡果不足惜侠讯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望暑刃。 院中可真熱鬧厢漩,春花似錦、人聲如沸岩臣。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)架谎。三九已至炸宵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間谷扣,已是汗流浹背土全。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留会涎,地道東北人裹匙。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像在塔,于是被迫代替她去往敵國(guó)和親幻件。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,872評(píng)論 25 707
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫(huà)效果蛔溃,實(shí)現(xiàn)這些動(dòng)畫(huà)的過(guò)程并不復(fù)雜绰沥,今天將帶大家一窺ios動(dòng)畫(huà)全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,473評(píng)論 6 30
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫(huà)效果贺待,實(shí)現(xiàn)這些動(dòng)畫(huà)的過(guò)程并不復(fù)雜徽曲,今天將帶大家一窺iOS動(dòng)畫(huà)全貌。在這里你可以看...
    F麥子閱讀 5,105評(píng)論 5 13
  • 像 一場(chǎng)遺憾麸塞。 是老鼠 每個(gè)夜晚的躥動(dòng) 也是生命的不安秃臣。 鉆洞是它的本能 但當(dāng)發(fā)現(xiàn)每個(gè)人的心臟都是空的 這個(gè)洞真...
    犭一閱讀 142評(píng)論 0 0
  • 元認(rèn)知能力:對(duì)自己思考過(guò)程的認(rèn)知與理解。 一個(gè)人的財(cái)富創(chuàng)造能力最終也只與元認(rèn)知能力有關(guān)哪工,其他...
    楓情物語(yǔ)閱讀 588評(píng)論 0 1