记录一次用 Vue 简单完成小游戏:十滴水(无动画)
前言
闲来无事,想起以前经常玩的一个小游戏《十滴水》,简单开发游戏逻辑,时间原因没编写动画和不同难度。
基于 Vue 开发,效果如下。
1.游戏规则
鼠标点击消耗子弹增大水滴,水滴饱和后再加水就会破裂,破裂后会向四方溅射水滴,引起一系列爆炸。
在这里用方块代替水滴,方块内部填满后意味着水滴饱和。
每次水滴爆炸时获得分数,所有水滴破裂即为获胜,进入下一关,奖励若干子弹。
用完子弹未进入下一关,则游戏结束。
2.开发思路
1.地图设计
随机散布水滴在一个矩阵上,每个元素即为水滴 水滴都有自身属性,如 x:行,y:列,level:当前层级。
2.子弹发射事件
每当点击元素时,则消耗一个子弹,并为元素层级累计 1,当层级大于 0 时,则表明该坐标存在一个水滴。
3.水滴状态
当元素的层级达到设定的最大层级,则代表水滴发生爆炸,自身消失(层级归 0),并向四周(上下左右)发射水滴。
4.水滴溅射
水滴溅射时会朝自身周围四个方向发射水滴,如果发射中的水滴经过的元素层级为 0,则表明该元素没水滴,继续朝该方向飞行,直到找到有层级(有水滴)的元素,为之层级+1,如超出边界,则不作处理,并重复上诉条件判断。
5.获胜条件
当所有水滴不存在时(矩阵中所有元素的层级为 0 时),则获胜,进入下一关卡。
3.源码
3.1 Template 部分
<template>
<div class="st">
hasBlock:{{ hasBlock }} bullets:{{ bullets }} scores:{{ scores }} level:{{
gLevel
}}
<div v-for="(ytem, y) in martix" :key="y">
<div
class="cbutton"
:class="ytem.class"
v-for="(xtem, x) in ytem"
:key="x"
@click="sendBullet(xtem)"
>
<div class="btn_cover" v-for="ltem in xtem.level" :key="ltem"></div>
</div>
</div>
</div>
</template>
3.2 Script 部分
<script>
export default {
data() {
return {
mapSize: {
x: 5,
y: 5
},// 矩阵大小
martix: [], // 矩阵数据
bullets: 0, // 可用子弹数
levelMax: 5, // 爆炸层级
scores: 0, // 分数
hasBlock: 0, // 当前剩余块 为0则胜利进入下一关
gLevel: 0 // 当前关卡
};
},
mounted() {
this.__INIT(); // 初始化
},
methods: {
GetRandomNum(Min, Max) {
const Range = Max - Min;
const Rand = Math.random();
return Min + Math.round(Rand * Range);
},
__INIT() {
// 地图大小
this.gLevel = 0;
this.scores = 0;
this.bullets = 15; // 初始子弹数
this.createMap();
},
// 下一关
__NextMap() {
this.gLevel++;
this.bullets += 8; // 每次过关,奖励8个子弹
this.createMap();
},
// 创建地图,初始化数据
createMap() {
let hasBlock = 0;
const m = this.mapSize;
const martix = [];
for (let y = 0; y < m.y; y++) {
for (let x = 0; x < m.x; x++) {
martix[x] = martix[x] || [];
martix[x][y] = {
x,
y,
level: this.GetRandomNum(0, 3), // 获取随机层级 随机0-3层
class: []
};
if (martix[x][y].level > 0) {
// 记录需要消灭的水滴数量
hasBlock++;
}
}
}
this.martix = martix;
this.hasBlock = hasBlock;
},
radioActive(tem, direction) {
let { x, y } = tem;
// 有方向朝指定方向扩展,没反向四散
if (direction) {
// 存在方向 去对应方向查找,直到找到为之加一层级或者找不到结束
let data = null;
switch (direction) {
case "up": {
while (x >= 0 && !data?.level) {
x--;
data = this.martix[x]?.[y];
}
data?.level && this.upLevelOnce(data);
break;
}
case "down": {
while (x < this.martix.length && !data?.level) {
x++;
data = this.martix[x]?.[y];
}
data?.level && this.upLevelOnce(data);
break;
}
case "left": {
while (y >= 0 && !data?.level) {
y--;
data = this.martix[x]?.[y];
}
data?.level && this.upLevelOnce(data);
break;
}
case "right": {
while (y < this.martix[x].length && !data?.level) {
y++;
data = this.martix[x]?.[y];
}
data?.level && this.upLevelOnce(data);
break;
}
default: {
}
}
} else {
// 不存在方向,说明需要四周执行
this.radioActive(tem, "up");
this.radioActive(tem, "down");
this.radioActive(tem, "left");
this.radioActive(tem, "right");
}
},
// 为某个点升一级
upLevelOnce(tem) {
if (!tem) return;
let { x, y, level } = tem;
if (level + 1 !== this.levelMax) {
this.martix[x][y].level++;
return;
} else {
// 达到爆炸层级 当前清零
this.martix[x][y].level = 0;
// 需要消灭的水滴减一
this.hasBlock--;
// 分数累计
this.scores++;
// 如果剩余消灭水滴为0进入下一级
if (this.hasBlock === 0) {
//
this.__NextMap();
return;
}
// TODO: 进行动画播放
// update other data
// setTimeout(() => {
this.radioActive(tem); // 因为爆炸,所以四周发射水滴
// }, 1000);
}
},
// 指定某个点升一级
sendBullet(tem) {
this.bullets--;
this.upLevelOnce(tem);
// 爆炸完后没子弹了也没通过当前关,结束
if (this.bullets === 0) {
alert("you are die");
this.__INIT(); // 重新初始化
return;
}
}
}
};
</script>
3.3 Style 部分
<style>
body {
background: #242943;
color: #ffffff;
}
.cbutton {
width: 120px;
position: relative;
display: inline-flex;
padding: 0;
box-shadow: inset 0 0 0 2px #ffffff;
color: #ffffff;
cursor: pointer;
height: 3.5em;
}
.btn_cover {
width: 25%;
background-color: #fff;
}
</style>