需求如下:
我现在要做一个Google 的浏览器插件, 该浏览器插件的核心功能是一款贪吃蛇的游戏, 此外我不希望这款游戏中存在任何图片素材, 关于该游戏的具体开发思路我没有相关规划, 请你先帮我生成一份具体的规划,然后根据这个规划开始生成前端的相关代码,
技术栈要求:vite/ts/vue3/支持 vue3 的 UI 框架等,我拥有相关的开发环境,需要你从创建项目到代码编写一步一步的实现功能
下面是腾讯云AI代码助手给我的全部回复:
chrome-snake-extension/
├── public/ # 静态资源
│ └── manifest.json # Chrome扩展配置文件
├── src/
│ ├── assets/ # 样式文件
│ │ └── game.css
│ ├── components/ # Vue组件
│ │ └── GameBoard.vue # 游戏核心组件
│ ├── utils/
│ │ └── gameLogic.ts # 游戏核心逻辑
│ ├── background.ts # 后台服务脚本
│ └── main.ts # 入口文件
├── index.html
├── vite.config.ts # Vite配置
└── package.json
npm create vite@latest chrome-snake-extension -- --template vue-ts
cd chrome-snake-extension
npm install
public/manifest.json
:
{
"manifest_version": 3,
"name": "Vue Snake",
"version": "1.0",
"description": "A modern Snake game extension",
"action": {
"default_popup": "index.html",
"default_icon": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
}
},
"permissions": ["storage"],
"background": {
"service_worker": "background.js"
},
"icons": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
}
}
src/components/GameBoard.vue
:
<template>
<div class="game-container">
<canvas ref="gameCanvas"></canvas>
<div class="control-panel">
<div class="score-board">
Score: {{ score }} | High Score: {{ highScore }}
</div>
<button @click="toggleGame">
{{ isPlaying ? 'Pause' : 'Start' }}
</button>
<button @click="resetGame">Reset</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { GameEngine } from '../utils/gameLogic';
const gameCanvas = ref<HTMLCanvasElement>();
const score = ref(0);
const highScore = ref(0);
const isPlaying = ref(false);
let gameEngine: GameEngine;
// 初始化游戏引擎
onMounted(() => {
if (gameCanvas.value) {
gameEngine = new GameEngine({
canvas: gameCanvas.value,
onScoreUpdate: (newScore) => {
score.value = newScore;
if (newScore > highScore.value) {
highScore.value = newScore;
}
},
onGameOver: () => {
isPlaying.value = false;
}
});
}
});
// 游戏控制
const toggleGame = () => {
isPlaying.value ? gameEngine.pause() : gameEngine.start();
isPlaying.value = !isPlaying.value;
};
const resetGame = () => {
gameEngine.reset();
score.value = 0;
isPlaying.value = false;
};
// 键盘事件监听
const handleKeyPress = (e: KeyboardEvent) => {
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
e.preventDefault();
gameEngine.changeDirection(e.key.replace('Arrow', '').toLowerCase() as Direction);
}
};
onMounted(() => window.addEventListener('keydown', handleKeyPress));
onUnmounted(() => window.removeEventListener('keydown', handleKeyPress));
</script>
src/utils/gameLogic.ts
:
type Direction = 'up' | 'down' | 'left' | 'right';
type Position = { x: number; y: number };
interface GameConfig {
canvas: HTMLCanvasElement;
gridSize?: number;
onScoreUpdate?: (score: number) => void;
onGameOver?: () => void;
}
export class GameEngine {
private ctx: CanvasRenderingContext2D;
private gridSize: number;
private snake: Position[] = [];
private food: Position = { x: 0, y: 0 };
private direction: Direction = 'right';
private nextDirection: Direction = 'right';
private gameLoopId: number | null = null;
private score = 0;
constructor(private config: GameConfig) {
this.ctx = config.canvas.getContext('2d')!;
this.gridSize = config.gridSize || 20;
// 初始化画布尺寸
config.canvas.width = 400;
config.canvas.height = 400;
this.reset();
}
private generateFood() {
do {
this.food = {
x: Math.floor(Math.random() * (this.ctx.canvas.width / this.gridSize)),
y: Math.floor(Math.random() * (this.ctx.canvas.height / this.gridSize))
};
} while (this.snake.some(segment =>
segment.x === this.food.x && segment.y === this.food.y
));
}
private draw() {
// 清空画布
this.ctx.fillStyle = '#2c3e50';
this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
// 绘制食物
this.ctx.fillStyle = '#e74c3c';
this.ctx.fillRect(
this.food.x * this.gridSize,
this.food.y * this.gridSize,
this.gridSize - 2,
this.gridSize - 2
);
// 绘制蛇
this.snake.forEach((segment, index) => {
this.ctx.fillStyle = index === 0 ? '#3498db' : '#2980b9';
this.ctx.fillRect(
segment.x * this.gridSize,
segment.y * this.gridSize,
this.gridSize - 2,
this.gridSize - 2
);
});
}
private move() {
const head = { ...this.snake[0] };
this.direction = this.nextDirection;
switch (this.direction) {
case 'up': head.y--; break;
case 'down': head.y++; break;
case 'left': head.x--; break;
case 'right': head.x++; break;
}
// 碰撞检测
if (
head.x < 0 || head.x >= this.ctx.canvas.width / this.gridSize ||
head.y < 0 || head.y >= this.ctx.canvas.height / this.gridSize ||
this.snake.some(segment => segment.x === head.x && segment.y === head.y)
) {
this.config.onGameOver?.();
return;
}
this.snake.unshift(head);
// 吃到食物
if (head.x === this.food.x && head.y === this.food.y) {
this.score++;
this.config.onScoreUpdate?.(this.score);
this.generateFood();
} else {
this.snake.pop();
}
}
start() {
if (!this.gameLoopId) {
const gameStep = () => {
this.move();
this.draw();
this.gameLoopId = requestAnimationFrame(gameStep);
};
gameStep();
}
}
pause() {
if (this.gameLoopId) {
cancelAnimationFrame(this.gameLoopId);
this.gameLoopId = null;
}
}
reset() {
this.pause();
this.snake = [{ x: 5, y: 5 }];
this.direction = 'right';
this.nextDirection = 'right';
this.score = 0;
this.generateFood();
this.draw();
}
changeDirection(newDirection: Direction) {
const oppositeDirections: Record<Direction, Direction> = {
up: 'down',
down: 'up',
left: 'right',
right: 'left'
};
if (newDirection !== oppositeDirections[this.direction]) {
this.nextDirection = newDirection;
}
}
}
src/assets/game.css
:
.game-container {
width: 400px;
padding: 20px;
background: #34495e;
border-radius: 10px;
color: white;
}
canvas {
border: 2px solid #2c3e50;
border-radius: 5px;
}
.control-panel {
margin-top: 15px;
display: flex;
gap: 10px;
align-items: center;
}
button {
padding: 8px 15px;
background: #3498db;
border: none;
border-radius: 4px;
color: white;
cursor: pointer;
transition: background 0.3s;
}
button:hover {
background: #2980b9;
}
.score-board {
flex-grow: 1;
font-family: monospace;
}
src/main.ts
:
import { createApp } from 'vue'
import './assets/game.css'
import GameBoard from './components/GameBoard.vue'
createApp(GameBoard).mount('#app')
chrome://extensions
npm run build
这个实现方案完全使用矢量图形绘制,不依赖任何图片素材,核心游戏逻辑与UI组件分离,便于维护和扩展。游戏控制逻辑完整包含开始/暂停/重置功能,并实现了经典贪吃蛇的所有核心机制。
在 npm run build
时报错,如下图所示
此时我们将报错内容复制给腾讯AI 代码助手,给出的建议如下图所示:
此时可以build 成功了
我们安装 步骤7:测试与打包
的流程 将dist 文件加载到扩展程序, 报错如下, 我们依旧将报错信息复制给腾讯ai 代码助手
GameBoard.vue
顶部添加类型定义:<script setup lang="ts">
// 新增类型定义 -------------------
type Direction = 'up' | 'down' | 'left' | 'right';
// ------------------------------
// 原有代码
const handleKeyPress = (e: KeyboardEvent) => {
if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
e.preventDefault();
gameEngine.changeDirection(
e.key.replace("Arrow", "").toLowerCase() as Direction
);
}
};
// ...其他代码
</script>
chrome-snake-extension/
├── public/
│ ├── manifest.json
│ └── icons/ (如果有图标)
├── src/
│ └── ...其他源码
└── vite.config.ts
vite.config.ts
:import { defineConfig } from 'vite'
import { resolve } from 'path'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html')
},
output: {
// 确保静态资源在 assets 目录
assetFileNames: 'assets/[name].[ext]'
}
},
// 输出到 dist 目录
outDir: 'dist'
}
})
{
"manifest_version": 3,
"name": "Snake Game",
"version": "1.0",
"action": {
"default_popup": "index.html"
},
"background": {
"service_worker": "background.js"
},
"permissions": ["storage"]
}
public
目录下创建 background.js
(可以是空文件,暂时用于测试)npm run build
dist
目录结构:dist/
├── assets/
│ ├── index.[hash].js
│ └── ...
├── index.html
├── manifest.json
└── background.js
chrome://extensions
dist
目录background.js 404
错误:vite.config.ts
中添加复制插件:npm install rollup-plugin-copy --save-dev
// vite.config.ts
import copy from 'rollup-plugin-copy'
export default defineConfig({
plugins: [
vue(),
copy({
targets: [
{ src: 'public/*', dest: 'dist' }
],
hook: 'writeBundle'
})
]
})
{
"background": {
"service_worker": "background.js",
"type": "module"
}
}
按照腾讯云ai 代码给出的建议, 我们修改后重新运行 效果如下图所示
体验过程中发现 运行速度过快等问题, 此时依旧可以通过 代码助手来逐步优化, 这里就不过多讲解啦。