【中秋特文】Vue3实现接月饼小游戏

1.前要

今天刷掘金,无意间看到了中秋创意投稿大赛,正所谓掘金王者霸业,岂可无我,随设计了一款简单的小游戏,以庆中秋,没想到这点小东西居然也花了我3个小时,太久没写代码了,一些基本的东西都有点生疏了啊,岂可修!多的不说了,祝大家中秋快乐,好运常来。

2.设计思路

本想着设计成金山打字风格的打字游戏,后来一想都过节了,玩个游戏还得敲键盘属实是有点不应该,我是个懒人就设计成用鼠标控制接月饼的游戏吧。接果子那就得有2个主要的元素---月饼还有盘子。然后考虑他们有那些动作和状态。

月饼

月饼作为得分道具,会从空中掉落,一直处于下落状态,直至落地或被盘子接触。落地或接触后随即消失,因此我将状态分为了2个状态:可视与不可视;唯二的事件:落地与接触盘子;唯一的动作:下落。如下图所示

  • 移动问题的解决

    那么我们来想想如何实现下落的效果,毕业后就一直从事与前端,一直写着无聊的业务代码,从没想过一个div还需要下落的事情,这可把我难到了,难道第一步就要放弃了,怎么可能,我不同意,还好大学用QT做过打砖块的游戏,仔细想想用QT定位元素的时候不是和现在的绝对定位相同吗?瞬间茅塞顿开,用setInterval和绝对定位就可以解决移动问题了。

  • 如何消失

    消失,那我可太熟了,哈哈,v-show,v-if直接上吧,看着上百个月饼,难道我得声明100个变量给它绑定吗?太可怕了。那就直接用数组和v-for来解决这个问题吧,至于怎么判断该消失哪个月饼呢?一开始的想法是按它的left值来判断,神奇的事情来了,当当,同一列的月饼集体消失了,敬请期待中秋大作<<消失的它>>,这可不行那该用什么来解决呢?要是有个唯一标识符就好了,要不我给月饼一个一个标号吧,列一个list?有一个月饼把list中的值pop一个?不行工程量太大了,我太懒肯定做不来,突然想到神奇的Symbol,唯一标识符,试了下果然能行,这样消失的问题也解决了,月饼死亡或触碰盘子的时候,获取其唯一标识符,通过filter从数组中剔除出去即可。

  • 如何判断落地和触碰盘子

    上文不是说了用setInterval来移动月饼吗?可以再setInterval中通过月饼的left和top以及月饼的width和height判断与盘子的距离以及与地面的距离,满足条件了就大吼一声"Jojo! 这是我最后的波纹了",随即消失即可。

代码如下,正如我所说我是个懒人加上写的急,所以没怎么打注释。

js 复制代码
<!--  -->
<template>
  <div class="cake" :style="{'background-image':imgUrl,transition: 'top 0.1s ease',position:'absolute' ,width: '20px', height: '20px', left: position.left + 'px', top: position.top + 'px'}"></div>
</template>

<script setup>
import { ref, reactive, onMounted, nextTick, defineProps, defineEmits, onBeforeUnmount } from 'vue';
import img from '../assets/cake.png'

let props = defineProps(['position','rabbitPosition','isBoom'])
let imgUrl = props.isBoom ? ref("url('/src/assets/boom.png')") : ref("url('/src/assets/cake.png')")
let isGet = false;
let position = reactive(props.position)
let rabbitPosition = reactive(props.rabbitPosition)
const emit = defineEmits(['getPoint','cakeDie','losePoint'])
let interVal = setInterval(() => {
    const pageHeight = document.documentElement.scrollHeight;
    if(position.top + 40 + rabbitPosition.bottom >= pageHeight && pageHeight - rabbitPosition.bottom >= position.top) {
        if(position.left + 20 >= rabbitPosition.left && position.left <= rabbitPosition.left + 200 && !isGet) {
            if(!props.isBoom)
                emit('getPoint')
            else
                emit('losePoint')
            isGet = true;
        }
    }
    if(position.top  > pageHeight - rabbitPosition.bottom + 10) {
        emit('cakeDie')
    }
    position.top += 10;
},100)

onBeforeUnmount(() => {
    clearInterval(interVal)
})

// 运动逻辑
</script>
<style scoped>
    .cake{
        background-size: cover;
        background-position: center;
        background-repeat: no-repeat;
    }
</style>

盘子

盘子的话,唯一状态就是可视,唯一动作是平移,有人问事件呢?刚刚说了得分事件我绑定到了月饼上,臭小子上课不听讲是吧,过来! 赏你一个爱的捏捏。因此实现的非常简单,我们只要告诉页面我们这个盘子在哪就好了。代码如下:

js 复制代码
<!--  -->
<template>
  <div class="rabbit" :style="{width: '100px', height: '20px', position:'absolute', left:left + 'px', bottom: '100px'}">
  </div>
</template>

<script setup>
import { ref, reactive, onMounted, nextTick,defineEmits} from 'vue';

const emit = defineEmits(['rabbitMove'])
let left = ref(0)
document.addEventListener('mousemove', handleMouseMove);
const pageWidth = document.documentElement.scrollWidth;
function handleMouseMove(event) {
  const mouseX = event.pageX; // 鼠标在页面中的水平坐标
  // 在这里可以使用获取到的坐标进行后续操作
  if(mouseX <= pageWidth - 50 && mouseX >= 50) {
    left.value = mouseX - 50;
    emit('rabbitMove',mouseX - 50);
  }
}

</script>
<style scoped>
    .rabbit {
        background-image: url('../assets/rabbit.png');
        background-size: cover;
        background-position: center;
        background-repeat: no-repeat;
    }
</style>

场地环境

有了月饼和盘子,还差什么?想想语文老师经常说什么,讲故事要有时间、地点、人物,我们来看看我们现在有什么,时间就是我们玩的时间可以不考虑;人物:玩家、月饼、盘子(不要问我为什么把盘子、月饼和人放在一起考虑,我是不会告诉你我语文没学好的,才不是的,哼!其实是玩游戏要有带入感嘛);那么就差一个了地点,我们需要给月饼和盘子提供一个场地,所有的事件都会在这里发生,月饼的list也存放在这里,得分、丢分也在这里,还需要通过这个'场地'告诉月饼盘子在哪里。当月饼告诉我们碰到盘子的时候,我们还需要在这里计分。(真可怕,得没得分居然是月饼告诉我得,万一它作弊骗我怎么办)

代码如下:

js 复制代码
<script setup>
import MoonCake from './components/moonCake.vue'
import Rabbit from './components/rabbit.vue'
import {reactive, ref} from 'vue'

let rabbitPosition = {bottom:100, left:0}
let texts = ref([{left:10,top:200},{left:30,top:100}]) 
let booms = ref([]) 
let point = ref(0)

let temp1 = setInterval(() => {
  const pageWidth = document.documentElement.clientWidth;
  console.log(pageWidth)
  const min = 1;
  const max = 10;
  const randomInt = Math.floor(Math.random() * (max - min + 1)) + min;
  let left = pageWidth * randomInt / 10;

  console.log(left)
  left = left * 0.8 | 0
  let key = Symbol(left)
  texts.value.push({left:left,top:100,key:key})
}, 1500);

let temp2 = setInterval(() => {
  const pageWidth = document.documentElement.clientWidth;
  console.log(pageWidth)
  const min = 1;
  const max = 10;
  const randomInt = Math.floor(Math.random() * (max - min + 1)) + min;
  let left = pageWidth * randomInt / 10;
  left = left * 0.8 | 0
  let key = Symbol(left)
  if((left / 2).toString().indexOf(".") != -1)
  booms.value.push({left:left,top:100,key:key})
}, 3000);

texts.value.forEach(item => {
  item.key = Symbol(item.left)
})

function rabbitMove(value) {
  rabbitPosition.left = value
}

function getPoint(value) {
  point.value++;
  texts.value = texts.value.filter(item => item.key != value.key)
}

function losePoint(value) {
  point.value--;
  booms.value = booms.value.filter(item => item.key != value.key)
}

function cakeDie(value) {
  texts.value = texts.value.filter(item => item.key != value.key)
}

function boomDie(value) {
  booms.value = booms.value.filter(item => item.key != value.key) 
}

</script>

<template>
  <div class="main" style="witdh: 100%; height: 100%">/
    <MoonCake :isBoom="false" @cakeDie="cakeDie(item)" @getPoint="getPoint(item)" :key="item.key" :rabbitPosition="rabbitPosition" v-for="item in texts" :position="item" ></MoonCake>
    <MoonCake :isBoom="true" @cakeDie="boomDie(item)" @losePoint="losePoint(item)" :key="item.key" :rabbitPosition="rabbitPosition" v-for="item in booms" :position="item" ></MoonCake>
    <Rabbit  @rabbitMove="rabbitMove"/>
    <div class="score">{{ point }}</div>
  </div>
</template>

<style scoped>
  .score{
    position:absolute;
    right: 50px;
    top: 50px;
    color: red;
    font-size: 30px;
    font-weight: 400;
  }

  .main{
    background-image: url('./assets/background.jpeg');
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
  }

  #app {
    width: 100%;
    height: 100%;
  }
  html, body {
    height: 100%;
  }
</style>

3.游玩效果展示

偷偷修改分数为1145分,孰能挡我,嘿嘿嘿,偷偷告诉你们我还加了炸弹哦。

4.需要改进的地方

  • 炸弹速度需要进行调整以增加难度。
  • 得分、丢分特效
  • 盘子太丑了
  • 关卡设置

5.总结

这个游戏可以说是我毕业以后做的最有趣得东西之一了,年复一年、日复一日得重复着业务代码,感觉代码之魂也沉寂了呢,这个小游戏又让我想起来了当初对一切都好奇的自己,回想当年天天一柱擎天的日子,现在却干什么都提不起劲了,唉。你们不要想太多了,我说的是写代码的热情啊,混蛋!

希望这份简单的小游戏,能让你也在忙碌之余,也能想起当初那个什么都没有,但对一切都充满期待的自己吧,忘记了过去的话,也是会迷失自我的吧。

相关推荐
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax