【中秋特文】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.总结

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

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

相关推荐
Momo__1 分钟前
MDN MCP Server——Mozilla 把 Web 文档接进 AI Agent,从此 LLM 不再瞎编 API
前端·ai编程·mcp
妙码生花1 分钟前
现代前端的极致性能 icon 加载方案(死磕成功版)
前端·vue.js·typescript
掘金者阿豪1 小时前
把业务数据变成共享仪表盘:Metabase可视化与远程访问实践
前端·后端
kyriewen1 小时前
折腾了半年 AI 编程工作流,最后发现效率瓶颈是桌上那块屏幕
前端·javascript·ai编程
蜗牛前端2 小时前
codex 全流程开发上线的高颜值礼簿小程序
前端·微信小程序
大龄秃头程序员2 小时前
我在图文流 App 里落地双层缓存、弱网降级与 OOM 治理
前端
老王以为2 小时前
React Renderer 分离的多平台架构
前端·react native·react.js
hunterandroid2 小时前
Kotlin Coroutines 与 Flow:让异步任务更清晰
前端
Bigger3 小时前
从零搭建 AI 代码审查服务:一份前端也能看懂的 Python 学习笔记
前端·ci/cd·ai编程
lichenyang4533 小时前
JSAPI、NAPI、Biz、Imp:ASCF Demo 如何真正调用系统能力和 C++ 能力
前端