菲比丘比&弗糯糯跑酷 | 鸣潮二创
by ThunderKnight37347 lines11.3 KB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>菲比丘比&弗糯糯跑酷 | 鸣潮二创</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;font-family:system-ui,sans-serif}
body{background:#1a1a2e;overflow:hidden;color:#fff;height:100vh}
#container{width:100%;height:100%;position:relative}
canvas{display:block;background:linear-gradient(#415a77,#778da9)}
/* 角色选择界面 */
.select-screen{
position:absolute;inset:0;background:rgba(0,0,0,0.85);
display:flex;flex-direction:column;align-items:center;justify-content:center;
z-index:100;gap:24px
}
.select-title{font-size:28px;font-weight:bold;color:#ffe699}
.char-wrap{display:flex;gap:20px;flex-wrap:wrap;justify-content:center;padding:10px}
.char-card{
width:160px;padding:16px;border-radius:14px;border:3px solid transparent;
background:#2b2d42;cursor:pointer;text-align:center;transition:0.25s
}
.char-card:hover{transform:scale(1.05)}
.char-card.selected{border-color:#ffd166;background:#3a3d5c}
.char-avatar{width:80px;height:80px;border-radius:50%;margin:0 auto 10px}
.phoebe{background:radial-gradient(circle,#ffe8a8,#ffb964)}
.nuonuo{background:radial-gradient(circle,#c9d6ff,#7289da)}
.char-name{font-size:18px;margin-bottom:6px}
.char-desc{font-size:12px;color:#ccc;line-height:1.4}
.start-btn{
padding:12px 32px;font-size:20px;border:none;border-radius:10px;
background:#ff9f1c;color:#000;font-weight:bold;cursor:pointer;
display:none
}
/* 游戏UI */
.game-ui{position:absolute;top:12px;left:12px;z-index:10}
.score-text,.high-text{font-size:20px;text-shadow:0 0 4px #000}
.tip{position:absolute;bottom:16px;left:50%;transform:translateX(-50%);opacity:0.8;font-size:14px}
.over-panel{
position:absolute;inset:0;background:rgba(0,0,0,0.75);
display:none;flex-direction:column;align-items:center;justify-content:center;
gap:16px;z-index:20
}
.over-title{font-size:32px;color:#ff5a5f}
.restart-btn,back-btn{
padding:10px 24px;font-size:18px;border-radius:8px;border:none;cursor:pointer;margin:4px
}
.restart-btn{background:#4cc9f0;color:#000}
.back-btn{background:#adb5bd;color:#000}
</style>
</head>
<body>
<div id="container">
<canvas id="gameCanvas"></canvas>
<div class="game-ui">
<div class="score-text">分数:<span id="score">0</span></div>
<div class="high-text">最高分:<span id="high">0</span></div>
</div>
<div class="tip">空格 / 点击屏幕跳跃</div>
<!-- 角色选择 -->
<div class="select-screen" id="selectScreen">
<div class="select-title">选择你的主角</div>
<div class="char-wrap">
<div class="char-card" data-char="phoebe" id="card-phoebe">
<div class="char-avatar phoebe"></div>
<div class="char-name">菲比丘比</div>
<div class="char-desc">菲比啾比~ 蹦蹦跳跳向前冲!</div>
</div>
<div class="char-card" data-char="nuonuo" id="card-nuonuo">
<div class="char-avatar nuonuo"></div>
<div class="char-name">弗糯糯</div>
<div class="char-desc">呜...小心不要撞到障碍物</div>
</div>
</div>
<button class="start-btn" id="startBtn">开始跑酷!</button>
</div>
<!-- 结束面板 -->
<div class="over-panel" id="overPanel">
<div class="over-title">游戏结束</div>
<div>本次分数:<span id="finalScore">0</span></div>
<div>最高分:<span id="finalHigh">0</span></div>
<div>
<button class="restart-btn" id="restartBtn">再来一局</button>
<button class="back-btn" id="backBtn">返回选角</button>
</div>
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
function resize(){
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize',resize);
// ========== 音频系统(WebAudio合成角色专属音效,无需外部音频文件) ==========
let audioCtx;
function initAudio(){
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
// 菲比丘比音效:轻快啾比声
function playPhoebeSound(){
if(!audioCtx) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(720, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(950, audioCtx.currentTime+0.12);
gain.gain.setValueAtTime(0.4, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime+0.22);
osc.connect(gain); gain.connect(audioCtx.destination);
osc.start(); osc.stop(audioCtx.currentTime+0.22);
}
// 弗糯糯音效:低沉委屈软糯音
function playNuonuoSound(){
if(!audioCtx) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(320, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(240, audioCtx.currentTime+0.25);
gain.gain.setValueAtTime(0.35, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime+0.3);
osc.connect(gain); gain.connect(audioCtx.destination);
osc.start(); osc.stop(audioCtx.currentTime+0.3);
}
// 跳跃通用音效
function playJumpSfx(){
if(!audioCtx) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type='square';
osc.frequency.setValueAtTime(300,audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(600,audioCtx.currentTime+0.08);
gain.gain.setValueAtTime(0.25,audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001,audioCtx.currentTime+0.12);
osc.connect(gain); gain.connect(audioCtx.destination);
osc.start(); osc.stop(audioCtx.currentTime+0.12);
}
// 碰撞失败音效
function playHitSfx(){
if(!audioCtx) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type='sawtooth';
osc.frequency.setValueAtTime(220,audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(80,audioCtx.currentTime+0.3);
gain.gain.setValueAtTime(0.4,audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001,audioCtx.currentTime+0.35);
osc.connect(gain); gain.connect(audioCtx.destination);
osc.start(); osc.stop(audioCtx.currentTime+0.35);
}
// ========== 游戏变量 ==========
let selectedChar = null;
let score = 0;
let highScore = +localStorage.getItem('phoebeNuonuoRunHigh') || 0;
let gameRunning = false;
let player, obstacles = [];
let speed = 6;
const gravity = 0.6;
const jumpForce = -14;
let groundY;
// 玩家类
class Player{
constructor(charType){
this.w = 44; this.h = 44;
this.x = 100;
this.y = 0;
this.vy = 0;
this.isJumping = false;
this.type = charType;
this.color = charType==='phoebe' ? '#ffd073' : '#94a8e8';
}
jump(){
if(!this.isJumping){
this.vy = jumpForce;
this.isJumping = true;
playJumpSfx();
// 跳跃时播放角色专属台词音效
if(this.type==='phoebe') playPhoebeSound();
else playNuonuoSound();
}
}
update(){
this.vy += gravity;
this.y += this.vy;
if(this.y >= groundY - this.h){
this.y = groundY - this.h;
this.vy = 0;
this.isJumping = false;
}
}
draw(){
ctx.beginPath();
ctx.arc(this.x+this.w/2, this.y+this.h/2, this.w/2, 0, Math.PI*2);
ctx.fillStyle = this.color;
ctx.fill();
// 眼睛
ctx.fillStyle="#222";
ctx.beginPath();
ctx.arc(this.x+14,this.y+16,4,0,Math.PI*2);
ctx.arc(this.x+30,this.y+16,4,0,Math.PI*2);
ctx.fill();
}
}
class Obstacle{
constructor(){
this.w = 32; this.h = 50 + Math.random()*25;
this.x = canvas.width + 20;
this.y = groundY - this.h;
}
update(){
this.x -= speed;
}
draw(){
ctx.fillStyle='#444654';
ctx.fillRect(this.x,this.y,this.w,this.h);
}
offScreen(){
return this.x + this.w < 0;
}
checkHit(p){
return p.x < this.x+this.w && p.x+p.w > this.x && p.y < this.y+this.h && p.y+p.h>this.y;
}
}
// ========== 界面元素 ==========
const selectScreen = document.getElementById('selectScreen');
const overPanel = document.getElementById('overPanel');
const startBtn = document.getElementById('startBtn');
const restartBtn = document.getElementById('restartBtn');
const backBtn = document.getElementById('backBtn');
const scoreDom = document.getElementById('score');
const highDom = document.getElementById('high');
const finalScoreDom = document.getElementById('finalScore');
const finalHighDom = document.getElementById('finalHigh');
highDom.textContent = highScore;
// 选角点击
document.querySelectorAll('.char-card').forEach(card=>{
card.onclick = ()=>{
document.querySelectorAll('.char-card').forEach(c=>c.classList.remove('selected'));
card.classList.add('selected');
selectedChar = card.dataset.char;
startBtn.style.display='block';
}
})
startBtn.onclick = ()=>{
initAudio();
startGame();
}
restartBtn.onclick = startGame;
backBtn.onclick = ()=>{
gameRunning = false;
overPanel.style.display='none';
selectScreen.style.display='flex';
}
function startGame(){
selectScreen.style.display='none';
overPanel.style.display='none';
score = 0; speed = 6; obstacles = [];
groundY = canvas.height - 80;
player = new Player(selectedChar);
gameRunning = true;
requestAnimationFrame(gameLoop);
}
function gameOver(){
gameRunning = false;
playHitSfx();
if(score>highScore){
highScore = score;
localStorage.setItem('phoebeNuonuoRunHigh',highScore);
}
finalScoreDom.textContent = score;
finalHighDom.textContent = highScore;
highDom.textContent = highScore;
overPanel.style.display='flex';
}
// 输入监听
function doJump(){
if(gameRunning && player) player.jump();
}
document.addEventListener('keydown',e=>{
if(e.code==='Space'){
e.preventDefault(); doJump();
}
})
canvas.addEventListener('click',doJump);
canvas.addEventListener('touchstart',(e)=>{
e.preventDefault(); doJump();
})
// 生成障碍物定时器
let spawnTimer = 0;
function spawnObstacle(){
obstacles.push(new Obstacle());
}
function gameLoop(){
if(!gameRunning) return;
ctx.clearRect(0,0,canvas.width,canvas.height);
// 地面
ctx.fillStyle='#333542';
ctx.fillRect(0,groundY,canvas.width,canvas.height-groundY);
player.update();
player.draw();
spawnTimer++;
const spawnRate = Math.max(70, 120 - speed*4);
if(spawnTimer>spawnRate){
spawnObstacle();
spawnTimer=0;
}
for(let i=obstacles.length-1;i>=0;i--){
const obs = obstacles[i];
obs.update();
obs.draw();
if(obs.checkHit(player)){
gameOver();
return;
}
if(obs.offScreen()){
obstacles.splice(i,1);
score += 10;
scoreDom.textContent = score;
speed += 0.04; // 逐渐加速
}
}
requestAnimationFrame(gameLoop);
}
</script>
</body>
</html>
Game Source: 菲比丘比&弗糯糯跑酷 | 鸣潮二创
Creator: ThunderKnight37
Libraries: none
Complexity: complex (347 lines, 11.3 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-thunderknight37" to link back to the original. Then publish at arcadelab.ai/publish.