下面是一个简单的H5水果忍者游戏实现,使用HTML5 Canvas绘制,包含基本的切水果、计分和游戏结束机制。游戏具有流畅的动画效果和响应式设计,适合在移动设备上体验。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>水果忍者</title>
<style>
body { margin: 0; padding: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background: #000; }
.game-container { position: relative; }
canvas { display: block; background: #fff; }
.score-board { position: absolute; top: 10px; left: 10px; color: white; font-size: 24px; font-weight: bold; }
.lives { position: absolute; top: 10px; right: 10px; color: white; font-size: 24px; font-weight: bold; }
.game-over { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.7); color: white; padding: 20px; text-align: center; border-radius: 10px; display: none; }
.start-screen { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); display: flex; flex-direction: column; justify-content: center; align-items: center; color: white; }
button { padding: 10px 20px; font-size: 18px; background: #FF5722; color: white; border: none; border-radius: 5px; cursor: pointer; margin-top: 20px; }
</style>
</head>
<body>
<div class="game-container">
<div class="start-screen" id="startScreen">
<h1>水果忍者</h1>
<p>滑动屏幕切开水果,避开炸弹</p>
<button id="startButton">开始游戏</button>
</div>
<div class="score-board">分数: <span id="score">0</span></div>
<div class="lives">生命: <span id="lives">3</span></div>
<div class="game-over" id="gameOver">
<h2>游戏结束</h2>
<p>最终得分: <span id="finalScore">0</span></p>
<button id="restartButton">重新开始</button>
</div>
<canvas id="gameCanvas"></canvas>
</div>
<script>
// 游戏配置
const config = {
canvasWidth: 800,
canvasHeight: 600,
fruitSpeed: 5,
fruitSize: 60,
fruitTypes: ['🍎', '🍌', '🍊', '🍇', '🍓', '🍉', '🍑', '🍒', '🍈', '🍋'],
bombType: '💣',
spawnRate: 1500, // 毫秒
spawnRateVariance: 500,
gravity: 0.2,
sliceForce: 8,
maxLives: 3
};
// 游戏状态
const gameState = {
canvas: null,
ctx: null,
score: 0,
lives: config.maxLives,
isPlaying: false,
isGameOver: false,
lastSpawnTime: 0,
objects: [],
sliceTrail: [],
mouseDown: false,
lastMousePos: { x: 0, y: 0 }
};
// 获取DOM元素
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
const livesElement = document.getElementById('lives');
const gameOverElement = document.getElementById('gameOver');
const finalScoreElement = document.getElementById('finalScore');
const startButton = document.getElementById('startButton');
const restartButton = document.getElementById('restartButton');
const startScreen = document.getElementById('startScreen');
// 设置Canvas尺寸
canvas.width = config.canvasWidth;
canvas.height = config.canvasHeight;
// 初始化游戏
function initGame() {
gameState.canvas = canvas;
gameState.ctx = ctx;
gameState.score = 0;
gameState.lives = config.maxLives;
gameState.isPlaying = false;
gameState.isGameOver = false;
gameState.objects = [];
gameState.sliceTrail = [];
// 更新UI
scoreElement.textContent = gameState.score;
livesElement.textContent = gameState.lives;
gameOverElement.style.display = 'none';
startScreen.style.display = 'block';
// 绑定事件
canvas.addEventListener('mousedown', handleMouseDown);
canvas.addEventListener('mousemove', handleMouseMove);
canvas.addEventListener('mouseup', handleMouseUp);
canvas.addEventListener('touchstart', handleTouchStart);
canvas.addEventListener('touchmove', handleTouchMove);
canvas.addEventListener('touchend', handleTouchEnd);
startButton.addEventListener('click', startGame);
restartButton.addEventListener('click', restartGame);
}
// 开始游戏
function startGame() {
gameState.isPlaying = true;
gameState.lastSpawnTime = Date.now();
startScreen.style.display = 'none';
gameLoop();
}
// 重新开始游戏
function restartGame() {
initGame();
startGame();
}
// 游戏主循环
function gameLoop() {
if (!gameState.isPlaying) return;
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 生成水果/炸弹
const currentTime = Date.now();
if (currentTime - gameState.lastSpawnTime > config.spawnRate + Math.random() * config.spawnRateVariance) {
spawnObject();
gameState.lastSpawnTime = currentTime;
}
// 更新和绘制所有物体
updateObjects();
drawObjects();
// 绘制切痕轨迹
drawSliceTrail();
// 检查游戏是否结束
if (gameState.lives <= 0 && !gameState.isGameOver) {
endGame();
}
// 继续游戏循环
requestAnimationFrame(gameLoop);
}
// 生成水果或炸弹
function spawnObject() {
const isBomb = Math.random() < 0.15; // 15%的概率生成炸弹
// 随机位置和速度
const x = Math.random() * (canvas.width - config.fruitSize);
const y = canvas.height + config.fruitSize;
const speedX = (Math.random() - 0.5) * 8;
const speedY = -(Math.random() * 12 + 10);
const obj = {
x,
y,
speedX,
speedY,
type: isBomb ? config.bombType : config.fruitTypes[Math.floor(Math.random() * config.fruitTypes.length)],
isBomb,
isSliced: false,
slicePart1: null,
slicePart2: null,
gravity: config.gravity,
size: config.fruitSize,
rotation: 0,
rotationSpeed: (Math.random() - 0.5) * 0.1
};
gameState.objects.push(obj);
}
// 更新所有物体
function updateObjects() {
for (let i = gameState.objects.length - 1; i >= 0; i--) {
const obj = gameState.objects[i];
// 更新位置
obj.x += obj.speedX;
obj.y += obj.speedY;
obj.speedY += obj.gravity;
obj.rotation += obj.rotationSpeed;
// 如果物体离开画布,移除它
if (obj.y > canvas.height + config.fruitSize ||
obj.x < -config.fruitSize ||
obj.x > canvas.width + config.fruitSize) {
// 如果是未切的水果,减少生命值
if (!obj.isBomb && !obj.isSliced) {
gameState.lives--;
livesElement.textContent = gameState.lives;
}
gameState.objects.splice(i, 1);
}
// 检查是否被切开
if (!obj.isSliced && obj.isBomb === false) {
for (let j = 0; j < gameState.sliceTrail.length; j++) {
const trailPoint = gameState.sliceTrail[j];
const distance = Math.hypot(obj.x + obj.size/2 - trailPoint.x, obj.y + obj.size/2 - trailPoint.y);
if (distance < obj.size/2) {
sliceFruit(obj);
break;
}
}
}
// 检查炸弹是否被点击
if (!obj.isSliced && obj.isBomb === true) {
for (let j = 0; j < gameState.sliceTrail.length; j++) {
const trailPoint = gameState.sliceTrail[j];
const distance = Math.hypot(obj.x + obj.size/2 - trailPoint.x, obj.y + obj.size/2 - trailPoint.y);
if (distance < obj.size/2) {
explodeBomb(obj);
break;
}
}
}
}
// 更新切痕轨迹
gameState.sliceTrail = gameState.sliceTrail.filter(point => point.life > 0);
gameState.sliceTrail.forEach(point => point.life--);
}
// 绘制所有物体
function drawObjects() {
gameState.objects.forEach(obj => {
ctx.save();
ctx.translate(obj.x + obj.size/2, obj.y + obj.size/2);
ctx.rotate(obj.rotation);
if (obj.isSliced && !obj.isBomb) {
// 绘制切开的水果
if (obj.slicePart1) {
ctx.save();
ctx.translate(obj.slicePart1.offsetX, obj.slicePart1.offsetY);
ctx.font = `${obj.size}px Arial`;
ctx.fillText(obj.slicePart1.char, -obj.size/2, obj.size/4);
ctx.restore();
}
if (obj.slicePart2) {
ctx.save();
ctx.translate(obj.slicePart2.offsetX, obj.slicePart2.offsetY);
ctx.font = `${obj.size}px Arial`;
ctx.fillText(obj.slicePart2.char, -obj.size/2, obj.size/4);
ctx.restore();
}
} else if (obj.isSliced && obj.isBomb) {
// 绘制爆炸效果
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.arc(0, 0, obj.size * 1.5, 0, Math.PI * 2);
ctx.fill();
} else {
// 绘制完整的水果或炸弹
ctx.font = `${obj.size}px Arial`;
ctx.fillText(obj.type, -obj.size/2, obj.size/4);
}
ctx.restore();
});
}
// 绘制切痕轨迹
function drawSliceTrail() {
if (gameState.sliceTrail.length < 2) return;
ctx.beginPath();
ctx.moveTo(gameState.sliceTrail[0].x, gameState.sliceTrail[0].y);
for (let i = 1; i < gameState.sliceTrail.length; i++) {
ctx.lineTo(gameState.sliceTrail[i].x, gameState.sliceTrail[i].y);
}
ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)';
ctx.lineWidth = 8;
ctx.lineCap = 'round';
ctx.stroke();
}
// 切开水果
function sliceFruit(obj) {
obj.isSliced = true;
// 增加分数
gameState.score += 10;
scoreElement.textContent = gameState.score;
// 创建水果切片
const char = obj.type;
const slice1 = {
char: char,
offsetX: -obj.size/4,
offsetY: 0,
speedX: obj.speedX - config.sliceForce,
speedY: obj.speedY + (Math.random() - 0.5) * 2
};
const slice2 = {
char: char,
offsetX: obj.size/4,
offsetY: 0,
speedX: obj.speedX + config.sliceForce,
speedY: obj.speedY + (Math.random() - 0.5) * 2
};
obj.slicePart1 = slice1;
obj.slicePart2 = slice2;
// 添加分数动画
addScoreAnimation(obj.x + obj.size/2, obj.y + obj.size/2);
}
// 炸弹爆炸
function explodeBomb(obj) {
obj.isSliced = true;
// 减少生命值
gameState.lives = 0;
livesElement.textContent = gameState.lives;
}
// 添加分数动画
function addScoreAnimation(x, y) {
const scoreAnim = {
x,
y,
text: "+10",
opacity: 1,
ySpeed: -2
};
// 显示分数动画
setTimeout(() => {
const scoreEl = document.createElement('div');
scoreEl.style.position = 'absolute';
scoreEl.style.left = `${x}px`;
scoreEl.style.top = `${y}px`;
scoreEl.style.color = 'white';
scoreEl.style.fontSize = '20px';
scoreEl.style.fontWeight = 'bold';
scoreEl.style.textShadow = '0 0 5px rgba(0,0,0,0.5)';
scoreEl.style.opacity = '1';
scoreEl.style.transition = 'all 1s';
scoreEl.textContent = scoreAnim.text;
document.body.appendChild(scoreEl);
// 动画效果
setTimeout(() => {
scoreEl.style.opacity = '0';
scoreEl.style.transform = 'translateY(-20px)';
setTimeout(() => {
document.body.removeChild(scoreEl);
}, 1000);
}, 10);
}, 0);
}
// 结束游戏
function endGame() {
gameState.isGameOver = true;
gameState.isPlaying = false;
finalScoreElement.textContent = gameState.score;
gameOverElement.style.display = 'block';
}
// 鼠标/触摸事件处理
function handleMouseDown(event) {
if (!gameState.isPlaying) return;
gameState.mouseDown = true;
const rect = canvas.getBoundingClientRect();
gameState.lastMousePos = {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
// 添加初始切痕点
gameState.sliceTrail.push({
x: gameState.lastMousePos.x,
y: gameState.lastMousePos.y,
life: 10
});
}
function handleMouseMove(event) {
if (!gameState.isPlaying || !gameState.mouseDown) return;
const rect = canvas.getBoundingClientRect();
const currentPos = {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
// 计算移动距离
const distance = Math.hypot(
currentPos.x - gameState.lastMousePos.x,
currentPos.y - gameState.lastMousePos.y
);
// 如果移动距离足够大,添加切痕点
if (distance > 5) {
// 添加多个点以确保连续性
const steps = Math.ceil(distance / 5);
for (let i = 1; i <= steps; i++) {
const t = i / steps;
gameState.sliceTrail.push({
x: gameState.lastMousePos.x + (currentPos.x - gameState.lastMousePos.x) * t,
y: gameState.lastMousePos.y + (currentPos.y - gameState.lastMousePos.y) * t,
life: 10
});
}
gameState.lastMousePos = currentPos;
}
}
function handleMouseUp() {
gameState.mouseDown = false;
}
function handleTouchStart(event) {
if (!gameState.isPlaying) return;
event.preventDefault();
const touch = event.touches[0];
const rect = canvas.getBoundingClientRect();
gameState.mouseDown = true;
gameState.lastMousePos = {
x: touch.clientX - rect.left,
y: touch.clientY - rect.top
};
// 添加初始切痕点
gameState.sliceTrail.push({
x: gameState.lastMousePos.x,
y: gameState.lastMousePos.y,
life: 10
});
}
function handleTouchMove(event) {
if (!gameState.isPlaying || !gameState.mouseDown) return;
event.preventDefault();
const touch = event.touches[0];
const rect = canvas.getBoundingClientRect();
const currentPos = {
x: touch.clientX - rect.left,
y: touch.clientY - rect.top
};
// 计算移动距离
const distance = Math.hypot(
currentPos.x - gameState.lastMousePos.x,
currentPos.y - gameState.lastMousePos.y
);
// 如果移动距离足够大,添加切痕点
if (distance > 5) {
// 添加多个点以确保连续性
const steps = Math.ceil(distance / 5);
for (let i = 1; i <= steps; i++) {
const t = i / steps;
gameState.sliceTrail.push({
x: gameState.lastMousePos.x + (currentPos.x - gameState.lastMousePos.x) * t,
y: gameState.lastMousePos.y + (currentPos.y - gameState.lastMousePos.y) * t,
life: 10
});
}
gameState.lastMousePos = currentPos;
}
}
function handleTouchEnd() {
gameState.mouseDown = false;
}
// 启动游戏
initGame();
</script>
</body>
</html>
这个水果忍者游戏具有以下特点:
- 基本玩法:滑动屏幕切开飞来的水果,避开炸弹
- 视觉效果:使用表情符号表示水果和炸弹,切水果时有飞溅效果
- 物理系统:水果和炸弹有抛物线运动轨迹和重力效果
- 游戏机制:
- 成功切水果获得分数
- 漏掉水果减少生命值
- 切到炸弹游戏结束
- 交互体验:支持鼠标和触摸操作,滑动时有切痕效果
游戏操作流程:
- 点击"开始游戏"按钮
- 用鼠标或手指在屏幕上滑动切开飞来的水果
- 避开炸弹,否则游戏立即结束
- 漏掉三个水果后游戏结束
- 游戏结束后可点击"重新开始"按钮再次游戏
你可以直接运行代码开始游戏,或根据需要进一步扩展功能,如添加不同难度级别、特殊水果或背景音乐等。