我正在参加「码上掘金挑战赛」详情请看:码上掘金挑战赛来了!
前言
这是一个我用来回顾Vue3+vite做的小游戏,因为我自从学习过Vue3后一直在使用Vue2全家桶,几乎已经把Vue3的东西忘完了,为了能尽快重新拿起新技术,同时也为了参加掘金的活动,花了一些时间做了这个小游戏,用代码操作小兔子是玩掘金的那个挖矿游戏想到的
整体代码量不算小也不算大,由于我第一次自己上手敲Vue3多少有点不适应,因此代码可能有点臃肿
介绍
- 整个小游戏使用的是Vue3制作
- 兔子固定被生成在左上角
- 月饼的位置每一局都是随机的
- 每一条指令需要按下回车才会开始读取与执行
- 若同时输入多个指令,则仅会执行第一条指令
- 共有5条指令,功能与写法如下表
| 指令 | 功能 | 示例 |
|---|---|---|
| /take | 让兔子挖掘 | /take |
| /up (int) | 让兔子向上移动指定个单位 | /up 1 |
| /right (int) | 让兔子向右移动指定个单位 | /right 1 |
| /down (int) | 让兔子向下移动指定个单位 | /down 1 |
| /left (int) | 让兔子向左移动指定个单位 | /left 1 |
关于挖掘
- 每一次让兔子挖掘,若月饼不在挖掘范围内(0.5个单位),则兔子会探测一次兔子与月饼的距离并输出,反之则游戏成功
- 兔子与月饼的距离是使用勾股定理计算得出的,但由于JavaScript在计算浮点数时有缺陷,因此输出的结果是经过了两次floor约等后得出的
关于移动
- 为了游戏运行稳定,请尽量不要让兔子移动非整数个单位(感觉会出问题,但是目前还没有)
- 兔子跑得很快,于是1个单位是10个像素(hhh)
运行图
码上掘金演示
注意:为了得到正常的演示效果,请在体验前收起左侧代码栏
代码解释
template
<template>
<div class="outer">
<div class="game">
<div class="player" ref="player">兔</div>
<div class="cake" ref="cake" v-show="gameFinish">饼</div>
</div>
<div class="text">
<div class="textArea" v-html="history" ref="textArea"></div>
<input type="text" @keyup.enter="getCode" v-model="input">
</div>
</div>
</template>
style
<style scoped>
.player {
height: 20px;
width: 20px;
border: 1px solid pink;
color: pink;
text-align: center;
line-height: 20px;
position: absolute;
}
.cake {
height: 20px;
width: 20px;
border: 1px solid orange;
color: orange;
text-align: center;
line-height: 20px;
position: absolute;
}
.outer {
display: flex;
flex-direction: row;
}
.game {
position: relative;
height: 300px;
width: 300px;
border: 1px solid #000;
}
.text {
height: 300px;
width: 300px;
}
.textArea {
height: 280px;
border: 1px solid #000;
overflow-x: auto;
}
input {
width: 294px;
margin: 0;
}
</style>
- 游戏界面加上了
position: relative,兔子、月饼加上了position: absolute属性,方便控制其位置 - HTML与CSS没有什么内容,下面将详细解释script部分
解析输入
let input = ref('')
let textArea = ref(null)
let coordinate = [0, 0]
let gameFinish = ref(false)
let cake = ref(null)
let cakeLocation = [Math.floor(Math.random() * (300 - 20)), Math.floor(Math.random() * (300 - 20))]
onMounted(() => {
console.log(player.value);
cake.value.style.top = cakeLocation[0] + 'px'
cake.value.style.left = cakeLocation[1] + 'px'
})
function getCode() {
console.log(input.value);
let strArr = input.value.split(' ')
if (strArr[0].indexOf('/') === 0) {
if (strArr.indexOf('/take') === 0) {
if(dig()){
outputText('挖到月饼啦~')
gameFinish.value = true
}else{
console.log(coordinate,cakeLocation);
outputText('没有挖到月饼')
outputText(`我感觉距离月饼还有${calculation(coordinate,cakeLocation)}个单位`)
}
} else {
move(strArr)
}
} else {
outputText('无法理解的语句:(')
}
inputText(input.value)
input.value = ''
nextTick(()=>{
textArea.value.scrollTop = textArea.value.scrollHeight
})
}
function dig() {
console.log(coordinate,cakeLocation);
for (let i = 0; i < coordinate.length; i++) {
if(Math.abs(coordinate[i] * 10 - cakeLocation[i]) > 10){
return false
}else{
return true
}
}
}
function calculation(player,cake){
let lineX = player[0]*10 - cake[0]
let lineY = player[1]*10 - cake[1]
return Math.floor(Math.sqrt(Math.floor(lineX*lineX + lineY*lineY))) / 10
}
- 游戏开始时需要让月饼随机放在一个位置,需要在组件挂载成功后改变其位置,因此在
onMounted钩子中操作使用ref取到的DOMcake - 一切输入都将进入
getCode函数中,指令将在这里校验基本语法与对语句的处理 outputText是一个自己封装的一个用来输出的函数,将会在下面展示讲解inputText是一个自己封装的用来操作input框的函数,同样将会在下面展示讲解- 值得注意的是在
nextTick中放的是用来控制历史内容的scrollBar的,使其在发生输入事件时置于最下方,类似于聊天软件界面 dig是挖掘函数,如果月饼的位置在挖掘范围内的话则游戏成功,否则探测月饼距离- 如果指令为take,则开始挖掘,若不是,则将会进入到下一模块“移动”
coordinate是兔子当前位置的坐标
兔子移动
let player = ref(null)
const num = /^[0-9]+$/
function move(strArr) {
let direction = strArr[0]
console.log(strArr[1]);
console.log(num.test(strArr[1]));
if (num.test(strArr[1]) == true) {
if (direction === '/up') {
console.log(player.value.style, coordinate[0] + strArr[1] * 10);
player.value.style.top = coordinate[0] * 10 - strArr[1] * 10 + 'px'
coordinate[0] -= Number(strArr[1])
outputText(`已向上移动${strArr[1]}个单位`)
} else if (direction === '/right') {
player.value.style.left = coordinate[1] * 10 + strArr[1] * 10 + 'px'
coordinate[1] += Number(strArr[1])
outputText(`已向右移动${strArr[1]}个单位`)
} else if (direction === '/down') {
player.value.style.top = coordinate[0] * 10 + strArr[1] * 10 + 'px'
coordinate[0] += Number(strArr[1])
outputText(`已向下移动${strArr[1]}个单位`)
} else if (direction === '/left') {
player.value.style.left = coordinate[1] * 10 - strArr[1] * 10 + 'px'
coordinate[1] -= Number(strArr[1])
outputText(`已向左移动${strArr[1]}个单位`)
} else {
outputText('无法理解的语句:(')
}
} else {
outputText('无法理解的语句:(')
}
console.log(coordinate);
for (let i = 0; i < coordinate.length; i++) {
if (coordinate[i] < 0) {
coordinate[i] = 0
if (i == 0) {
player.value.style.top = 0
} else if (i == 1) {
player.value.style.left = 0
}
outputText('越界啦!嫦娥姐姐说月饼就在这一片地方')
} else if (coordinate[i] > 28) {
coordinate[i] = 28
if (i == 0) {
player.value.style.top = 280 + 'px'
} else if (i == 1) {
player.value.style.left = 280 + 'px'
}
outputText('越界啦!嫦娥姐姐说月饼就在这一片地方')
}
}
}
num是一个正则表达式,表示只允许输入数字direction提取出了输入指令的第一段,即移动方向- 在代码的第一行,使用了ref来获取到表示兔子的DOM
历史记录
let history = ref('')
function inputText(value) {
history.value = history.value.concat(value, '<br>')
}
function outputText(value) {
nextTick(()=>{
history.value = history.value.concat('兔子:',value, '<br>')
})
}
- 这一片很简单,控制右侧输入输出容器的历史记录
history初始是一个空字符串,后续将会对其进行字符串的拼接- 每一次拼接都会在结尾加上
<br>表示换行,注意只有在template中加上v-html才会对字符串进行HTML解析