🎮ArcadeLab

Sky Pilot X

by CrystalTiger19
2495 lines30.7 KB
▶ Play
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">

<title>Sky Pilot X</title>

<style>
*{
    margin:0;
    padding:0;
    box-sizing:border-box;
}

html,body{
    width:100%;
    height:100%;
    overflow:hidden;
    background:#061b35;
    font-family:Arial,sans-serif;
}

canvas{
    width:100%;
    height:100%;
    display:block;
    touch-action:none;
}
</style>
</head>

<body>

<canvas id="game"></canvas>

<script>

/* =========================================================
   SKY PILOT X
   INTRO + LOADING + MENU + GAME
========================================================= */

const canvas=document.getElementById("game");
const ctx=canvas.getContext("2d");

let W=innerWidth;
let H=innerHeight;
let DPR=devicePixelRatio||1;

function resize(){

    W=innerWidth;
    H=innerHeight;

    DPR=devicePixelRatio||1;

    canvas.width=W*DPR;
    canvas.height=H*DPR;

    ctx.setTransform(DPR,0,0,DPR,0,0);
}

resize();

addEventListener("resize",resize);


/* =========================================================
   SCREEN STATES
========================================================= */

let screen="unity";

let loading=0;

let menuAlpha=0;

let gameStarted=false;

let crashed=false;


/* =========================================================
   GAME VARIABLES
========================================================= */

let score=0;

let best=
Number(localStorage.getItem("skyPilotBest")||0);

let speed=3.2;

let difficulty=0;

let frame=0;

let obstacles=[];

let particles=[];

let clouds=[];

let stars=[];

let shake=0;


/* =========================================================
   AIRPLANE
========================================================= */

const plane={

    x:0,
    y:0,

    vx:0,
    vy:0,

    width:68,
    height:38,

    angle:0

};


function resetPlane(){

    plane.x=W*.23;
    plane.y=H*.45;

    plane.vx=0;
    plane.vy=0;

    plane.angle=0;

}


/* =========================================================
   CLOUDS
========================================================= */

function makeCloud(x){

    clouds.push({

        x:x,

        y:
        40+
        Math.random()*H*.38,

        size:
        .55+
        Math.random()*.9,

        speed:
        .2+
        Math.random()*.45

    });

}


for(let i=0;i<10;i++){

    makeCloud(
        Math.random()*W
    );

}


/* =========================================================
   STARS
========================================================= */

for(let i=0;i<90;i++){

    stars.push({

        x:Math.random()*W,

        y:Math.random()*H*.7,

        size:
        Math.random()*2,

        alpha:
        .3+
        Math.random()*.7

    });

}


/* =========================================================
   UNITY OPENING
========================================================= */

function updateUnity(){

    if(screen!=="unity")
        return;

    loading+=0.7;

    if(loading>=100){

        loading=100;

        setTimeout(()=>{

            screen="loading";

            loading=0;

        },500);

    }

}


/* =========================================================
   LOADING
========================================================= */

function updateLoading(){

    if(screen!=="loading")
        return;

    loading+=1.4;

    if(loading>=100){

        loading=100;

        setTimeout(()=>{

            screen="menu";

        },400);

    }

}


/* =========================================================
   DRAW BEAUTIFUL BACKGROUND
========================================================= */

function drawBeautifulBackground(){

    const g=
        ctx.createLinearGradient(
            0,
            0,
            0,
            H
        );


    g.addColorStop(
        0,
        "#071b3d"
    );

    g.addColorStop(
        .35,
        "#124d83"
    );

    g.addColorStop(
        .68,
        "#2ba9d5"
    );

    g.addColorStop(
        1,
        "#ffd27a"
    );


    ctx.fillStyle=g;

    ctx.fillRect(
        0,
        0,
        W,
        H
    );


    /* Stars */

    for(const s of stars){

        ctx.globalAlpha=
            s.alpha*
            (
                .65+
                Math.sin(frame*.03+s.x)*.35
            );

        ctx.fillStyle="#fff";

        ctx.beginPath();

        ctx.arc(
            s.x,
            s.y,
            s.size,
            0,
            Math.PI*2
        );

        ctx.fill();

    }

    ctx.globalAlpha=1;


    /* Moon */

    ctx.fillStyle=
        "rgba(255,245,190,.95)";

    ctx.beginPath();

    ctx.arc(
        W*.82,
        H*.18,
        42,
        0,
        Math.PI*2
    );

    ctx.fill();


    /* Moon shadow */

    ctx.fillStyle=
        "#124d83";

    ctx.beginPath();

    ctx.arc(
        W*.85,
        H*.15,
        42,
        0,
        Math.PI*2
    );

    ctx.fill();


    /* Sun glow */

    const glow=
        ctx.createRadialGradient(
            W*.2,
            H*.62,
            5,
            W*.2,
            H*.62,
            170
        );

    glow.addColorStop(
        0,
        "rgba(255,220,120,.65)"
    );

    glow.addColorStop(
        1,
        "rgba(255,220,120,0)"
    );

    ctx.fillStyle=glow;

    ctx.fillRect(
        0,
        0,
        W,
        H
    );


    /* Mountains */

    ctx.fillStyle=
        "rgba(20,55,75,.65)";

    ctx.beginPath();

    ctx.moveTo(0,H*.73);

    for(
        let x=0;
        x<=W;
        x+=80
    ){

        const y=
            H*.63+
            Math.sin(x*.012)*50+
            Math.sin(x*.027)*25;

        ctx.lineTo(
            x,
            y
        );

    }

    ctx.lineTo(W,H);

    ctx.lineTo(0,H);

    ctx.closePath();

    ctx.fill();


    /* City silhouette */

    const ground=H*.82;

    for(
        let x=0;
        x<W;
        x+=45
    ){

        const bh=
            40+
            ((x*17)%100);

        ctx.fillStyle=
            "rgba(7,30,45,.85)";

        ctx.fillRect(
            x,
            ground-bh,
            38,
            bh
        );


        /* Windows */

        ctx.fillStyle=
            "rgba(255,213,91,.7)";

        for(
            let y=
            ground-bh+15;

            y<
            ground-8;

            y+=20
        ){

            ctx.fillRect(
                x+8,
                y,
                5,
                7
            );

            ctx.fillRect(
                x+23,
                y,
                5,
                7
            );

        }

    }


    /* Ground */

    ctx.fillStyle="#081b29";

    ctx.fillRect(
        0,
        ground,
        W,
        H-ground
    );


    /* Animated horizon */

    ctx.fillStyle=
        "rgba(255,190,80,.12)";

    ctx.fillRect(
        0,
        ground,
        W,
        5
    );

}


/* =========================================================
   DRAW CLOUDS
========================================================= */

function drawCloud(c){

    ctx.save();

    ctx.translate(
        c.x,
        c.y
    );

    ctx.scale(
        c.size,
        c.size
    );

    ctx.fillStyle=
        "rgba(255,255,255,.16)";

    ctx.beginPath();

    ctx.arc(
        -35,
        8,
        24,
        0,
        Math.PI*2
    );

    ctx.arc(
        0,
        -7,
        33,
        0,
        Math.PI*2
    );

    ctx.arc(
        35,
        8,
        24,
        0,
        Math.PI*2
    );

    ctx.fill();

    ctx.restore();

}


/* =========================================================
   INTRO SCREEN
========================================================= */

function drawUnityScreen(){

    drawBeautifulBackground();


    for(const c of clouds){

        c.x-=c.speed*.15;

        if(c.x<-180)
            c.x=W+100;

        drawCloud(c);

    }


    ctx.textAlign="center";


    /* Unity-style title */

    ctx.shadowColor=
        "rgba(0,0,0,.5)";

    ctx.shadowBlur=20;

    ctx.fillStyle="#fff";

    ctx.font=
        "900 46px Arial";

    ctx.fillText(
        "UNITY",
        W/2,
        H*.43
    );


    ctx.shadowBlur=0;


    ctx.fillStyle=
        "rgba(255,255,255,.75)";

    ctx.font=
        "14px Arial";

    ctx.fillText(
        "POWERED BY UNITY",
        W/2,
        H*.48
    );


    /* Loading line */

    ctx.fillStyle=
        "rgba(255,255,255,.2)";

    ctx.fillRect(
        W*.25,
        H*.82,
        W*.5,
        5
    );


    ctx.fillStyle="#fff";

    ctx.fillRect(
        W*.25,
        H*.82,
        W*.5*(loading/100),
        5
    );


    ctx.textAlign="left";

}


/* =========================================================
   LOADING SCREEN
========================================================= */

function drawLoadingScreen(){

    drawBeautifulBackground();


    for(const c of clouds){

        c.x-=c.speed*.25;

        if(c.x<-180)
            c.x=W+100;

        drawCloud(c);

    }


    ctx.textAlign="center";


    ctx.fillStyle="#fff";

    ctx.font=
        "900 42px Arial";

    ctx.fillText(
        "SKY PILOT X",
        W/2,
        H*.38
    );


    ctx.font=
        "bold 17px Arial";

    ctx.fillStyle=
        "rgba(255,255,255,.8)";

    ctx.fillText(
        "PREPARING FLIGHT...",
        W/2,
        H*.46
    );


    /* Loading box */

    const barW=
        Math.min(
            420,
            W*.65
        );


    const barX=
        W/2-barW/2;


    ctx.fillStyle=
        "rgba(255,255,255,.2)";

    ctx.beginPath();

    ctx.roundRect(
        barX,
        H*.54,
        barW,
        14,
        7
    );

    ctx.fill();


    ctx.fillStyle="#fff";

    ctx.beginPath();

    ctx.roundRect(
        barX,
        H*.54,
        barW*(loading/100),
        14,
        7
    );

    ctx.fill();


    ctx.font=
        "bold 14px Arial";

    ctx.fillStyle="#fff";

    ctx.fillText(
        Math.floor(loading)+"%",
        W/2,
        H*.60
    );


    ctx.textAlign="left";

}


/* =========================================================
   MENU
========================================================= */

function drawMenu(){

    drawBeautifulBackground();


    for(const c of clouds){

        c.x-=c.speed*.35;

        if(c.x<-180)
            c.x=W+100;

        drawCloud(c);

    }


    /* Dark overlay */

    const overlay=
        ctx.createLinearGradient(
            0,
            0,
            0,
            H
        );

    overlay.addColorStop(
        0,
        "rgba(0,0,0,.15)"
    );

    overlay.addColorStop(
        1,
        "rgba(0,0,0,.5)"
    );

    ctx.fillStyle=overlay;

    ctx.fillRect(
        0,
        0,
        W,
        H
    );


    ctx.textAlign="center";


    /* Game title */

    ctx.shadowColor=
        "rgba(0,0,0,.55)";

    ctx.shadowBlur=18;

    ctx.fillStyle="#fff";

    ctx.font=
        "900 clamp(42px,10vw,75px) Arial";

    ctx.fillText(
        "SKY PILOT",
        W/2,
        H*.25
    );


    ctx.fillStyle="#ffd34d";

    ctx.font=
        "900 clamp(18px,4vw,30px) Arial";

    ctx.fillText(
        "X",
        W/2,
        H*.31
    );


    ctx.shadowBlur=0;


    /* Subtitle */

    ctx.fillStyle=
        "rgba(255,255,255,.85)";

    ctx.font=
        "16px Arial";

    ctx.fillText(
        "FLY • DODGE • SURVIVE",
        W/2,
        H*.37
    );


    /* =================================================
       START BUTTON
    ================================================= */

    const buttonW=
        Math.min(
            230,
            W*.48
        );

    const buttonH=68;

    const buttonY=
        H*.56;


    /* Button shadow */

    ctx.fillStyle=
        "rgba(0,0,0,.35)";

    ctx.beginPath();

    ctx.roundRect(
        W/2-
        buttonW/2+
        4,
        buttonY+7,
        buttonW,
        buttonH,
        18
    );

    ctx.fill();


    /* Button */

    const buttonGradient=
        ctx.createLinearGradient(
            0,
            buttonY,
            0,
            buttonY+buttonH
        );

    buttonGradient.addColorStop(
        0,
        "#ffe56a"
    );

    buttonGradient.addColorStop(
        1,
        "#ff9d25"
    );


    ctx.fillStyle=
        buttonGradient;


    ctx.beginPath();

    ctx.roundRect(
        W/2-
        buttonW/2,
        buttonY,
        buttonW,
        buttonH,
        18
    );

    ctx.fill();


    ctx.fillStyle="#172c3b";

    ctx.font=
        "900 25px Arial";

    ctx.fillText(
        "▶  START",
        W/2,
        buttonY+43
    );


    /* =================================================
       HIGH SCORE
    ================================================= */

    const scoreW=
        Math.min(
            230,
            W*.48
        );

    const scoreX=
        W/2-
        scoreW/2;


    const scoreY=
        buttonY+
        buttonH+
        18;


    ctx.fillStyle=
        "rgba(0,20,35,.55)";


    ctx.beginPath();

    ctx.roundRect(
        scoreX,
        scoreY,
        scoreW,
        55,
        15
    );

    ctx.fill();


    ctx.fillStyle=
        "rgba(255,255,255,.7)";

    ctx.font=
        "bold 12px Arial";

    ctx.fillText(
        "HIGHEST SCORE",
        W/2,
        scoreY+20
    );


    ctx.fillStyle="#fff";

    ctx.font=
        "900 21px Arial";

    ctx.fillText(
        best,
        W/2,
        scoreY+43
    );


    /* Bottom text */

    ctx.fillStyle=
        "rgba(255,255,255,.6)";

    ctx.font=
        "12px Arial";

    ctx.fillText(
        "TAP START TO BEGIN",
        W/2,
        H*.91
    );


    ctx.textAlign="left";

}


/* =========================================================
   MENU TOUCH
========================================================= */

function menuTouch(x,y){

    const buttonW=
        Math.min(
            230,
            W*.48
        );

    const buttonH=68;

    const buttonY=H*.56;


    if(
        x>
        W/2-buttonW/2 &&
        x<
        W/2+buttonW/2 &&
        y>
        buttonY &&
        y<
        buttonY+buttonH
    ){

        startGame();

    }

}


/* =========================================================
   START GAME
========================================================= */

function startGame(){

    score=0;

    speed=3.2;

    difficulty=0;

    obstacles=[];

    particles=[];

    resetPlane();

    crashed=false;

    gameStarted=true;

    screen="game";

    createObstacle();

}


/* =========================================================
   PLAYER FLIGHT
========================================================= */

function fly(){

    if(screen==="menu"){

        return;

    }


    if(screen!=="game")
        return;


    if(crashed){

        startGame();

        return;

    }


    plane.vy=-7.5;


    for(let i=0;i<7;i++){

        particles.push({

            x:
            plane.x-30,

            y:
            plane.y+
            (Math.random()-.5)*10,

            vx:
            -1-
            Math.random()*3,

            vy:
            (Math.random()-.5)*2,

            life:1,

            size:
            2+
            Math.random()*4

        });

    }

}


/* =========================================================
   CONTROLS
========================================================= */

canvas.addEventListener(
    "pointerdown",
    function(e){

        e.preventDefault();


        if(screen==="menu"){

            menuTouch(
                e.clientX,
                e.clientY
            );

            return;

        }


        fly();

    }
);


addEventListener(
    "keydown",
    function(e){

        if(
            e.code==="Space" ||
            e.code==="ArrowUp" ||
            e.code==="Enter"
        ){

            e.preventDefault();


            if(screen==="menu"){

                startGame();

            }else{

                fly();

            }

        }

    }
);


/* =========================================================
   OBSTACLE
========================================================= */

function createObstacle(){

    difficulty=
        Math.floor(score/5);


    const gap=
        Math.max(
            112,
            205-
            difficulty*8
        );


    const minTop=60;


    const maxTop=
        H-
        gap-
        95;


    const top=
        minTop+
        Math.random()*
        Math.max(
            20,
            maxTop-minTop
        );


    const width=
        65+
        Math.random()*38;


    obstacles.push({

        x:W+100,

        width:width,

        top:top,

        gap:gap,

        passed:false

    });

}


/* =========================================================
   COLLISION
========================================================= */

function collision(){

    if(
        plane.y-
        plane.height*.45<0
    )
        return true;


    if(
        plane.y+
        plane.height*.45>
        H-55
    )
        return true;


    for(const o of obstacles){

        const px1=
            plane.x-
            plane.width*.36;

        const px2=
            plane.x+
            plane.width*.36;

        const py1=
            plane.y-
            plane.height*.36;

        const py2=
            plane.y+
            plane.height*.36;


        if(
            px2>o.x &&
            px1<o.x+o.width
        ){

            if(
                py1<o.top ||
                py2>o.top+o.gap
            ){

                return true;

            }

        }

    }


    return false;

}


/* =========================================================
   UPDATE GAME
========================================================= */

function updateGame(){

    if(!gameStarted || crashed)
        return;


    plane.vy+=.37;


    plane.vy=
        Math.min(
            plane.vy,
            9
        );


    plane.y+=plane.vy;


    plane.vx+=
        (speed-plane.vx)*.02;


    plane.x+=
        plane.vx*.02;


    plane.x=
        Math.max(
            W*.14,
            Math.min(
                W*.36,
                plane.x
            )
        );


    let target=
        plane.vy*.065;


    target=
        Math.max(
            -.55,
            Math.min(
                .8,
                target
            )
        );


    plane.angle+=
        (target-plane.angle)*.13;


    /* Difficulty */

    difficulty=
        Math.floor(score/5);


    const targetSpeed=
        3.2+
        difficulty*.32;


    speed+=
        (targetSpeed-speed)*.015;


    speed=
        Math.min(
            speed,
            10
        );


    /* Obstacles */

    for(
        let i=
        obstacles.length-1;

        i>=0;

        i--
    ){

        const o=
            obstacles[i];


        o.x-=speed;


        if(
            !o.passed &&
            o.x+o.width<
            plane.x
        ){

            o.passed=true;

            score++;


            if(score>best){

                best=score;

                localStorage.setItem(
                    "skyPilotBest",
                    best
                );

            }

        }


        if(
            o.x+o.width<
            -120
        ){

            obstacles.splice(
                i,
                1
            );

        }

    }


    const spawnDistance=
        Math.max(
            175,
            315-
            difficulty*10
        );


    if(
        obstacles.length===0 ||
        obstacles[
            obstacles.length-1
        ].x<
        W-spawnDistance
    ){

        createObstacle();

    }


    if(collision()){

        crash();

    }

}


/* =========================================================
   PARTICLES
========================================================= */

function updateParticles(){

    for(
        let i=
        particles.length-1;

        i>=0;

        i--
    ){

        const p=
            particles[i];


        p.x+=p.vx;

        p.y+=p.vy;

        p.vy+=.06;

        p.life-=.025;


        if(p.life<=0){

            particles.splice(
                i,
                1
            );

        }

    }

}


/* =========================================================
   GAME SKY
========================================================= */

function drawGameSky(){

    const g=
        ctx.createLinearGradient(
            0,
            0,
            0,
            H
        );


    g.addColorStop(
        0,
        "#31bdf4"
    );

    g.addColorStop(
        .55,
        "#9ce8ff"
    );

    g.addColorStop(
        1,
        "#eafaff"
    );


    ctx.fillStyle=g;

    ctx.fillRect(
        0,
        0,
        W,
        H
    );


    ctx.fillStyle=
        "#ffe46b";

    ctx.beginPath();

    ctx.arc(
        W*.82,
        H*.15,
        48,
        0,
        Math.PI*2
    );

    ctx.fill();


    for(const c of clouds){

        c.x-=c.speed;

        if(c.x<-180)
            c.x=W+100;

        drawCloudGame(c);

    }

}


/* =========================================================
   GAME CLOUD
========================================================= */

function drawCloudGame(c){

    ctx.save();

    ctx.translate(
        c.x,
        c.y
    );

    ctx.scale(
        c.size,
        c.size
    );

    ctx.fillStyle=
        "rgba(255,255,255,.8)";


    ctx.beginPath();

    ctx.arc(
        -35,
        7,
        25,
        0,
        Math.PI*2
    );

    ctx.arc(
        0,
        -7,
        34,
        0,
        Math.PI*2
    );

    ctx.arc(
        36,
        7,
        25,
        0,
        Math.PI*2
    );

    ctx.fill();

    ctx.restore();

}


/* =========================================================
   GAME CITY
========================================================= */

function drawGameCity(){

    const ground=H-55;


    for(
        let x=0;
        x<W;
        x+=45
    ){

        const h=
            45+
            ((x*31)%105);


        ctx.fillStyle=
            "#638897";


        ctx.fillRect(
            x,
            ground-h,
            37,
            h
        );


        ctx.fillStyle=
            "rgba(230,250,255,.8)";


        for(
            let y=
            ground-h+14;

            y<
            ground-8;

            y+=22
        ){

            ctx.fillRect(
                x+7,
                y,
                6,
                9
            );

            ctx.fillRect(
                x+23,
                y,
                6,
                9
            );

        }

    }


    ctx.fillStyle="#3ba548";

    ctx.fillRect(
        0,
        H-55,
        W,
        55
    );


    ctx.fillStyle="#555b60";

    ctx.fillRect(
        0,
        H-30,
        W,
        30
    );


    ctx.strokeStyle="#fff";

    ctx.lineWidth=3;


    const offset=
        -(frame*speed*.8)%70;


    for(
        let x=offset;
        x<W;
        x+=70
    ){

        ctx.beginPath();

        ctx.moveTo(
            x,
            H-15
        );

        ctx.lineTo(
            x+35,
            H-15
        );

        ctx.stroke();

    }

}


/* =========================================================
   OBSTACLE DRAW
========================================================= */

function drawObstacle(o){

    ctx.fillStyle="#405a66";

    ctx.fillRect(
        o.x,
        0,
        o.width,
        o.top
    );


    ctx.fillStyle="#293f49";

    ctx.fillRect(
        o.x-7,
        o.top-18,
        o.width+14,
        18
    );


    ctx.fillStyle="#d8edf2";


    for(
        let y=18;
        y<o.top-25;
        y+=25
    ){

        ctx.fillRect(
            o.x+10,
            y,
            12,
            10
        );

        ctx.fillRect(
            o.x+o.width-22,
            y,
            12,
            10
        );

    }


    const bottom=
        o.top+o.gap;


    ctx.fillStyle="#405a66";

    ctx.fillRect(
        o.x,
        bottom,
        o.width,
        H-bottom
    );


    ctx.fillStyle="#293f49";

    ctx.fillRect(
        o.x-7,
        bottom,
        o.width+14,
        18
    );


    ctx.fillStyle="#d8edf2";


    for(
        let y=bottom+30;
        y<H-65;
        y+=25
    ){

        ctx.fillRect(
            o.x+10,
            y,
            12,
            10
        );

        ctx.fillRect(
            o.x+o.width-22,
            y,
            12,
            10
        );

    }

}


/* =========================================================
   PLANE
========================================================= */

function drawPlane(){

    ctx.save();

    ctx.translate(
        plane.x,
        plane.y
    );

    ctx.rotate(
        plane.angle
    );


    ctx.fillStyle=
        "rgba(0,0,0,.15)";


    ctx.beginPath();

    ctx.ellipse(
        0,
        25,
        38,
        7,
        0,
        0,
        Math.PI*2
    );

    ctx.fill();


    /* Body */

    ctx.fillStyle="#f2f4f5";


    ctx.beginPath();

    ctx.moveTo(-38,-2);

    ctx.lineTo(-18,-13);

    ctx.lineTo(25,-10);

    ctx.lineTo(40,0);

    ctx.lineTo(25,10);

    ctx.lineTo(-18,13);

    ctx.closePath();

    ctx.fill();


    /* Nose */

    ctx.fillStyle="#e43737";

    ctx.beginPath();

    ctx.moveTo(22,-10);

    ctx.lineTo(43,0);

    ctx.lineTo(22,10);

    ctx.closePath();

    ctx.fill();


    /* Wings */

    ctx.fillStyle="#d62832";


    ctx.beginPath();

    ctx.moveTo(0,-5);

    ctx.lineTo(-26,-34);

    ctx.lineTo(-10,-29);

    ctx.lineTo(13,-6);

    ctx.closePath();

    ctx.fill();


    ctx.beginPath();

    ctx.moveTo(0,5);

    ctx.lineTo(-26,34);

    ctx.lineTo(-10,29);

    ctx.lineTo(13,6);

    ctx.closePath();

    ctx.fill();


    /* Tail */

    ctx.fillStyle="#c9252d";

    ctx.beginPath();

    ctx.moveTo(-28,-4);

    ctx.lineTo(-39,-23);

    ctx.lineTo(-27,-18);

    ctx.lineTo(-17,-5);

    ctx.closePath();

    ctx.fill();


    /* Cockpit */

    ctx.fillStyle="#42bfe6";

    ctx.beginPath();

    ctx.ellipse(
        7,
        -5,
        11,
        6,
        0,
        0,
        Math.PI*2
    );

    ctx.fill();


    /* Engine */

    ctx.fillStyle="#657178";

    ctx.beginPath();

    ctx.arc(
        -3,
        11,
        6,
        0,
        Math.PI*2
    );

    ctx.fill();


    /* Flame */

    ctx.fillStyle=
        frame%8<4
        ?
        "#ffd22b"
        :
        "#ff711c";


    ctx.beginPath();

    ctx.moveTo(-8,11);

    ctx.lineTo(-32,12);

    ctx.lineTo(-10,18);

    ctx.closePath();

    ctx.fill();


    ctx.restore();

}


/* =========================================================
   PARTICLES
========================================================= */

function drawParticles(){

    for(const p of particles){

        ctx.globalAlpha=
            Math.max(0,p.life);

        ctx.fillStyle=
            p.size>3
            ?
            "#ff7020"
            :
            "#ffd329";


        ctx.beginPath();

        ctx.arc(
            p.x,
            p.y,
            p.size,
            0,
            Math.PI*2
        );

        ctx.fill();

    }

    ctx.globalAlpha=1;

}


/* =========================================================
   GAME HUD
========================================================= */

function drawHUD(){

    ctx.fillStyle=
        "rgba(0,0,0,.23)";

    ctx.beginPath();

    ctx.roundRect(
        12,
        12,
        155,
        48,
        12
    );

    ctx.fill();


    ctx.fillStyle="#fff";

    ctx.font="bold 19px Arial";

    ctx.fillText(
        "SCORE  "+score,
        27,
        43
    );


    ctx.fillStyle=
        "rgba(0,0,0,.23)";

    ctx.beginPath();

    ctx.roundRect(
        W-155,
        12,
        143,
        48,
        12
    );

    ctx.fill();


    ctx.fillStyle="#fff";

    ctx.fillText(
        "BEST  "+best,
        W-140,
        43
    );


    /* Speed */

    const percent=
        Math.min(
            100,
            ((speed-3.2)/6.8)*100
        );


    ctx.fillStyle=
        "rgba(0,0,0,.28)";

    ctx.fillRect(
        12,
        70,
        155,
        12
    );


    ctx.fillStyle="#ffd12b";

    ctx.fillRect(
        12,
        70,
        155*percent/100,
        12
    );


    ctx.fillStyle="#fff";

    ctx.font="bold 11px Arial";

    ctx.fillText(
        "SPEED",
        174,
        80
    );

}


/* =========================================================
   CRASH
========================================================= */

function crash(){

    if(crashed)
        return;


    crashed=true;

    shake=20;


    for(let i=0;i<45;i++){

        particles.push({

            x:plane.x,

            y:plane.y,

            vx:
            (Math.random()-.5)*10,

            vy:
            (Math.random()-.5)*10,

            life:1,

            size:
            2+
            Math.random()*6

        });

    }


    if(score>best){

        best=score;

        localStorage.setItem(
            "skyPilotBest",
            best
        );

    }

}


/* =========================================================
   GAME OVER
========================================================= */

function drawGameOver(){

    if(!crashed)
        return;


    ctx.fillStyle=
        "rgba(0,0,0,.5)";

    ctx.fillRect(
        0,
        0,
        W,
        H
    );


    ctx.textAlign="center";


    ctx.fillStyle="#fff";

    ctx.font=
        "900 44px Arial";

    ctx.fillText(
        "CRASH!",
        W/2,
        H*.39
    );


    ctx.font=
        "bold 23px Arial";

    ctx.fillText(
        "SCORE  "+score,
        W/2,
        H*.47
    );


    ctx.fillText(
        "BEST  "+best,
        W/2,
        H*.52
    );


    ctx.font=
        "bold 18px Arial";

    ctx.fillText(
        "TAP TO RESTART",
        W/2,
        H*.61
    );


    ctx.textAlign="left";

}


/* =========================================================
   MAIN UPDATE
========================================================= */

function update(){

    frame++;


    updateUnity();

    updateLoading();


    if(screen==="game"){

        updateGame();

    }


    updateParticles();

}


/* =========================================================
   MAIN DRAW
========================================================= */

function draw(){

    ctx.clearRect(
        0,
        0,
        W,
        H
    );


    if(screen==="unity"){

        drawUnityScreen();

    }

    else if(screen==="loading"){

        drawLoadingScreen();

    }

    else if(screen==="menu"){

        drawMenu();

    }

    else if(screen==="game"){

        ctx.save();


        if(shake>0){

            ctx.translate(
                (Math.random()-.5)*shake,
                (Math.random()-.5)*shake
            );

            shake*=.88;

        }


        drawGameSky();

        drawGameCity();


        for(const o of obstacles){

            drawObstacle(o);

        }


        drawParticles();

        drawPlane();


        ctx.restore();


        drawHUD();

        drawGameOver();

    }

}


/* =========================================================
   LOOP
========================================================= */

function loop(){

    update();

    draw();

    requestAnimationFrame(loop);

}


resetPlane();

loop();

</script>

</body>
</html>

Game Source: Sky Pilot X

Creator: CrystalTiger19

Libraries: none

Complexity: complex (2495 lines, 30.7 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: sky-pilot-x-crystaltiger19" to link back to the original. Then publish at arcadelab.ai/publish.