欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS 动画实战之钓鱼小游戏

程序员文章站 2022-11-05 12:16:13
ios 动画实战之钓鱼小游戏。最近写了一款钓鱼小游戏,自己平时也没做过游戏,本来以为这种游戏要用cocos2d什么的实现,后来发现其实动画就可以实现很棒了效果,废话不多说,看看效果图。 思维导图...

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

iOS 动画实战之钓鱼小游戏

思维导图

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

iOS 动画实战之钓鱼小游戏

项目结构

iOS 动画实战之钓鱼小游戏

准备工作

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

iOS 动画实战之钓鱼小游戏

鱼钩模块

摆动动画
鱼钩的摆动范围是[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地址传送门,喜欢的话给个star吧,谢谢您的支持。