🎮ArcadeLab

登山赛车|无限地形 永不消失 完美版

by AtomicTurtle21
332 lines12.1 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登山赛车|无限地形 永不消失 完美版</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
            background: linear-gradient(#72b9e8,#b8e9b8);
            font-family: Arial,sans-serif;
            overflow: hidden;
        }
        #gameContainer {
            position: relative;
            width: 100%;
            max-width: 1200px;
            height: 600px;
            border:4px solid #222;
            border-radius:8px;
            overflow:hidden;
            background: linear-gradient(#84c9f5,#bce8bc);
        }
        #scoreBoard {
            position:absolute;top:15px;left:15px;
            font-size:24px;font-weight:bold;
            z-index:100;color:#111;
            text-shadow: 1px 1px 3px #fff;
        }
        #carName{
            position:absolute;top:50px;left:15px;font-size:20px;color:#222;font-weight:bold;
        }
        #gameOver {
            position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
            font-size:46px;color:#e63939;font-weight:bold;
            text-shadow:2px 2px 3px #000;display:none;z-index:200;text-align:center;
            background: rgba(0,0,0,0.5);
            padding:30px;
            border-radius:12px;
        }
        #gameOver span {display:block;font-size:22px;margin-top:8px;color:#fff;}
        #car {
            position:absolute;z-index:50;
            border-radius:9px;
            overflow: hidden;
        }
        .wheel {
            position:absolute;width:21px;height:21px;background:#222;
            border-radius:50%;border:3px solid #f1f1f1;
            box-shadow:inset 0 0 4px #000;
        }
        #wheel1 {bottom:-10px;left:10px;}
        #wheel2 {bottom:-10px;right:10px;}
        #terrainCanvas {
            position:absolute;bottom:0;left:0;width:100%;height:100%;
            pointer-events:none;z-index:10;
        }
        .tips {margin-top:12px;font-size:16px;color:#222;text-align:center;}
        .coin{
            position:absolute;width:24px;height:24px;border-radius:50%;
            background:radial-gradient(circle at 30% 30%,#fff9b0,#ffd700,#b8860b,#805000);
            box-shadow:0 0 7px #ffcc22,inset -3px -3px 6px rgba(80,40,0,0.55);
            animation:coinShine 1.2s infinite alternate ease-in-out;
            z-index:35;
        }
        @keyframes coinShine{
            from{box-shadow:0 0 5px #ffdd33,inset -3px -3px 6px rgba(80,40,0,0.55);}
            to{box-shadow:0 0 12px #ffef55,inset -1px -1px 3px rgba(255,240,180,0.7);}
        }
    </style>
</head>
<body>
    <div id="gameContainer">
        <div id="scoreBoard">距离:0m|金币:0枚</div>
        <div id="carName">当前车辆:家用轿车【按1~7切换车型】</div>
        <div id="gameOver">车辆翻车!游戏结束!<span>按 R 重新开始</span></div>
        <div id="car">
            <div class="wheel" id="wheel1"></div>
            <div class="wheel2" id="wheel2"></div>
        </div>
        <canvas id="terrainCanvas"></canvas>
    </div>
    <div class="tips">↑加速 ↓减速 R重开|1轿车 2吉普 3摩托 4重卡 5蹦蹦 6跑车 7桑塔纳</div>

    <script>
        const car = document.getElementById('car');
        const scoreBoard = document.getElementById('scoreBoard');
        const carNameDom = document.getElementById('carName');
        const gameOver = document.getElementById('gameOver');
        const canvas = document.getElementById('terrainCanvas');
        const ctx = canvas.getContext('2d');
        const gameBox = document.getElementById('gameContainer');
        const w1 = document.getElementById('wheel1');
        const w2 = document.getElementById('wheel2');

        const carList = [
            {name:"家用轿车",w:82,h:42,color:"#dd3838",acc:0.32,maxSp:7.2,roll:78,airRot:0.12},
            {name:"越野吉普",w:88,h:48,color:"#2d882d",acc:0.38,maxSp:5.8,roll:92,airRot:0.07},
            {name:"越野摩托",w:52,h:32,color:"#222",acc:0.48,maxSp:8.5,roll:52,airRot:0.19},
            {name:"重型卡车",w:120,h:55,color:"#445577",acc:0.22,maxSp:4.2,roll:85,airRot:0.04},
            {name:"沙滩蹦蹦",w:70,h:38,color:"#ff9922",acc:0.35,maxSp:6.3,roll:82,airRot:0.09},
            {name:"竞速跑车",w:90,h:36,color:"#9922aa",acc:0.42,maxSp:10.2,roll:62,airRot:0.15},
            {name:"大众桑塔纳",w:100,h:45,color:"#c1272d",acc:0.35,maxSp:6.8,roll:88,airRot:0.08}
        ];
        let curCarIdx = 0;
        let carCfg = carList[curCarIdx];

        function setCarStyle(){
            carCfg = carList[curCarIdx];
            carNameDom.innerText = "当前车辆:"+carCfg.name+"【按1~7切换车型】";
            car.style.width = carCfg.w+"px";
            car.style.height = carCfg.h+"px";
            car.style.background = `linear-gradient(${carCfg.color}, #881111)`;
            if(curCarIdx === 6) {
                car.style.boxShadow = `inset 0 -${carCfg.h*0.25}px 0 #222, inset 0 -4px 6px rgba(0,0,0,0.35)`;
            } else {
                car.style.boxShadow = `inset 0 -4px 6px rgba(0,0,0,0.35)`;
            }
            w1.style.left = (carCfg.w*0.12)+"px";
            w2.style.right = (carCfg.w*0.12)+"px";
        }
        setCarStyle();

        function resizeCanvas(){
            canvas.width = gameBox.offsetWidth;
            canvas.height = gameBox.offsetHeight;
        }
        resizeCanvas();
        window.addEventListener('resize',resizeCanvas);

        let gameRunning = true;
        let distanceScore = 0;
        let coinTotal = 0;

        let carX = 150,carY = 200;
        let carSpeedX = 0,carSpeedY = 0;
        let carRotation = 0;
        const gravity = 0.6;
        const friction = 0.95;

        let terrainPoints = [];
        let coinList = [];
        const keys = {};

        function generateTerrain(){
            terrainPoints = [];
            coinList = [];
            document.querySelectorAll('.coin').forEach(el=>el.remove());
            let x=0;
            let baseY = canvas.height - 100;
            terrainPoints.push({x,y:baseY});

            while(x < canvas.width * 10){
                let rand = Math.random();
                let type;
                if(rand < 0.20) type = 0;
                else if(rand < 0.75) type = 1;
                else type = 2;

                let segLength = 60 + Math.random()*80;
                x += segLength;

                switch(type){
                    case 0: baseY += (Math.random()-0.5)*15; break;
                    case 1: baseY += (Math.random()-0.5)*80; break;
                    case 2: baseY += (Math.random()-0.5)*110; break;
                }
                baseY = Math.max(canvas.height-240, Math.min(canvas.height-60, baseY));
                terrainPoints.push({x,y:baseY});

                if(Math.random()<0.4){
                    const coinY = baseY - 30 - Math.random()*70;
                    coinList.push({baseX:x, posX:x, posY:coinY, vy:0, collected:false, dom:null});
                }
            }
            createCoinDom();
        }

        function createCoinDom(){
            coinList.forEach(item=>{
                if(item.collected) return;
                const div = document.createElement('div');
                div.className = 'coin';
                gameBox.appendChild(div);
                item.dom = div;
            })
        }

        function getTerrainHeight(xPos){
            if (xPos < 0) xPos = 0;
            let last = terrainPoints[terrainPoints.length-1];
            if (xPos > last.x) return last.y;
            for(let i=0;i<terrainPoints.length-1;i++){
                const p1=terrainPoints[i],p2=terrainPoints[i+1];
                if(xPos>=p1.x && xPos<=p2.x){
                    let r = (xPos-p1.x)/(p2.x-p1.x);
                    return p1.y+(p2.y-p1.y)*r;
                }
            }
            return canvas.height-50;
        }

        function drawTerrain(){
            ctx.clearRect(0,0,canvas.width,canvas.height);
            ctx.fillStyle='#4c3627';
            ctx.beginPath();
            ctx.moveTo(0,canvas.height);
            for(let p of terrainPoints){
                ctx.lineTo(p.x-carX+150,p.y);
            }
            ctx.lineTo(canvas.width,canvas.height);
            ctx.closePath();
            ctx.fill();
            ctx.fillStyle='#70523c';
            ctx.beginPath();
            ctx.moveTo(0,canvas.height);
            for(let p of terrainPoints){
                ctx.lineTo(p.x-carX+150,p.y-3);
            }
            ctx.lineTo(canvas.width,canvas.height);
            ctx.closePath();
            ctx.fill();
        }

        document.addEventListener('keydown',e=>{
            keys[e.key]=true;
            let num = parseInt(e.key);
            if(num>=1&&num<=7){
                curCarIdx = num-1;
                setCarStyle();
            }
        });
        document.addEventListener('keyup',e=>keys[e.key]=false);

        function updateCoins(){
            const carCenterX = carX+carCfg.w/2;
            const carCenterY = carY+carCfg.h/2;
            coinList.forEach(coin=>{
                if(coin.collected || !coin.dom) return;
                coin.vy += 0.35;
                coin.posY += coin.vy;
                const groundH = getTerrainHeight(coin.baseX);
                if(coin.posY >= groundH-12){
                    coin.posY = groundH-12;
                    coin.vy = -coin.vy*0.32;
                    if(Math.abs(coin.vy)<0.4) coin.vy=0;
                }
                let screenX = coin.posX - carX + 150;
                coin.dom.style.left = screenX+'px';
                coin.dom.style.bottom = canvas.height - coin.posY +'px';
                const dist = Math.hypot(carCenterX-coin.posX,carCenterY-coin.posY);
                if(dist<32){
                    coin.collected=true;
                    coinTotal++;
                    coin.dom.style.display='none';
                }
            })
        }

        function updateGame(){
            if(!gameRunning) return;

            if(keys['ArrowUp']) carSpeedX+=carCfg.acc;
            if(keys['ArrowDown']) carSpeedX-=carCfg.acc;
            carSpeedX = Math.max(-carCfg.maxSp/2,Math.min(carCfg.maxSp,carSpeedX));
            carSpeedX *= friction;
            carSpeedY += gravity;

            carX += carSpeedX;
            carY += carSpeedY;

            const w1H = getTerrainHeight(carX+carCfg.w*0.15);
            const w2H = getTerrainHeight(carX+carCfg.w*0.85);
            const groundY = Math.min(w1H,w2H);

            if(carY+carCfg.h>=groundY){
                carY = groundY-carCfg.h;
                carSpeedY=0;
                let ang = Math.atan2(w2H-w1H,carCfg.w*0.7);
                carRotation = ang*180/Math.PI;
            }else{
                carRotation += carSpeedX * carCfg.airRot;
            }

            distanceScore = Math.floor(carX/10);
            scoreBoard.textContent = `距离:${distanceScore}m|金币:${coinTotal}枚`;

            if(Math.abs(carRotation) > carCfg.roll || carY > canvas.height + 500){
                endGame();
            }

            car.style.left='150px';
            car.style.bottom = canvas.height-carY+'px';
            car.style.transform = `rotate(${carRotation}deg)`;

            drawTerrain();
            updateCoins();
            requestAnimationFrame(updateGame);
        }

        function endGame(){
            gameRunning=false;
            gameOver.style.display='block';
        }

        function restart(){
            gameRunning=true;
            distanceScore=0;coinTotal=0;
            carX=150;carY=200;carSpeedX=0;carSpeedY=0;carRotation=0;
            gameOver.style.display='none';
            generateTerrain();
            drawTerrain();
        }

        document.addEventListener('keydown',e=>{
            if(e.key.toLowerCase()==='r' && !gameRunning) restart();
        })

        generateTerrain();
        updateGame();
    </script>
</body>
</html>

Game Source: 登山赛车|无限地形 永不消失 完美版

Creator: AtomicTurtle21

Libraries: none

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