🎮ArcadeLab

像素跑酷 手机修复完整版

by SparkPilot94
396 lines11.2 KB
▶ Play
帮我生成图片:
<!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>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body {
    background: #1a1a2e;
    color: #fff;
    font-family: "Courier New", monospace;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    padding: 10px;
    overflow: hidden;
  }
  #gameContainer {
    position: relative;
    width: 100%;
    max-width: 900px;
    aspect-ratio: 16 / 9;
    border: 3px solid #fff;
    box-shadow: 0 0 20px rgba(255,255,255,0.2);
    background: #87ceeb;
    overflow: hidden;
  }
  canvas {
    display: block;
    width: 100%;
    height: 100%;
    image-rendering: pixelated;
  }
  #ui {
    position: absolute;
    top: 10px;
    left: 10px;
    right: 10px;
    display: flex;
    justify-content: space-between;
    font-size: 18px;
    font-weight: bold;
    text-shadow: 2px 2px 0 #000;
    pointer-events: none;
  }
  #ui span {
    background: rgba(0,0,0,0.4);
    padding: 4px 12px;
    border-radius: 4px;
  }
  #overlay {
    position: absolute;
    inset: 0;
    background: rgba(0,0,0,0.75);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 20px;
    z-index: 10;
  }
  #overlay h1 {
    font-size: 36px;
    margin-bottom: 10px;
    color: #ffd700;
    text-shadow: 3px 3px 0 #000;
  }
  #overlay p {
    font-size: 16px;
    margin-bottom: 20px;
    line-height: 1.6;
  }
  #overlay .btn {
    background: #ff6b35;
    color: #fff;
    border: none;
    padding: 12px 32px;
    font-size: 20px;
    font-weight: bold;
    font-family: inherit;
    cursor: pointer;
    border-radius: 6px;
    box-shadow: 0 4px 0 #c94f24;
    margin: 6px;
  }
  #overlay .btn.green { 
    background: #4caf50; 
    box-shadow: 0 4px 0 #388e3c; 
  }
  .hidden { display: none !important; }
  #tipText{margin-top:10px;color:#aaa;font-size:14px}
</style>
</head>
<body>
  <div id="gameContainer">
    <canvas id="game" width="900" height="506"></canvas>
    <div id="ui">
      <span id="level">第 1 关:草原</span>
      <span id="score">分数:0</span>
      <span id="coins">0</span>
    </div>
    <div id="overlay">
      <h1>像素跑酷</h1>
      <p>手机点击屏幕跳跃<br>避开障碍收集金币冲到终点</p>
      <button class="btn green" id="startBtn">开始游戏</button>
    </div>
  </div>
  <div id="tipText">手机:点击画布任意位置跳跃</div>

<script>
(() => {
  const canvas = document.getElementById('game');
  const ctx = canvas.getContext('2d');
  const uiLevel = document.getElementById('level');
  const uiScore = document.getElementById('score');
  const uiCoins = document.getElementById('coins');
  const overlay = document.getElementById('overlay');
  const startBtn = document.getElementById('startBtn');

  const GRAVITY = 0.7;
  const JUMP = -13;
  const GROUND_Y = 380;
  const PLAYER_SCREEN_X = 120;

  const LEVELS = [
    { name: '草原', bg1: '#87ceeb', bg2: '#4a90e2', ground: '#6b8e23', ground2: '#556b2f', obstacles: ['rock', 'stump'], coins: 8, length: 1800 },
    { name: '沙漠', bg1: '#ffd27f', bg2: '#f4a460', ground: '#deb887', ground2: '#d2b48c', obstacles: ['cactus', 'pit'], coins: 10, length: 2000 },
    { name: '森林', bg1: '#7ec8e3', bg2: '#3a6b35', ground: '#5a3a1b', ground2: '#3d2817', obstacles: ['log', 'vine'], coins: 12, length: 2200 },
    { name: '雪地', bg1: '#bde0fe', bg2: '#a2d2ff', ground: '#f0f8ff', ground2: '#d6eaf8', obstacles: ['ice', 'snowball'], coins: 12, length: 2400 },
    { name: '城市', bg1: '#ffb3ba', bg2: '#89cff0', ground: '#555', ground2: '#333', obstacles: ['barrier', 'roof'], coins: 15, length: 2800 }
  ];

  let gameState = 'menu';
  let currentLevel = 0;
  let score = 0;
  let coins = 0;
  let worldOffset = 0;
  let levelLength = 0;
  let obstacles = [];
  let coinItems = [];
  let spawnTimer = 0;
  let coinSpawnTimer = 0;
  let gameSpeed = 5;

  const player = {
    screenX: PLAYER_SCREEN_X,
    y: GROUND_Y,
    w: 32,
    h: 40,
    vy: 0,
    onGround: true,
    runFrame: 0,
    runTimer: 0
  };

  function resetPlayer() {
    player.y = GROUND_Y;
    player.vy = 0;
    player.onGround = true;
  }

  function startLevel(index) {
    currentLevel = index;
    const level = LEVELS[index];
    levelLength = level.length;
    worldOffset = 0;
    obstacles = [];
    coinItems = [];
    spawnTimer = 0;
    coinSpawnTimer = 0;
    gameSpeed = 5 + index * 0.5;
    resetPlayer();
    gameState = 'playing';
    uiLevel.textContent = `第 ${index + 1} 关:${level.name}`;
    overlay.classList.add('hidden');
  }

  function startGame() {
    score = 0;
    coins = 0;
    updateUI();
    startLevel(0);
  }

  function updateUI() {
    uiScore.textContent = `分数:${score}`;
    uiCoins.textContent = `${coins}`;
  }

  function jump() {
    if (player.onGround && gameState === 'playing') {
      player.vy = JUMP;
      player.onGround = false;
    }
  }

  function spawnObstacle() {
    const level = LEVELS[currentLevel];
    const type = level.obstacles[Math.floor(Math.random() * level.obstacles.length)];
    let obstacle = {
      worldX: worldOffset + 950,
      y: GROUND_Y,
      type: type,
      passed: false
    };
    if (type === 'rock') { obstacle.w = 36; obstacle.h = 32; obstacle.y = GROUND_Y - 32; }
    else if (type === 'stump') { obstacle.w = 32; obstacle.h = 28; obstacle.y = GROUND_Y - 28; }
    else if (type === 'cactus') { obstacle.w = 28; obstacle.h = 44; obstacle.y = GROUND_Y - 44; }
    else if (type === 'pit') { obstacle.w = 60; obstacle.h = 20; obstacle.y = GROUND_Y - 10; }
    else if (type === 'log') { obstacle.w = 42; obstacle.h = 26; obstacle.y = GROUND_Y - 26; }
    else if (type === 'vine') { obstacle.w = 22; obstacle.h = 46; obstacle.y = GROUND_Y - 46; }
    else if (type === 'ice') { obstacle.w = 38; obstacle.h = 22; obstacle.y = GROUND_Y - 22; }
    else if (type === 'snowball') { obstacle.w = 34; obstacle.h = 34; obstacle.y = GROUND_Y - 34; }
    else if (type === 'barrier') { obstacle.w = 26; obstacle.h = 38; obstacle.y = GROUND_Y - 38; }
    else if (type === 'roof') { obstacle.w = 48; obstacle.h = 20; obstacle.y = GROUND_Y - 20; }
    obstacles.push(obstacle);
  }

  function spawnCoin() {
    const wx = worldOffset + 950 + Math.random() * 200;
    const wy = GROUND_Y - 60 - Math.random() * 80;
    coinItems.push({ worldX: wx, y: wy, w: 20, h: 20, collected: false, frame: 0 });
  }

  function worldToScreen(worldX) {
    return worldX - worldOffset;
  }

  function rectIntersectScreen(ax,ay,aw,ah,bx,by,bw,bh) {
    return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by;
  }

  function update() {
    if (gameState !== 'playing') return;
    worldOffset += gameSpeed;

    player.vy += GRAVITY;
    player.y += player.vy;
    if (player.y >= GROUND_Y) {
      player.y = GROUND_Y;
      player.vy = 0;
      player.onGround = true;
    }

    player.runTimer++;
    if(player.runTimer>6){
      player.runTimer=0; 
      player.runFrame=(player.runFrame+1)%4;
    }

    spawnTimer += gameSpeed;
    if(spawnTimer>220-currentLevel*15){ 
      spawnObstacle(); 
      spawnTimer=0; 
    }
    coinSpawnTimer += gameSpeed;
    if(coinSpawnTimer>150){ 
      spawnCoin(); 
      coinSpawnTimer=0; 
    }

    obstacles = obstacles.filter(o => worldToScreen(o.worldX) > -120);
    coinItems = coinItems.filter(c => worldToScreen(c.worldX) > -120 && !c.collected);

    for(let o of obstacles){
      const sx = worldToScreen(o.worldX);
      if(rectIntersectScreen(player.screenX, player.y, player.w, player.h, sx, o.y, o.w, o.h)){
        gameOver();
        return;
      }
      if(!o.passed && sx+o.w < player.screenX){
        o.passed=true; 
        score+=10; 
        updateUI();
      }
    }

    for(let c of coinItems){
      const sx = worldToScreen(c.worldX);
      c.frame++;
      if(!c.collected && rectIntersectScreen(player.screenX, player.y, player.w, player.h, sx, c.y, c.w, c.h)){
        c.collected=true; 
        coins++; 
        score+=20; 
        updateUI();
      }
    }

    if(worldOffset >= levelLength){
      if(currentLevel < LEVELS.length-1){
        showOverlay(`第${currentLevel+1}关完成`,`准备进入第${currentLevel+2}关:${LEVELS[currentLevel+1].name}`,"下一关",()=>startLevel(currentLevel+1));
      }else{
        showOverlay("全部通关!",`总分${score}|金币${coins}`,"再来一局",startGame);
      }
      gameState="levelComplete";
    }
  }

  function gameOver(){
    gameState="gameOver";
    showOverlay("游戏结束",`第${currentLevel+1}关|分数${score}`,"重新开始",startGame);
  }

  function showOverlay(title,txt,btnText,onClick){
    overlay.innerHTML=`<h1>${title}</h1><p>${txt}</p><button class="btn green" id="obtn">${btnText}</button>`;
    overlay.classList.remove("hidden");
    document.getElementById("obtn").onclick=onClick;
  }

  function draw(){
    if(gameState==="menu") return;
    const level = LEVELS[currentLevel];
    const grad = ctx.createLinearGradient(0,0,0,GROUND_Y);
    grad.addColorStop(0,level.bg1); 
    grad.addColorStop(1,level.bg2);
    ctx.fillStyle=grad; 
    ctx.fillRect(0,0,canvas.width,canvas.height);

    ctx.fillStyle="rgba(255,255,255,0.6)";
    for(let i=0;i<3;i++){
      const cx = ((i*320 - worldOffset*0.2) % 1200 +1200)%1200 -100;
      ctx.fillRect(cx,60+i*20,60,16);
      ctx.fillRect(cx+10,60+i*20-10,40,14);
    }

    ctx.fillStyle=level.ground2;
    ctx.fillRect(0,GROUND_Y,canvas.width,canvas.height-GROUND_Y);
    ctx.fillStyle=level.ground;
    ctx.fillRect(0,GROUND_Y,canvas.width,8);

    for(let o of obstacles){
      const sx = worldToScreen(o.worldX);
      ctx.fillStyle="#777";
      ctx.fillRect(sx,o.y,o.w,o.h);
    }

    for(let c of coinItems){
      if(c.collected) continue;
      const sx = worldToScreen(c.worldX);
      const bounce = Math.sin(c.frame*0.15)*2;
      ctx.fillStyle="#ffd700";
      ctx.fillRect(sx+2,c.y+bounce,16,16);
    }

    if(worldOffset >= levelLength-100 && worldOffset < levelLength){
      const finishScreenX = worldToScreen(levelLength);
      ctx.fillStyle="rgba(255,215,0,0.8)";
      ctx.fillRect(finishScreenX,GROUND_Y-80,6,80);
    }

    const px = player.screenX;
    const py = player.y;
    ctx.fillStyle='#4285f4';
    ctx.fillRect(px+8,py+12,16,20);
    ctx.fillStyle='#ffd9a8';
    ctx.fillRect(px+10,py+2,12,12);
    ctx.fillStyle='#ff6b35';
    ctx.fillRect(px+8,py,16,6);
    ctx.fillRect(px+4,py+4,6,4);
    ctx.fillStyle='#000';
    ctx.fillRect(px+18,py+6,2,2);
    const legOff = player.onGround ? (player.runFrame%2===0?0:4):0;
    ctx.fillStyle="#333";
    ctx.fillRect(px+10,py+32,5,8-legOff);
    ctx.fillRect(px+17,py+32,5,8+legOff);
  }

  function loop(){ 
    update(); 
    draw(); 
    requestAnimationFrame(loop); 
  }

  startBtn.addEventListener("click",startGame);
  canvas.addEventListener("touchstart",e=>{ 
    e.preventDefault(); 
    jump(); 
  });
  canvas.addEventListener("mousedown",jump);
  window.addEventListener("keydown",e=>{
    if(e.code==="Space"||e.code==="ArrowUp"){ 
      e.preventDefault(); 
      jump(); 
    }
  });

  loop();
})();
</script>
</body>
</html>

Game Source: 像素跑酷 手机修复完整版

Creator: SparkPilot94

Libraries: none

Complexity: complex (396 lines, 11.2 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-sparkpilot94" to link back to the original. Then publish at arcadelab.ai/publish.