拼图游戏
最近玩了玩孩子的拼图游戏,感觉还挺好玩的,心血来潮要不动手做一个吧,偷懒摸鱼的时候可以来一把。
以下就是拼图游戏的界面截图。
体验地址
代码开源地址
心得体会
虽说是一个小游戏,但是需要注意的地方还是挺多的
方块大小
譬如说这个方块的显示就比较麻烦,也是经历过几个版本的迭代,比较直观的想法可能是这个方块有九种,分为好几个尺寸,开始也是这么弄的。不过各种鼠标拖动之类的计算就比较麻烦了。后来的拼接也比较费劲,各种计算位置是否合适。
后来又更新了一个版本把所有的方块都弄成同样大小了,这样后续的计算就会简单很多,代码看起来也简洁了。
这样也方便后续如果说想要支持不同尺寸的方块,只需要改改相应的大小及偏移就能完全搞定了。
方块显示
每个方块都是在原图的一小部分,很容易就想到了使用背景偏移显示的方式,不过方块还有个问题就是他有地方突出来点,有的地方凹进去点,这个就用到了css的蒙版图片功能,也就做了上边的那种蒙版图片。
拖动拼接
由于方块目前设计的是5x8的方块进行游戏,如果太多了话其实是有一个缩放的问题,目前没有考虑,毕竟这样操作起来感觉还是挺麻烦的。实现来说就是改改相应的大小,倒是没有那么难弄。后续考虑给加上。
方块的拼接能够使用选中的一个图片进行拼接,也可能是已经拼好的部分进行拼接,也都是设计的取舍,当前游戏来说感觉还是以选中拖动移动的为主,只拼接这个方块周围的方块,更加专注一下,避免大力出奇迹的随便就完成游戏了。
代码
整体代码量也不多,也涉及到一些素材,可以从开源项目位置查看。
以下是部分代码,仅供参考。
<script setup>
import { onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import HeaderCompoent from '../../components/HeaderComponent.vue'const itemStyles = ref([])
var currentIndex = -1
var fixed = []
var relatives = []
var currentRelative = new Set()
const row = 5
const column = 8
const route = useRoute()
const picId = ref('0001')
const router = useRouter()const handleNaviBack = () => {router.back()
}onMounted(() => {picId.value = route.params.idlet styles = []let index = 0for (let i = 0; i < row; i++) {for (let j = 0; j < column; j++) {let classIndex = 0if (i == 0) {if (j == 0) {classIndex = 1} else if (j == (column - 1)) {classIndex = 3} else {classIndex = 2}} else if (i == (row - 1)) {if (j == 0) {classIndex = 7} else if (j == (column - 1)) {classIndex = 9} else {classIndex = 8}} else {if (j == 0) {classIndex = 4} else if (j == (column - 1)) {classIndex = 6} else {classIndex = 5}}styles.push({offsetx: -j * 100,offsety: -i * 100,classIndex: classIndex,// top: i * 100,// left: j * 100,top: (row-1) * 100 * (Math.random() * 1.2 - 0.1),left: (column-1) * 100 * (Math.random() * 1.2 - 0.1),x: i,y: j,w: 160,h: 160,index: index,})fixed.push(0)index += 1}}itemStyles.value = styleswindow.onmousemove = handleMouseMovewindow.onmouseup = () => {console.log('onmouseup')if (currentIndex == -1) {return}let styles = itemStyles.valuelet x = styles[currentIndex].xlet y = styles[currentIndex].yfor(let offset of [[0,1],[0,-1],[1,0],[-1,0]]) {// 判断一下是否越界let xx = x + offset[0]let yy = y + offset[1]if (xx < 0 || xx > (row-1) || yy < 0 || yy > (column-1)) {continue}let otherIndex = xx * column + yyif (otherIndex >= styles.length) {continue}if (currentRelative.has(otherIndex)) {continue}console.log('currentIndex ' + currentIndex)console.log('otherIndex ' + otherIndex)// 判断试一下是否很近if (Math.abs(styles[currentIndex].top - styles[otherIndex].top + (styles[otherIndex].x - x) * 100) > 20 || Math.abs(styles[currentIndex].left - styles[otherIndex].left + (styles[otherIndex].y - y) * 100) > 20) {continue}// 判断一下是否已经设置过fixed[currentIndex] = 1fixed[otherIndex] = 1for(let oIndex of currentRelative) {styles[oIndex].top = styles[otherIndex].top - (styles[otherIndex].x - styles[oIndex].x) * 100styles[oIndex].left = styles[otherIndex].left - (styles[otherIndex].y - styles[oIndex].y) * 100}if (!currentRelative.has(otherIndex)) {currentRelative.add(otherIndex)}if (relatives.indexOf(currentRelative) < 0) {relatives.push(currentRelative)}for(let sindex in relatives) {if (relatives[sindex] == currentRelative) {continue}if (relatives[sindex].has(otherIndex)) { // 如果说对上的元素在其他的分组里边for(let v of relatives[sindex]) {currentRelative.add(v)}relatives.splice(sindex, 1)}}console.log('fixed '+ currentIndex + ' ' + otherIndex)console.log('handleMouseUp - ' + Array.from(currentRelative), relatives)}let count = 0for(let i = 0; i < row * column; i++) {count += fixed[i]}if (count == row * column && relatives.length == 1) {setTimeout(() => {alert('恭喜完成拼图')}, 200);}currentIndex = -1}
})const handleMouseMove = (event) => {if (currentIndex > -1) {let ele = document.getElementById('grid')let rect = ele.getBoundingClientRect()let styles = itemStyles.valuelet x = styles[currentIndex].xlet y = styles[currentIndex].ylet classIndex = styles[currentIndex].classIndexlet offsetx = event.clientX - rect.leftlet offsety = event.clientY - rect.topstyles[currentIndex].left = offsetx - styles[currentIndex].w / 2 + 30styles[currentIndex].top = offsety - styles[currentIndex].h / 2 + 30for(let otherIndex of currentRelative) {styles[otherIndex].left = (styles[otherIndex].y - y) * 100 + styles[currentIndex].leftstyles[otherIndex].top = (styles[otherIndex].x - x) * 100 + styles[currentIndex].top}itemStyles.value = styles}
}const handleMouseDown = (index) => {console.log('handleMouseDown ' + index)currentIndex = indexcurrentRelative = new Set()for(let s of relatives) {if (s.has(currentIndex)) {currentRelative = sbreak}}if (currentRelative.size == 0) {currentRelative.add(currentIndex)}console.log('handleMouseDown - ' + Array.from(currentRelative), relatives)
}</script><template><div class="container"><div id="grid" class="grid"><img :src="`/images/pintu/${picId}.jpg`" alt="" class="backgroundImage"><div v-for="ss in itemStyles" :key="ss" class="imageContainer":style="{top: `${ss.top}px`,left: `${ss.left}px`,}"@mousedown="handleMouseDown(ss.index)"><div :class="['imageClass', `imageCover00${ss.classIndex}`]":style="{backgroundPosition: `${ss.offsetx}px ${ss.offsety}px`,backgroundImage: `url(/images/pintu/${picId}.jpg)`}"></div></div></div></div><HeaderCompoent></HeaderCompoent><div class="backBtn" @click="handleNaviBack">返回目录</div>
</template><style scoped>
.container {width: 100%;height: 100vh;display: flex;flex-direction: column;justify-content: center;align-items: center
}
.backBtn {position: fixed;top: 20px;right: 20px;
}
.grid {position: relative;width: 800px;height: 500px;
}
.backgroundImage {width: 100%;height: 100%;position: absolute;top: 0;left: 0;right: 0;bottom: 0;opacity: 0.2;
}
.imageContainer {position: absolute;width: 100px;height: 100px;
}
.imageClass {width: 160px;height: 160px;background-image: url('/images/pintu/0001.jpg');background-size: 800px 500px;overflow: visible;
}
.imageCover001 {mask-image: url('/images/pintu/cover/001.png');mask-repeat: no-repeat; mask-position: -30px -30px;mask-size: 160px;-webkit-mask-image: url('/images/pintu/cover/001.png');-webkit-mask-repeat: no-repeat; -webkit-mask-position: -30px -30px;-webkit-mask-size: 160px;
}
.imageCover002 {mask-image: url('/images/pintu/cover/002.png');mask-repeat: no-repeat; mask-position: -30px -30px;mask-size: 100%;-webkit-mask-image: url('/images/pintu/cover/002.png');-webkit-mask-repeat: no-repeat; -webkit-mask-position: -30px -30px;-webkit-mask-size: 100%;
}
.imageCover003 {mask-image: url('/images/pintu/cover/003.png');mask-repeat: no-repeat; mask-position: -30px -30px;mask-size: 100%;-webkit-mask-image: url('/images/pintu/cover/003.png');-webkit-mask-repeat: no-repeat; -webkit-mask-position: -30px -30px;-webkit-mask-size: 100%;
}
.imageCover004 {mask-image: url('/images/pintu/cover/004.png');mask-repeat: no-repeat; mask-position: -30px -30px;mask-size: 100%;-webkit-mask-image: url('/images/pintu/cover/004.png');-webkit-mask-repeat: no-repeat; -webkit-mask-position: -30px -30px;-webkit-mask-size: 100%;
}
.imageCover005 {mask-image: url('/images/pintu/cover/005.png');mask-repeat: no-repeat; mask-position: -30px -30px;mask-size: 100%;-webkit-mask-image: url('/images/pintu/cover/005.png');-webkit-mask-repeat: no-repeat; -webkit-mask-position: -30px -30px;-webkit-mask-size: 100%;
}
.imageCover006 {mask-image: url('/images/pintu/cover/006.png');mask-repeat: no-repeat; mask-position: -30px -30px;mask-size: 100%;-webkit-mask-image: url('/images/pintu/cover/006.png');-webkit-mask-repeat: no-repeat; -webkit-mask-position: -30px -30px;-webkit-mask-size: 100%;
}
.imageCover007 {mask-image: url('/images/pintu/cover/007.png');mask-repeat: no-repeat; mask-position: -30px -30px;mask-size: 100%;-webkit-mask-image: url('/images/pintu/cover/007.png');-webkit-mask-repeat: no-repeat; -webkit-mask-position: -30px -30px;-webkit-mask-size: 100%;
}
.imageCover008 {mask-image: url('/images/pintu/cover/008.png');mask-repeat: no-repeat; mask-position: -30px -30px;mask-size: 100%;-webkit-mask-image: url('/images/pintu/cover/008.png');-webkit-mask-repeat: no-repeat; -webkit-mask-position: -30px -30px;-webkit-mask-size: 100%;
}
.imageCover009 {mask-image: url('/images/pintu/cover/009.png');mask-repeat: no-repeat; mask-position: -30px -30px;mask-size: 100%;-webkit-mask-image: url('/images/pintu/cover/009.png');-webkit-mask-repeat: no-repeat; -webkit-mask-position: -30px -30px;-webkit-mask-size: 100%;
}
</style>