🎮ArcadeLab

FPS Arena - Map 2

by NeonGamer52
2148 lines34.6 KB
▶ Play
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>FPS Arena - Map 2</title>

<style>
html,body{
    margin:0;
    width:100%;
    height:100%;
    overflow:hidden;
    background:#000;
    font-family:Arial,sans-serif;
}

#game{
    position:fixed;
    inset:0;
    width:100%;
    height:100%;
}

#mini{
    position:fixed;
    right:15px;
    top:15px;
    width:220px;
    height:180px;
    border:3px solid white;
    background:#111;
    z-index:20;
}

#hud{
    position:fixed;
    left:15px;
    top:15px;
    z-index:20;
    color:white;
    background:rgba(0,0,0,.82);
    padding:12px;
    border-radius:10px;
    line-height:1.5;
}

.bar{
    width:180px;
    height:13px;
    background:#222;
    border:1px solid white;
    border-radius:5px;
    overflow:hidden;
}

.health{
    width:100%;
    height:100%;
    background:#19df45;
}

#crosshair{
    position:fixed;
    left:50%;
    top:50%;
    transform:translate(-50%,-50%);
    color:white;
    font-size:30px;
    z-index:10;
    pointer-events:none;
}

#message{
    position:fixed;
    left:50%;
    top:18%;
    transform:translateX(-50%);
    color:white;
    font-size:32px;
    font-weight:bold;
    text-align:center;
    z-index:30;
    text-shadow:0 3px 8px black;
}

#weapon{
    position:fixed;
    bottom:0;
    left:50%;
    transform:translateX(-50%);
    z-index:10;
}

#weaponName{
    position:fixed;
    bottom:90px;
    left:50%;
    transform:translateX(-50%);
    color:white;
    font-weight:bold;
    z-index:10;
}

#menu,#pause,#death{
    position:fixed;
    inset:0;
    background:rgba(0,0,0,.94);
    color:white;
    z-index:100;
    display:flex;
    align-items:center;
    justify-content:center;
    text-align:center;
}

#pause,#death{
    display:none;
}

.panel{
    width:430px;
    max-width:85%;
    background:#18182b;
    padding:30px;
    border-radius:16px;
    border:2px solid #754cff;
}

button{
    padding:11px 17px;
    margin:4px;
    border:0;
    border-radius:7px;
    background:#7045ff;
    color:white;
    font-size:16px;
    cursor:pointer;
}

button:hover{
    background:#936cff;
}

input{
    padding:12px;
    width:80%;
    border:0;
    border-radius:7px;
    background:#292940;
    color:white;
    font-size:16px;
}

#bossBar{
    display:none;
    position:fixed;
    bottom:20px;
    left:50%;
    transform:translateX(-50%);
    width:500px;
    max-width:70%;
    z-index:30;
}

#bossTitle{
    color:red;
    text-align:center;
    font-size:23px;
    font-weight:bold;
}

#bossOuter{
    height:25px;
    background:#222;
    border:2px solid white;
    border-radius:8px;
    overflow:hidden;
}

#bossHealth{
    width:100%;
    height:100%;
    background:linear-gradient(90deg,red,orange);
}
</style>
</head>

<body>

<canvas id="game"></canvas>
<canvas id="mini" width="220" height="180"></canvas>

<div id="crosshair">+</div>
<div id="message"></div>

<div id="weaponName"></div>
<div id="weapon"></div>

<div id="hud">

👤 <span id="playerName">Player</span><br>

❤️ Health: <span id="hp">100</span>/100

<div class="bar">
<div id="playerHealth" class="health"></div>
</div>

🔫 Ammo: <span id="ammo">12 / 60</span><br>
🏆 Score: <span id="score">0</span><br>
⭐ Level: <span id="level">1</span><br>
🤖 Bots: <span id="bots">0</span><br>
🗺️ Map: <span id="mapNumber">1</span>

<br>

<button onclick="lockMouse()">🖱️ LOCK MOUSE</button>
<button onclick="togglePause()">⏸️ PAUSE</button>
<button onclick="fullScreen()">⛶ FULLSCREEN</button>

<br><br>

WASD = Move<br>
Mouse = Aim<br>
Click = Shoot<br>
1 = Pistol<br>
2 = Shotgun<br>
3 = Rifle<br>
R = Reload<br>
P = Pause<br>
F = Fullscreen

</div>


<div id="menu">
<div class="panel">

<h1>🎯 FPS ARENA</h1>

<p>
LEVELS 1–2: MAP 1<br>
LEVELS 3–8: MAP 2<br>
LEVEL 8: 👹 MEGA BOSS
</p>

<input id="nameInput" placeholder="Player name">

<br><br>

<button onclick="startGame()">🎮 START GAME</button>
<button onclick="fullScreen()">⛶ FULLSCREEN</button>

</div>
</div>


<div id="pause">
<div class="panel">

<h1>⏸️ PAUSED</h1>

<button onclick="togglePause()">▶️ RESUME</button>

</div>
</div>


<div id="death">
<div class="panel">

<h1>💀 YOU DIED</h1>

<p id="deathText"></p>

<button onclick="respawn()">🔄 RESPAWN</button>

</div>
</div>


<div id="bossBar">

<div id="bossTitle">
👹 MEGA BOSS
</div>

<div id="bossOuter">
<div id="bossHealth"></div>
</div>

</div>


<script>

/* =====================================================
   CANVAS
===================================================== */

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

const mini=document.getElementById("mini");
const mctx=mini.getContext("2d");

function resize(){
    canvas.width=innerWidth;
    canvas.height=innerHeight;
}

resize();
addEventListener("resize",resize);


/* =====================================================
   MAP 1
===================================================== */

const MAP1=[

"111111111111111111111111",
"100000000000000000000001",
"100011111000000111110001",
"100010001000000100010001",
"100010001000000100010001",
"100000001000000100000001",
"101111111000000111111101",
"100000001000000100000001",
"100000001000000100000001",
"100000001000000100000001",
"100000001000000100000001",
"100000000000000000000001",
"100000001000000100000001",
"100000001000000100000001",
"100000001000000100000001",
"100111110000000011111001",
"100100010000000010001001",
"100100010000000010001001",
"100000000000000000000001",
"111111111111111111111111"

];


/* =====================================================
   MAP 2
   THE MIDDLE IS FILLED WITH WALLS.
   
   IMPORTANT:
   There are OPENINGS at the LEFT and RIGHT
   so the player can go around the center.
===================================================== */

const MAP2=[

"111111111111111111111111",
"100000000000000000000001",
"101111111011111111110101",
"101000001010000000010101",
"101011101010111111010101",
"101010001010100001010101",
"101010001010100001010101",
"101010000000000001010101",

/* LEFT + RIGHT OPEN AROUND CENTER */

"100000000011111100000001",
"100000000011111100000001",
"100000000011111100000001",
"100000000011111100000001",

"101010000000000001010101",
"101010001010100001010101",
"101011101010111111010101",
"101000001010000000010101",
"101111111011111111110101",
"100000000000000000000001",
"100000000000000000000001",
"111111111111111111111111"

];


let currentMap=MAP1;


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

const player={
    x:2.5,
    y:9.5,
    angle:0,
    health:100,
    maxHealth:100,
    speed:3
};


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

let playing=false;
let paused=false;
let level=1;
let score=0;

let enemies=[];
let pickups=[];

let keys={};

let lastTime=0;
let changing=false;


/* =====================================================
   WEAPONS
===================================================== */

const weapons=[

{
    name:"PISTOL",
    damage:25,
    maxAmmo:12,
    ammo:12,
    reserve:60,
    rate:350,
    range:12
},

{
    name:"SHOTGUN",
    damage:75,
    maxAmmo:6,
    ammo:6,
    reserve:30,
    rate:800,
    range:8
},

{
    name:"RIFLE",
    damage:15,
    maxAmmo:30,
    ammo:30,
    reserve:120,
    rate:110,
    range:18
}

];

let weapon=0;
let lastShot=0;
let reloading=false;


/* =====================================================
   KEYBOARD
===================================================== */

addEventListener("keydown",e=>{

    keys[e.key.toLowerCase()]=true;

    if(e.key==="1")changeWeapon(0);
    if(e.key==="2")changeWeapon(1);
    if(e.key==="3")changeWeapon(2);

    if(e.key.toLowerCase()==="r")reload();
    if(e.key.toLowerCase()==="p")togglePause();
    if(e.key.toLowerCase()==="f")fullScreen();

});

addEventListener("keyup",e=>{
    keys[e.key.toLowerCase()]=false;
});


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

function wall(x,y){

    const mx=Math.floor(x);
    const my=Math.floor(y);

    if(
        mx<0 ||
        my<0 ||
        mx>=24 ||
        my>=20
    )
        return true;

    return currentMap[my][mx]==="1";
}


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

function startGame(){

    document.getElementById("playerName").textContent=
        document.getElementById("nameInput").value ||
        "Player";

    document.getElementById("menu").style.display="none";

    level=1;
    score=0;
    currentMap=MAP1;

    player.x=2.5;
    player.y=9.5;
    player.angle=0;
    player.health=100;

    playing=true;
    paused=false;
    changing=false;

    resetWeapons();

    spawnLevel();

    updateWeapon();

    message("LEVEL 1<br>MAP 1");

    lockMouse();
}


/* =====================================================
   WEAPONS RESET
===================================================== */

function resetWeapons(){

    weapons.forEach(w=>{
        w.ammo=w.maxAmmo;
    });

}


/* =====================================================
   SPAWN LEVEL
===================================================== */

function spawnLevel(){

    enemies=[];
    pickups=[];

    document.getElementById(
        "bossBar"
    ).style.display="none";


    /* SWITCH MAP AFTER LEVEL 2 */

    if(level===3){

        currentMap=MAP2;

        /*
           This is an OPEN AREA on Map 2.
           It is NOT inside the middle wall.
        */

        player.x=2.5;
        player.y=8.5;
        player.angle=0;

        message(
            "🗺️ MAP 2<br>LEVEL 3"
        );

    }


    /* LEVEL 8 BOSS */

    if(level===8){

        spawnBoss();
        return;

    }


    /* FIND OPEN SPACES */

    let spots=[];

    for(let y=1;y<19;y++){

        for(let x=1;x<23;x++){

            if(!wall(x+.5,y+.5)){

                if(
                    Math.hypot(
                        x+.5-player.x,
                        y+.5-player.y
                    )>6
                ){

                    spots.push([
                        x+.5,
                        y+.5
                    ]);

                }

            }

        }

    }


    spots.sort(
        ()=>Math.random()-.5
    );


    let amount=
        Math.min(
            4+level*2,
            18
        );


    for(
        let i=0;
        i<amount && i<spots.length;
        i++
    ){

        const p=spots[i];

        enemies.push({

            x:p[0],
            y:p[1],

            health:100+level*15,
            maxHealth:100+level*15,

            speed:.5+level*.035,

            attack:0,

            alive:true,
            boss:false

        });

    }


    /* PICKUPS */

    spawnPickups();

    updateHUD();

}


/* =====================================================
   PICKUPS
===================================================== */

function spawnPickups(){

    let spots=[];

    for(let y=1;y<19;y++){

        for(let x=1;x<23;x++){

            if(!wall(x+.5,y+.5)){

                if(
                    Math.hypot(
                        x+.5-player.x,
                        y+.5-player.y
                    )>3
                ){

                    spots.push([
                        x+.5,
                        y+.5
                    ]);

                }

            }

        }

    }

    spots.sort(
        ()=>Math.random()-.5
    );


    for(
        let i=0;
        i<Math.min(4,spots.length);
        i++
    ){

        pickups.push({
            x:spots[i][0],
            y:spots[i][1],
            type:"ammo"
        });

    }


    for(
        let i=4;
        i<Math.min(7,spots.length);
        i++
    ){

        pickups.push({
            x:spots[i][0],
            y:spots[i][1],
            type:"health"
        });

    }

}


/* =====================================================
   BOSS
===================================================== */

function spawnBoss(){

    enemies=[{

        /*
           BOSS STARTS IN THE OPEN
           RIGHT SIDE OF MAP 2.
        */

        x:20.5,
        y:8.5,

        health:1800,
        maxHealth:1800,

        speed:.55,
        attack:0,

        alive:true,
        boss:true

    }];


    document.getElementById(
        "bossBar"
    ).style.display="block";


    message(
        "👹 LEVEL 8<br>MEGA BOSS!"
    );

}


/* =====================================================
   PLAYER MOVEMENT
===================================================== */

function updatePlayer(dt){

    if(!playing||paused)
        return;


    let forward=0;
    let strafe=0;

    if(keys.w)forward++;
    if(keys.s)forward--;

    if(keys.a)strafe--;
    if(keys.d)strafe++;


    if(forward&&strafe){

        forward*=.707;
        strafe*=.707;

    }


    const dx=
        Math.cos(player.angle)*
        forward*
        player.speed*
        dt+

        Math.cos(
            player.angle+Math.PI/2
        )*
        strafe*
        player.speed*
        dt;


    const dy=
        Math.sin(player.angle)*
        forward*
        player.speed*
        dt+

        Math.sin(
            player.angle+Math.PI/2
        )*
        strafe*
        player.speed*
        dt;


    if(!wall(player.x+dx,player.y))
        player.x+=dx;

    if(!wall(player.x,player.y+dy))
        player.y+=dy;


    /* PICKUPS */

    pickups.forEach(p=>{

        if(
            Math.hypot(
                p.x-player.x,
                p.y-player.y
            )<.7
        ){

            if(p.type==="health"){

                player.health=
                    Math.min(
                        player.maxHealth,
                        player.health+35
                    );

                message("❤️ HEALTH +35");

            }


            if(p.type==="ammo"){

                weapons.forEach(w=>{
                    w.reserve+=20;
                });

                message("📦 AMMO +20");

            }

            p.collected=true;

        }

    });


    pickups=
        pickups.filter(
            p=>!p.collected
        );

}


/* =====================================================
   MOUSE LOCK
===================================================== */

canvas.addEventListener("click",()=>{

    if(
        playing &&
        !paused
    ){

        if(
            document.pointerLockElement!==canvas
        ){

            lockMouse();

        }else{

            shoot();

        }

    }

});


function lockMouse(){

    if(
        playing &&
        !paused
    ){

        canvas.requestPointerLock();

    }

}


document.addEventListener(
    "mousemove",
    e=>{

        if(
            document.pointerLockElement===canvas &&
            playing &&
            !paused
        ){

            player.angle+=
                e.movementX*.0025;

        }

    }
);


/* =====================================================
   PAUSE
===================================================== */

function togglePause(){

    if(!playing)
        return;

    paused=!paused;

    document.getElementById(
        "pause"
    ).style.display=
        paused?"flex":"none";


    if(paused){

        if(document.pointerLockElement)
            document.exitPointerLock();

    }else{

        lockMouse();

    }

}


/* =====================================================
   FULLSCREEN
===================================================== */

function fullScreen(){

    if(!document.fullscreenElement){

        document.documentElement
        .requestFullscreen()
        .catch(()=>{});

    }else{

        document.exitFullscreen();

    }

}


/* =====================================================
   SHOOT
===================================================== */

function shoot(){

    if(reloading)
        return;


    const w=weapons[weapon];

    const now=performance.now();


    if(now-lastShot<w.rate)
        return;


    if(w.ammo<=0){

        reload();
        return;

    }


    lastShot=now;
    w.ammo--;


    let target=null;
    let closest=999;


    enemies.forEach(enemy=>{

        if(!enemy.alive)
            return;


        const dx=
            enemy.x-player.x;

        const dy=
            enemy.y-player.y;

        const distance=
            Math.hypot(dx,dy);


        if(distance>w.range)
            return;


        const angle=
            Math.atan2(dy,dx);


        const difference=
            Math.atan2(
                Math.sin(angle-player.angle),
                Math.cos(angle-player.angle)
            );


        if(
            Math.abs(difference)<.14 &&
            distance<closest
        ){

            target=enemy;
            closest=distance;

        }

    });


    if(target){

        target.health-=
            target.boss
            ?w.damage*.75
            :w.damage;


        if(target.health<=0){

            target.health=0;
            target.alive=false;


            if(target.boss){

                score+=5000;

                document.getElementById(
                    "bossBar"
                ).style.display="none";

                message("🏆 BOSS DEFEATED!");

                setTimeout(()=>{

                    level++;
                    spawnLevel();

                },2000);

            }else{

                score+=100;
                checkLevel();

            }

        }

    }


    updateHUD();

}


/* =====================================================
   RELOAD
===================================================== */

function reload(){

    if(reloading)
        return;


    const w=weapons[weapon];


    if(
        w.ammo>=w.maxAmmo ||
        w.reserve<=0
    )
        return;


    reloading=true;

    message("🔄 RELOADING");


    setTimeout(()=>{

        const need=
            w.maxAmmo-w.ammo;

        const amount=
            Math.min(
                need,
                w.reserve
            );

        w.ammo+=amount;
        w.reserve-=amount;

        reloading=false;

        updateHUD();

    },700);

}


/* =====================================================
   WEAPON DISPLAY
===================================================== */

function changeWeapon(i){

    weapon=i;
    updateWeapon();
    updateHUD();

}


function updateWeapon(){

    const w=
        document.getElementById("weapon");

    document.getElementById(
        "weaponName"
    ).textContent=
        weapons[weapon].name;


    if(weapon===0){

        /* PISTOL */

        w.innerHTML=`

        <div style="
        position:relative;
        width:110px;
        height:80px">

            <div style="
            position:absolute;
            left:10px;
            top:20px;
            width:75px;
            height:18px;
            background:#202020;
            border:3px solid #888;
            border-radius:4px">
            </div>

            <div style="
            position:absolute;
            left:35px;
            top:36px;
            width:25px;
            height:40px;
            background:#444;
            transform:rotate(12deg)">
            </div>

        </div>

        `;

    }


    if(weapon===1){

        /* SHOTGUN */

        w.innerHTML=`

        <div style="
        position:relative;
        width:240px;
        height:80px">

            <div style="
            position:absolute;
            left:5px;
            top:22px;
            width:200px;
            height:18px;
            background:#111;
            border:3px solid #777">
            </div>

            <div style="
            position:absolute;
            left:75px;
            top:40px;
            width:90px;
            height:24px;
            background:#74452b;
            border-radius:5px">
            </div>

        </div>

        `;

    }


    if(weapon===2){

        /* RIFLE */

        w.innerHTML=`

        <div style="
        position:relative;
        width:310px;
        height:90px">

            <div style="
            position:absolute;
            left:10px;
            top:32px;
            width:220px;
            height:15px;
            background:#222;
            border:3px solid #777">
            </div>

            <div style="
            position:absolute;
            left:75px;
            top:47px;
            width:65px;
            height:40px;
            background:#202020;
            transform:rotate(10deg)">
            </div>

            <div style="
            position:absolute;
            left:95px;
            top:10px;
            width:90px;
            height:8px;
            background:#111;
            border:2px solid #777">
            </div>

            <div style="
            position:absolute;
            right:0;
            top:35px;
            width:65px;
            height:8px;
            background:#111">
            </div>

        </div>

        `;

    }

}


/* =====================================================
   BOT AI
===================================================== */

function updateEnemies(dt){

    if(!playing||paused)
        return;


    enemies.forEach(enemy=>{

        if(!enemy.alive)
            return;


        const dx=
            player.x-enemy.x;

        const dy=
            player.y-enemy.y;

        const distance=
            Math.hypot(dx,dy);


        if(distance>1.3){

            const nx=
                enemy.x+
                dx/distance*
                enemy.speed*
                dt;

            const ny=
                enemy.y+
                dy/distance*
                enemy.speed*
                dt;


            if(!wall(nx,enemy.y))
                enemy.x=nx;

            if(!wall(enemy.x,ny))
                enemy.y=ny;

        }


        if(distance<1.5){

            enemy.attack-=dt;

            if(enemy.attack<=0){

                enemy.attack=
                    enemy.boss?.6:1;

                damagePlayer(
                    enemy.boss
                    ?18
                    :6+level
                );

            }

        }

    });

}


/* =====================================================
   PLAYER DAMAGE
===================================================== */

function damagePlayer(amount){

    player.health-=amount;

    if(player.health<=0){

        player.health=0;
        die();

    }

}


/* =====================================================
   DEATH
===================================================== */

function die(){

    playing=false;

    if(document.pointerLockElement)
        document.exitPointerLock();

    document.getElementById(
        "deathText"
    ).textContent=
        "Level "+level+
        " • Score "+score;

    document.getElementById(
        "death"
    ).style.display="flex";

}


/* =====================================================
   RESPAWN
===================================================== */

function respawn(){

    document.getElementById(
        "death"
    ).style.display="none";


    player.health=100;


    if(currentMap===MAP1){

        player.x=2.5;
        player.y=9.5;

    }else{

        player.x=2.5;
        player.y=8.5;

    }


    resetWeapons();

    playing=true;

    spawnLevel();

    lockMouse();

}


/* =====================================================
   LEVEL COMPLETE
===================================================== */

function checkLevel(){

    if(changing)
        return;


    if(
        enemies.some(
            e=>e.alive
        )
    )
        return;


    changing=true;

    message("⭐ LEVEL COMPLETE!");


    setTimeout(()=>{

        level++;
        changing=false;

        spawnLevel();

    },1500);

}


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

function updateHUD(){

    const w=weapons[weapon];

    document.getElementById("hp")
        .textContent=
        Math.ceil(player.health);

    document.getElementById(
        "playerHealth"
    ).style.width=
        Math.max(
            0,
            player.health
        )+"%";

    document.getElementById("ammo")
        .textContent=
        w.ammo+
        " / "+
        w.reserve;

    document.getElementById("score")
        .textContent=score;

    document.getElementById("level")
        .textContent=level;

    document.getElementById("bots")
        .textContent=
        enemies.filter(
            e=>e.alive
        ).length;

    document.getElementById("mapNumber")
        .textContent=
        currentMap===MAP1
        ?1
        :2;


    const boss=
        enemies.find(
            e=>e.boss&&e.alive
        );


    if(boss){

        document.getElementById(
            "bossHealth"
        ).style.width=
            (
                boss.health/
                boss.maxHealth*
                100
            )+"%";

    }

}


/* =====================================================
   MESSAGE
===================================================== */

let messageTimer;

function message(text){

    document.getElementById(
        "message"
    ).innerHTML=text;

    clearTimeout(messageTimer);

    messageTimer=setTimeout(()=>{

        document.getElementById(
            "message"
        ).innerHTML="";

    },2500);

}


/* =====================================================
   RAYCAST
===================================================== */

function ray(angle){

    let distance=0;

    while(distance<30){

        distance+=.03;

        const x=
            player.x+
            Math.cos(angle)*
            distance;

        const y=
            player.y+
            Math.sin(angle)*
            distance;

        if(wall(x,y))
            return distance;

    }

    return 30;

}


/* =====================================================
   3D RENDER
===================================================== */

const FOV=Math.PI/3;

function render(){

    const W=canvas.width;
    const H=canvas.height;


    ctx.fillStyle=
        currentMap===MAP1
        ?" #182845".trim()
        :"#082938";

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


    ctx.fillStyle=
        currentMap===MAP1
        ?" #342517".trim()
        :"#123035";

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


    const rays=
        Math.min(W,700);

    const column=W/rays;


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

        const camera=
            i/rays-.5;

        const angle=
            player.angle+
            camera*FOV;

        let distance=
            ray(angle);

        distance*=
            Math.cos(
                camera*FOV
            );

        const height=
            Math.min(
                H,
                H/(distance*.8)
            );

        const top=
            (H-height)/2;

        const brightness=
            Math.max(
                25,
                200-distance*10
            );


        ctx.fillStyle=
            currentMap===MAP1
            ?
            `rgb(
                ${brightness},
                ${brightness*.7},
                ${brightness*.45}
            )`
            :
            `rgb(
                ${brightness*.25},
                ${brightness*.6},
                ${brightness}
            )`;


        ctx.fillRect(
            i*column,
            top,
            column+1,
            height
        );

    }


    /* BOTS */

    enemies.forEach(enemy=>{

        if(!enemy.alive)
            return;


        const dx=
            enemy.x-player.x;

        const dy=
            enemy.y-player.y;

        const distance=
            Math.hypot(dx,dy);

        const angle=
            Math.atan2(dy,dx);

        const difference=
            Math.atan2(
                Math.sin(angle-player.angle),
                Math.cos(angle-player.angle)
            );


        if(
            Math.abs(difference)>FOV/2
        )
            return;


        if(
            distance>ray(angle)
        )
            return;


        const x=
            W/2+
            difference/(FOV/2)*
            W/2;

        let size=
            Math.min(
                500,
                H/(distance*.8)
            );


        if(enemy.boss)
            size*=2;


        ctx.textAlign="center";
        ctx.font=size+"px Arial";

        ctx.fillText(
            enemy.boss
            ?"👹"
            :"🤖",
            x,
            H/2+size*.35
        );


        /* BOT HEALTH BAR */

        const barWidth=
            Math.max(
                50,
                size*.8
            );

        const barX=
            x-barWidth/2;

        const barY=
            H/2-size*.6;


        ctx.fillStyle="#222";

        ctx.fillRect(
            barX,
            barY,
            barWidth,
            9
        );


        ctx.fillStyle=
            enemy.boss
            ?"orange"
            :"red";


        ctx.fillRect(
            barX,
            barY,
            barWidth*
            Math.max(
                0,
                enemy.health/
                enemy.maxHealth
            ),
            9
        );


        ctx.strokeStyle="white";

        ctx.strokeRect(
            barX,
            barY,
            barWidth,
            9
        );

    });


    /* PICKUPS */

    pickups.forEach(p=>{

        const dx=
            p.x-player.x;

        const dy=
            p.y-player.y;

        const distance=
            Math.hypot(dx,dy);

        const angle=
            Math.atan2(dy,dx);

        const difference=
            Math.atan2(
                Math.sin(angle-player.angle),
                Math.cos(angle-player.angle)
            );


        if(
            Math.abs(difference)>FOV/2 ||
            distance>ray(angle)
        )
            return;


        const x=
            W/2+
            difference/(FOV/2)*
            W/2;

        const size=
            Math.min(
                80,
                H/(distance*1.5)
            );


        ctx.font=size+"px Arial";
        ctx.textAlign="center";

        ctx.fillText(
            p.type==="health"
            ?"❤️"
            :"📦",
            x,
            H/2+size*.3
        );

    });

}


/* =====================================================
   MINIMAP
===================================================== */

function drawMini(){

    const W=220;
    const H=180;

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


    const sx=W/24;
    const sy=H/20;


    for(
        let y=0;
        y<20;
        y++
    ){

        for(
            let x=0;
            x<24;
            x++
        ){

            if(
                currentMap[y][x]==="1"
            ){

                mctx.fillStyle=
                    currentMap===MAP2
                    ?" #17536a".trim()
                    :"#555";

            }else{

                mctx.fillStyle=
                    currentMap===MAP2
                    ?" #06151c".trim()
                    :"#111";

            }


            mctx.fillRect(
                x*sx,
                y*sy,
                sx+1,
                sy+1
            );

        }

    }


    /* PICKUPS */

    pickups.forEach(p=>{

        mctx.fillStyle=
            p.type==="health"
            ?"lime"
            :"yellow";

        mctx.fillRect(
            p.x*sx-3,
            p.y*sy-3,
            6,
            6
        );

    });


    /* RED BOTS */

    enemies.forEach(e=>{

        if(!e.alive)
            return;

        mctx.fillStyle=
            e.boss
            ?"orange"
            :"red";

        mctx.beginPath();

        mctx.arc(
            e.x*sx,
            e.y*sy,
            e.boss?8:5,
            0,
            Math.PI*2
        );

        mctx.fill();

    });


    /* BLUE PLAYER */

    mctx.fillStyle="#168cff";

    mctx.beginPath();

    mctx.arc(
        player.x*sx,
        player.y*sy,
        6,
        0,
        Math.PI*2
    );

    mctx.fill();


    /* PLAYER DIRECTION */

    mctx.strokeStyle="#168cff";
    mctx.lineWidth=2;

    mctx.beginPath();

    mctx.moveTo(
        player.x*sx,
        player.y*sy
    );

    mctx.lineTo(
        (
            player.x+
            Math.cos(player.angle)*1.5
        )*sx,

        (
            player.y+
            Math.sin(player.angle)*1.5
        )*sy
    );

    mctx.stroke();

}


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

function loop(time){

    if(!lastTime)
        lastTime=time;

    let dt=
        (time-lastTime)/1000;

    lastTime=time;

    if(dt>.05)
        dt=.05;


    updatePlayer(dt);
    updateEnemies(dt);

    updateHUD();

    render();
    drawMini();

    requestAnimationFrame(loop);

}


updateWeapon();

requestAnimationFrame(loop);

</script>

</body>
</html> 

Game Source: FPS Arena - Map 2

Creator: NeonGamer52

Libraries: none

Complexity: complex (2148 lines, 34.6 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: fps-arena-map-2-neongamer52" to link back to the original. Then publish at arcadelab.ai/publish.