教你用ECharts轻松做一个FlappyBird小游戏
本文分享自华为云社区《没想到吧!这个可可爱爱的游戏居然是用 ECharts 实现的!-云社区-华为云》,作者:DevUI。前言
echarts是一个很强大的图表库,除了我们常见的图表功能,echarts有一个自定义图形的功能,这个功能可以让我们很简单地在画布上绘制一些非常规的图形,基于此,我们来玩一些花哨的。
Flappy Bird小游戏体验地址(看看你能玩几分):
foolmadao.github.io/echart-flap…
下面我们来一步步实现他。1 在坐标系中画一只会动的小鸟
首先实例化一个echart容器,再从网上找一个像素小鸟的图片,将散点图的散点形状,用自定义图片的方式改为小鸟。const myChart = echarts.init(document.getElementById("main")); option = { series: [ { name: "bird", type: "scatter", symbolSize: 50, symbol: "image://bird.png", data: [ [50, 80] ], animation: false }, ] }; myChart.setOption(option);
要让小鸟动起来,就需要给一个向右的速度和向下的加速度,并在每一帧的场景中刷新小鸟的位置。而小鸟向上飞的动作,则可以靠角度的旋转来实现,向上飞的触发条件设置为空格事件。option = { series: [ { xAxis: { show: false, type: "value", min: 0, max: 200, }, yAxis: { show: false, min: 0, max: 100 }, name: "bird", type: "scatter", symbolSize: 50, symbol: "image://bird.png", data: [ [50, 80] ], animation: false }, ] }; // 设置速度和加速度 let a = 0.05; let vh = 0; let vw = 0.5 timer = setInterval(() => { // 小鸟位置和仰角调整 vh = vh - a; option.series[0].data[0][1] += vh; option.series[0].data[0][0] += vw; option.series[0].symbolRotate = option.series[0].symbolRotate ? option.series[0].symbolRotate - 5 : 0; // 坐标系范围调整 option.xAxis.min += vw; option.xAxis.max += vw; myChart.setOption(option); }, 25);
效果如下
2 用自定义图形绘制障碍物
echarts自定义系列,渲染逻辑由开发者通过renderItem函数实现。该函数接收两个参数params和api,params包含了当前数据信息和坐标系的信息,api是一些开发者可调用的方法集合,常用的方法有:api.value(…),意思是取出 dataItem 中的数值。例如 api.value(0) 表示取出当前 dataItem 中第一个维度的数值。api.coord(…),意思是进行坐标转换计算。例如 var point = api.coord([api.value(0), api.value(1)]) 表示 dataItem 中的数值转换成坐标系上的点。api.size(…), 可以得到坐标系上一段数值范围对应的长度。api.style(…),可以获取到series.itemStyle 中定义的样式信息。
灵活使用上述api,就可以将用户传入的Data数据转换为自己想要的坐标系上的像素位置。
renderItem函数返回一个echarts中的graphic类,可以多种图形组合成你需要的形状,graphic类型。对于我们游戏中的障碍物只需要使用矩形即可绘制出来,我们使用到下面两个类。type: group, 组合类,可以将多个图形类组合成一个图形,子类放在children中。type: rect, 矩形类,通过定义矩形左上角坐标点,和矩形宽高确定图形。// 数据项定义为[x坐标,下方水管上侧y坐标, 上方水管下侧y坐标] data: [ [150, 50, 80], ... ] renderItem: function (params, api) { // 获取每个水管主体矩形的起始坐标点 let start1 = api.coord([api.value(0) - 10, api.value(1)]); let start2 = api.coord([api.value(0) - 10, 100]); // 获取两个水管头矩形的起始坐标点 let startHead1 = api.coord([api.value(0) - 12, api.value(1)]); let startHead2 = api.coord([api.value(0) - 12, api.value(2) + 8]) // 水管头矩形的宽高 let headSize = api.size([24, 8]) // 水管头矩形的宽高 let rect = api.size([20, api.value(1)]); let rect2 = api.size([20, 100 - api.value(2)]); // 坐标系配置 const common = { x: params.coordSys.x, y: params.coordSys.y, width: params.coordSys.width, height: params.coordSys.height } // 水管形状 const rectShape = echarts.graphic.clipRectByRect( { x: start1[0], y: start1[1], width: rect[0], height: rect[1] },common ); const rectShape2 = echarts.graphic.clipRectByRect( { x: start2[0], y: start2[1], width: rect2[0], height: rect2[1] }, common ) // 水管头形状 const rectHeadShape = echarts.graphic.clipRectByRect( { x: startHead1[0], y: startHead1[1], width: headSize[0], height: headSize[1] },common ); const rectHeadShape2 = echarts.graphic.clipRectByRect( { x: startHead2[0], y: startHead2[1], width: headSize[0], height: headSize[1] },common ); // 返回一个group类,由四个矩形组成 return { type: "group", children: [{ type: "rect", shape: rectShape, style: { ...api.style(), lineWidth: 1, stroke: "#000" } }, { type: "rect", shape: rectShape2, style: { ...api.style(), lineWidth: 1, stroke: "#000" } }, { type: "rect", shape: rectHeadShape, style: { ...api.style(), lineWidth: 1, stroke: "#000" } }, { type: "rect", shape: rectHeadShape2, style: { ...api.style(), lineWidth: 1, stroke: "#000" } }] }; },
颜色定义, 我们为了让水管具有光泽使用了echarts的线性渐变色对象。itemStyle: { // 渐变色对象 color: { type: "linear", x: 0, y: 0, x2: 1, y2: 0, colorStops: [{ offset: 0, color: "#ddf38c" // 0% 处的颜色 }, { offset: 1, color: "#587d2a" // 100% 处的颜色 }], global: false // 缺省为 false }, borderWidth: 3 },
另外,用一个for循环一次性随机出多个柱子的数据function initObstacleData() { // 添加minHeight防止空隙太小 let minHeight = 20; let start = 150; obstacleData = []; for (let index = 0; index < 50; index++) { const height = Math.random() * 30 + minHeight; const obstacleStart = Math.random() * (90 - minHeight); obstacleData.push( [ start + 50 * index, obstacleStart, obstacleStart + height > 100 ? 100 : obstacleStart + height ] ) } }
再将背景用游戏图片填充,我们就将整个游戏场景,绘制完成:
3 进行碰撞检测
由于飞行轨迹和障碍物数据都很简单,所以我们可以将碰撞逻辑简化为小鸟图片的正方形中,我们判断右上和右下角是否进入了自定义图形的范围内。
对于特定坐标下的碰撞范围,因为柱子固定每格50坐标值一个,宽度也是固定的,所以,可碰撞的横坐标范围就可以简化为 (x / 50 % 1) < 0.6
在特定范围内,依据Math.floor(x / 50)获取到对应的数据,即可判断出两个边角坐标是否和柱子区域有重叠了。在动画帧中判断,如果重叠了,就停止动画播放,游戏结束。// centerCoord为散点坐标点 function judgeCollision(centerCoord) { if (centerCoord[1] < 0 || centerCoord[1] > 100) { return false; } let coordList = [ [centerCoord[0] + 15, centerCoord[1] + 1], [centerCoord[0] + 15, centerCoord[1] - 1], ] for (let i = 0; i < 2; i++) { const coord = coordList[i]; const index = coord[0] / 50; if (index % 1 < 0.6 && obstacleData[Math.floor(index) - 3]) { if (obstacleData[Math.floor(index) - 3][1] > coord[1] || obstacleData[Math.floor(index) - 3][2] < coord[1]) { return false; } } } return false } function initAnimation() { // 动画设置 timer = setInterval(() => { // 小鸟速度和仰角调整 vh = vh - a; option.series[0].data[0][1] += vh; option.series[0].data[0][0] += vw; option.series[0].symbolRotate = option.series[0].symbolRotate ? option.series[0].symbolRotate - 5 : 0; // 坐标系范围调整 option.xAxis.min += vw; option.xAxis.max += vw; // 碰撞判断 const result = judgeCollision(option.series[0].data[0]) if(result) { // 产生碰撞后结束动画 endAnimation(); } myChart.setOption(option); }, 25); }总结
echarts提供了强大的图形绘制自定义能力,要使用好这种能力,一定要理解好数据坐标点和像素坐标点之间的转换逻辑,这是将数据具象到画布上的重要一步。
运用好这个功能,再也不怕产品提出奇奇怪怪的图表需求。
源码地址:github.com/foolmadao/e…
点击下方,第一时间了解华为云新鲜技术~
华为云博客_大数据博客_AI博客_云计算博客_开发者中心-华为云
华为P50Pro麒麟9000版有现货了经典之作华为P50Pro手机是很多玩家心中的神机,不过这款手机发售之后就一直面临缺货,目前麒麟9000版华为P50Pro在京东商城补货,8256GB版本的售价为7388元,从颜值到
喜大普奔,弹窗广告可以一键关闭大家在使用电脑打开网页时候,最讨厌的应该就是各种狗皮膏药弹窗广告,有些内容甚至低俗不堪,对于未成年人的影响甚是坏。市面上常见的就是下面这些全家桶1360(奇虎)安全卫士安全浏览器3
从犹豫不决,到落子vivoX80用了五个多月,没买iPhone14是正确的今年4月底发布的vivoX80手机个人还是蛮喜欢的,但是一直都没有入手,因为我在等着9月份发布的iPhone14,结果和我预期的一样,iPhone14相比iPhone13可以说几乎
明年iPhone15升级巨大,值得果粉等上一年吗?文机siriPhone14设备已正式向果粉们售卖,部分果粉们已开始使用。一年一度的iPhone发布都是热点,果粉们又开始期待明年的iPhone15设备了。这不iPhone14系列目
短短1个月,长江大学连获多项大奖,被全省全国点名表彰青春该如何奋斗才能不负韶华?是在前进的过程中,始终带着努力坚持与热爱。在长江大学,学子们扬起青春的理想风帆,认真学习积极实践,活跃在不同的赛场之上。前后不过1个月左右的时间,他们就
跟着Jisoo郑秀文莫莉等用上大手袋才是新潮流小废包于近年横行时装界,由奢侈品牌如CHANELPradaJacquemus到小资女品牌CharlesKeithCOS等也相继推出林林总总的小废包,烧得一众时装爱好者体无完肤。但近
人在酒中便是仙我其实很少喝酒,更没喝多过,也无从谈起酒中神仙的感觉。但很喜欢这个名字,酒中仙很容易让人联想到才华横溢,诗情如天马行空的李白联想到桃园三结义那生死同悲欢共杯酒定乾坤的豪情壮志联想到
我不曾信佛可佛渡了我一遍又一遍网友大师,暗恋会有结果吗?大师你们在一个下雨的屋檐下,你不敢对她说,我们一起走吧因为你连伞都没有网友大师在这个爱意泛滥的世界里我该怎么出众大师生活是公平的,对每个人都一样如果你觉得
修行的本质是什么?随着新冠肺炎疫情全球的扩散,大大限制了人们的出行,旅游人数也减少了很多。有的人将旅游的热情转化成追求内心平静和丰富自己的精神世界,并因此加入了修行者的队伍。修行者的世界和普通人的世
秋色如画,秋韵如诗风渐凉,水渐寒。回眸处,秋意阑珊。日子,越发消瘦时光,越发清浅。蒹葭苍苍,正在为一场寒霜作彩排草木摇落,正在为一场初雪作铺垫。无边落木萧萧下,不尽长江滚滚来。曾经的繁华终将落幕,曾
刘国梁,当选新职北京时间10月5日,WTT世界乒联董事会会议在成都召开,国际乒联主席佩特拉索林受邀参会。中国乒协主席刘国梁在会上当选为WTT世界乒联首任董事会主席。会后,佩特拉索林任命刘国梁为国际
攀爬圆明园遗址游客将被列入黑名单,禁止入园游览昨天,一组游客在圆明园遗址攀爬的视频引发广泛关注,人们纷纷指责这种不文明游园行为。今天上午,圆明园管理处相关人员表示,目前已将该情况上报,游客一旦有这样的不文明游园行为,将被列入黑
广州00后醒狮女孩们用4年苦练打破百年偏见2023年,在广州市文化广电旅游局与携程集团共同举办的广州旅游超级目的地年度盛典上,一群00后舞狮女孩们作为广州舞狮文化代表登上了舞台,不仅收获了现场几百人的喝彩,广州市文化广电旅
摄影界的网红,高颜值梯田,随手一拍就是大片春耕和秋收是欣赏梯田的最佳季节,万亩梯田层层叠叠,如链似带,犹如巨幅五线谱,奏响在山间田野。今天,古道君就推荐3个美丽的春耕梯田,不用远去云南,在浙里也能一睹地球最美的曲线。温州永
敛财涉案两亿元,出逃国外生活苦不堪言,悔之晚矣后主动投案回国六旬老人成为百名红通嫌犯第一人,外逃时放下豪言壮语,死也要死在美国。杨秀珠,原温州市副市长,出身极为普通的杨秀珠当年只是粮站一名普通卖馒头的工作人员。当年恰逢赶上1967年,联总造
三个臭皮匠真的能顶一个诸葛亮吗?俗话说,三个臭皮匠顶个诸葛亮,说这话的人多少有点看不起武乡侯了。那么,什么样的三个臭皮匠能顶得上一个诸葛亮呢,首先,我们来看看汉初三杰,韩信,萧何,张良,你仔细想想,是不是能在诸葛
为什么科学家不敢打开中国皇帝的陵墓?1974年,取得了人类历史上最大的考古发现之一。当农民杨志发和他的五个兄弟以及一个邻居在中国的骊山附近挖井时,发现了一尊被误认为是佛像的雕像。一群考古学家到达现场并开始挖掘原来,农
南宋时期,潜邸出身官员与大元帅府幕僚,他们有什么差别?皇储皇子龙飞后,潜邸出身官员可谓盼的云开见月明,部分潜邸出身官员因为与皇帝的亲密情感,被委以重任。南宋诸帝受个人资质社会环境等因素的影响,故南宋诸朝对潜邸出身官员的任用情况也各不相
秦始皇两大秘密不设皇后,不立太子,他到底想干什么?秦始皇被称为千古一帝,开创了中国封建大一统的先河,在他的身上有太多的秘密,但有两件事情却被大家忽略,那就是他没有设立皇后,也没有设立太子。第一秦始皇是中国封建王朝上,唯一没有设立皇
东晋为什么会成为中国历史上最黑暗的朝代?历史开讲提起晋代,我们想到的就是八王之乱,想到的就是五胡乱华。想到的就是那些被外族掳走沦为粮食的女子。晋朝为何会沦落至此?是政治上的失误,还是司马氏集团内部的腐朽?短短几代皇帝就把
古代流通的大量银子,后来都去哪儿了?大人,这五百两银子是小的孝敬您的,xx的事就全仰仗您操劳了。这是古装剧中常常出现的送礼场面,往往一边说一边命人抬进来大箱子,揭开箱盖,里面是白得晃眼的银子。除了送礼,我们还经常在古
以剑南节度浅论其在南诏与唐朝关系中所起的作用文依壹Talk编辑依壹Talk前言唐代初期,吐蕃在中国青藏高地上兴起,并以其为中心,从西北地区扩展到了西南地区,给唐代带来了巨大的危险。在唐朝末期,在面对吐蕃入侵滇西北地区的情况下