「羊了个羊」通关不了,心血来潮,自己动手撸一个Android版🐏

前言

「 随着小游戏《羊了个羊》蹿红,年轻人打招呼的 方式也变成了、今天你“羊了个羊”了吗?简单来 说, “羊了个羊”是一款升级版消消乐,将玩家按 照地域分省排名。游戏不仅带上头了玩家,也席 卷了朋友圈,甚至带火了相关行业,令人匪夷所 思。
本人作为一名职业杠精,当然也不例外,从点开第一把开始就无法自拔,鬼知道我一天能看多少广告,经过几天碰壁后,我决定自己复刻一个出来」

不废话,最终效果如下

ezgif.com-gif-maker_5.gif

ezgif.com-gif-maker_6.gif
ezgif.com-gif-maker_7.gif

ezgif.com-gif-maker_8.gif

项目已上传Github,各位可自行下载和游玩

羊了个羊Github地址

羊了个羊APK下载地址

游戏逻辑

1、在思路上先在屏幕中间默认一个宽14×高16的坐标轴,之后就可以用坐标轴来判断棋子了。(每个棋子宽高为2×2,方便计算覆盖面)

2、无论是第一关还是第二关,首先需要创建3个list(棋子坐标list,图标list,层级list),当然官方版本是通过接口获取,我这边就自行想办法创造了。(用实体类的方式也可以)

3、从屏幕正上方实现棋子下落动画,落位之后实现棋子变暗动画

4、实现棋子点击事件,点击之后,实现位移动画,移动到底部木栏里,并计算是否消除

5、每次点击事件之后判断坐标轴之内的棋子,如果为零,回调过关。

实现过程

我把所有的过程的封装在Sheepview中,直接来看sheepview

① 初始化一些参数

private Context mContext;
private View sheepLayout;
private RelativeLayout rlAlllayout;
private SuperTextView stv_container;

private List<GrassView> GrassViewList;  //草list
private List<Integer[]> mGrasslocationList;  //草坐标列表
private int grassNum = 28;  //草的数量

private final int ChessSize = 24;  //棋子size的一半
private List<ImageView> showChessList;   //棋牌区域image的list
private List<ImageView> takeinChessList;   //收纳区image的list
private List<Integer> rescoureList;   //图片资源list
private int MAX_DEGREE = 20;  //第二关最多层数
private final int MAX_CHESSNUM = 36; //第二关 单层棋子最多数量
private int degreeNum = 0;  //获取层数 (为0时,从10-MAX_DEGREE中随机获取层数,有传入用传入)

private final long downDelayTime = 500;  //下落动画时间
private final long ToDarkTime = 500;   //变暗动画时间
private final long ToLightTime = 500;   //变亮动画时间
private final long takeinTime = 40;   //移动到木栏动画时间
private final long leftTime = 40;   //配对消失后其他棋子左移动画时间
private final long changeTime = 600;   //旋转动画时间

private List<Double[]> locationList;  //棋子坐标列表
private List<Integer> iconList;   //图标列表
private List<Integer> degreeList;   //等级列表

private int barrierNum;  //关卡
private MySheepListener mySheepListener; 

② 创建赋值棋子坐标list,图标list,层级list

第一关:
层数:9个第一层 9个第二层

for (int i = 0; i < 18; i++) {
    if (i<9){
        degreeList.add(1);
    }else {
        degreeList.add(2);
    }
}

坐标:位置是确定的。

* 第一关的坐标为2层
* 底部:
* (3,3) (6,3) (9,3)
* (3,7) (6,7) (9,7)
* (3,11) (6,11) (9,11)
* 顶部:
* (3,4) (6,4) (9,4)
* (3,8) (6,8) (9,8)
* (3,12) (6,12) (9,12)

图标:从16个图标中随机获取3个即可(取的为resource的序号)

//取3个不同的随机数 选出3个icon
int i1 = (int) (Math.round(Math.random() * 15));
int i2 = (int) (Math.round(Math.random() * 15));
while (i2 == i1){
    i2 = (int) (Math.round(Math.random() * 15));
}
int i3 = (int) (Math.round(Math.random() * 15));
while (i3 == i1||i3 == i2){
    i3 = (int) (Math.round(Math.random() * 15));
}

for (int i = 0; i < 6; i++) {
    iconList.add(i1);
    iconList.add(i2);
    iconList.add(i3);
}
Collections.shuffle(iconList); //打乱

第二关:
层数:获取一个小于max的随机数

//获取随机层数
if (degreeNum == 0){
    degreeNum = (int) (Math.random() * (MAX_DEGREE - 10)) + 10;
}else if (degreeNum<5){
    degreeNum = 5;
}

坐标:每层随机创建小于一个max的数量,从小到大排列后,每层循环给坐标list增加不重叠的坐标,当然,最后需要判断list是否整除3,如果不整除,从最上层去除,直到整除为止。(这边的坐标均为随机出来,以后如果要优化游戏可玩性,可在这部分优化)

       //生成小于36的层数的随机数
        List<Integer> degreeContentNum = new ArrayList<>();
        for (int i = 0; i < degreeNum; i++) {
            int num = (int) (Math.random() * (MAX_CHESSNUM-6)) + 6;
            degreeContentNum.add(num);
        }

        //从小到大排列
        Collections.sort(degreeContentNum,new Comparator(){
            @Override
            public int compare(Object o1, Object o2) {
                int diff = (Integer)o1 -(Integer) o2;
                if (diff>0){
                    return 1;
                }
                else  if (diff <0){
                    return -1;
                }
                else {
                    return  0;
                }
            }
        });

        //生成坐标
        for (int i = 0; i < degreeContentNum.size(); i++) { //每层
            List<Double[]> everyDegreeLocationList = new ArrayList<>();
            for (Integer x = 0; x < degreeContentNum.get(i); x++) {  //每层中每个的判断
                addSmallLocation(everyDegreeLocationList,i+1, 0);
            }

//            Log.i("孙", "每层数量: "+degreeContentNum.get(i));
            locationList.addAll(everyDegreeLocationList);
        }

        //必须为3的倍数 删除前几个
        if (locationList.size()%3 !=0){
            for (int i = 0; i < locationList.size() % 3; i++) {
                locationList.remove(i);
                degreeList.remove(i);
            }
        }

(第二关还有底部额外的两行)

//第二层 底部额外的部分
int i = degreeNum / 3;
//获取底部层数
int bottomGegreeNum = i*3;
for (int x = 0; x < bottomGegreeNum; x++) {
    locationList.add(new Double[]{3.0- ((double) 5.0)/ChessSize*x,17.0});
    degreeList.add(x+1);
    locationList.add(new Double[]{9.0+ ((double) 5.0)/ChessSize*x,17.0});
    degreeList.add(x+1);
}

图标:根据数量随机取即可

for (int i = 0; i < locationList.size() / 3; i++) {
    int i1 = (int) (Math.round(Math.random() * 15));
    for (int x = 0; x < 3; x++) {
        iconList.add(i1);
    }
}
Collections.shuffle(iconList); //打乱

③ 所有list均需倒序,因为层数越小越在上层

Collections.reverse(locationList);
Collections.reverse(iconList);
Collections.reverse(degreeList);

④ 根据list创建所有imageview,实现下落动画和点击事件

for (int i = 0; i < iconList.size(); i++) {
    ImageView imageView = new ImageView(mContext);
    locationToMargin(imageView,locationList.get(i),degreeList.get(i));
    imageView.setImageResource(rescoureList.get(iconList.get(i)));
    imageView.setTag(R.id.sheepview_imageview_picrescoure,iconList.get(i));
    imageView.setOnClickListener(new NoFastClickListener() {
        @Override
        protected void onSingleClick() {
            PlayVoice.playClickVoice(mContext);  //播放点击音效
            showChessList.remove(imageView);    //棋牌区域remove该imageview
            scaleChangeAnim(imageView);       //点击棋子时的,放大缩小动画

            judgeCanClick(false);   //当然,每次点击需要判断所有棋子是否变亮和可点击
        }
    });

    showChessList.add(imageView);
    rlAlllayout.addView(imageView);
}

for (ImageView imageView : showChessList) {
    int mTime = (int) (Math.random() * 700);
    startDownAnim(imageView,mTime);   //下落动画,各棋子有不同的延迟

    if (judgeNeedToDark(imageView)){   //判断是否需要变暗和不可点击
        startToDarkAnim(imageView,mTime+downDelayTime);
    }
}

judgeCanClick:(只需要判断层级比自己小的imageview的宽高差是否在坐标2之内即可)

private void judgeCanClick(boolean onlyChangeViewClickable){   //判断剩下棋子是否可以点击
    for (int i = 0; i < showChessList.size(); i++) {
        boolean canClick = true;
        List<ImageView> mlist = new ArrayList<>();
        mlist.addAll(showChessList);
        mlist.remove(i);

        Double[] ilocation = (Double[]) showChessList.get(i).getTag(R.id.sheepview_imageview_location);
        int idegree = ((int) showChessList.get(i).getTag(R.id.sheepview_imageview_degree));
        for (ImageView imageView : mlist) {
            Double[] lt = (Double[]) imageView.getTag(R.id.sheepview_imageview_location);
            int dg = ((int) imageView.getTag(R.id.sheepview_imageview_degree));
            if (idegree > dg){
                if (Math.abs(lt[0]-ilocation[0])<2&&Math.abs(lt[1]-ilocation[1])<2){
                    canClick = false;
                    break;
                }
            }
        }

        if (canClick){
            showChessList.get(i).setEnabled(true);
            if (!onlyChangeViewClickable){
                if (null!=showChessList.get(i).getForeground()&&showChessList.get(i).getForeground().getAlpha() < 255){
                    startToLightAnim(showChessList.get(i));
                }
            }
        }else {
            showChessList.get(i).setEnabled(false);
        }
    }
}

⑤ 点击事件中,实现棋子位移到底部木栏的动画以及游戏成功失败的判断(使用的是属性动画,监听动画结束后,根据资源文件序号是否有3个相同,判断是否要消除,否则如果木栏中有7个即为失败,棋盘区域为0即为成功)

private void startTakeinAnim(ImageView imageView){  //放大缩小后,从上方容器到下方容器 移动动画
    ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 100f);
    valueAnimator.setDuration(takeinTime);
    valueAnimator.setInterpolator(new LinearInterpolator());  //线性变化
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            float animatedValue = (float) valueAnimator.getAnimatedValue();

            int targetLeftMargein = (PUtil.getScreenW(mContext)- PUtil.dip2px(mContext,14*ChessSize))/2 + (takeinChessList.size())*PUtil.dip2px(mContext,2*ChessSize);
            int targettopMargein = PUtil.getScreenH(mContext)- PUtil.dip2px(mContext,25+2*ChessSize);

            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) imageView.getLayoutParams();
            int leftMargin = layoutParams.leftMargin;
            float value1 = (((float) leftMargin)-targetLeftMargein)/100 *animatedValue;
            layoutParams.leftMargin = (int) (leftMargin - value1);
            int topMargin = layoutParams.topMargin;
            float value2 = (((float) topMargin)-targettopMargein)/100 *animatedValue;
            layoutParams.topMargin = (int) (topMargin - value2);

            imageView.setLayoutParams(layoutParams);
        }
    });
    valueAnimator.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationStart(Animator animation) {
            super.onAnimationStart(animation);
        }
        @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);

            takeinChessList.add(imageView);
            Map<Integer,List<Integer>> mMap= new HashMap<>();  //key资源序号,list 图片在takeinChessList的位置
            for (int i = 0; i < takeinChessList.size(); i++) {
                if (null == mMap.get(((int) takeinChessList.get(i).getTag(R.id.sheepview_imageview_picrescoure)))){
                    List<Integer> picNumList = new ArrayList<>();
                    picNumList.add(i);
                    mMap.put(((int) takeinChessList.get(i).getTag(R.id.sheepview_imageview_picrescoure)),picNumList);
                }else {
                    mMap.get(((int) takeinChessList.get(i).getTag(R.id.sheepview_imageview_picrescoure))).add(i);
                }
            }

            boolean canClear = false;  //能消除
            for (Integer key : mMap.keySet()) {
                if (mMap.get(key).size() == 3){
                    canClear = true;

                    startClearAnim(mMap.get(key));
                    break;
                }
            }

            if (!canClear){
                if (takeinChessList.size()>6){  //数量为7,即失败
                    if (null!=mySheepListener){
                        mySheepListener.failListener();
                    }
                }
            }else {
                //冻结所有上层棋子的点击事件
                for (ImageView view : showChessList) {
                    view.setEnabled(false);
                }
            }
        }
    });
    valueAnimator.start();
}

结语

本文纯娱乐开发,考虑的东西都是边玩边做的,当然还有些没有说明的部分大家可以看看源码。
如果有侵犯了游戏厂商权益的地方,我会及时删除。

·最后,致敬所有被羊了个羊虐过的玩家们❤❤❤

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

推荐阅读更多精彩内容