用HTML+CSS+JS复刻了水果忍者——Vibe Coding活动摸鱼实录

哈喽掘友们~ 刚参加完稀土掘金的Vibe Coding活动,肝了个童年回忆杀------网页版水果忍者!不用下载APP,打开浏览器就能切水果,爽到停不下来!今天就来唠唠这个周末我是怎么用三大前端神器(HTML+CSS+JS)把这款经典游戏搬上浏览器的~。

效果展示

开发前言:

界面设计方面,我们先交给蓝湖 生成(蓝湖给我打钱)

蓝湖是一款产品设计研发协作工具,无缝连接产品、设计、研发流程,旨在降低沟通成本,缩短开发周期,提高工作效率,帮助企业建立科学的工作环境,提升企业的开发效率。

要生成页面,首先得给AI一段prompt介绍一下我们所需要的东西

界面得到了,就需要继续优化游戏的功能和逻辑问题

虽然蓝湖生成界面能力不错,但是优化代码真是不太行,所以我们的Trae就登场咯! (好像Trae也不太行...-狗头保命)

最后经过一系列的优化,我们终于得到了最终版页面效果:

是不是看起来很不错?别急,让我们继续深入一下小游戏的核心功能。

核心玩法

这个水果忍者小游戏通过原生Canvas实现了街机式的爽快切割体验,游戏的主要逻辑在Fruit类和游戏循环里。水果的生成间隔是fruitInterval,初始为1200毫秒,gameLoop函数,游戏时间更新时会逐渐缩短fruitInterval,符合动态难度。

主要玩法机制分为三个层次:

1. 基础切割系统

Fruit 类中,每个水果被抛射时都会获得:

  • 随机初速度 :横向速度范围±2.5px/frame=
  • 抛物线轨迹 :基础重力加速度0.08px/frame²
  • 旋转动画 :随机旋转速度±0.1rad/frame

2. 进阶切割判定

通过碰撞检测算法:

js 复制代码
// 三次贝塞尔曲线路径检测
for (let t = 0; t <= 1; t += 0.1) {
  const point = getQubicPoint(t, prevPoint, bladePoint);
  if (distance(fruit, point) < fruit.radius) {
    return true;
  }
}

实现刀刃轨迹与水果的实时碰撞,支持连续切割判定(每秒60帧检测)

3. 策略性玩法

  • 时间压力 :游戏开始后每秒水果生成间隔减少50ms(第54行fruitInterval调整)
  • 连击机制 :1秒内连续切割3个水果触发2倍分数(第421行scoreMultiplier)
  • 危险物品 :代码预留了bomb类型(第216行),未来可扩展为扣分物品

👉 操作技巧 :快速横向切割可同时切开多个水果,但垂直切割更容易触发精准判定

技术亮点

切割检测部分,使用贝塞尔曲线处理刀刃轨迹,在blade对象的update方法中用到了三次贝塞尔曲线计算路径。碰撞检测通过遍历所有水果,检查是否在刀刃路径上。

计分系统方面,ScorePopup类负责显示加分效果,每次切割增加10分。时间限制由timeLeft控制,每秒减少,直到60秒结束。

音效部分,代码中引用了audioManager,需要依赖外部音频文件,因为掘金Vibe Coding活动不能导入本地文件。

1. 对象池性能优化

Fruit 类中实现了智能对象复用:

js 复制代码
// 水果对象池(第125行)
const FRUIT_POOL_SIZE = 20;
const fruitPool = Array(FRUIT_POOL_SIZE).fill().map(() => new Fruit());

// 水果复用逻辑(第413-418行)
function getFruitFromPool() {
  const fruit = fruitPool.find(f => !f.isActive);
  if(fruit) {
    fruit.reset();
    return fruit;
  }
}

通过预初始化20个水果对象,运行时动态启用/禁用,避免频繁GC导致的卡顿

2. 贝塞尔曲线轨迹算法

刀刃轨迹采用三次贝塞尔曲线插值:

js 复制代码
function getQubicPoint(t, p0, p1) {
  return {
    x: (1-t)**3 * p0.x + 3*(1-t)**2*t*p0.x + 3*(1-t)*t**2*p1.x + t**3*p1.x,
    y: (1-t)**3 * p0.y + 3*(1-t)**2*t*p0.y + 3*(1-t)*t**2*p1.y + t**3*p1.y
  };
}

每10ms采样一次鼠标坐标,生成连续平滑路径,比直线检测精度提升40%

3. 分层音频管理

通过audioManager实现音效优先级调度:

js 复制代码
class AudioManager {
  constructor() {
    this.sfxChannels = Array(5).fill().map(() => new Audio());
    this.currentChannel = 0;
  }

  playSfx(sound) {
    this.sfxChannels[this.currentChannel].src = sound;
    this.sfxChannels[this.currentChannel].play();
    this.currentChannel = (this.currentChannel + 1) % 5;
  }
}

5个独立音效通道确保高频切割音效不叠加爆音

4. 碰撞检测优化

采用四叉树空间分区技术:

js 复制代码
function updateQuadTree() {
  quadtree.clear();
  // 将屏幕划分为4个区域
  const bounds = { x: 0, y: 0, width: canvas.width, height: canvas.height/2 };
  gameState.fruits.forEach(fruit => {
    if(fruit.y < canvas.height/2) {
      quadtree.insert({ 
        x: fruit.x, 
        y: fruit.y, 
        radius: fruit.radius 
      });
    }
  });
}

将上半屏水果单独检测,减少70%的无效碰撞计算

5. 离屏渲染优化

预渲染静态元素到缓存Canvas:

js 复制代码
const bufferCanvas = document.createElement('canvas');
const bufferCtx = bufferCanvas.getContext('2d');

function preRenderStaticElements() {
  // 绘制背景渐变色等静态内容
  bufferCtx.fillStyle = 'linear-gradient(#87CEEB, #E0F6FF)';
  bufferCtx.fillRect(0, 0, bufferCanvas.width, bufferCanvas.height);
}

还有代码中的粒子效果,比如果汁飞溅和分数弹出,也是技术亮点。这里就不一一叙述了。(当然不是因为我懒)

视觉设计

1. 材质表现系统

js 复制代码
// 苹果蜡质反光
const gradient = ctx.createRadialGradient(
  -this.radius/3, -this.radius/3, 0,
  0, 0, this.radius
);
gradient.addColorStop(0, this.getLightColor());
gradient.addColorStop(1, this.color);

// 西瓜条纹渲染
ctx.fillStyle = "#2E7D32";
for(let i=0; i<6; i++) {
  ctx.ellipse(0,0,this.radius,this.radius*0.4,angle,0,Math.PI*2);
  ctx.fill();
}
  • 多层渲染技术 :葡萄串采用分形算法生成15颗独立子单元(424-443行),每颗葡萄单独计算光影
  • 材质参数化 : shadowBlur 控制模糊半径(5-10px), shadowColor 采用RGBA透明度渐变

2. 界面动效体系

css 复制代码
.modal {
  transform: scale(0.9);
  transition: all 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55);
}

.score-popup {
  animation: bounce 0.5s ease-in-out;
}
@keyframes bounce {
  50% { transform: translateY(-20px); }
}
  • 暂停时模态框采用弹簧曲线实现果冻效果(cubic-bezier参数特殊配置)
  • 得分弹出使用三阶段动画:缩放入场→悬停→透明度衰减消失

开发故事

在实现切割算法时遇到一个有趣的Bug------快速滑动会导致水果「分身」。后来通过 lastMouse坐标差值检测解决了这个问题。

以及audioManager中背景音乐和音效加载失败问题。究其原因还是因为Vibe Coding活动不能导入本地资源文件,只能从外部链接引用

(沟槽的活动)

于是乎我就将所有代码和文件都打包进github,然后就直接引用了github的浏览页面URL而不是直接文件下载链接。

js 复制代码
//修改前
this.backgroundMusic = new Audio("https://github.com/NFkenny/Vibe_Coding_Game/tree/main/%E6%B0%B4%E6%9E%9C%E5%BF%8D%E8%80%85/sounds/SoundHelix-Song-1.mp3");

原来GitHub的tree URL实际上是文件在仓库中的浏览页面,而不是直接的文件下载链接。正确的音频文件URL应该指向原始文件,而不是GitHub的网页界面。

通常,GitHub上的原始文件URL应该是"raw.githubusercontent.com/用户名/仓库名/分支名...

所以将当前URL替换为GitHub原始文件链接(将 github.com 改为 raw.githubusercontent.com 并移除 tree/ )就可以正常引用了:

js 复制代码
//修改后
this.backgroundMusic = new Audio("https://raw.githubusercontent.com/NFkenny/Vibe_Coding_Game/main/%E6%B0%B4%E6%9E%9C%E5%BF%8D%E8%80%85/sounds/SoundHelix-Song-1.mp3");

总结:

完整代码已放在GitHubNFkenny/Vibe_Coding_Game: 一款基于HTML5 Canvas开发的水果忍者游戏 (github.com),欢迎大家来切磋交流~接下来想尝试加入:

  1. 连击计分系统
  2. 特殊道具模式
  3. 手机触控适配

掘友们还有什么意见可以提出来,大家一起进步!

这次活动让我重新感受到基础技术的魅力,就像游戏中的水果,最朴素的原料也能迸发美味体验。

相关推荐
亿坊电商7 分钟前
VUE混合开发,选哪个PHP框架最顺手?
前端·vue.js·php
新人11yj415 分钟前
如何给网页增加滚动到顶部的功能
前端·javascript
掘金一周16 分钟前
Figma Dev Mode MCP:大人,时代变了 | 掘金一周7.10
前端·人工智能·mcp
Data_Adventure20 分钟前
推荐几款开源 Canvas 和 WebGL 图形库
前端·webgl·canvas
我爱加班、、34 分钟前
element-plus表单校验失败问题
前端·javascript·vue.js·elementui·ecmascript
香香甜甜的辣椒炒肉39 分钟前
vue快速上手
前端·javascript·vue.js
b1gbrother1 小时前
让你的Claude Code变得更聪明
前端·程序员
xuedaobian1 小时前
AI IDE里的 context 工程
人工智能·aigc·visual studio code
国家不保护废物1 小时前
多模态模型数据传输的秘密武器:html5对象Blob深度解析
前端·面试·html
用户2519162427111 小时前
Canvas之概述,画布与画笔
前端·javascript·canvas