当前位置: 首页 > news >正文

vue3 实现贪吃蛇手机版01

在这里插入图片描述

<template><div class="game-container"><div class="game-header"><h1>贪吃蛇</h1><div class="scores"><div>分数: {{ score }}</div><div>最高分: {{ highScore }}</div></div></div><!-- 游戏区域 --><div class="game-wrapper"><div class="game-board" :style="{width: `${boardSize}px`,height: `${boardSize}px`}"><!-- 蛇身 --><divv-for="(segment, index) in snake":key="index"class="snake-segment":style="{left: `${segment.x * cellSize}px`,top: `${segment.y * cellSize}px`,width: `${cellSize}px`,height: `${cellSize}px`,background: index === 0 ? 'darkgreen' : 'green'}"></div><!-- 食物 --><divv-if="food"class="food":style="{left: `${food.x * cellSize}px`,top: `${food.y * cellSize}px`,width: `${cellSize}px`,height: `${cellSize}px`}"></div><!-- 游戏结束提示 --><div v-if="gameOver" class="game-over"><h2>游戏结束!</h2><p>最终得分: {{ score }}</p><button @click="startGame">再来一局</button></div><!-- 开始游戏按钮 --><div v-if="!isPlaying && !gameOver" class="start-screen"><button @click="startGame">开始游戏</button></div></div></div><!-- 虚拟摇杆控制器(缩小50%) --><div class="controller" v-if="isPlaying || gameOver"><div class="joystick-outer"@touchstart="handleJoystickStart"@touchmove="handleJoystickMove"@touchend="handleJoystickEnd"@touchcancel="handleJoystickEnd"><!-- 拖拽轨迹指示器 --><div class="joystick-track"v-if="joystickActive":style="{width: `${distance * 2}px`,height: `${distance * 2}px`,left: `${joystickCenter.x - distance}px`,top: `${joystickCenter.y - distance}px`,transform: `rotate(${angle}deg)`}"></div><div class="joystick-inner":style="{left: `${joystickPosition.x}px`,top: `${joystickPosition.y}px`,transform: `translate(-50%, -50%) scale(${joystickActive ? 1.1 : 1})`}"></div></div></div></div>
</template><script setup>
import { ref, onMounted, onUnmounted, watch, computed } from 'vue';// 响应式计算游戏板大小(适应不同屏幕)
const boardSize = computed(() => {// 取屏幕宽度的90%,但不超过500pxconst maxSize = Math.min(window.innerWidth * 0.9, 500);// 确保是单元格大小的整数倍return Math.floor(maxSize / 20) * 20;
});// 游戏配置
const gridSize = 20; // 网格数量
const cellSize = computed(() => boardSize.value / gridSize); // 每个格子的像素大小
const initialSpeed = 200; // 初始速度(毫秒)// 游戏状态
const snake = ref([{ x: 10, y: 10 }]); // 蛇的身体,初始位置
const food = ref(null); // 食物位置
const direction = ref({ x: 1, y: 0 }); // 移动方向,初始向右
const nextDirection = ref({ ...direction.value }); // 下一次移动方向
const score = ref(0); // 当前分数
const highScore = ref(0); // 最高分
const isPlaying = ref(false); // 是否正在游戏中
const gameOver = ref(false); // 游戏是否结束
const gameLoop = ref(null); // 游戏循环计时器// 虚拟摇杆状态(尺寸缩小50%)
const joystickActive = ref(false);
const joystickPosition = ref({ x: 0, y: 0 });
const joystickCenter = ref({ x: 0, y: 0 });
const joystickRadius = 30; // 摇杆半径(原60px,缩小50%)
const distance = ref(0); // 摇杆偏离中心的距离
const angle = ref(0); // 摇杆角度(用于轨迹显示)// 初始化游戏
const startGame = () => {// 重置游戏状态snake.value = [{ x: 10, y: 10 }];direction.value = { x: 1, y: 0 };nextDirection.value = { ...direction.value };score.value = 0;gameOver.value = false;isPlaying.value = true;// 生成初始食物generateFood();// 开始游戏循环if (gameLoop.value) clearInterval(gameLoop.value);gameLoop.value = setInterval(moveSnake, initialSpeed);
};// 生成食物
const generateFood = () => {// 确保食物不会出现在蛇身上let newFood;do {newFood = {x: Math.floor(Math.random() * gridSize),y: Math.floor(Math.random() * gridSize)};} while (snake.value.some(segment => segment.x === newFood.x && segment.y === newFood.y));food.value = newFood;
};// 移动蛇
const moveSnake = () => {if (!isPlaying.value) return;// 更新方向direction.value = { ...nextDirection.value };// 创建新头部const head = {x: snake.value[0].x + direction.value.x,y: snake.value[0].y + direction.value.y};// 检查碰撞if (checkCollision(head)) {endGame();return;}// 添加新头部snake.value.unshift(head);// 检查是否吃到食物if (head.x === food.value.x && head.y === food.value.y) {score.value += 10;generateFood();} else {// 没吃到食物就移除尾部snake.value.pop();}
};// 检查碰撞
const checkCollision = (head) => {// 撞到墙壁if (head.x < 0 || head.x >= gridSize || head.y < 0 || head.y >= gridSize) {return true;}// 撞到自己return snake.value.some((segment, index) => {return index !== 0 && segment.x === head.x && segment.y === head.y;});
};// 结束游戏
const endGame = () => {isPlaying.value = false;gameOver.value = true;clearInterval(gameLoop.value);// 更新最高分if (score.value > highScore.value) {highScore.value = score.value;}
};// 虚拟摇杆控制(增强拖拽效果)
const handleJoystickStart = (e) => {e.preventDefault();if (!isPlaying.value) return;// 获取摇杆外框的位置和尺寸const rect = e.currentTarget.getBoundingClientRect();joystickCenter.value = {x: rect.width / 2,y: rect.height / 2};// 设置初始位置为中心joystickPosition.value = { ...joystickCenter.value };joystickActive.value = true;// 处理初始触摸handleJoystickMove(e);
};const handleJoystickMove = (e) => {e.preventDefault();if (!joystickActive.value || !isPlaying.value) return;// 获取触摸位置const touch = e.touches[0];const rect = e.currentTarget.getBoundingClientRect();const touchX = touch.clientX - rect.left;const touchY = touch.clientY - rect.top;// 计算相对于中心的偏移const deltaX = touchX - joystickCenter.value.x;const deltaY = touchY - joystickCenter.value.y;const dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);// 更新拖拽轨迹参数distance.value = Math.min(dist, joystickRadius);angle.value = Math.atan2(deltaY, deltaX) * (180 / Math.PI);// 限制在摇杆范围内let constrainedX, constrainedY;if (dist <= joystickRadius) {constrainedX = touchX;constrainedY = touchY;} else {// 计算限制在圆内的位置const ratio = joystickRadius / dist;constrainedX = joystickCenter.value.x + deltaX * ratio;constrainedY = joystickCenter.value.y + deltaY * ratio;}// 更新摇杆位置joystickPosition.value = { x: constrainedX, y: constrainedY };// 根据偏移方向确定蛇的移动方向if (Math.abs(deltaX) > Math.abs(deltaY)) {// 水平方向if (deltaX > 0 && direction.value.x !== -1) {nextDirection.value = { x: 1, y: 0 }; // 右} else if (deltaX < 0 && direction.value.x !== 1) {nextDirection.value = { x: -1, y: 0 }; // 左}} else {// 垂直方向if (deltaY > 0 && direction.value.y !== -1) {nextDirection.value = { x: 0, y: 1 }; // 下} else if (deltaY < 0 && direction.value.y !== 1) {nextDirection.value = { x: 0, y: -1 }; // 上}}
};const handleJoystickEnd = (e) => {e.preventDefault();if (!joystickActive.value) return;// 重置摇杆状态joystickPosition.value = { ...joystickCenter.value };joystickActive.value = false;distance.value = 0; // 重置轨迹
};// 监听窗口大小变化
const handleResize = () => {// 重新计算boardSize会触发响应式更新
};// 生命周期钩子
onMounted(() => {window.addEventListener('resize', handleResize);// 初始化摇杆位置joystickPosition.value = { x: joystickRadius, y: joystickRadius };joystickCenter.value = { x: joystickRadius, y: joystickRadius };
});onUnmounted(() => {window.removeEventListener('resize', handleResize);if (gameLoop.value) clearInterval(gameLoop.value);
});// 监听游戏状态变化
watch(isPlaying, (newVal) => {if (!newVal && gameLoop.value) {clearInterval(gameLoop.value);}
});
</script><style scoped>
.game-container {display: flex;flex-direction: column;align-items: center;justify-content: center;min-height: 100vh;padding: 20px;box-sizing: border-box;background-color: #f5f5f5;font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}.game-header {width: 100%;max-width: 500px;text-align: center;margin-bottom: 20px;
}.game-header h1 {color: #333;margin: 0 0 15px 0;font-size: 1.8rem;
}.scores {display: flex;justify-content: space-around;font-size: 1.1rem;color: #555;background-color: #fff;padding: 10px;border-radius: 8px;box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}.game-wrapper {position: relative;margin-bottom: 40px;
}.game-board {position: relative;border: 2px solid #333;background-color: #e8f5e9;border-radius: 4px;overflow: hidden;
}.snake-segment {position: absolute;box-sizing: border-box;border: 1px solid rgba(255, 255, 255, 0.3);border-radius: 3px;
}.food {position: absolute;box-sizing: border-box;background-color: #e53935;border-radius: 50%;border: 1px solid #c62828;
}.game-over {position: absolute;top: 0;left: 0;width: 100%;height: 100%;background-color: rgba(0, 0, 0, 0.7);color: white;display: flex;flex-direction: column;align-items: center;justify-content: center;gap: 15px;
}.game-over h2 {margin: 0;font-size: 1.5rem;
}.game-over p {font-size: 1.2rem;margin: 0;
}.start-screen {position: absolute;top: 0;left: 0;width: 100%;height: 100%;background-color: rgba(0, 0, 0, 0.5);display: flex;align-items: center;justify-content: center;
}button {padding: 12px 24px;background-color: #4caf50;color: white;border: none;border-radius: 6px;font-size: 1rem;font-weight: bold;cursor: pointer;transition: background-color 0.2s;
}button:hover {background-color: #388e3c;
}/* 虚拟摇杆样式(缩小50%) */
.controller {width: 100px;  /* 原200px,缩小50% */height: 100px; /* 原200px,缩小50% */position: relative;
}.joystick-outer {width: 100%;height: 100%;background-color: rgba(0, 0, 0, 0.1);border-radius: 50%;position: relative;touch-action: none; /* 防止触摸时页面滚动 */border: 2px solid rgba(0,0,0,0.2);
}/* 拖拽轨迹指示器 */
.joystick-track {position: absolute;background-color: rgba(76, 175, 80, 0.2);border-radius: 50%;pointer-events: none;z-index: 1;
}.joystick-inner {position: absolute;background-color: #4caf50;border-radius: 50%;width: 60%; /* 相对于外框的比例 */height: 60%;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);transition: transform 0.1s ease;z-index: 2;
}/* 适配小屏幕设备 */
@media (max-width: 360px) {.game-header h1 {font-size: 1.5rem;}.scores {font-size: 1rem;}
}
</style>
http://www.dtcms.com/a/540682.html

相关文章:

  • 胶州网站建设dch100室内装修设计师工资一般多少钱
  • 计算机视觉、医学图像处理、深度学习、多模态融合方向分析
  • 小白入门:基于k8s搭建训练集群,实战CIFAR-10图像分类
  • 关系型数据库大王Mysql——DML语句操作示例
  • VNC安装
  • 网站建设论文 php苏州关键词排名提升
  • 【MySQL】用户管理详解
  • 怎么制作手机网站金坛区建设工程质量监督网站
  • 企业网站的布局类型怎样免费建设免费网站
  • Unity UGC IDE实现深度解析(一):节点图的核心架构设计
  • h5游戏免费下载:搭汉堡
  • 中外商贸网站建设网站怎样做权重
  • 做雇主品牌的网站logo设计网页
  • RocketMQ核心技术精讲-----详解消息发送样例
  • 解锁 PySpark SQL 的强大功能:有关 App Store 数据的端到端教程
  • MousePlus(鼠标增强工具) 中文绿色版
  • 源码学习:MyBatis源码深度解析与实战
  • RAG项目中知识库的检索优化
  • Java IO 流之转换流:InputStreamReader/OutputStreamWriter(字节与字符的桥梁)
  • 熊掌号做网站推广的注意事项品牌网页
  • shell脚本curl命令发送钉钉通知(加签方式)——筑梦之路
  • [无人机sdk] AdvancedSensing | 获取实时视频流 | VGA分辨率
  • 海康相机通过透明通道控制串口收发数据
  • 建网站科技公司做校服的网站
  • 设计模式简介
  • PyTorch torch.unique() 基础与实战
  • 【图像处理基石】图像滤镜的算法原理:从基础到进阶的技术解析
  • 信宜网站建设网站开发配置表格
  • 提示词(Prompt)——指令型提示词在大模型中的调用(以 Qwen 模型为例)
  • python-88-实时消费kafka数据批量追加写入CSV文件