我正在参加「初夏创意投稿大赛」详情请看:初夏创意投稿大赛
介绍
端午将至,大家都开始吃粽子了么,你是喜欢吃北方的甜的红枣粽?还是南方的大肉粽呢?
本期我们将使用vite2与vue3开发出一个招财猫小游戏,通过考验眼力和预判能力,在图案不停滚动的同时选出可以转出不同的素材最终得到粽子奖励,康康你能用多少次才会转出自己喜爱口味的粽子吧~
演示
预览地址:https://jsmask.gitee.io/dwgame_laohuji/
正文
游戏分析
在开发之前,我们要想好游戏设计和规则是如何,才能进行往下进行。通过上图的演示和规则介绍,我们大致可以了解游戏流程了。
然后,根据了解的流程,进行拆解,接下来我们主要会讲解这些问题:
招财猫css3帧动画。
图案条带中的素材在vite2的批量加载。
老虎机图案自定义条带生成。
老虎机无限滚动。
让老虎机停止并且获取抽奖码。
获得粽子后的纸屑飞舞效果的实现。
招财动画
下图是本期用的所有素材,招财猫招手的是由四张图拼凑而成的,用了一些在线的雪碧图生成工具。在里面我们会得到每张图对应的 background-position
, 最后再用 animation
来完成这个帧动画。其中,animation-timing-function:steps(1, end)
是帧动画实现的核心,steps()
函数符号定义了一个阶梯函数,将输出值的域划分为等距阶梯。第一个值是需要传入正数,表示等距的数,而后一个表示插值的位置。
.cat { width: 574px; height: 630px; margin: 0px auto; position: relative; background-image: url("../assets/image/cat.png"); background-position: -10px -10px; &.active { animation: play-game 0.64s steps(1, end) infinite; } @keyframes play-game { 0% { background-position: -10px -10px; } 33% { background-position: -604px -10px; } 66% { background-position: -1198px -10px; } 100% { background-position: -10px -660px; } }}
素材加载
我们后面需要通过canvas合成条带,所以要先加载出需要转出的图案来。
按照原始的方案来,我们要手写好多图片资源文件的引入,所以十分麻烦。
import item0 from "../assets/image/item_0.png"import item1 from "../assets/image/item_1.png"import item2 from "../assets/image/item_2.png"// ...moreimport item9 from "../assets/image/item_9.png"
但是,Vite 中提供了 import.meta.glob
的语法糖来解决这种批量导入的问题,一次性加载出这些图片文件来。
const imgs = import.meta.globEager("../assets/image/item_*.png");let num = Object.keys(imgs).length;let items = Object.values(imgs).map((mod) => { let img = new Image(); img.onload = () => --num <= 0 && initGame(); img.src = mod.default; return img;});
当然我们这里用 import.meta.globEager
来可以同步加载这些资源。
条带生成
所谓的条带,就是老虎机中滚动的背景图,不停改变 backgroundPositionY
来实现滚动效果,是老虎机的核心,所使用到的条带自然就是重中之重,但在我们平时开发条带一般都是设计给的图片,但经常替换图片后又要重新问他们要新图甚是麻烦。所以,这里我想用 canvas
把刚才的那十张图案拼接起来,形成条带供我们使用。上一步,我们已经把资源加载完成了,接下来,可以需要写一个 createBackgroundImage
函数。
function createBackgroundImage({items = [], w = 45, h = 60, size = 40,test=false}) { let canvas = document.createElement("canvas"); let ctx = canvas.getContext("2d"); canvas.width = ctx.width = w; canvas.height = ctx.height = h * items.length; let BackgroundImage = [...items]; BackgroundImage.forEach((img, i) => { ctx.save(); ctx.drawImage(img, (w - size) / 2, (h - size) / 2 + h * i, size, size); if(test){ ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.font = "bold 36px Baloo Bhaijaan"; ctx.fillText(i, w / 2, h * i + h / 2 + 5, w); } ctx.restore(); }); return convertCanvasToImage(canvas);}function convertCanvasToImage(canvas) { var image = new Image(); image.src = canvas.toDataURL("image/png"); return image;}export default createBackgroundImage;
这里我们不光可以传入资源数组,而且可以传入块的宽和高,还有图案的大小,为了方便测试后面验证开奖码的正确性,加入 test
字段,如果开启,则会绘制上对应的数字编号。
无限滚动
当我们点击开始游戏按钮后,刚才生成出的条带就会不停的改变 backgroundPositionY
,那有什么好办法可以轻松控制这件事呢?
这里我推荐使用anime.js ,它一个功能强大且轻量级的 JavaScript 动画库。
# NPMnpm i animejs -S# YARNyarn add animejs# PNPMpnpm i animejs -S
<ul class="content"> <li ref="block" v-for="(item, index) in 3" :key="index" :style="{ backgroundImage: `url(${backgroundImage.src})` }" > <button :disabled="stops[index]" @click="handleStop(index)"> Stop </button> </li></ul>
import anime from "animejs";function play() { if (isActive.value) return false; isActive.value = true; count.value += 1; block.value.forEach((el, index) => { setTimeout(() => { stops.value[index] = false; let y = parseInt(el.style.backgroundPositionY || "0", 10); animes.value[index] = anime({ targets: el, backgroundPositionY: [h / 2, h * items.length + h / 2], loop: true, // 循环播放 direction: "normal", // 方向 easing: "linear", // 时间曲线 duration: 1200, // 播放时间 autoplay: true, // 是否立即播放 }); }, index * 240); });}
在 play
方法时,我们获取当绑定好条带的元素块,通过 animejs
给 backgroundPositionY
属性设置一个数组,这个数组第0位代表起始状态,第1位代表要到达的状态。然后把 loop
属性设置 true 。那么一个简单的条带无限滚动就完成了。
中奖判定
function handleStop(index) { stops.value[index] = true; let el = block.value[index]; let y = parseInt(el.style.backgroundPositionY || "0", 10); animes.value[index].remove(el); let n = Math.round((y - h / 2) / h); el.style.backgroundPositionY = n * h + h / 2 + "px"; giftCode.value[index] = (10 - n) % 10; if (stops.value.find((item) => !item) === undefined) { getResult(); }}
当我们每按停一个时,我们会迅速移除对应元素 animejs
动画,计算并赋值给最数值最接近的坐标点。然后获取到其对应的数字编号存起来形成抽奖码数组,当三个全部停止时,会调用 getResult
方法来根据刚才得到的抽奖码来开奖。
const giftData = [ { name: "红枣粽", score: 100, show: true, type:1, value: ["012", "025", "126", "256"], }, // ...]function getResult() { let str = giftCode.value.sort().join(""); let _obj = giftData.find((item) => item.value.includes(str)); if (!_obj) return (isActive.value = false); if (_obj.show) { giftObj.value = _obj; ruleShow.value = false; giftShow.value = true; } else { confetti(); hideGift(); }}
这里,我们把得到的抽奖码进行从小往大排列生成字符串,然后在 giftData
数组中找寻配合的组合,达成后进行不同的奖励画面。
纸屑飞舞
这里我使用了 canvas-confetti,它是专门来制作纸屑飞散的动画库。其原理是在页面创建了canvas
(也可以指定容器), 然后在里面绘制了几种形状可供选择,通过数学计算,模拟了很多物理运动来完成纸屑动画多彩的效果。
# NPMnpm i canvas-confetti -S# YARNyarn add canvas-confetti# PNPMpnpm i canvas-confetti -S
import item0 from "../assets/image/item_0.png"import item1 from "../assets/image/item_1.png"import item2 from "../assets/image/item_2.png"// ...moreimport item9 from "../assets/image/item_9.png"0
结语
现在已经把大家开发这类项目可能遭遇的问题或者方案大致给大家讲述完了。不知道,给你带来的帮助是多是少,也不知道大家点击了多少次才得到喜欢吃的粽子了,但不管是什么,开心快乐就好,最后祝大家端午安康,财运,福运多多,幸福满满。
原文:https://juejin.cn/post/7101111168571277349