iOS 动画实战之钓鱼小游戏

前言

最近写了一款钓鱼小游戏,自己平时也没做过游戏,本来以为这种游戏要用cocos2d什么的实现,后来发现其实动画就可以实现很棒的效果,先看看效果图。

钓鱼游戏.gif

思维导图

首先我们看下思维导图,本游戏主要分为4大块,其中鱼的实现最为复杂

思维导图

项目结构

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

准备工作

首先将需要的图准备好,这个鱼其实就是一组图片,图片大小固定,每一帧位置变化,所以看起来 是一个上下游动的鱼。


动态.gif
单张图片

鱼钩模块

  • 摆动动画
    鱼钩的摆动范围是[M_PI/4.0,-M_PI/4.0] (垂直向下为0度,顺时针为正),这里利用了计时器进行角度的更改,计时器用的CADisplayLink,它是一个和屏幕刷新率一致的定时器,如果没有卡顿,每秒刷新次数是60次,本Demo很多计时器用的都是CADisplayLink。下面是鱼钩的主要代码(重点:1、设置锚点后重置frame,2、更改角度,3、旋转)。 其中定义了一个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];
    //  创建一个对象计时器
    _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);
//旋转动画
    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、动态的鱼

加载动态鱼的方法

  //初始化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、移动的鱼

提供的图片都是头朝左的(见上面的动图),所以从左往右游的话图片需要进行镜像反转
对于鱼是否可以垂钓是用通知进行传递信息的,可垂钓、不可垂钓两种状态
可垂钓:鱼钩沉到鱼塘时受到垂钓通知(将鱼钩底部的坐标传过来),现在鱼可以垂钓,当根据上钩概率等因素判断鱼上钩后,对鱼进行旋转,然后执行上钩动画。动画结束后执行代理。

//初始化可以垂钓的鱼
- (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];
    //创建计时器
    _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);//旋转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;//计时器暂停
        }
    }
}

鱼上钩的概率和赢得的金币个数

//鱼是否可以被钓上来(根据概率计算)
- (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);//旋转90度
            self.transform = transform;
        }else {
            self.transform = CGAffineTransformMakeRotation(M_PI_2);
        }
    }
    return self;
}

当鱼被抓到后,执行上钩动画

//鱼被抓到后往上游
- (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];
}

鱼上游动画结束后将翻转的鱼复位,然后执行代理将钓到的鱼通过代理传递出去

#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);//旋转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 金币入袋(收金币)动画
加分动画比较简单,一个位移加透明度的组合动画实现,具体可看代码

钓鱼View

这是实现界面了,本来是写在VC里的,后来发现也能提取出来,所有就提取出来了,在调用时非常简单,像正常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];
}

下钩动画:鱼塘增加了点击手势,点击后执行钓鱼动作,暂停鱼钩摆动计时器,下钩动画结束后发送通知高速鱼模块可以上钩了,并将鱼钩的底部中心坐标传递过去,鱼线用CAShapeLayer绘制,并执行strokeEnd动画

//钓鱼动作
- (void)fishBtnAction{
    
    if (self.fishHookState != FishHookStateShake) return; //不是摇摆状态不可出杆
    
    [self.fishHookView hookTimerPause];//暂停鱼钩的计时器
    
    double degree = _angle*180/M_PI;//度数
    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"];
    
}

钓鱼动作:下钩动画结束后计时器打开,执行此方法;倒计时为最后一秒时鱼不可上钩(鱼上钩动画0.7s,要留上钩动画的时间);计时器为0时发送不可垂钓通知告诉鱼模块不可上钩了,并执行上钩动画。

//钩子停在底部
- (void)hookStop:(NSTimer *)timer{
    _stopDuration-=1;
    
    //最后一秒不可上钩
    if (_stopDuration == 1){
        //发送不可垂钓的通知
        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个
上钩动画开始,发送不可垂钓通知,鱼钩状态为上钩状态
如果有捉到鱼(根据鱼模块代理是否执行判断是否捉到),执行金币动画和加分动画
下钩动画结束,发送可以垂钓的通知给鱼模块,并将鱼钩坐标传递过去,开启上钩的计时器
上钩动画结束,更改鱼钩状态,移除一些View,鱼钩继续摆动

#pragma mark - CAAnimationDelegate 动画代理
//动画开始
- (void)animationDidStart:(CAAnimation *)anim{
    
    //下钩动画开始
    if ([[anim valueForKey:kLineDownAnimationKey] isEqualToString:kLineDownAnimationValue]){
        self.fishHookState = FishHookStateDown;//下钩状态
        //钱数
        self.moneyLabel.text = [NSString stringWithFormat:@"%d", _totalMoney-=10];
        self.winMoney = 0;
        
    }else if ([[anim valueForKey:kLineUpAnimationKey] isEqualToString:kLineUpAnimationValue]){//上钩动画开始
        self.fishHookState = FishHookStateUp;//上钩状态
        [[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;
        //金币总数
        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;//垂钓状态
            //钩的位置
            NSDictionary *dic = @{@"offsetX":[NSString stringWithFormat:@"%.2f",_hookBottomX],@"offsetY":[NSString stringWithFormat:@"%.2f",_hookBottomY]};
            //发送可以垂钓的通知,钩的位置传过去
            [[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;//摇摆状态
            [_linePathLayer removeFromSuperlayer];
            [_fishHookView hoolTimerGoOn];//鱼钩计时器继续
            _catchedHeight = 0;
            //移除钓上来的鱼
            [self removeTheCatchedFishes];
        }
    }
}

鱼模块的代理方法
创建一个被钓到的鱼,加在鱼钩上,这样便可和鱼钩一起执行上钩动画了

#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坐标往下移
    
    //赢得钱数
    self.winMoney += count;
}

  • 2.初始化鱼塘
    简单的创建鱼背景并添加点击手势

  • 3.初始化鱼
    通过for循环可以创建出多个某种鱼

//小黄鱼
    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.资源移除
    由于计时器不销毁会造成循环引用,导致内存泄漏,所以必须手动移除他,还有动画如果执行了代理,并且设置了结束后停留在结束位置,也会得不到释放,所以都要手动释放资源
- (void)removeFishViewResource{
    //解决鱼钩上钩动画循环引用的问题
    _linePathLayer = nil;
    //钓鱼计时器关闭
    [_fishTimer invalidate];
    _fishTimer = nil;
    //释放鱼钩的计时器
    [self.fishHookView hoolTimerInvalidate];
    //发送通知释放小鱼资源
    [[NSNotificationCenter defaultCenter] postNotificationName:NotificationRemoveFishModelTimer object:nil];
}

总结

至此,本游戏已经完成了,写的比较多,也比较乱,有什么不好的地方欢迎批评指正,希望对大伙有所帮助吧,本demo地址传送门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,517评论 25 707
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,465评论 6 30
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 5,094评论 5 13
  • 像 一场遗憾。 是老鼠 每个夜晚的蹿动 也是生命的不安。 钻洞是它的本能 但当发现每个人的心脏都是空的 这个洞真...
    犭一阅读 141评论 0 0
  • 元认知能力:对自己思考过程的认知与理解。 一个人的财富创造能力最终也只与元认知能力有关,其他...
    枫情物语阅读 585评论 0 1