不知名提交
This commit is contained in:
261
InfoGenie-frontend/public/smallgame/躲树叶/script.js
Normal file
261
InfoGenie-frontend/public/smallgame/躲树叶/script.js
Normal file
@@ -0,0 +1,261 @@
|
||||
(() => {
|
||||
const canvas = document.getElementById('game');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const hudScoreEl = document.getElementById('score');
|
||||
const pauseBtn = document.getElementById('pauseBtn');
|
||||
const startOverlay = document.getElementById('startOverlay');
|
||||
const startBtn = document.getElementById('startBtn');
|
||||
const gameOverOverlay = document.getElementById('gameOverOverlay');
|
||||
const finalScoreEl = document.getElementById('finalScore');
|
||||
const restartBtn = document.getElementById('restartBtn');
|
||||
const rotateOverlay = document.getElementById('rotateOverlay');
|
||||
|
||||
let width = 0, height = 0, DPR = 1;
|
||||
let running = false, paused = false;
|
||||
let lastTime = 0, timeElapsed = 0, score = 0, spawnTimer = 0;
|
||||
|
||||
const player = { x: 0, y: 0, r: 18, vx: 0, targetX: null };
|
||||
const obstacles = [];
|
||||
let pointerActive = false;
|
||||
|
||||
function clamp(v, min, max){ return Math.max(min, Math.min(max, v)); }
|
||||
function rand(min, max){ return Math.random()*(max-min)+min; }
|
||||
|
||||
function updateOrientationOverlay(){
|
||||
const landscape = window.innerWidth > window.innerHeight;
|
||||
rotateOverlay.style.display = landscape ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
function resize(){
|
||||
updateOrientationOverlay();
|
||||
DPR = Math.min(2, window.devicePixelRatio || 1);
|
||||
width = window.innerWidth;
|
||||
height = window.innerHeight;
|
||||
canvas.width = Math.floor(width * DPR);
|
||||
canvas.height = Math.floor(height * DPR);
|
||||
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
|
||||
if (!running){
|
||||
player.x = width * 0.5;
|
||||
player.y = height - Math.max(80, height * 0.12);
|
||||
} else {
|
||||
player.y = height - Math.max(80, height * 0.12);
|
||||
player.x = clamp(player.x, player.r + 8, width - player.r - 8);
|
||||
}
|
||||
}
|
||||
window.addEventListener('resize', resize);
|
||||
resize();
|
||||
|
||||
function drawBackground(){
|
||||
// 轻微的顶部高光,让画面更通透
|
||||
const g = ctx.createLinearGradient(0,0,0,height);
|
||||
g.addColorStop(0,'rgba(255,255,255,0.10)');
|
||||
g.addColorStop(1,'rgba(255,255,255,0.00)');
|
||||
ctx.fillStyle = g;
|
||||
ctx.fillRect(0,0,width,height);
|
||||
}
|
||||
|
||||
function drawPlayer(){
|
||||
ctx.save();
|
||||
ctx.translate(player.x, player.y);
|
||||
const r = player.r;
|
||||
const grad = ctx.createRadialGradient(-r*0.3, -r*0.3, r*0.2, 0, 0, r);
|
||||
grad.addColorStop(0, '#5fca7e');
|
||||
grad.addColorStop(1, '#3a9e5a');
|
||||
ctx.fillStyle = grad;
|
||||
// 圆形带小叶柄的简化“叶子”角色
|
||||
ctx.beginPath();
|
||||
ctx.arc(0,0,r,0,Math.PI*2);
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = 'rgba(58,158,90,0.7)';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, -r*0.9);
|
||||
ctx.quadraticCurveTo(r*0.2, -r*1.3, r*0.5, -r*1.0);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function spawnObstacle(){
|
||||
const difficulty = 1 + timeElapsed * 0.08; // 随时间慢慢提升
|
||||
const r = rand(10, 22);
|
||||
const x = rand(r+8, width - r - 8);
|
||||
const speed = rand(90, 140) * (0.9 + difficulty * 0.5);
|
||||
const rot = rand(-Math.PI*0.5, Math.PI*0.5);
|
||||
obstacles.push({ x, y: -r - 20, r, speed, rot, swayPhase: Math.random()*Math.PI*2, swayAmp: rand(6,12) });
|
||||
}
|
||||
|
||||
function drawObstacle(o){
|
||||
ctx.save();
|
||||
ctx.translate(o.x, o.y);
|
||||
ctx.rotate(o.rot);
|
||||
const r = o.r;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, 0, r*0.9, r*0.6, 0, 0, Math.PI*2);
|
||||
const grad = ctx.createLinearGradient(-r, -r, r, r);
|
||||
grad.addColorStop(0, '#d8f7c2');
|
||||
grad.addColorStop(0.5, '#b9ef9f');
|
||||
grad.addColorStop(1, '#9edf77');
|
||||
ctx.fillStyle = grad;
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = 'rgba(90,150,90,0.5)';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-r*0.5, 0);
|
||||
ctx.quadraticCurveTo(0, -r*0.3, r*0.5, 0);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function update(dt){
|
||||
const difficulty = 1 + timeElapsed * 0.08;
|
||||
const spawnInterval = Math.max(0.12, 0.9 / difficulty);
|
||||
spawnTimer -= dt;
|
||||
if (spawnTimer <= 0){
|
||||
spawnObstacle();
|
||||
spawnTimer = spawnInterval;
|
||||
}
|
||||
|
||||
// 障碍移动 + 轻微左右摆动
|
||||
for (let i = 0; i < obstacles.length; i++){
|
||||
const o = obstacles[i];
|
||||
o.y += o.speed * dt;
|
||||
o.x += Math.sin(o.swayPhase + timeElapsed * 1.6) * (o.swayAmp * dt);
|
||||
}
|
||||
|
||||
// 清除离开屏幕的障碍
|
||||
for (let i = obstacles.length - 1; i >= 0; i--){
|
||||
const o = obstacles[i];
|
||||
if (o.y > height + o.r + 60){
|
||||
obstacles.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 键盘轻推(桌面端备用)
|
||||
if (player.targetX != null){
|
||||
const dir = player.targetX - player.x;
|
||||
player.vx = clamp(dir, -500, 500);
|
||||
player.x += player.vx * dt;
|
||||
if (Math.abs(dir) < 2){
|
||||
player.targetX = null;
|
||||
player.vx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 限制玩家范围
|
||||
player.x = clamp(player.x, player.r + 8, width - player.r - 8);
|
||||
|
||||
// 碰撞检测(近似圆形)
|
||||
for (const o of obstacles){
|
||||
const dx = o.x - player.x;
|
||||
const dy = o.y - player.y;
|
||||
const dist = Math.hypot(dx, dy);
|
||||
if (dist < player.r + o.r * 0.65){
|
||||
endGame();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 计分:按生存时间累计
|
||||
score += dt * 10; // 每秒约10分
|
||||
hudScoreEl.textContent = Math.floor(score);
|
||||
}
|
||||
|
||||
function render(){
|
||||
ctx.clearRect(0,0,width,height);
|
||||
drawBackground();
|
||||
for (const o of obstacles) drawObstacle(o);
|
||||
drawPlayer();
|
||||
}
|
||||
|
||||
function loop(t){
|
||||
if (!running){ return; }
|
||||
if (paused){
|
||||
lastTime = t;
|
||||
requestAnimationFrame(loop);
|
||||
return;
|
||||
}
|
||||
if (!lastTime) lastTime = t;
|
||||
const dt = Math.min(0.033, (t - lastTime)/1000);
|
||||
lastTime = t;
|
||||
timeElapsed += dt;
|
||||
update(dt);
|
||||
render();
|
||||
requestAnimationFrame(loop);
|
||||
}
|
||||
|
||||
function startGame(){
|
||||
obstacles.length = 0;
|
||||
score = 0;
|
||||
timeElapsed = 0;
|
||||
spawnTimer = 0;
|
||||
running = true;
|
||||
paused = false;
|
||||
lastTime = 0;
|
||||
startOverlay.classList.remove('show');
|
||||
gameOverOverlay.classList.remove('show');
|
||||
resize();
|
||||
requestAnimationFrame(loop);
|
||||
}
|
||||
|
||||
function endGame(){
|
||||
running = false;
|
||||
paused = false;
|
||||
finalScoreEl.textContent = Math.floor(score);
|
||||
gameOverOverlay.classList.add('show');
|
||||
}
|
||||
|
||||
function pointerToCanvasX(e){
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
return clamp(e.clientX - rect.left, 0, rect.width);
|
||||
}
|
||||
|
||||
// 触控与指针事件:按住并左右拖动
|
||||
canvas.addEventListener('pointerdown', e => {
|
||||
pointerActive = true;
|
||||
player.targetX = null;
|
||||
player.x = pointerToCanvasX(e);
|
||||
});
|
||||
canvas.addEventListener('pointermove', e => {
|
||||
if (!pointerActive) return;
|
||||
player.x = pointerToCanvasX(e);
|
||||
});
|
||||
canvas.addEventListener('pointerup', () => { pointerActive = false; });
|
||||
canvas.addEventListener('pointercancel', () => { pointerActive = false; });
|
||||
|
||||
// 轻点屏幕:向左/右轻推一段距离
|
||||
canvas.addEventListener('click', e => {
|
||||
const x = pointerToCanvasX(e);
|
||||
const center = width / 2;
|
||||
const dir = x < center ? -1 : 1;
|
||||
player.targetX = clamp(player.x + dir * Math.max(50, width * 0.12), player.r + 8, width - player.r - 8);
|
||||
});
|
||||
|
||||
// 键盘备用控制(桌面端)
|
||||
window.addEventListener('keydown', e => {
|
||||
if (e.key === 'ArrowLeft' || e.key === 'a'){
|
||||
player.targetX = clamp(player.x - Math.max(50, width * 0.12), player.r + 8, width - player.r - 8);
|
||||
} else if (e.key === 'ArrowRight' || e.key === 'd'){
|
||||
player.targetX = clamp(player.x + Math.max(50, width * 0.12), player.r + 8, width - player.r - 8);
|
||||
} else if (e.key === ' ') {
|
||||
togglePause();
|
||||
} else if (e.key === 'Enter' && !running){
|
||||
startGame();
|
||||
}
|
||||
});
|
||||
|
||||
// 按钮
|
||||
pauseBtn.addEventListener('click', togglePause);
|
||||
startBtn.addEventListener('click', startGame);
|
||||
restartBtn.addEventListener('click', startGame);
|
||||
|
||||
function togglePause(){
|
||||
if (!running) return;
|
||||
paused = !paused;
|
||||
pauseBtn.textContent = paused ? '▶' : 'Ⅱ';
|
||||
}
|
||||
|
||||
// 避免滚动与系统手势干扰
|
||||
['touchstart','touchmove','touchend'].forEach(type => {
|
||||
window.addEventListener(type, e => { if (pointerActive) e.preventDefault(); }, { passive: false });
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user