iOS 動畫實戰(zhàn)之釣魚小游戲

前言

最近寫了一款釣魚小游戲,自己平時也沒做過游戲,本來以為這種游戲要用cocos2d什么的實現(xiàn),后來發(fā)現(xiàn)其實動畫就可以實現(xiàn)很棒的效果,先看看效果圖。

釣魚游戲.gif

思維導圖

首先我們看下思維導圖,本游戲主要分為4大塊,其中魚的實現(xiàn)最為復雜

思維導圖

項目結構

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

準備工作

首先將需要的圖準備好,這個魚其實就是一組圖片,圖片大小固定,每一幀位置變化,所以看起來 是一個上下游動的魚。


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

魚鉤模塊

  • 擺動動畫
    魚鉤的擺動范圍是[M_PI/4.0,-M_PI/4.0] (垂直向下為0度,順時針為正),這里利用了計時器進行角度的更改,計時器用的CADisplayLink,它是一個和屏幕刷新率一致的定時器,如果沒有卡頓,每秒刷新次數(shù)是60次,本Demo很多計時器用的都是CADisplayLink。下面是魚鉤的主要代碼(重點:1、設置錨點后重置frame,2、更改角度,3、旋轉(zhuǎn))。 其中定義了一個block將角度angle回傳到FishingView界面計算魚鉤落到池塘的位置。
@property (nonatomic, strong) CADisplayLink *linkTimer;
@property (nonatomic, assign) BOOL isReduce;//改變方向
@property (nonatomic, assign) CGFloat angle;//擺動的角度
- (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)建一個對象計時器
    _linkTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(hookMove)];
    //啟動這個link
    [_linkTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}

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

#pragma mark - 魚鉤擺動
- (void)hookMove{
    
    if (self.isReduce){
        _angle-=1.8*cos(1.5*_angle)*0.01;//計算角度,利用cos模擬上升過程中減慢,下降加快
        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(@"魚鉤角度%f",_angle);
//旋轉(zhuǎn)動畫
    self.transform = CGAffineTransformMakeRotation(_angle);
}

魚模塊

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

  • 1、動態(tài)的魚

加載動態(tài)魚的方法

  //初始化UIImageView
   UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 55, 55)];
 //如果圖片的名字是有順序的,例如xhy1,xhy2,xhy3...,可以取去掉序號的名字,然后會自動將所有的圖片都加載進來,duration是動畫時長
    imageView.image = [UIImage animatedImageNamed:@"xhy" duration:1];
    [self.view addSubview:imageView];

初始化不同的魚,不同的魚大小不同,移動的速度不同,所以動畫時長不一樣

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

  • 2、移動的魚

提供的圖片都是頭朝左的(見上面的動圖),所以從左往右游的話圖片需要進行鏡像反轉(zhuǎn)
對于魚是否可以垂釣是用通知進行傳遞信息的,可垂釣、不可垂釣兩種狀態(tài)
可垂釣:魚鉤沉到魚塘時受到垂釣通知(將魚鉤底部的坐標傳過來),現(xiàn)在魚可以垂釣,當根據(jù)上鉤概率等因素判斷魚上鉤后,對魚進行旋轉(zhuǎn),然后執(zhí)行上鉤動畫。動畫結束后執(zhí)行代理。

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

#pragma mark - 可以垂釣的魚(計時器)
- (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)建計時器
    _linkTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(fishMove)];
    //啟動這個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;//游動速度
    self.changeX = self.speed/60.0;//計時器每秒60次
    DLog(@"魚游動的速度:%f,每次位移:%f", self.speed,self.changeX);
}

魚移動動畫和上鉤動畫

- (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(@"釣到從左到右的魚了:%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;//重置起點
            _linkTimer.paused = YES;//計時器暫停
        }
        
    }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(@"釣到從右到左的魚了:%ld",(long)self.fishType);
            self.transform = CGAffineTransformMakeRotation(M_PI_2);
            self.frame = [self resetFrameOrigin:CGPointMake(ScreenWidth*2, 0)];
            
            [self fishCatchedMoveUpWithOffsetX:_offsetX];
            _offsetX = -_fishWidth-1;//重置起點
            _linkTimer.paused = YES;//計時器暫停
        }
    }
}

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

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

初始化被釣到的魚方法

//初始化釣到的小魚
- (instancetype)initCatchedFishWithType:(FishModelImageViewType)type andDirection:(FishModelImageViewDirection)dir{
    if (self = [super init]){
        self.direction = dir;
        [self initViewWithType:type andDuration:0.5];
        //重制x,y坐標, 30為魚鉤的寬度,85為魚鉤的長度
        self.x = (30 - self.width)/2.0;
        self.y = 85 - 6;
        if (dir == FishModelImageViewFromLeft){//從左往右,默認所有的魚都是從右往左
            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;
}

當魚被抓到后,執(zhí)行上鉤動畫

//魚被抓到后往上游
- (void)fishCatchedMoveUpWithOffsetX:(CGFloat) offsetX{
    
    //鉤沉到魚塘的高度為45
    //位移動畫
    CABasicAnimation *ani = [CABasicAnimation animationWithKeyPath:@"position"];
    ani.duration = 0.7;
    if (self.fishType == FishModelImageViewTypeSY){//鯊魚由于太長,所以不進行上游動畫了
        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)];//離鉤子近的話則不進行動畫
        ani.toValue = [NSValue valueWithCGPoint:CGPointMake(_hookX, 45 + _fishWidth/2.0)];
    }
    ani.delegate = self;
    //設置這兩句動畫結束會停止在結束位置
    [ani setValue:kFishCatchedMoveUpValue forKey:kFishCatchedMoveUpKey];
    [self.layer addAnimation:ani forKey:kFishCatchedMoveUpKey];
}

魚上游動畫結束后將翻轉(zhuǎn)的魚復位,然后執(zhí)行代理將釣到的魚通過代理傳遞出去

#pragma mark - CAAnimationDelegate
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    if (flag){
         if ([[anim valueForKey:kFishCatchedMoveUpKey] isEqualToString:kFishCatchedMoveUpValue]){//魚上游
            
            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];
            }
        }
   }
}

金幣動畫&&加分動畫

金幣動畫可以參考我的這篇文章:iOS 金幣入袋(收金幣)動畫
加分動畫比較簡單,一個位移加透明度的組合動畫實現(xiàn),具體可看代碼

釣魚View

這是實現(xiàn)界面了,本來是寫在VC里的,后來發(fā)現(xiàn)也能提取出來,所有就提取出來了,在調(diào)用時非常簡單,像正常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.初始化魚鉤

初始化魚鉤
講魚鉤擺動的角度通過代理傳到本界面

#pragma mark - 魚鉤
- (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];
}

下鉤動畫:魚塘增加了點擊手勢,點擊后執(zhí)行釣魚動作,暫停魚鉤擺動計時器,下鉤動畫結束后發(fā)送通知高速魚模塊可以上鉤了,并將魚鉤的底部中心坐標傳遞過去,魚線用CAShapeLayer繪制,并執(zhí)行strokeEnd動畫

//釣魚動作
- (void)fishBtnAction{
    
    if (self.fishHookState != FishHookStateShake) return; //不是搖擺狀態(tài)不可出桿
    
    [self.fishHookView hookTimerPause];//暫停魚鉤的計時器
    
    double degree = _angle*180/M_PI;//度數(shù)
    double rate = tan(_angle);//比列
    DLog(@"degree:%f---rate:%f",degree,rate);
    //計算出來線終點x的位置 , 鉤到水里的深度不變,即y是固定的
    _lineOffsetX = ScreenWidth/2.0 - (FishLineHeigth)*rate;
    
    //鉤子底部xy值
    _hookBottomX = ScreenWidth/2.0 - (FishLineHeigth + FishHookHeight)*rate;
    _hookBottomY = FishLineHeigth + FishHookHeight;
    
    //動畫時間
    double aniDuration = [self hookOutOfRiver] ? 0.5 : 1;
    
    //繪制路徑
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(ScreenWidth/2.0 ,5)];
    [path addLineToPoint:CGPointMake(_lineOffsetX, FishLineHeigth)];
    
    //圖形設置
    _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];
    
    //下鉤動畫
    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];
    
    //位移動畫
    _hookAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    //移動路徑
    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];//動畫分段時間
    //設置這兩句動畫結束會停止在結束位置
    _hookAnimation.removedOnCompletion = NO;
    _hookAnimation.fillMode=kCAFillModeForwards;
    [_fishHookView.layer addAnimation:_hookAnimation forKey:@"goukey"];
    
}

釣魚動作:下鉤動畫結束后計時器打開,執(zhí)行此方法;倒計時為最后一秒時魚不可上鉤(魚上鉤動畫0.7s,要留上鉤動畫的時間);計時器為0時發(fā)送不可垂釣通知告訴魚模塊不可上鉤了,并執(zhí)行上鉤動畫。

//鉤子停在底部
- (void)hookStop:(NSTimer *)timer{
    _stopDuration-=1;
    
    //最后一秒不可上鉤
    if (_stopDuration == 1){
        //發(fā)送不可垂釣的通知
        self.fishHookState = FishHookStateUp;
        [[NSNotificationCenter defaultCenter] postNotificationName:NotificationFishHookMove object:nil];
    }
    if (_stopDuration <= 0){
        //關閉計時器
        [timer setFireDate:[NSDate distantFuture]];
        
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:CGPointMake(_lineOffsetX, FishLineHeigth)];
        [path addLineToPoint:CGPointMake(ScreenWidth/2.0 ,5)];
        _linePathLayer.path = path.CGPath;
        
        //動畫時間
        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"];
    }
}

金幣動畫&加分動畫
下鉤動畫開始,總金幣減少10個
上鉤動畫開始,發(fā)送不可垂釣通知,魚鉤狀態(tài)為上鉤狀態(tài)
如果有捉到魚(根據(jù)魚模塊代理是否執(zhí)行判斷是否捉到),執(zhí)行金幣動畫和加分動畫
下鉤動畫結束,發(fā)送可以垂釣的通知給魚模塊,并將魚鉤坐標傳遞過去,開啟上鉤的計時器
上鉤動畫結束,更改魚鉤狀態(tài),移除一些View,魚鉤繼續(xù)擺動

#pragma mark - CAAnimationDelegate 動畫代理
//動畫開始
- (void)animationDidStart:(CAAnimation *)anim{
    
    //下鉤動畫開始
    if ([[anim valueForKey:kLineDownAnimationKey] isEqualToString:kLineDownAnimationValue]){
        self.fishHookState = FishHookStateDown;//下鉤狀態(tài)
        //錢數(shù)
        self.moneyLabel.text = [NSString stringWithFormat:@"%d", _totalMoney-=10];
        self.winMoney = 0;
        
    }else if ([[anim valueForKey:kLineUpAnimationKey] isEqualToString:kLineUpAnimationValue]){//上鉤動畫開始
        self.fishHookState = FishHookStateUp;//上鉤狀態(tài)
        [[NSNotificationCenter defaultCenter] postNotificationName:NotificationFishHookMove object:nil];
    }
    
    if (self.isCatched){//釣到魚后落金幣
        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];
    }
}

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

魚模塊的代理方法
創(chuàng)建一個被釣到的魚,加在魚鉤上,這樣便可和魚鉤一起執(zhí)行上鉤動畫了

#pragma mark - FishModelImageViewDelegate  釣到魚后的代理
- (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;//每釣到一個y坐標往下移
    
    //贏得錢數(shù)
    self.winMoney += count;
}

  • 2.初始化魚塘
    簡單的創(chuàng)建魚背景并添加點擊手勢

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

//小黃魚
    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.資源移除
    由于計時器不銷毀會造成循環(huán)引用,導致內(nèi)存泄漏,所以必須手動移除他,還有動畫如果執(zhí)行了代理,并且設置了結束后停留在結束位置,也會得不到釋放,所以都要手動釋放資源
- (void)removeFishViewResource{
    //解決魚鉤上鉤動畫循環(huán)引用的問題
    _linePathLayer = nil;
    //釣魚計時器關閉
    [_fishTimer invalidate];
    _fishTimer = nil;
    //釋放魚鉤的計時器
    [self.fishHookView hoolTimerInvalidate];
    //發(fā)送通知釋放小魚資源
    [[NSNotificationCenter defaultCenter] postNotificationName:NotificationRemoveFishModelTimer object:nil];
}

總結

至此,本游戲已經(jīng)完成了,寫的比較多,也比較亂,有什么不好的地方歡迎批評指正,希望對大伙有所幫助吧,本demo地址傳送門。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,365評論 25 708
  • 在iOS中隨處都可以看到絢麗的動畫效果,實現(xiàn)這些動畫的過程并不復雜,今天將帶大家一窺ios動畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,699評論 6 30
  • 在iOS中隨處都可以看到絢麗的動畫效果,實現(xiàn)這些動畫的過程并不復雜,今天將帶大家一窺iOS動畫全貌。在這里你可以看...
    F麥子閱讀 5,275評論 5 13
  • 像 一場遺憾。 是老鼠 每個夜晚的躥動 也是生命的不安。 鉆洞是它的本能 但當發(fā)現(xiàn)每個人的心臟都是空的 這個洞真...
    犭一閱讀 176評論 0 0
  • 元認知能力:對自己思考過程的認知與理解。 一個人的財富創(chuàng)造能力最終也只與元認知能力有關,其他...
    楓情物語閱讀 710評論 0 1

友情鏈接更多精彩內(nèi)容