国庆假后,摸鱼1天,vue手撸了一个简易版的羊了个羊。
github地址:github.com/yujinhongMM…
在线体验地址(手机模式下打开):yujinhongmm.github.io/yangleyang/
注:使用到的所有的图片都可以到阿里巴巴矢量图标库去下载哦。
第一关的地图绘制
- 首先把我们的视口宽分成8份,每份占比12.5vw。
- 装卡片的容器设计为87.5vw * 87.5vw,每个卡片占比12.5vw。
- 将容器设计为相对定位,卡片设置为绝对定位。
- 设计地图数组的数据格式。每个卡片应该具备绝对定位需要的z-index,top,left,以及卡片的点击状态click,卡片类型type,id等。这里以最简单的第1关为例。
export default [
// 第一层
{
id: 1,
type: 0,
top: 12.5,
left: 12.5,
click: false,
zIndex: 1,
},
{
id: 2,
type: 0,
top: 12.5,
left: 37.5,
click: false,
zIndex: 1,
},
{
id: 3,
type: 1,
top: 12.5,
left: 62.5,
click: false,
zIndex: 1,
},
{
id: 4,
type: 0,
top: 37.5,
left: 12.5,
click: false,
zIndex: 1,
},
{
id: 5,
type: 0,
top: 37.5,
left: 37.5,
click: false,
zIndex: 1,
},
{
id: 6,
type: 1,
top: 37.5,
left: 62.5,
click: false,
zIndex: 1,
},
{
id: 7,
type: 1,
top: 62.5,
left: 12.5,
click: false,
zIndex: 1,
},
{
id: 8,
type: 0,
top: 62.5,
left: 37.5,
click: false,
zIndex: 1,
},
{
id: 9,
type: 2,
top: 62.5,
left: 62.5,
click: false,
zIndex: 1,
},
// 第2层
{
id: 10,
type: 2,
top: 12.5 + 5,
left: 12.5,
click: false,
zIndex: 2,
},
{
id: 11,
type: 2,
top: 12.5 + 5,
left: 37.5,
click: false,
zIndex: 2,
},
{
id: 12,
type: 2,
top: 12.5 + 5,
left: 62.5,
click: false,
zIndex: 2,
},
{
id: 13,
type: 1,
top: 37.5 + 5,
left: 12.5,
click: false,
zIndex: 2,
},
{
id: 14,
type: 0,
top: 37.5 + 5,
left: 37.5,
click: false,
zIndex: 2,
},
{
id: 15,
type: 2,
top: 37.5 + 5,
left: 62.5,
click: false,
zIndex: 2,
},
{
id: 16,
type: 1,
top: 62.5 + 5,
left: 12.5,
click: false,
zIndex: 2,
},
{
id: 17,
type: 2,
top: 62.5 + 5,
left: 37.5,
click: false,
zIndex: 2,
},
{
id: 18,
type: 1,
top: 62.5 + 5,
left: 62.5,
click: false,
zIndex: 2,
}
]
- 有了地图以后我们就可以开始绘制,用for循环遍历生成卡牌,同时设置卡片的top,left,z-index。用click来判断当前的卡片的样式是亮色还是灰色。clickCard是我们点击卡片的方法,待会儿会在拾取的部分讲到。
<div class="container">
<div
v-for="lattice in map"
:key="lattice.id"
:class="lattice.click ? 'lattice' : 'lattice gray' "
:style="`top: ${lattice.top}vw; left: ${lattice.left}vw; z-index: ${lattice.zIndex}`"
@click="() => clickCard(lattice)">
<img :src="images[lattice.type]" class="lattice-img" />
</div>
</div>
判断卡片能否拾取
在羊了个羊中,卡片为灰色的时候是不可以点击拾取的,亮色的时候可以拾取。我们可以通过先把所有卡片的click状态置为true,再通过2个for循环遍历去比较低z-index卡牌j是否被高z-index卡牌i档住,如果是的话将j的click置为false。
判断B是否在A下面的条件
- 不考虑边界
- j的左上角在i的覆盖范围内
- j的右上角在i的覆盖范围内
- j的左下角在i的覆盖范围内
- j的右下角在i的覆盖范围内
- j的左上角在i的覆盖范围内
- 考虑边界
代码如下:
// 计算当前map可拾取的状态
export const changeMapClick = (originMap) => {
const map = [...originMap];
const length = map.length;
// 全部变成true
for (let i = 0; i < length; i++) {
map[i].click = true;
}
// 修改不可点击的状态
// 全部变成true
for (let i = 0; i < length; i++) {
const left = map[i].left;
const top = map[i].top;
for (let j = 0; j < length; j++) {
if (map[i].zIndex > map[j].zIndex) {
// 判断j在不在i的范围内
const jTop = map[j].top, jLeft = map[j].left;
// 判断左上角
const LT = jTop > top && jTop < top + WIDTH && jLeft > left && jLeft < left + WIDTH;
// 判断右上角
const RT = jTop > top && jTop < top + WIDTH && jLeft + WIDTH > left && jLeft + WIDTH < left + WIDTH;
// 判断左下角
const LB = jTop + WIDTH > top && jTop + WIDTH < top + WIDTH && jLeft > left && jLeft < left + WIDTH;
// 判断右下角
const RB = jTop + WIDTH > top && jTop + WIDTH < top + WIDTH && jLeft + WIDTH > left && jLeft + WIDTH < left + WIDTH;
// 边界重合情况
const boundary =
(top === jTop && jLeft >= left && jLeft < left + WIDTH) ||
(top === jTop && jLeft <= left && jLeft + WIDTH > left) ||
(left === jLeft && jTop >= top && jTop < top + WIDTH) ||
(left === jLeft && jTop <= top && jTop + WIDTH > top)
if (LT || RT || LB || RB || boundary) {
map[j].click = false;
continue;
}
}
}
}
return map;
}
卡片click设置时机
- 初始化的时候
- 每次卡片被点击的时候
卡片收集框
我们卡片收集框中的卡片通过数组list来维护。通过for循环遍历到我们的视图上就行。
<div class="drawer">
<div v-for="lattice in list" :key="lattice.id" class="lattice drawer-lattice">
<img :src="images[lattice.type]" class="lattice-img" />
</div>
</div>
卡片点击时需要做的事情
- 判断该卡片是否能够点击,能点击进行步骤2。
- 对countSelected(当前已选择的全部卡片数量)进行累加1。
- 把选择的卡牌从地图map上删除,并重新计算地图map的所有click状态。
- 判断当前被选择的卡片是否在list中有相同类型的卡片type。
- 如果有的话放在同类型卡片后面,没有的话放在list最后。
- 判断当前list中是否存在被消除的卡片。
- 判断当前list的长度是否等于7,是的话挑战失败。
- 判断当前countSelected(当前已选择的全部卡片数量)是否等于卡片的总数量,如果是的话挑战成功。
const clickCard = (lattice) => {
if (lattice.click && gameState.value === GAME_STATE.PENDING) {
countSelected++;
map.value = changeMapClick(map.value.filter(item => item.id !== lattice.id));
// 需要放进被选择框的列表
const index = list.value.findIndex(item => item.type === lattice.type)
if (index === -1) {
list.value.push(lattice);
} else {
list.value.splice(index, 0, lattice);
}
setTimeout(() => {
// 判断能不能消除
for (let i = 0; i < list.value.length - 2; i++) {
// 可以消除
if (list.value[i + 2].type === list.value[i].type) {
list.value.splice(i, 3);
}
}
// 判断length是否为7
if (list.value.length === 7) {
gameState.value = GAME_STATE.FAIL;
}
if (countSelected === count.value) {
gameState.value = GAME_STATE.SUCCESS;
}
}, 300)
}
}
现在这个游戏已经基本实现了羊了个羊的第一关了。
实现地图动态生成
地图的动态生成,我做得比较简单,主要就是产生随机数。我主要是绘制了一个7 * 7(1层),6 * 6(2层),5 * 5(3层)的一个随机生成卡片固定的地图。再去判断卡片中不是3的倍数的卡片类型,随机生成该卡片类型的层数z-index(因为我这里的id是累加的,所以不会存在相同的情况,这里z-index用id生成),top,left再随机生成,最后得到动态生成的地图。
export const generateMap = () => {
const mapList = [];
const map = new Map([[0, 0], [1, 0], [2, 0], [3, 0], [4, 0], [5, 0]])
let id = 0;
for (let i = 0; i < 7; i++) {
for (let j = 0; j < 7; j++) {
const type = Math.floor(Math.random()*6);
map.set(type, map.get(type) + 1)
id++;
const obj = {
id,
type,
top: WIDTH * i,
left: WIDTH * j,
click: false,
zIndex: 1,
}
mapList.push(obj);
}
}
for (let i = 0; i < 6; i++) {
for (let j = 0; j < 6; j++) {
const type = Math.floor(Math.random()*6);
map.set(type, map.get(type) + 1)
id++;
const obj = {
id,
type,
top: WIDTH * i + WIDTH / 2,
left: WIDTH * j + WIDTH / 2,
click: false,
zIndex: 2,
}
mapList.push(obj);
}
}
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 5; j++) {
const type = Math.floor(Math.random()*6);
map.set(type, map.get(type) + 1)
id++;
const obj = {
id,
type,
top: WIDTH * i + WIDTH,
left: WIDTH * j + WIDTH,
click: false,
zIndex: 3,
}
mapList.push(obj);
}
}
// 遍历map
for (let [key, value] of map) {
const remainder = 3 - value % 3;
if (remainder !== 3) {
for (let j = 0; j < remainder; j++) {
id++;
const random_1 = Math.floor(Math.random() * 75);
const random_2 = Math.floor(Math.random() * 75);
const obj = {
id,
type: key,
top: random_1 % WIDTH < 3 ? random_1 + 4 : random_1,
left: random_2 % WIDTH < 3 ? random_2 + 4 : random_2,
click: false,
zIndex: id,
}
mapList.push(obj);
}
}
}
return mapList;
}
游戏展示图
游戏的主要内容就是这些,具体的代码,有兴趣的话可以去github地址:github.com/yujinhongMM…