用Trae做一个游戏给猫咪玩 ------ 追蝌蚪

目标用户
- 爱猫人士:为家中猫咪提供互动娱乐
- 宠物主人:想通过屏幕游戏与宠物互动的主人
- 休闲游戏玩家:喜欢轻松治愈系游戏的用户

猫蝌蚪 介绍
一款专为猫咪设计的网页互动游戏,屏幕中会出现游动的虚拟蝌蚪,猫咪可以用爪子拍打屏幕与蝌蚪互动,蝌蚪会根据互动做出逼真的反应。
🎮 游戏机制
- 智能蝌蚪AI:蝌蚪会自主游泳,遇到边界时会自动转向
- 逃跑行为:点击蝌蚪附近时,它会发出"尖叫"并快速逃离
- 边界检测:蝌蚪不会游出屏幕边界,会智能地转向安全区域

🎨 视觉效果
- 渐变背景:模拟水中环境的蓝色渐变背景
- 游泳动画:蝌蚪有尾巴摆动的游泳动画效果
- 平滑转向:转向时有平滑的角度过渡动画
🔊 音效系统
- 游泳音效:每秒播放"哒哒"声模拟游泳声音
- 碰撞音效:撞到边界时的反弹声音
- 尖叫音效:被点击时的惊吓声音
- 使用Web Audio API生成程序化音效
📱 交互控制
- 速度滑块:可以调节蝌蚪的游泳速度(0.5-5倍速)
- 震动反馈:支持移动设备的震动反馈(可开关)
- 实时调试信息:显示游戏状态的调试面板


如何与 猫蝌蚪 互动
- 将设备放在猫咪常活动区域
- 开启全屏模式,调整合适音量
- 初始会显示1-3只游动的蝌蚪
- 猫咪可用爪子触碰屏幕与蝌蚪互动
- 游戏会自动记录互动数据


开发项目的心路历程


html
<!-- 画布 -->
<canvas id="gameCanvas"></canvas>
<div id="debug"></div>
<div id="controls">
<div class="control-group">
<label for="speedSlider">游泳速度</label>
<input type="range" id="speedSlider" class="slider" min="0.5" max="5" step="0.1" value="2">
<div class="speed-display">速度: <span id="speedValue">2.0</span></div>
</div>
<div class="checkbox-group">
<input type="checkbox" id="vibrationToggle" checked>
<label for="vibrationToggle">震动反馈</label>
<span id="vibrationStatus" class="vibration-status">检测中...</span>
</div>
</div>
1、canvas -> 画布
。画布用来绘制游戏的图形。
2、class为debug
的div
-> 调试信息显示区域
。
3、class为controls
的div
-> 控制面板容器
。其中包括:(1)、可调节游泳速度;(2)、支持手机震动;(3)、显示位置、角度、速度等信息。
js
/**
* 蝌蚪游戏主类
* 负责管理整个游戏的逻辑、渲染和交互
*/
class TadpoleGame {
/**
* 构造函数 - 初始化游戏
* 设置画布、音频上下文、游戏状态和蝌蚪属性
*/
constructor() {
// 获取画布元素和2D渲染上下文
this.canvas = document.getElementById('gameCanvas');
this.ctx = this.canvas.getContext('2d');
this.audioContext = null; // Web Audio API上下文,用于播放声音
this.gameStarted = false; // 游戏是否已开始的标志
// 蝌蚪对象的属性配置
this.tadpole = {
x: 0, // X坐标位置
y: 0, // Y坐标位置
angle: 0, // 游泳方向角度(弧度)
baseSpeed: 2, // 基础游泳速度
currentSpeed: 2, // 当前实际速度
size: 20, // 蝌蚪头部大小
tailLength: 8, // 尾巴段数
tailSegments: [], // 尾巴各段的位置数组
escaping: false, // 是否处于逃跑状态
escapeTime: 0 // 开始逃跑的时间戳
};
// 鼠标/触摸位置追踪
this.mouse = { x: 0, y: 0 };
this.vibrationEnabled = true; // 震动反馈是否启用
// 初始化各个组件
this.setupCanvas();
this.setupControls();
this.setupEventListeners();
this.initTadpole();
}
/**
* 设置画布尺寸和响应式调整
* 使画布填满整个窗口,并在窗口大小改变时自动调整
*/
setupCanvas() {
// 设置画布尺寸为窗口大小
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
// 监听窗口大小变化事件,动态调整画布尺寸
window.addEventListener('resize', () => {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
});
}
/**
* 设置游戏控制面板的交互逻辑
* 包括速度滑块和震动开关的事件处理
*/
setupControls() {
// 获取控制元素
const speedSlider = document.getElementById('speedSlider');
const speedValue = document.getElementById('speedValue');
const vibrationToggle = document.getElementById('vibrationToggle');
const vibrationLabel = document.getElementById('vibrationLabel');
// 速度滑块变化事件 - 调整蝌蚪游泳速度
speedSlider.addEventListener('input', (e) => {
this.tadpole.baseSpeed = parseFloat(e.target.value);
speedValue.textContent = e.target.value;
});
// 震动开关切换事件 - 启用/禁用震动反馈
vibrationToggle.addEventListener('change', (e) => {
this.vibrationEnabled = e.target.checked;
vibrationLabel.textContent = e.target.checked ? '✅' : '❌';
document.getElementById('vibrationStatus').textContent = e.target.checked ? '开启 ✓' : '关闭';
});
}
/**
* 设置游戏的事件监听器
* 包括游戏启动、鼠标移动和触摸事件
*/
setupEventListeners() {
const clickHint = document.getElementById('clickHint');
// 游戏启动函数 - 初始化音频并开始游戏循环
const startGame = async () => {
if (!this.gameStarted) {
await this.initAudio(); // 初始化音频上下文
this.gameStarted = true; // 标记游戏已开始
clickHint.classList.add('hidden'); // 隐藏点击提示
this.gameLoop(); // 开始游戏主循环
}
};
// 点击事件监听 - 启动游戏
clickHint.addEventListener('click', startGame);
document.addEventListener('click', startGame);
// 鼠标移动事件 - 追踪鼠标位置
document.addEventListener('mousemove', (e) => {
this.mouse.x = e.clientX;
this.mouse.y = e.clientY;
});
// 触摸移动事件 - 追踪触摸位置(移动设备支持)
document.addEventListener('touchmove', (e) => {
e.preventDefault(); // 防止页面滚动
const touch = e.touches[0];
this.mouse.x = touch.clientX;
this.mouse.y = touch.clientY;
});
}
/**
* 初始化Web Audio API
* 创建音频上下文,用于播放游戏音效
*/
async initAudio() {
try {
// 创建音频上下文(兼容不同浏览器)
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
await this.audioContext.resume(); // 恢复音频上下文(Chrome需要用户交互后才能播放音频)
} catch (error) {
console.log('音频初始化失败:', error);
}
}
/**
* 播放音效
* @param {number} frequency - 音频频率(Hz),默认440Hz
* @param {number} duration - 播放时长(毫秒),默认100ms
*/
playSound(frequency = 440, duration = 100) {
if (!this.audioContext) return; // 如果音频上下文未初始化则返回
// 创建振荡器(音频发生器)
const oscillator = this.audioContext.createOscillator();
const gainNode = this.audioContext.createGain(); // 创建音量控制节点
// 连接音频节点:振荡器 -> 音量控制 -> 输出
oscillator.connect(gainNode);
gainNode.connect(this.audioContext.destination);
// 设置音频参数
oscillator.frequency.setValueAtTime(frequency, this.audioContext.currentTime);
oscillator.type = 'sine'; // 使用正弦波
// 设置音量渐变效果(避免爆音)
gainNode.gain.setValueAtTime(0.1, this.audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, this.audioContext.currentTime + duration / 1000);
// 播放音频
oscillator.start(this.audioContext.currentTime);
oscillator.stop(this.audioContext.currentTime + duration / 1000);
}
/**
* 触发设备震动
* 在支持的设备上产生触觉反馈
*/
vibrate() {
// 检查震动是否启用且设备支持震动API
if (this.vibrationEnabled && navigator.vibrate) {
navigator.vibrate(50); // 震动50毫秒
}
}
/**
* 初始化蝌蚪的位置和状态
* 将蝌蚪放置在屏幕中央,设置随机方向,初始化尾巴段
*/
initTadpole() {
// 将蝌蚪放置在屏幕中央
this.tadpole.x = this.canvas.width / 2;
this.tadpole.y = this.canvas.height / 2;
this.tadpole.angle = Math.random() * Math.PI * 2; // 随机初始方向
// 初始化尾巴段数组,每段都从头部位置开始
for (let i = 0; i < this.tadpole.tailLength; i++) {
this.tadpole.tailSegments.push({
x: this.tadpole.x,
y: this.tadpole.y
});
}
}
/**
* 更新蝌蚪的位置、状态和行为
* 这是游戏的核心逻辑,处理移动、逃跑、边界检测等
*/
updateTadpole() {
// 计算蝌蚪与鼠标之间的距离
const distance = Math.sqrt(
Math.pow(this.mouse.x - this.tadpole.x, 2) +
Math.pow(this.mouse.y - this.tadpole.y, 2)
);
// 检测是否需要进入逃跑状态(鼠标靠近且未在逃跑)
if (distance < 100 && !this.tadpole.escaping) {
this.tadpole.escaping = true; // 进入逃跑状态
this.tadpole.escapeTime = Date.now(); // 记录逃跑开始时间
this.playSound(800, 150); // 播放逃跑音效(高频率)
this.vibrate(); // 触发震动反馈
document.getElementById('status').textContent = '逃跑!'; // 更新状态显示
}
// 逃跑状态管理
if (this.tadpole.escaping) {
// 检查是否应该结束逃跑状态(2秒后)
if (Date.now() - this.tadpole.escapeTime > 2000) {
this.tadpole.escaping = false;
document.getElementById('status').textContent = '游泳';
}
// 计算逃离方向(远离鼠标)
const escapeAngle = Math.atan2(
this.tadpole.y - this.mouse.y, // Y方向差值
this.tadpole.x - this.mouse.x // X方向差值
);
this.tadpole.angle = escapeAngle; // 设置逃跑方向
this.tadpole.currentSpeed = this.tadpole.baseSpeed * 3; // 逃跑时速度加倍
} else {
// 正常游泳状态 - 随机改变方向(2%概率)
if (Math.random() < 0.02) {
this.tadpole.angle += (Math.random() - 0.5) * 0.3; // 随机转向
}
this.tadpole.currentSpeed = this.tadpole.baseSpeed; // 使用基础速度
}
// 根据当前角度和速度移动蝌蚪
this.tadpole.x += Math.cos(this.tadpole.angle) * this.tadpole.currentSpeed;
this.tadpole.y += Math.sin(this.tadpole.angle) * this.tadpole.currentSpeed;
// 边界碰撞检测和反弹
if (this.tadpole.x < 0 || this.tadpole.x > this.canvas.width) {
this.tadpole.angle = Math.PI - this.tadpole.angle; // 水平反弹
}
if (this.tadpole.y < 0 || this.tadpole.y > this.canvas.height) {
this.tadpole.angle = -this.tadpole.angle; // 垂直反弹
}
// 确保蝌蚪位置在画布范围内
this.tadpole.x = Math.max(0, Math.min(this.canvas.width, this.tadpole.x));
this.tadpole.y = Math.max(0, Math.min(this.canvas.height, this.tadpole.y));
// 更新尾巴段位置(在数组开头添加当前位置)
this.tadpole.tailSegments.unshift({
x: this.tadpole.x,
y: this.tadpole.y
});
// 移除多余的尾巴段(保持固定长度)
if (this.tadpole.tailSegments.length > this.tadpole.tailLength) {
this.tadpole.tailSegments.pop();
}
// 更新信息面板显示
this.updateInfo();
}
/**
* 更新游戏信息面板的显示内容
* 显示蝌蚪的位置、角度、速度等实时数据
*/
updateInfo() {
document.getElementById('posX').textContent = Math.round(this.tadpole.x);
document.getElementById('posY').textContent = Math.round(this.tadpole.y);
document.getElementById('angle').textContent = Math.round(this.tadpole.angle * 180 / Math.PI); // 弧度转角度
document.getElementById('baseSpeed').textContent = this.tadpole.baseSpeed.toFixed(1);
document.getElementById('currentSpeed').textContent = this.tadpole.currentSpeed.toFixed(1);
}
/**
* 绘制蝌蚪(包括尾巴、头部、眼睛)
* 根据当前状态使用不同颜色,实现渐变尾巴效果
*/
drawTadpole() {
const ctx = this.ctx;
// 设置尾巴颜色(逃跑时为红色,正常时为青色)
ctx.strokeStyle = this.tadpole.escaping ? '#ff6b6b' : '#4ecdc4';
ctx.lineWidth = 8;
ctx.lineCap = 'round'; // 圆形线帽
ctx.lineJoin = 'round'; // 圆形连接
// 绘制尾巴(如果有多个段)
if (this.tadpole.tailSegments.length > 1) {
ctx.beginPath();
ctx.moveTo(this.tadpole.tailSegments[0].x, this.tadpole.tailSegments[0].y);
// 绘制每个尾巴段,越往后透明度越低,线条越细
for (let i = 1; i < this.tadpole.tailSegments.length; i++) {
const segment = this.tadpole.tailSegments[i];
const alpha = 1 - (i / this.tadpole.tailSegments.length); // 计算透明度
ctx.globalAlpha = alpha * 0.8; // 设置透明度
ctx.lineWidth = 8 * alpha; // 设置线条粗细
ctx.lineTo(segment.x, segment.y);
}
ctx.stroke(); // 绘制路径
ctx.globalAlpha = 1; // 重置透明度
}
// 绘制头部(圆形)
ctx.fillStyle = this.tadpole.escaping ? '#ff4757' : '#2ed573'; // 逃跑时红色,正常时绿色
ctx.beginPath();
ctx.arc(this.tadpole.x, this.tadpole.y, this.tadpole.size, 0, Math.PI * 2);
ctx.fill();
// 绘制眼睛(白色圆圈)
ctx.fillStyle = 'white';
const eyeOffset = this.tadpole.size * 0.3; // 眼睛距离头部中心的距离
const eyeAngle1 = this.tadpole.angle + 0.5; // 第一只眼睛的角度
const eyeAngle2 = this.tadpole.angle - 0.5; // 第二只眼睛的角度
// 绘制第一只眼睛
ctx.beginPath();
ctx.arc(
this.tadpole.x + Math.cos(eyeAngle1) * eyeOffset,
this.tadpole.y + Math.sin(eyeAngle1) * eyeOffset,
4, 0, Math.PI * 2
);
ctx.fill();
// 绘制第二只眼睛
ctx.beginPath();
ctx.arc(
this.tadpole.x + Math.cos(eyeAngle2) * eyeOffset,
this.tadpole.y + Math.sin(eyeAngle2) * eyeOffset,
4, 0, Math.PI * 2
);
ctx.fill();
// 绘制瞳孔(黑色小圆点)
ctx.fillStyle = 'black';
// 第一只眼睛的瞳孔
ctx.beginPath();
ctx.arc(
this.tadpole.x + Math.cos(eyeAngle1) * eyeOffset,
this.tadpole.y + Math.sin(eyeAngle1) * eyeOffset,
2, 0, Math.PI * 2
);
ctx.fill();
// 第二只眼睛的瞳孔
ctx.beginPath();
ctx.arc(
this.tadpole.x + Math.cos(eyeAngle2) * eyeOffset,
this.tadpole.y + Math.sin(eyeAngle2) * eyeOffset,
2, 0, Math.PI * 2
);
ctx.fill();
}
/**
* 绘制背景和水波纹效果
* 清除画布并绘制围绕蝌蚪的动态水波纹
*/
drawBackground() {
// 清除整个画布
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// 绘制动态水波纹效果
const time = Date.now() * 0.001; // 获取当前时间(秒)
this.ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)'; // 半透明白色
this.ctx.lineWidth = 1;
// 绘制5个同心圆水波纹
for (let i = 0; i < 5; i++) {
this.ctx.beginPath();
// 计算波纹半径(基础半径 + 间距 + 时间动画)
const radius = 50 + i * 30 + Math.sin(time + i) * 10;
this.ctx.arc(this.tadpole.x, this.tadpole.y, radius, 0, Math.PI * 2);
this.ctx.stroke();
}
}
/**
* 游戏主循环
* 每帧调用一次,负责更新游戏状态和重绘画面
*/
gameLoop() {
if (!this.gameStarted) return; // 如果游戏未开始则返回
// 按顺序执行每帧的操作
this.drawBackground(); // 绘制背景
this.updateTadpole(); // 更新蝌蚪状态
this.drawTadpole(); // 绘制蝌蚪
// 请求下一帧动画(60FPS)
requestAnimationFrame(() => this.gameLoop());
}
}

效果:
