“羊了个羊”复写版

大家好呀!

这几天,“羊了个羊”真的火爆全网,连续好几天了,热度仍然不减。我也不太懂这种游戏有什么好玩的,但是看到微信群里不断发来的链接,我也忍不住玩了一会。可惜呀!实在是过不了呀。正好小组内的技术分享轮到我了,我决定把这个游戏仿写出来在公司内讲讲。

话不多说,先上效果图!!!

 怎么样,看起来是不是有那个味了!

不过我的功能框每个都能用10次,这次再也不怕高血压上来了。哈哈哈!!!

闲聊结束,现在开始真正到技术篇了。本游戏使用原生js+canvas开发,开发总共耗时大约15个小时,游戏实现了关卡制(3关),并且每关提供3种道具,每种道具提供10次使用机会(第二种道具暂时没做)。

源码地址放在最后!!!

第一部分:货物绘制与消除

游戏的大致思路是:定义一个全局的货物列表goodList,然后使用轮询的方式,不断绘制goodList,goodList里面包含层次信息,坐标位置,可以精准的绘制每一层的货物。初次之外,开始全局监听canvas的点击事件,每次点击事件发生时,会拿到点击的坐标位置,然后到goodsList里面进行比对,判断是点击了哪一层的哪一个货物,然后返回对应的坐标位置 [i,j] ,之后通过坐标位置,将货物从goodList里面取出,放到货架上,然后执行“三消”程序。

 这里所谓的“三消”,其实是一个算法,算法的大致内容是,取出一个列表内3个相同数字的坐标。拿到坐标之后,会从货架上删除。

// 关卡货物的数据结构
{
    "goodArray": {
    "1": [
      {
        "name": "chicken", // 对应得图片名
        "width": 60,
        "height": 66,
        "x": 240,
        "y": 300,          // 坐标
        "canClick": false  // 是否可点击
      }
    ],
    "2": [
      {
        "name": "cabbage",
        "width": 60,
        "height": 66,
        "x": 120,
        "y": 240,
        "canClick": true
      }
    ],
    "3": [
      {
        "name": "chicken",
        "width": 60,
        "height": 66,
        "x": 360,
        "y": 400,
        "canClick": true
      },
      {
        "name": "cabbage",
        "width": 60,
        "height": 66,
        "x": 310,
        "y": 300,
        "canClick": true
      }
    ]
}

接下来,介绍具体的实现逻辑,大家可以先看看游戏的背景,背景当中的小草在不断抖动,这里使用的是canvas提供的api,这个api叫做二次贝塞尔曲线。这个函数可以弧度好看的小草。看下面是从文档上截取的介绍,这个曲线的绘制,需要三个点,(20,100)相当于一个锚点,为其他两个点绘制一条漂亮的曲线。

 绘制代码如下:

// 绘制小草
ctx.beginPath();
// 控制点
ctx.moveTo(this.x[i], this.y[i]);
// 二次贝塞尔曲线,结束点
ctx.quadraticCurveTo(this.x[i] + 2, this.y[i] - (this.alpha + l), this.x[i] + 8, this.y[i]);
ctx.moveTo(this.x[i] + 5, this.y[i]);
ctx.quadraticCurveTo(this.x[i] + 8 + 2, this.y[i] - (this.alpha + l), this.x[i] + 16, this.y[i]);
ctx.moveTo(this.x[i] + 11, this.y[i]);
ctx.quadraticCurveTo(this.x[i] + 11 + 2, this.y[i] - (this.alpha + l * 2 ), this.x[i] + 24, this.y[i]);
ctx.stroke();

 接下来绘制货物,通过全局的goodList,canvas不断绘制,通过点击,达到消除的目的。

// 绘制货物 
for (var i = 0; i < this.goodsList.length; i++) {
        var indexGoods = this.goodsList[i];
        for (var j = 0; j < indexGoods.length; j++) {
            // ---部分代码先删除,后面放出---
            ctx.drawImage(picResourceMap.get("blank"), goods.x, goods.y, goods.width, goods.height);
            ctx.drawImage(picResourceMap.get(goods.name), goods.x + 8, goods.y + 8, goods.width * 0.7, goods.height * 0.7);
            if (!goods.canClick) {
                ctx.drawImage(picResourceMap.get("shadow"), goods.x, goods.y, goods.width, goods.height);
            }
        }
    }

 然后就是“三消”的代码

// 这块是一个算法,计算一个队列里有3个相同元素的货物,然后删除掉
// 消除数量超过3个相同的
Box.prototype.clearSame = function () {
    var boxArray = {};
    this.boxList.forEach((it, index) => {
        boxArray[it.name] = (boxArray[it.name] ? boxArray[it.name] : 0) + 1;
    });
    // 消除目标
    var target;
    for (var item in boxArray) {
        if (boxArray[item] >= 3) {
            target = item;
        }
    }
    console.log("消除", target);
    if (!target) return;
    // 删除列表,用来显示星星
    var removeList = [];
    // 临时盒子列表,用来替换旧的盒子列表
    var tempBoxList = [];
    this.boxList.forEach((it, index) => {
        if (it.name === target) {
            removeList.push(index);
        } else {
            tempBoxList.push(it);
        }
    });
    this.boxList = tempBoxList;
  
}

这里需要注意的是,由于货物的摆放是分层的,如果消除了上一层,那么判断这个货物压住的下一层货物是否可以点击,后面介绍“碰撞检测”时会说到。 

第二部分:功能按钮 

现在介绍功能按钮,第一个时移出货价,如下图,

 其实实现原理很简单,需要再定义一个列表,从货架上取出3个货物,放到新的列表里,然后再函数绘制的时候绘画出来就可以了。

撤回一步的按钮目前还没有做,其实也是需要做一个临时变量,临时存储。目前此游戏只剩这一个功能,后期会补上。

最后一个就是洗牌逻辑,洗牌的动画后面一起提到,大家可以猜猜,这是怎么画出来的,其实用到是高中学的数学公式呦!

洗牌的逻辑其实就是定义一个栈,然后遍历goodList里面的所有获取,依次放入栈中,之后在使用pop()方法,依次将货物放入原先的位置,这样就可以实现洗牌的底层逻辑。

Goods.prototype.refreshGoods = function () {
    var goodListStack = [];
    for (var i = 0; i < this.goodsList.length; i++) {
        for (var j = 0; j < this.goodsList[i].length; j++) {
            goodListStack.push(this.goodsList[i][j].name);
        }
    }
    for (var i = 0; i < this.goodsList.length; i++) {
        for (var j = 0; j < this.goodsList[i].length; j++) {
            this.goodsList[i][j].name = goodListStack.pop();
        }
    }
    this.tempGoodsList = JSON.stringify(this.goodsList);
    this.refreshSwitch = true;
}

第三部分:关卡设计

 一开始已经说了,本游戏是设计了三关的,为了实现这三关,定义了三个json文件,每个json代表每一关的货物排列方式(理论上应该做一个服务器的)。

Level.prototype.draw = function () {
    if (this.levelSwitch) {
        ctx.drawImage(picResourceMap.get("shadow"), -50, -50, 540, 890);
        ctx.drawImage(picResourceMap.get("blackBlock"), 20 + this.offset, 300, 420, 200);
        ctx.save();
        ctx.font="50px Arial";
        ctx.strokeStyle = "#000";
        ctx.strokeText("第  " + this.level + "  关",140 + this.offset, 365);
        ctx.font="20px Arial";
        ctx.strokeStyle = "#fff";
        ctx.drawImage(picResourceMap.get("nextLevel"), 160 + this.offset, 390, 150, 60);
        ctx.strokeText("开始关卡",200 + this.offset, 425);
        ctx.restore();
        if (this.offset >= 0) {
            this.offset -= 3;
        }
    }
}

这里levelSwitch默认游戏开始是开启的,就是直接加载关卡动画,关卡动画默认是在画布外的,随着不断的加载,会移动到屏幕的中间位置。点击开始游戏后,会调用init()函数,并且将levelSwitch开关关闭,然后调用json文件,获取关卡信息。

 游戏结束之后,会再次打开游戏关卡,然后将游戏等级+1,相应的恢复所有的道具。

至此,此游戏逻辑结束,其实还是比较简单的。

 

第四部分:碰撞检测与洗牌动画

碰撞检测使用的是一个数学公式,两点的直线距离完成的,如下图

 假设两个物体相撞,那么必然如图,其实也就是勾股定理的一个扩展,可以计算两个点之间的距离,如果小于l,则就是碰撞。

var coverGoods = [];
for (var i = 0; i < this.goodsList[nextIndex].length; i++) {
     if (goods.calLength(this.goodsList[nextIndex][i].x, this.goodsList[nextIndex][i].y, selectGoods.x, selectGoods.y) < 4356) {
          coverGoods.push({"line": nextIndex, "row": i});
     }
}

 洗牌动画使用的也是高中的数学题,一直圆心坐标(x,y),半径为r,以圆心为顶点,做射线,角度为a,求与圆的交点坐标。

// 圆心坐标
this.dots = {
     x: 210,
     y: 200
};
// 半径
this.radius = 100;
// 圆角度
this.angle = 0;


// -----------------------------------
// 0.9 是用来减速的
this.angle = this.angle + deltaTime * 0.9;
goods.x = this.dots.x + this.radius * Math.sin(this.angle * 3.14 / 180);
goods.y = this.dots.y + this.radius * Math.cos(this.angle * 3.14 / 180);

源码地址:sheep: “羊了个羊”复写版,使用原生js与canvas开发,实现三消,回退,洗牌功能

马上要国庆了,大家国庆快乐呀!希望疫情早点结束,都能与家人团聚呀!

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值