gratitude.durian.xpcool.com/wechat-miniprogram/pages/game/index.js
2026-08-14 18:01:45 +08:00

803 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// pages/game/index.js - 游戏主逻辑(纯原生实现)
const app = getApp();
const audioManager = require('../../utils/AudioManager');
// 水果配置
const FRUIT_CONFIGS = [
{ level: 1, name: '果切丁', radius: 18, color: '#FF6B6B', score: 1 },
{ level: 2, name: '西瓜片', radius: 25, color: '#4ECDC4', score: 3 },
{ level: 3, name: '紫葡萄', radius: 32, color: '#9B59B6', score: 9 },
{ level: 4, name: '黄柠檬', radius: 40, color: '#F1C40F', score: 27 },
{ level: 5, name: '红苹果', radius: 48, color: '#E74C3C', score: 81 },
{ level: 6, name: '甜橙', radius: 56, color: '#F39C12', score: 243 },
{ level: 7, name: '粉桃子', radius: 64, color: '#FF69B4', score: 729 },
{ level: 8, name: '金菠萝', radius: 76, color: '#FFD700', score: 2187 },
{ level: 9, name: '报恩榴莲', radius: 90, color: '#FFA500', score: 6561 }
];
// 关卡配置
const LEVEL_CONFIGS = [
{ level: 1, bossName: '贪吃喵星人', targetLevel: 3, feedCount: 3, dropMin: 1, dropMax: 2 },
{ level: 2, bossName: '吃货柴犬', targetLevel: 4, feedCount: 4, dropMin: 1, dropMax: 3 },
{ level: 3, bossName: '美食家熊猫', targetLevel: 5, feedCount: 5, dropMin: 2, dropMax: 4 },
// ... 更多关卡可后续添加
];
Page({
data: {
currentLevel: 1,
score: 0,
combo: 0,
bossName: '',
fedCount: 0,
targetFeed: 0,
feedProgress: 0,
items: {
bomb: 3,
freeze: 2
},
showReviveBtn: false,
isPaused: false,
showResult: false,
isClear: false,
finalScore: 0,
finalCombo: 0,
finalTime: 0,
showGoldenEffect: false
},
// 游戏状态
gameState: {
fruits: [], // 场上的水果数组
nextFruitLevel: 1, // 下一个掉落的水果等级
fruitIdCounter: 0, // 水果ID计数器
dropCooldown: 0, // 掉落冷却时间
failTimer: 0, // 失败计时器
startTime: 0, // 游戏开始时间
sessionMaxCombo: 0, // 本局最高连击
mergeCore: null // 合成核心引擎
},
// Canvas 上下文
gameCanvas: null,
previewCanvas: null,
ctx: null,
previewCtx: null,
onLoad() {
// 初始化音频管理器
audioManager.init();
// 初始化游戏
this.initGame();
// 获取 Canvas 上下文
this.gameCanvas = wx.createCanvasContext('gameCanvas', this);
this.previewCanvas = wx.createCanvasContext('previewCanvas', this);
},
onReady() {
// 页面渲染完成后启动游戏循环
this.startGameLoop();
},
/**
* 初始化游戏
*/
initGame() {
const levelConfig = LEVEL_CONFIGS[this.data.currentLevel - 1] || LEVEL_CONFIGS[0];
this.setData({
bossName: levelConfig.bossName,
targetFeed: levelConfig.feedCount,
fedCount: 0,
feedProgress: 0
});
// 重置游戏状态
this.gameState = {
fruits: [],
nextFruitLevel: levelConfig.dropMin,
fruitIdCounter: 0,
dropCooldown: 0,
failTimer: 0,
startTime: Date.now(),
sessionMaxCombo: 0,
mergeCore: new MergeCore()
};
// 生成第一个预览水果
this.generateNextFruit();
// 更新预览
this.drawPreview();
// 播放背景音乐
this.playBGM();
},
/**
* 生成下一个水果
*/
generateNextFruit() {
const levelConfig = LEVEL_CONFIGS[this.data.currentLevel - 1] || LEVEL_CONFIGS[0];
const min = levelConfig.dropMin;
const max = levelConfig.dropMax;
this.gameState.nextFruitLevel = Math.floor(Math.random() * (max - min + 1)) + min;
this.drawPreview();
},
/**
* 绘制预览水果
*/
drawPreview() {
if (!this.previewCtx) return;
const config = FRUIT_CONFIGS[this.gameState.nextFruitLevel - 1];
const centerX = 30;
const centerY = 30;
const radius = Math.min(config.radius, 25); // 限制预览大小
// 清空画布
this.previewCtx.clearRect(0, 0, 60, 60);
// 绘制水果
this.previewCtx.beginPath();
this.previewCtx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
this.previewCtx.fillStyle = config.color;
this.previewCtx.fill();
// 绘制边框
this.previewCtx.strokeStyle = '#333';
this.previewCtx.lineWidth = 2;
this.previewCtx.stroke();
// 绘制等级
this.previewCtx.fillStyle = 'white';
this.previewCtx.font = 'bold 16px Arial';
this.previewCtx.textAlign = 'center';
this.previewCtx.textBaseline = 'middle';
this.previewCtx.fillText(config.level.toString(), centerX, centerY);
this.previewCtx.draw();
},
/**
* 触摸开始事件 - 掉落水果
*/
onTouchStart(e) {
if (this.data.isPaused) return;
if (this.gameState.dropCooldown > 0) return;
const touch = e.touches[0];
const x = touch.x;
// 限制在容器范围内
const clampedX = this.clampToContainer(x);
// 掉落水果
this.dropFruit(clampedX, this.gameState.nextFruitLevel);
// 设置冷却
this.gameState.dropCooldown = 300; // 300ms
// 生成下一个
this.generateNextFruit();
},
/**
* 掉落水果
*/
dropFruit(x, level) {
const config = FRUIT_CONFIGS[level - 1];
const fruitId = `fruit_${++this.gameState.fruitIdCounter}`;
// 创建水果对象
const fruit = {
id: fruitId,
level: level,
x: x,
y: 50, // 从顶部开始
vx: 0,
vy: 0,
radius: config.radius,
color: config.color,
score: config.score,
isMerging: false
};
this.gameState.fruits.push(fruit);
// 播放音效(可选)
this.playSound('drop');
},
/**
* 限制X坐标到容器内
*/
clampToContainer(x) {
const containerWidth = 360; // 容器宽度
const halfWidth = containerWidth / 2;
return Math.max(halfWidth - 20, Math.min(halfWidth + 20, x));
},
/**
* 游戏主循环
*/
startGameLoop() {
const loop = () => {
if (!this.data.isPaused) {
this.update();
this.render();
}
requestAnimationFrame(loop);
};
loop();
},
/**
* 更新游戏逻辑
*/
update() {
const dt = 16; // 假设60fps每帧约16ms
// 更新掉落冷却
if (this.gameState.dropCooldown > 0) {
this.gameState.dropCooldown -= dt;
}
// 更新水果物理
this.updateFruits(dt);
// 检测合成
this.checkMerges();
// 检测失败条件
this.checkFailCondition(dt);
},
/**
* 更新水果物理
*/
updateFruits(dt) {
const gravity = 0.5;
const damping = 0.98;
const bounce = 0.3;
const groundY = 600; // 地面Y坐标
for (let i = this.gameState.fruits.length - 1; i >= 0; i--) {
const fruit = this.gameState.fruits[i];
if (fruit.isMerging) continue;
// 应用重力
fruit.vy += gravity;
// 更新位置
fruit.y += fruit.vy;
fruit.x += fruit.vx;
// 地面碰撞
if (fruit.y + fruit.radius > groundY) {
fruit.y = groundY - fruit.radius;
fruit.vy *= -bounce;
fruit.vx *= damping;
// 如果速度很小,停止弹跳
if (Math.abs(fruit.vy) < 1) {
fruit.vy = 0;
}
}
// 墙壁碰撞
if (fruit.x - fruit.radius < 0) {
fruit.x = fruit.radius;
fruit.vx *= -bounce;
} else if (fruit.x + fruit.radius > 360) {
fruit.x = 360 - fruit.radius;
fruit.vx *= -bounce;
}
}
},
/**
* 检测并处理合成
*/
checkMerges() {
const fruits = this.gameState.fruits;
const merged = new Set();
for (let i = 0; i < fruits.length; i++) {
if (merged.has(i) || fruits[i].isMerging) continue;
for (let j = i + 1; j < fruits.length; j++) {
if (merged.has(j) || fruits[j].isMerging) continue;
const f1 = fruits[i];
const f2 = fruits[j];
// 检查是否相同等级且距离足够近
if (f1.level === f2.level && !f1.isMerging && !f2.isMerging) {
const dx = f1.x - f2.x;
const dy = f1.y - f2.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < f1.radius + f2.radius - 5) {
// 触发合成
this.mergeFruits(i, j);
merged.add(i);
merged.add(j);
break;
}
}
}
}
},
/**
* 合并两个水果
*/
mergeFruits(index1, index2) {
const f1 = this.gameState.fruits[index1];
const f2 = this.gameState.fruits[index2];
// 计算新等级
const newLevel = f1.level + 1;
if (newLevel > 9) {
// 已经是最高级,不合成
return;
}
// 标记为正在合成
f1.isMerging = true;
f2.isMerging = true;
// 计算质心位置
const newX = (f1.x + f2.x) / 2;
const newY = (f1.y + f2.y) / 2;
// 移除旧水果
this.gameState.fruits = this.gameState.fruits.filter((f, idx) =>
idx !== index1 && idx !== index2
);
// 创建新水果
const config = FRUIT_CONFIGS[newLevel - 1];
const newFruit = {
id: `merged_${++this.gameState.fruitIdCounter}`,
level: newLevel,
x: newX,
y: newY,
vx: 0,
vy: -2, // 轻微向上弹起
radius: config.radius,
color: config.color,
score: config.score,
isMerging: false
};
this.gameState.fruits.push(newFruit);
// 更新分数
const addScore = f1.score + f2.score + config.score;
this.setData({
score: this.data.score + addScore
});
// 更新连击
const newCombo = this.gameState.mergeCore.incrementCombo();
this.setData({
combo: newCombo
});
if (newCombo > this.gameState.sessionMaxCombo) {
this.gameState.sessionMaxCombo = newCombo;
}
// 播放音效
this.playSound('merge');
// 检查是否为金色传说合成出Lv9
if (newLevel === 9) {
this.triggerGoldenLegend();
}
// 尝试投喂Boss
this.tryFeedBoss(newLevel);
},
/**
* 投喂Boss
*/
tryFeedBoss(fruitLevel) {
const levelConfig = LEVEL_CONFIGS[this.data.currentLevel - 1];
if (!levelConfig) return;
if (fruitLevel >= levelConfig.targetLevel) {
const newFedCount = this.data.fedCount + 1;
const progress = (newFedCount / levelConfig.feedCount) * 100;
this.setData({
fedCount: newFedCount,
feedProgress: progress
});
// 检查是否通关
if (newFedCount >= levelConfig.feedCount) {
this.handleLevelClear();
}
}
},
/**
* 处理通关
*/
handleLevelClear() {
console.log('[Game] 通关!');
const elapsed = (Date.now() - this.gameState.startTime) / 1000;
this.setData({
isClear: true,
finalScore: this.data.score,
finalCombo: this.gameState.sessionMaxCombo,
finalTime: elapsed.toFixed(1),
showResult: true
});
// 保存进度
app.saveGameData();
// 播放通关音效
this.playSound('clear');
},
/**
* 触发金色传说特效
*/
triggerGoldenLegend() {
console.log('[Game] ✨ 金色传说!✨');
this.setData({
showGoldenEffect: true
});
// 播放金色传说音效
this.playSound('golden');
// 3秒后隐藏特效
setTimeout(() => {
this.setData({
showGoldenEffect: false
});
}, 3000);
},
/**
* 检测失败条件
*/
checkFailCondition(dt) {
const warningY = 150; // 警戒线Y坐标
let hasFruitAboveWarning = false;
for (const fruit of this.gameState.fruits) {
if (fruit.isMerging) continue;
if (fruit.y - fruit.radius < warningY) {
hasFruitAboveWarning = true;
break;
}
}
if (hasFruitAboveWarning) {
this.gameState.failTimer += dt;
if (this.gameState.failTimer >= 3000) { // 3秒后失败
this.handleGameOver();
}
} else {
this.gameState.failTimer = 0;
}
},
/**
* 处理游戏失败
*/
handleGameOver() {
console.log('[Game] 游戏失败');
const elapsed = (Date.now() - this.gameState.startTime) / 1000;
this.setData({
isClear: false,
finalScore: this.data.score,
finalCombo: this.gameState.sessionMaxCombo,
finalTime: elapsed.toFixed(1),
showResult: true,
showReviveBtn: true
});
// 播放失败音效
this.playSound('gameover');
},
/**
* 渲染游戏画面
*/
render() {
if (!this.gameCanvas) return;
const ctx = this.gameCanvas;
const width = 360;
const height = 600;
// 清空画布
ctx.clearRect(0, 0, width, height);
// 绘制背景
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.fillRect(0, 0, width, height);
// 绘制警戒线
ctx.strokeStyle = '#ff4444';
ctx.lineWidth = 3;
ctx.setLineDash([10, 5]);
ctx.beginPath();
ctx.moveTo(0, 150);
ctx.lineTo(width, 150);
ctx.stroke();
ctx.setLineDash([]);
// 绘制地面
ctx.strokeStyle = '#4CAF50';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(0, 600);
ctx.lineTo(width, 600);
ctx.stroke();
// 绘制所有水果
for (const fruit of this.gameState.fruits) {
if (fruit.isMerging) continue;
// 绘制水果圆形
ctx.beginPath();
ctx.arc(fruit.x, fruit.y, fruit.radius, 0, 2 * Math.PI);
ctx.fillStyle = fruit.color;
ctx.fill();
// 绘制边框
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
ctx.stroke();
// 绘制等级数字
ctx.fillStyle = 'white';
ctx.font = `bold ${Math.max(12, fruit.radius / 3)}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(fruit.level.toString(), fruit.x, fruit.y);
}
ctx.draw();
},
/**
* 播放音效
*/
playSound(type) {
audioManager.playSFX(type);
},
/**
* 播放背景音乐
*/
playBGM() {
audioManager.playBGM('bgm_main');
},
/**
* 停止背景音乐
*/
stopBGM() {
audioManager.stopBGM();
},
/**
* 使用道具 - 炸弹
*/
useBomb() {
if (this.data.items.bomb <= 0) {
wx.showToast({
title: '炸弹不足',
icon: 'none'
});
return;
}
// 移除最低等级的水果
let lowestLevel = 999;
let lowestIndex = -1;
for (let i = 0; i < this.gameState.fruits.length; i++) {
if (this.gameState.fruits[i].level < lowestLevel && !this.gameState.fruits[i].isMerging) {
lowestLevel = this.gameState.fruits[i].level;
lowestIndex = i;
}
}
if (lowestIndex >= 0) {
this.gameState.fruits.splice(lowestIndex, 1);
this.setData({
'items.bomb': this.data.items.bomb - 1
});
wx.showToast({
title: '使用炸弹',
icon: 'success'
});
}
},
/**
* 使用道具 - 冰冻
*/
useFreeze() {
if (this.data.items.freeze <= 0) {
wx.showToast({
title: '冰冻不足',
icon: 'none'
});
return;
}
// 暂停所有水果运动3秒
for (const fruit of this.gameState.fruits) {
fruit.vx = 0;
fruit.vy = 0;
}
this.setData({
'items.freeze': this.data.items.freeze - 1
});
wx.showToast({
title: '冰冻3秒',
icon: 'success'
});
setTimeout(() => {
// 恢复物理
}, 3000);
},
/**
* 看视频复活
*/
revive() {
app.showRewardedAd(() => {
// 奖励移除最上层30%的水果
const fruits = this.gameState.fruits;
fruits.sort((a, b) => a.y - b.y);
const removeCount = Math.ceil(fruits.length * 0.3);
this.gameState.fruits.splice(0, removeCount);
this.setData({
showReviveBtn: false,
showResult: false
});
wx.showToast({
title: '复活成功',
icon: 'success'
});
}, (success) => {
if (!success) {
wx.showToast({
title: '广告未看完',
icon: 'none'
});
}
});
},
/**
* 下一关
*/
nextLevel() {
if (this.data.currentLevel < 20) {
this.setData({
currentLevel: this.data.currentLevel + 1,
showResult: false,
score: 0,
combo: 0
});
this.initGame();
} else {
wx.showToast({
title: '恭喜通关全部关卡!',
icon: 'success'
});
this.returnHome();
}
},
/**
* 重新开始
*/
restart() {
this.setData({
showResult: false,
score: 0,
combo: 0
});
this.initGame();
},
/**
* 返回主页
*/
returnHome() {
wx.navigateBack({
delta: 1
});
},
/**
* 暂停/继续
*/
togglePause() {
const isPaused = !this.data.isPaused;
this.setData({
isPaused: isPaused
});
// 暂停时停止BGM继续时恢复播放
if (isPaused) {
this.stopBGM();
} else {
this.playBGM();
}
},
onUnload() {
// 页面卸载时保存游戏数据并停止音频
app.saveGameData();
audioManager.destroy();
}
});
/**
* 简单的合成核心引擎
*/
class MergeCore {
constructor() {
this.combo = 0;
this.lastMergeTime = 0;
}
incrementCombo() {
const now = Date.now();
if (now - this.lastMergeTime < 2000) {
// 2秒内连续合成连击+1
this.combo++;
} else {
// 超过2秒重置连击
this.combo = 1;
}
this.lastMergeTime = now;
return this.combo;
}
reset() {
this.combo = 0;
this.lastMergeTime = 0;
}
}