用HTML5+CSS3+JavaScript实现龙舟游戏
使用说明
使用左右方向键控制龙舟移动
当龙舟到达右边界时,会从左侧重新出现
当龙舟到达左边界时,会从右侧重新出现
使用空格键进行冲刺加速
避开障碍物,尽量保持生命值
当龙舟与障碍物(岩石、原木和波浪)发生碰撞时,会减少一条生命值,成功避开障碍物会获得分。
初始生命值:3 条,生命值归零时游戏结束。
界面如下
源码如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>龙舟游戏改进版</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#E63946',
secondary: '#FFB703',
water: '#1D3557',
boat: '#A8DADC',
accent: '#F1FAEE',
danger: '#E63946',
},
fontFamily: {
game: ['"Ma Shan Zheng"', 'cursive', 'sans-serif'],
},
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.text-shadow {
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.water-animation {
background-image: linear-gradient(to bottom, #1D3557 0%, #457B9D 100%);
animation: water-move 2s linear infinite;
}
.paddle-animation {
animation: paddle 0.5s ease-in-out infinite alternate;
}
.boat-bounce {
animation: boat-bounce 1s ease-in-out infinite alternate;
}
.obstacle-move {
animation: obstacle-move 3s linear infinite;
}
.btn-hover {
transition: all 0.3s ease;
}
.btn-hover:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
}
.portal-effect {
position: absolute;
width: 100px;
height: 100px;
border-radius: 50%;
background: radial-gradient(circle, #FFB703, transparent);
opacity: 0;
pointer-events: none;
z-index: 5;
}
}
@keyframes water-move {
0% { background-position: 0 0; }
100% { background-position: 0 50px; }
}
@keyframes paddle {
0% { transform: rotate(-15deg); }
100% { transform: rotate(15deg); }
}
@keyframes boat-bounce {
0% { transform: translateY(0); }
100% { transform: translateY(-10px); }
}
@keyframes obstacle-move {
0% { transform: translateX(100vw); }
100% { transform: translateX(-100px); }
}
@keyframes portal {
0% { transform: scale(0.2); opacity: 0.8; }
100% { transform: scale(1.5); opacity: 0; }
}
</style>
<link href="https://fonts.googleapis.com/css2?family=Ma+Shan+Zheng&display=swap" rel="stylesheet">
</head>
<body class="bg-gradient-to-b from-blue-900 to-blue-700 min-h-screen overflow-hidden font-game">
<!-- 游戏容器 -->
<div id="game-container" class="relative w-full h-screen">
<!-- 开始屏幕 -->
<div id="start-screen" class="absolute inset-0 flex flex-col items-center justify-center bg-water/90 z-50 transition-opacity duration-500">
<h1 class="text-[clamp(2.5rem,8vw,5rem)] text-secondary font-bold text-shadow mb-8 animate-bounce">龙舟游戏</h1>
<p class="text-accent text-[clamp(1rem,3vw,1.5rem)] mb-10 text-center max-w-md px-4">
划动龙舟,避开障碍物,冲向终点!<br>
左右方向键控制龙舟并划船,空格键快速冲刺<br>
<span class="text-secondary">新功能:龙舟可穿越边界循环移动!</span>
</p>
<button id="start-btn" class="bg-primary hover:bg-primary/80 text-white font-bold py-4 px-10 rounded-full text-2xl shadow-lg btn-hover">
<i class="fa fa-play mr-2"></i>开始游戏
</button>
<div class="mt-10 flex gap-4">
<div class="flex items-center gap-2 text-accent">
<i class="fa fa-trophy text-secondary"></i>
<span>最高分: <span id="high-score" class="font-bold">0</span></span>
</div>
<div class="flex items-center gap-2 text-accent">
<i class="fa fa-users text-secondary"></i>
<span>龙舟手: <span id="crew-count" class="font-bold">10</span></span>
</div>
</div>
</div>
<!-- 游戏界面 -->
<div id="game-screen" class="absolute inset-0 hidden">
<!-- 背景 -->
<div class="water-animation absolute inset-0 opacity-70"></div>
<div class="absolute inset-0 bg-gradient-to-b from-blue-900/50 to-blue-700/30"></div>
<!-- 水面波浪装饰 -->
<div class="absolute bottom-0 left-0 right-0 h-20 bg-blue-500/50"></div>
<div class="absolute bottom-10 left-0 right-0 h-10 bg-blue-400/30 rounded-t-[50%]"></div>
<div class="absolute bottom-16 left-0 right-0 h-6 bg-blue-300/20 rounded-t-[50%]"></div>
<!-- 游戏元素 -->
<canvas id="game-canvas" class="absolute inset-0 w-full h-full"></canvas>
<!-- 游戏UI -->
<div class="absolute top-4 left-4 right-4 flex justify-between items-center z-10">
<div class="bg-black/40 backdrop-blur-sm text-white py-2 px-4 rounded-full flex items-center gap-2">
<i class="fa fa-trophy text-secondary"></i>
<span>得分: <span id="score" class="font-bold">0</span></span>
</div>
<div class="bg-black/40 backdrop-blur-sm text-white py-2 px-4 rounded-full flex items-center gap-2">
<i class="fa fa-heart text-danger"></i>
<span>生命: <span id="lives" class="font-bold">3</span></span>
</div>
<button id="pause-btn" class="bg-black/40 backdrop-blur-sm text-white py-2 px-4 rounded-full hover:bg-black/60 transition-all">
<i class="fa fa-pause"></i>
</button>
</div>
<!-- 倒计时 -->
<div id="countdown" class="absolute inset-0 flex items-center justify-center z-20 text-[clamp(3rem,15vw,10rem)] font-bold text-white text-shadow hidden">
3
</div>
<!-- 暂停菜单 -->
<div id="pause-menu" class="absolute inset-0 flex flex-col items-center justify-center bg-black/70 z-30 hidden">
<h2 class="text-[clamp(2rem,6vw,4rem)] text-secondary font-bold text-shadow mb-10">游戏暂停</h2>
<button id="resume-btn" class="bg-primary hover:bg-primary/80 text-white font-bold py-3 px-8 rounded-full text-xl shadow-lg mb-4 btn-hover">
<i class="fa fa-play mr-2"></i>继续游戏
</button>
<button id="restart-btn" class="bg-secondary hover:bg-secondary/80 text-white font-bold py-3 px-8 rounded-full text-xl shadow-lg mb-4 btn-hover">
<i class="fa fa-refresh mr-2"></i>重新开始
</button>
<button id="exit-btn" class="bg-gray-600 hover:bg-gray-700 text-white font-bold py-3 px-8 rounded-full text-xl shadow-lg btn-hover">
<i class="fa fa-home mr-2"></i>退出游戏
</button>
</div>
<!-- 游戏结束 -->
<div id="game-over" class="absolute inset-0 flex flex-col items-center justify-center bg-black/70 z-30 hidden">
<h2 class="text-[clamp(2rem,6vw,4rem)] text-danger font-bold text-shadow mb-4">游戏结束</h2>
<p class="text-white text-[clamp(1.2rem,3vw,2rem)] mb-2">你的得分: <span id="final-score" class="font-bold">0</span></p>
<p class="text-white text-[clamp(1rem,2vw,1.5rem)] mb-8">最高分: <span id="game-over-high-score" class="font-bold">0</span></p>
<button id="play-again-btn" class="bg-primary hover:bg-primary/80 text-white font-bold py-3 px-8 rounded-full text-xl shadow-lg mb-4 btn-hover">
<i class="fa fa-refresh mr-2"></i>再玩一次
</button>
<button id="back-to-menu-btn" class="bg-gray-600 hover:bg-gray-700 text-white font-bold py-3 px-8 rounded-full text-xl shadow-lg btn-hover">
<i class="fa fa-home mr-2"></i>返回主菜单
</button>
</div>
<!-- 操作提示 -->
<div class="absolute bottom-4 left-4 right-4 flex justify-center z-10">
<div class="bg-black/40 backdrop-blur-sm text-white py-2 px-4 rounded-full text-sm md:text-base">
<span class="mr-4"><i class="fa fa-arrow-left mr-1"></i><i class="fa fa-arrow-right mr-1"></i>左右移动并划船</span>
<span class="mr-4"><i class="fa fa-space-shuttle mr-1"></i>空格键快速冲刺</span>
<span><i class="fa fa-pause mr-1"></i>暂停游戏</span>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// 获取DOM元素
const startScreen = document.getElementById('start-screen');
const gameScreen = document.getElementById('game-screen');
const countdown = document.getElementById('countdown');
const pauseMenu = document.getElementById('pause-menu');
const gameOver = document.getElementById('game-over');
const startBtn = document.getElementById('start-btn');
const pauseBtn = document.getElementById('pause-btn');
const resumeBtn = document.getElementById('resume-btn');
const restartBtn = document.getElementById('restart-btn');
const exitBtn = document.getElementById('exit-btn');
const playAgainBtn = document.getElementById('play-again-btn');
const backToMenuBtn = document.getElementById('back-to-menu-btn');
const scoreElement = document.getElementById('score');
const livesElement = document.getElementById('lives');
const finalScoreElement = document.getElementById('final-score');
const highScoreElement = document.getElementById('high-score');
const gameOverHighScoreElement = document.getElementById('game-over-high-score');
const crewCountElement = document.getElementById('crew-count');
const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d');
// 设置Canvas尺寸
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 游戏状态
let gameState = 'start'; // start, countdown, playing, paused, gameOver
let score = 0;
let lives = 3;
let highScore = localStorage.getItem('dragonBoatHighScore') || 0;
let crewCount = 10; // 龙舟手数量,影响速度
let animationId;
let lastTime = 0;
let obstacleTimer = 0;
let obstacleInterval = 2000; // 毫秒
let isPaddling = false;
let paddlePower = 0;
let keys = {};
let lastDirection = 0; // 0: 静止, 1: 右, -1: 左
let portalEffects = []; // 存储边界穿越特效
// 更新高分显示
highScoreElement.textContent = highScore;
gameOverHighScoreElement.textContent = highScore;
// 游戏对象
const boat = {
x: canvas.width / 2 - 60,
y: canvas.height - 150,
width: 120,
height: 60,
speed: 5,
dx: 0,
draw() {
// 龙舟主体
ctx.fillStyle = '#E63946';
ctx.beginPath();
ctx.moveTo(this.x, this.y + this.height / 2);
ctx.lineTo(this.x + this.width, this.y + this.height / 2);
ctx.lineTo(this.x + this.width - 10, this.y);
ctx.lineTo(this.x + 10, this.y);
ctx.closePath();
ctx.fill();
// 龙舟龙头
ctx.fillStyle = '#FFB703';
ctx.beginPath();
ctx.moveTo(this.x + this.width, this.y + this.height / 2);
ctx.quadraticCurveTo(this.x + this.width + 30, this.y + this.height / 2 - 10, this.x + this.width + 20, this.y - 10);
ctx.quadraticCurveTo(this.x + this.width + 10, this.y - 20, this.x + this.width - 10, this.y);
ctx.closePath();
ctx.fill();
// 龙眼
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(this.x + this.width + 15, this.y - 15, 3, 0, Math.PI * 2);
ctx.fill();
// 龙舟桨手
const paddleCount = crewCount;
const paddleSpacing = this.width / (paddleCount + 1);
for (let i = 1; i <= paddleCount; i++) {
const paddleX = this.x + paddleSpacing * i;
const paddleY = this.y - 5;
// 桨手
ctx.fillStyle = '#1D3557';
ctx.beginPath();
ctx.arc(paddleX, paddleY, 5, 0, Math.PI * 2);
ctx.fill();
// 桨
ctx.strokeStyle = '#A8DADC';
ctx.lineWidth = 2;
ctx.beginPath();
// 根据方向和划桨状态设置桨的位置
if (isPaddling) {
if (lastDirection > 0) {
// 向右划
if (i % 2 === 0) {
ctx.moveTo(paddleX, paddleY);
ctx.lineTo(paddleX - 15, paddleY + 10);
} else {
ctx.moveTo(paddleX, paddleY);
ctx.lineTo(paddleX + 15, paddleY + 10);
}
} else {
// 向左划
if (i % 2 === 0) {
ctx.moveTo(paddleX, paddleY);
ctx.lineTo(paddleX + 15, paddleY + 10);
} else {
ctx.moveTo(paddleX, paddleY);
ctx.lineTo(paddleX - 15, paddleY + 10);
}
}
} else {
// 静止状态
ctx.moveTo(paddleX, paddleY);
ctx.lineTo(paddleX, paddleY + 15);
}
ctx.stroke();
}
// 船尾浪花效果 - 增强版
if (isPaddling) {
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
for (let i = 0; i < 5 + Math.floor(paddlePower / 2); i++) {
const splashX = this.x + Math.random() * this.width;
const splashY = this.y + this.height / 2 + Math.random() * 10;
const splashSize = 2 + Math.random() * 3;
ctx.beginPath();
ctx.arc(splashX, splashY, splashSize, 0, Math.PI * 2);
ctx.fill();
}
// 船尾浪花
ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
for (let i = 0; i < 10 + paddlePower; i++) {
const tailX = this.x + Math.random() * this.width;
const tailY = this.y + this.height + Math.random() * 20;
const tailSize = 3 + Math.random() * 4;
ctx.beginPath();
ctx.arc(tailX, tailY, tailSize, 0, Math.PI * 2);
ctx.fill();
}
}
},
update(deltaTime) {
// 处理按键输入
if (keys.ArrowLeft) {
this.dx = -this.speed;
isPaddling = true;
lastDirection = -1;
} else if (keys.ArrowRight) {
this.dx = this.speed;
isPaddling = true;
lastDirection = 1;
} else {
this.dx = 0;
// 当没有按键时,逐渐停止划桨动画
if (paddlePower <= 0) {
isPaddling = false;
}
}
// 空格键冲刺
if (keys[' ']) {
isPaddling = true;
paddlePower = Math.min(paddlePower + 1, 15);
} else {
paddlePower = Math.max(paddlePower - 0.3, 0);
}
// 更新位置
this.x += this.dx;
// 循环边界检查(新增)
if (this.x + this.width < 0) {
// 龙舟完全离开左侧边界,从右侧出现
this.x = canvas.width;
createPortalEffect(canvas.width, this.y + this.height/2, 'right');
} else if (this.x > canvas.width) {
// 龙舟完全离开右侧边界,从左侧出现
this.x = -this.width;
createPortalEffect(0, this.y + this.height/2, 'left');
}
// 绘制龙舟
this.draw();
}
};
// 创建边界穿越特效(新增)
function createPortalEffect(x, y, direction) {
portalEffects.push({
x: x,
y: y,
direction: direction,
size: 0,
opacity: 1,
createdAt: Date.now()
});
}
// 绘制边界穿越特效(新增)
function drawPortalEffects() {
const now = Date.now();
for (let i = portalEffects.length - 1; i >= 0; i--) {
const effect = portalEffects[i];
const elapsed = now - effect.createdAt;
if (elapsed > 1000) {
portalEffects.splice(i, 1);
continue;
}
const progress = elapsed / 1000;
const size = 50 + progress * 100;
const opacity = 1 - progress;
ctx.globalAlpha = opacity;
// 绘制圆形传送门
ctx.beginPath();
ctx.arc(effect.x, effect.y, size, 0, Math.PI * 2);
ctx.strokeStyle = '#FFB703';
ctx.lineWidth = 3;
ctx.stroke();
// 绘制内部粒子
for (let j = 0; j < 20; j++) {
const angle = Math.random() * Math.PI * 2;
const radius = Math.random() * size;
const particleX = effect.x + Math.cos(angle) * radius;
const particleY = effect.y + Math.sin(angle) * radius;
const particleSize = 2 + Math.random() * 3;
ctx.fillStyle = j % 2 === 0 ? '#FFB703' : '#E63946';
ctx.beginPath();
ctx.arc(particleX, particleY, particleSize, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
}
}
// 障碍物数组
let obstacles = [];
// 障碍物类型
const obstacleTypes = [
{ type: 'rock', width: 40, height: 30, color: '#5C4033' },
{ type: 'log', width: 60, height: 20, color: '#8B4513' },
{ type: 'wave', width: 80, height: 40, color: '#457B9D' }
];
// 背景元素
let clouds = [];
let birds = [];
// 初始化背景元素
function initBackground() {
clouds = [];
birds = [];
// 创建云朵
for (let i = 0; i < 5; i++) {
clouds.push({
x: Math.random() * canvas.width,
y: Math.random() * 150,
width: 100 + Math.random() * 100,
height: 60 + Math.random() * 40,
speed: 0.5 + Math.random() * 0.5
});
}
// 创建飞鸟
for (let i = 0; i < 3; i++) {
birds.push({
x: Math.random() * canvas.width,
y: 50 + Math.random() * 100,
width: 30,
height: 20,
speed: 2 + Math.random() * 2,
direction: Math.random() > 0.5 ? 1 : -1
});
}
}
// 绘制背景
function drawBackground() {
// 绘制云朵
clouds.forEach(cloud => {
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
// 绘制云朵形状
ctx.beginPath();
ctx.arc(cloud.x, cloud.y, cloud.height / 2, 0, Math.PI * 2);
ctx.arc(cloud.x + cloud.width / 3, cloud.y - cloud.height / 4, cloud.height / 2 + 10, 0, Math.PI * 2);
ctx.arc(cloud.x + cloud.width * 2/3, cloud.y, cloud.height / 2, 0, Math.PI * 2);
ctx.fill();
// 更新云朵位置
cloud.x += cloud.speed;
if (cloud.x > canvas.width) cloud.x = -cloud.width;
});
// 绘制飞鸟
birds.forEach(bird => {
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
// 绘制飞鸟形状
ctx.beginPath();
ctx.moveTo(bird.x, bird.y);
ctx.quadraticCurveTo(
bird.x + bird.width / 3 * bird.direction,
bird.y - bird.height / 2,
bird.x + bird.width * 2/3 * bird.direction,
bird.y
);
ctx.quadraticCurveTo(
bird.x + bird.width / 3 * bird.direction,
bird.y + bird.height / 2,
bird.x,
bird.y
);
ctx.stroke();
// 更新飞鸟位置
bird.x += bird.speed * bird.direction;
if (bird.direction > 0 && bird.x > canvas.width) bird.x = -bird.width;
if (bird.direction < 0 && bird.x < -bird.width) bird.x = canvas.width;
});
}
// 创建障碍物
function createObstacle() {
const type = obstacleTypes[Math.floor(Math.random() * obstacleTypes.length)];
const x = Math.random() * (canvas.width - type.width);
const y = -type.height;
const speed = 3 + Math.random() * 2;
obstacles.push({
...type,
x,
y,
speed
});
// 随时间增加难度
if (obstacleInterval > 800) {
obstacleInterval -= 0.5;
}
}
// 更新障碍物
function updateObstacles(deltaTime) {
// 创建新障碍物
obstacleTimer += deltaTime;
if (obstacleTimer > obstacleInterval) {
createObstacle();
obstacleTimer = 0;
}
// 更新和绘制现有障碍物
for (let i = obstacles.length - 1; i >= 0; i--) {
const obstacle = obstacles[i];
obstacle.y += obstacle.speed + (isPaddling ? paddlePower * 0.1 : 0);
// 绘制障碍物
ctx.fillStyle = obstacle.color;
if (obstacle.type === 'rock') {
// 岩石
ctx.beginPath();
ctx.ellipse(
obstacle.x + obstacle.width / 2,
obstacle.y + obstacle.height,
obstacle.width / 2,
obstacle.height / 2,
0,
0,
Math.PI * 2
);
ctx.fill();
} else if (obstacle.type === 'log') {
// 原木
ctx.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
ctx.fillStyle = '#6B3A10';
ctx.beginPath();
ctx.arc(obstacle.x + obstacle.width / 4, obstacle.y + obstacle.height / 2, 3, 0, Math.PI * 2);
ctx.arc(obstacle.x + obstacle.width * 3/4, obstacle.y + obstacle.height / 2, 3, 0, Math.PI * 2);
ctx.fill();
} else if (obstacle.type === 'wave') {
// 波浪
ctx.fillStyle = 'rgba(69, 123, 157, 0.7)';
ctx.beginPath();
ctx.moveTo(obstacle.x, obstacle.y + obstacle.height);
for (let j = 0; j < 4; j++) {
const curveX = obstacle.x + j * (obstacle.width / 4);
const curveY = obstacle.y + (j % 2 === 0 ? 0 : obstacle.height / 2);
const nextCurveX = obstacle.x + (j + 1) * (obstacle.width / 4);
ctx.quadraticCurveTo(curveX, curveY, nextCurveX, obstacle.y + obstacle.height);
}
ctx.fill();
}
// 检测碰撞
if (
boat.x < obstacle.x + obstacle.width &&
boat.x + boat.width > obstacle.x &&
boat.y < obstacle.y + obstacle.height &&
boat.y + boat.height > obstacle.y
) {
// 发生碰撞
obstacles.splice(i, 1);
lives--;
livesElement.textContent = lives;
// 显示碰撞效果
ctx.fillStyle = 'rgba(230, 57, 70, 0.5)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (lives <= 0) {
gameOverHandler();
}
}
// 移除离开画布的障碍物
if (obstacle.y > canvas.height) {
obstacles.splice(i, 1);
// 得分增加
score += 10 + Math.floor(paddlePower / 2);
scoreElement.textContent = score;
}
}
}
// 绘制终点线
function drawFinishLine() {
const finishLineY = 100;
ctx.strokeStyle = '#FFB703';
ctx.lineWidth = 5;
ctx.setLineDash([20, 10]);
ctx.beginPath();
ctx.moveTo(0, finishLineY);
ctx.lineTo(canvas.width, finishLineY);
ctx.stroke();
ctx.setLineDash([]);
// 终点旗帜
const flagSpacing = canvas.width / 10;
for (let i = 0; i <= 10; i++) {
const flagX = i * flagSpacing;
// 旗杆
ctx.strokeStyle = '#A8DADC';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(flagX, finishLineY);
ctx.lineTo(flagX, finishLineY - 40);
ctx.stroke();
// 旗帜
ctx.fillStyle = i % 2 === 0 ? '#E63946' : '#FFB703';
ctx.beginPath();
ctx.moveTo(flagX, finishLineY - 40);
ctx.lineTo(flagX + 20, finishLineY - 30);
ctx.lineTo(flagX, finishLineY - 20);
ctx.closePath();
ctx.fill();
}
// 检测是否到达终点
if (boat.y < finishLineY + 30) {
// 加分
score += 500 + crewCount * 20 + Math.floor(paddlePower * 10);
scoreElement.textContent = score;
// 显示胜利消息
gameOverHandler(true);
}
}
// 游戏结束处理
function gameOverHandler(isWin = false) {
cancelAnimationFrame(animationId);
gameState = 'gameOver';
gameOver.classList.remove('hidden');
// 更新最终得分
finalScoreElement.textContent = score;
// 更新最高分
if (score > highScore) {
highScore = score;
localStorage.setItem('dragonBoatHighScore', highScore);
gameOverHighScoreElement.textContent = highScore;
highScoreElement.textContent = highScore;
// 显示新纪录消息
const newRecord = document.createElement('div');
newRecord.className = 'text-secondary text-2xl mb-4 font-bold';
newRecord.textContent = '新纪录!';
gameOver.querySelector('h2').after(newRecord);
// 5秒后移除新纪录消息
setTimeout(() => {
if (newRecord.parentNode) {
newRecord.parentNode.removeChild(newRecord);
}
}, 5000);
}
// 如果是胜利,显示特殊消息
if (isWin) {
gameOver.querySelector('h2').textContent = '恭喜获胜!';
gameOver.querySelector('h2').className = 'text-[clamp(2rem,6vw,4rem)] text-secondary font-bold text-shadow mb-4';
// 添加烟花效果
createFireworks();
}
}
// 创建烟花效果
function createFireworks() {
const fireworks = [];
// 创建5个烟花
for (let i = 0; i < 5; i++) {
setTimeout(() => {
const x = Math.random() * canvas.width;
const y = canvas.height;
const targetY = Math.random() * canvas.height / 2;
fireworks.push({
x,
y,
targetY,
speed: 5 + Math.random() * 5,
exploded: false,
particles: []
});
}, i * 500);
}
// 渲染烟花
function renderFireworks() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = fireworks.length - 1; i >= 0; i--) {
const firework = fireworks[i];
if (!firework.exploded) {
// 烟花上升
firework.y -= firework.speed;
// 绘制烟花
ctx.fillStyle = '#FFB703';
ctx.beginPath();
ctx.arc(firework.x, firework.y, 5, 0, Math.PI * 2);
ctx.fill();
// 到达目标位置,爆炸
if (firework.y <= firework.targetY) {
firework.exploded = true;
// 创建爆炸粒子
for (let j = 0; j < 50; j++) {
const angle = Math.random() * Math.PI * 2;
const speed = 1 + Math.random() * 3;
const color = `hsl(${Math.random() * 360}, 100%, 70%)`;
firework.particles.push({
x: firework.x,
y: firework.y,
dx: Math.cos(angle) * speed,
dy: Math.sin(angle) * speed,
radius: 2 + Math.random() * 2,
color,
alpha: 1,
gravity: 0.05
});
}
}
} else {
// 绘制爆炸粒子
for (let j = firework.particles.length - 1; j >= 0; j--) {
const particle = firework.particles[j];
// 更新粒子位置
particle.x += particle.dx;
particle.y += particle.dy;
particle.dy += particle.gravity;
particle.alpha -= 0.02;
// 绘制粒子
ctx.fillStyle = `rgba(${parseInt(particle.color.substr(4, 3))}, ${parseInt(particle.color.substr(9, 3))}, ${parseInt(particle.color.substr(14, 3))}, ${particle.alpha})`;
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
ctx.fill();
// 移除消失的粒子
if (particle.alpha <= 0) {
firework.particles.splice(j, 1);
}
}
// 移除所有粒子都消失的烟花
if (firework.particles.length === 0) {
fireworks.splice(i, 1);
}
}
}
// 如果还有烟花,继续渲染
if (fireworks.length > 0) {
requestAnimationFrame(renderFireworks);
}
}
renderFireworks();
}
// 游戏主循环
function gameLoop(timestamp) {
const deltaTime = timestamp - lastTime || 0;
lastTime = timestamp;
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制背景
drawBackground();
// 更新和绘制龙舟
boat.update(deltaTime);
// 更新和绘制障碍物
updateObstacles(deltaTime);
// 绘制终点线
drawFinishLine();
// 绘制边界穿越特效(新增)
drawPortalEffects();
// 继续游戏循环
if (gameState === 'playing') {
animationId = requestAnimationFrame(gameLoop);
}
}
// 开始游戏
function startGame() {
// 重置游戏状态
score = 0;
lives = 3;
livesElement.textContent = lives;
scoreElement.textContent = score;
obstacles = [];
portalEffects = [];
obstacleInterval = 2000;
boat.x = canvas.width / 2 - 60;
boat.y = canvas.height - 150;
isPaddling = false;
paddlePower = 0;
lastDirection = 0;
// 初始化背景
initBackground();
// 隐藏开始屏幕,显示游戏屏幕
startScreen.classList.add('hidden');
gameScreen.classList.remove('hidden');
// 显示倒计时
gameState = 'countdown';
countdown.classList.remove('hidden');
let count = 3;
countdown.textContent = count;
const countdownInterval = setInterval(() => {
count--;
if (count > 0) {
countdown.textContent = count;
} else {
clearInterval(countdownInterval);
countdown.classList.add('hidden');
gameState = 'playing';
lastTime = 0;
animationId = requestAnimationFrame(gameLoop);
}
}, 1000);
}
// 暂停游戏
function pauseGame() {
if (gameState === 'playing') {
gameState = 'paused';
pauseMenu.classList.remove('hidden');
cancelAnimationFrame(animationId);
}
}
// 恢复游戏
function resumeGame() {
if (gameState === 'paused') {
gameState = 'playing';
pauseMenu.classList.add('hidden');
animationId = requestAnimationFrame(gameLoop);
}
}
// 重新开始游戏
function restartGame() {
pauseMenu.classList.add('hidden');
gameOver.classList.add('hidden');
startGame();
}
// 返回主菜单
function backToMenu() {
gameState = 'start';
gameScreen.classList.add('hidden');
pauseMenu.classList.add('hidden');
gameOver.classList.add('hidden');
startScreen.classList.remove('hidden');
cancelAnimationFrame(animationId);
}
// 事件监听
startBtn.addEventListener('click', startGame);
pauseBtn.addEventListener('click', pauseGame);
resumeBtn.addEventListener('click', resumeGame);
restartBtn.addEventListener('click', restartGame);
exitBtn.addEventListener('click', backToMenu);
playAgainBtn.addEventListener('click', restartGame);
backToMenuBtn.addEventListener('click', backToMenu);
// 键盘事件
window.addEventListener('keydown', (e) => {
keys[e.key] = true;
// P键暂停/恢复游戏
if (e.key === 'p' || e.key === 'P') {
if (gameState === 'playing') {
pauseGame();
} else if (gameState === 'paused') {
resumeGame();
}
}
});
window.addEventListener('keyup', (e) => {
keys[e.key] = false;
});
// 鼠标点击/触摸事件
canvas.addEventListener('mousedown', () => {
if (gameState === 'playing') {
keys[' '] = true;
}
});
canvas.addEventListener('mouseup', () => {
keys[' '] = false;
});
canvas.addEventListener('touchstart', (e) => {
if (gameState === 'playing') {
e.preventDefault();
keys[' '] = true;
}
});
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
keys[' '] = false;
});
// 窗口调整大小事件
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
// 调整龙舟手数量
crewCountElement.addEventListener('click', () => {
crewCount = (crewCount % 15) + 5; // 5-15人
crewCountElement.textContent = crewCount;
});
});
</script>
</body>
</html>