最近刚好了解低代码平台,顺手写了一个拖拽版羊了个羊。第二关写的有点难了,也是卡自己的bug才过关了
一、实现拖拽效果
利用vuedraggable实现拖拽效果,官方文档:github.com/SortableJS/…
主要props:
- group 保持一致既可相互拖拽
- sort 表示在组内能否拖拽改变顺序
拖拽demo:
<template>
<div>
<!-- 第一个数据组 -->
<draggable class="group" :list="arr1" item-key="index" group="person" :sort="false">
<template #item="{ element }">
<div>{{element}}</div>
</template>
</draggable>
<!-- 第二个数据组 -->
<draggable class="group" :list="arr2" group="person" @change="endDrag" item-key="index" :sort="false">
<template #item="{ element }">
<div>{{element}}</div>
</template>
</draggable>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue'
import draggable from 'vuedraggable'
interface Date {
arr1: string[]
arr2: string[]
}
export default defineComponent({
components: { draggable },
setup() {
const state = reactive<Date>({
arr1: ['张三', '李四', '王五', '赵六'],
arr2: [],
})
// arr2的数据发生变化时的回调
const endDrag = () => {
console.log('arr1拖到arr2,拖拽完成')
}
return {
...toRefs(state),
endDrag
}
}
})
</script>
效果:
二、生成icon数据
- 生成icon需要3个相同的才能被消除
- 根据光卡不同,生成icon数量不同
- 每个icon的位置是随机的
// 定义一个icon的接口类型
interface oneIcon {
id: number
icon: string
path: string
x: number
y: number
pos: number
isCover: boolean
}
// 获取icon图片
const getIconPath = (icon: string): string => {
return require(`@/assets/img/${icon}.png`)
}
// 获取随机数字
const getNumber = function (min: number, max: number): number {
return Math.round(Math.random() * (max - min) + min)
}
// 生成数据,参数为关卡
export const getList = (level: number) => {
const list: Array<oneIcon> = []
const iconList: string[] = ['fruit1', 'fruit2', 'fruit3', 'fruit4', 'fruit5', 'fruit6', 'fruit7', 'fruit8', 'fruit9', 'fruit10']
let i = level === 1 ? 3 : 6 // 需要是3的倍数才能消除,第一关不重复
while (i--) {
// 根据关卡不同,选择不同数量的icon,这里采用指数,使关卡难度陡增
iconList.slice(0, 3 ** (level ** 2)).forEach(icon => {
list.push({
id: Math.random(), // 随机一个id
icon,// icon名字,后续用于消除对比
path: getIconPath(icon),// 图片的src
// 以下数据可根据实际情况设置,我的每个icon大小是50x50,容器大小是250x250
x: getNumber(0, 4 * 50), // left,x轴上打乱些,可以增加难度
y: getNumber(0, 4 * 2) * 25, // top,让y轴上排列整齐些,结果均是:0,25,50,75...
pos: getNumber(0, 4), // z-index
isCover: false // 是否被覆盖了,默认都没有被覆盖
})
})
}
return list
}
三、设置遮挡效果
- 通过设置每个icon上的isCover,控制遮挡效果,被覆盖的元素不能拖拽
- 通过碰撞算法检测是否有遮挡
- 通过数组的index和pos确定哪一个元素在上或下
1. 检查是否覆盖
// 检测是否覆盖
export const checkCover = (list: Array<oneIcon>): void => {
const len = list.length
// 先将所有的icon设置为不覆盖
list.forEach(item => {
item.isCover = false
})
// 双层循环,两两对比
for (let i = 0; i < len - 1; i++) {
const curcat = list[i]
for (let j = i + 1; j < len; j++) {
const otherCat = list[j]
// 50是icon的大小
if (!(curcat.x >= otherCat.x + 50 || otherCat.x >= curcat.x + 50 || curcat.y >= otherCat.y + 50 || otherCat.y >= curcat.y + 50)) {
// 有重叠
if (curcat.pos > otherCat.pos) {
// 判断z-index,较小的被覆盖
otherCat.isCover = true
} else {
// curcat的index小于otherCat的index,所以curcat被覆盖
curcat.isCover = true
}
}
}
}
}
2. 设置被覆盖的元素不能被拖拽
- filter过滤不能被拖拽的元素
<!-- 被覆盖的不能拖拽 -->
<draggable
:list="iconArray"
item-key="id"
group="icon"
:sort="false"
filter=".cover"
>
<template #item="{ element }">
<div
:style="{
top: element.y + 'px',
left: element.x + 'px',
zIndex: element.pos
}"
:class="{ cover: element.isCover }"
>
<!-- 被覆盖增加一个半透明蒙层 -->
<div class="iscover" v-if="element.isCover"></div>
<img :src="element.path" alt="" />
</div>
</template>
</draggable>
四、消除
- 监听每次槽内的数据变化,执行消除回调
- 判断数组相邻的三个元素是否相等,进行消除
1. 消除回调
export const mergeCat = (list: Array<oneIcon>): void => {
let i = 0
while (i < list.length - 2) {
// 判断相邻三个元素是否相同
if (list[i].icon === list[i + 1].icon && list[i + 1].icon === list[i + 2].icon) {
list.splice(i, 3)
} else {
i++
}
}
}
2. 监听数据变化
- @change="endDrag",监听变化
<draggable
class="res-group"
:list="iconArray2"
group="icon"
@change="endDrag"
item-key="id"
:sort="false"
>
<template #item="{ element }">
<div class="item">
<img :src="element.path" alt="" />
</div>
</template>
</draggable>
...
const endDrag = () => {
// 重新计算元素是否覆盖
checkCover(state.iconArray)
// 槽内元素小于3,不进行消除
const len = state.iconArray2.length
if (len < 3) return
// 消除
mergeIcon(state.iconArray2)
// 槽内元素大于7,游戏结束
if (state.iconArray2.length >= 7) {
context.emit('gameOver')
}
// 容器内没有元素了,过关了
if (state.iconArray.length === 0) {
context.emit('complete')
}
}