乐于分享
好东西不私藏

超甜!我用 Vue+UniApp 写了款情侣专属飞行棋

超甜!我用 Vue+UniApp 写了款情侣专属飞行棋

大家好!我有个朋友用 Vue 连夜开发了一款情侣专属飞行棋小游戏,现在他俩每天晚上都要玩上三百回合,感情直接原地升温!今天就把这个超有趣的项目拆解开来,分享核心实现思路和关键代码,手把手教你打造专属情侣小游戏。

先看效果

游戏界面粉嫩可爱,棋盘随机生成,玩家轮流掷骰子,走到特定格子会触发甜蜜任务或者炸弹惩罚。女生走到任务格必须完成一个任务(比如“亲对方一下”),男生走到惩罚格也要接受挑战。终点还有神秘奖励哦~

技术栈

  • 前端框架:Vue 2(其实Vue 3也通用)
  • 多端支持:UniApp(可打包成小程序或App)
  • 动画:CSS3 + JS定时器
  • 状态管理:Vue组件内状态(简单项目无需Vuex)

核心实现

1. 页面布局

<!-- 游戏主体 --><viewclass="game-content">    <!-- 棋盘容器 -->    <viewclass="chessboard-container">        <viewclass="chessboard">            <!-- 动态生成棋盘格子 -->            <blockv-for="(row, rowIndex) in checkerboard":key="rowIndex">                <blockv-for="(cell, colIndex) in row":key="cell.id">                    <viewclass="cell":class="{ show: cell.pathNum > 0,                                    start:cell.type==='start',                                    end:cell.type==='end',                                    task:cell.type==='task',                                    warn:cell.type==='warn',                                    trap:cell.type==='trap',                                    normal:cell.type==='normal' }" :style="{ backgroundColor: cell.bgColor }">                        <viewv-if="cell.pathNum>0">                            <viewclass="image-container glowPulse"v-if="cell.type==='trap'">                                <cl-icontype="icon-bomb"size="30"color="#111"></cl-icon>                            </view>                            <viewclass="image-container scaleAnimation"v-if="cell.type==='task'">                                <cl-icontype="icon-qinglv"size="20"color="#ff758c"></cl-icon>                            </view>                            <viewclass="image-container pulse"v-if="cell.type==='warn'">                                <cl-icontype="icon-baojingshu"size="20"color="#f44336"></cl-icon>                            </view>                            <viewclass="image-container"v-if="cell.type==='start'">                                <cl-icontype="icon-qidian"size="50"color="#4CAF50"></cl-icon>                            </view>                            <viewclass="image-container"v-else-if="cell.type==='end'">                                <cl-icontype="icon-zhongdian"size="40"color="#FF5722"></cl-icon>                            </view>                            <viewv-elseclass="step"> {{ cell.pathNum }}</view>                        </view>                    </view>                </block>            </block>        </view>        <!-- 人物 -->        <viewclass="character man":style="[manStyle]">            <imageclass="image"src="/static/images/fly/man.png"mode="aspectFill" />        </view>        <viewclass="character woman":style="[womanStyle]">            <imageclass="image"src="/static/images/fly/woman.png"mode="aspectFill" />        </view>    </view>    <!-- 控制面板 -->    <viewclass="control-panel":class="{man:isCurrentPlayerMan,woman:!isCurrentPlayerMan}">        <viewclass="dice-container":class="{ rolling: isRolling }" @click="rollDice">            <cl-diceref="flyDice" @result="getDiceData":size="100"backgroundColor="#ff758c"></cl-dice>        </view>        <viewclass="operateBtn">            <viewclass="reload-btn" @click="reloadBoard">                <cl-icontype="icon-shuaxin"size="26"color="#fff"class="icon" />                <text>刷新棋盘</text>            </view>            <viewclass="player-turn">                <text>{{ isCurrentPlayerMan? '男生' : '女生' }}回合</text>            </view>        </view>    </view></view>

棋盘容器是一个白色半透明的卡片,内边距10rpx,圆角16px,带内阴影,模拟纸张质感。里面用CSS Grid布局实现7×7的格子:

.chessboard {    display: grid;    grid-template-columnsrepeat(71fr);    gap10rpx;    margin0 auto;    position: relative;}

每个格子(.cell)高100rpx,圆角10px,默认是柔和的粉色系(#FFF0E6)。根据格子类型(起点、终点、任务、陷阱等),背景色和图标会变化:

  • 起点(绿色,icon-qidian
  • 终点(橙色,icon-zhongdian
  • 任务(粉色,爱心图标,带缩放动画)
  • 惩罚(浅橙色,警告图标,带脉冲光效)
  • 炸弹(深粉色,炸弹图标,带忽明忽暗的呼吸效果)

每个格子右下角还显示路径序号(pathNum),方便玩家知道当前走到第几步,细节拉满。

角色头像是绝对定位的两个圆形头像(男生蓝色渐变,女生粉色渐变),它们通过topleft属性动态定位到对应格子,并且移动时有平滑的CSS过渡(transition: all 0.5s ease-in-out)。这里用了一个小技巧:格子尺寸是calc(100%/7 * 坐标),这样无论屏幕大小,头像总能精准落在格子中央。

2. 棋盘生成算法

飞行棋的棋盘是个7×7的网格,我们需要生成一条连续且足够长的路径(至少30个格子),并随机分布任务、陷阱、惩罚点。

这里我用了DFS + 回溯 + 分支优先的策略,保证路径最长且不重复:

generateLongContinuousPath(size, minLength) {  // 四个方向  const directions = [[-1,0],[1,0],[0,-1],[0,1]];  const visited = Array(size).fill().map(() => Array(size).fill(false));  const path = [];  // 随机起点  let row = Math.floor(Math.random() * size);  let col = Math.floor(Math.random() * size);  path.push([row, col]);  visited[row][col] = true;  // 回溯栈和分支点记录  const stack = [[row, col]];  const branchPoints = [];  while (path.length < minLength) {    const current = stack[stack.length - 1];    const possibleMoves = [];    // 找出所有未访问的相邻格子,并计算分支数    directions.forEach(dir => {      const newRow = current[0] + dir[0];      const newCol = current[1] + dir[1];      if (newRow>=0 && newRow<size && newCol>=0 && newCol<size && !visited[newRow][newCol]) {        // 计算这个格子还有几个未访问邻居(分支数)        const branches = directions.filter(d => {          const r = newRow + d[0], c = newCol + d[1];          return r>=0 && r<size && c>=0 && c<size && !visited[r][c];        }).length;        possibleMoves.push({ coord: [newRow, newCol], branches });      }    });    // 按照分支数排序,优先走分支多的(贪心)    possibleMoves.sort((a,b) => b.branches - a.branches);    if (possibleMoves.length > 0) {      const next = possibleMoves[0].coord;      path.push(next);      visited[next[0]][next[1]] = true;      stack.push(next);      branchPoints.push([...stack]); // 记录分支点    } else {      // 无路可走,回溯      stack.pop();      if (stack.length === 0) {        // 从最近的分支点重新开始        const newStart = branchPoints.pop();        if (!newStart) return null;        stack.length = 0;        stack.push(...newStart);        path.length = newStart.length;      } else {        path.pop();      }    }  }  return path;}

生成路径后,再随机把中间的一些格子设为 task(任务)、warn(惩罚)、trap(炸弹)。炸弹格子会让玩家直接回到起点,刺激吧?

3. 玩家移动与动画

两个玩家(男生和女生)各自有一个位置对象,包含当前坐标、下一个格子坐标、缩放旋转等动画参数。移动时使用 setTimeout 逐步移动,配合CSS过渡实现平滑动画:

async move(playerData, moveNum) {  this.isRolling = true;  for (let i = 0; i < moveNum; i++) {    if (this.gameOver) break;    this.moveNext(playerData);    // 播放音效    this.playMusic();    await this.sleep(700); // 每一步等待0.7秒  }  // 移动结束后检查格子类型  const targetCell = this.checkerboard[playerData.top][playerData.left];  if (targetCell.type === 'task') {    this.showTask = true// 弹出任务框  } else if (targetCell.type === 'trap') {    // 炸弹:回到原点    this.resetUserData(this.srcCell, playerData, 33601.5);    this.playMusic('boom');  }  // ... 切换回合}

注意这里用了 async/await 配合 sleep,让每一步移动都有节奏感。

4. 骰子组件

骰子是自己封装的一个小组件 cl-dice (之前文章有介绍过),通过 startRolling 方法触发随机动画,完成后通过 @result 事件返回点数。为了真实感,我还加上了掷骰子的音效。

<cl-diceref="flyDice" @result="getDiceData":size="100"backgroundColor="#ff758c"></cl-dice>

在父组件中监听结果,然后触发移动。

5. 任务系统

任务数据是外置的,可以灵活切换不同版本(甜蜜版、羞羞版、18禁版)。任务弹窗 cl-fly-task 接收任务内容,用户可以选择“完成”或“未完成”,然后根据选择奖励或惩罚步数。

handleTaskResult(isCompleted) {  const steps = isCompleted ? Math.floor(Math.random()*3)+1 : Math.floor(Math.random()*3)+3;  this.move(this.manTask ? this.manData : this.womanData, steps, truefalse);  this.showTask = false;}

完成任务就少走几步,没完成就多走几步,增加博弈趣味。

6.自定义你的版本

你可以轻松修改任务列表,在 data/flyTask.js 中添加自己的版本:

export const taskMap = {  'sweet': {    version'甜蜜版',    tasks: ['亲对方一下''说一句情话''喂对方一颗糖']  },  'hot': {    version'火热版',    tasks: ['拥抱10秒''挠痒痒''真心话']  }}

甚至可以在设置界面动态切换版本,满足不同场合需求。

写在最后

这个小小的情侣飞行棋项目,不仅让朋友的日常充满乐趣,也很好地实践了 Vue 组件化开发、算法设计和动效实现。技术的价值不仅在于完成功能,更在于创造美好和快乐~

如果你也想为另一半打造专属小游戏,这个项目绝对值得一试!遇到任何问题,欢迎在评论区交流探讨。

想要获取完整源码的,公众号发送:情侣飞行棋 ,后面我整理了会一一发给大家。

互动时间

你和另一半尝试过哪些有趣的情侣游戏?

如果是你,会在飞行棋里加入什么专属任务?

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 超甜!我用 Vue+UniApp 写了款情侣专属飞行棋

评论 抢沙发

5 + 3 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮