🎮ArcadeLab

Flying Challenge

by BlazeGalaxy19
485 lines10.4 KB
▶ Play
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Flying Challenge</title>
<style>
*{box-sizing:border-box}
body{
 margin:0;background:#080d16;color:white;
 font-family:Arial,sans-serif;
 display:flex;justify-content:center;align-items:center;
 min-height:100vh;overflow:hidden
}
#gamebox{width:min(960px,100vw);position:relative}
canvas{
 display:block;width:100%;height:auto;
 aspect-ratio:16/9;background:#17283a;
 border:2px solid #8795a5;border-radius:10px;
 touch-action:none;cursor:pointer
}
#hud{
 position:absolute;top:14px;width:100%;
 display:flex;justify-content:space-around;
 font-weight:bold;text-shadow:0 2px 5px black;
 pointer-events:none;font-size:17px
}
#menu{
 position:absolute;inset:0;
 display:flex;flex-direction:column;
 align-items:center;justify-content:center;
 text-align:center;padding:15px;
 background:#07101bdc;border-radius:10px
}
h1{font-size:clamp(28px,6vw,48px);color:#f6d58d}
button{
 border:0;border-radius:9px;padding:13px 24px;
 background:#f1c15e;color:#171717;
 font-size:17px;font-weight:bold;cursor:pointer;
 margin:6px
}
button:hover{background:#ffe4a2}
#soundbtn{
 position:absolute;bottom:12px;right:12px;
 font-size:12px;padding:9px 12px
}
.small{font-size:12px;color:#d5dce5}
</style>
</head>
<body>

<div id="gamebox">
 <canvas id="game" width="960" height="540"></canvas>

 <div id="hud">
  <div>SCORE: <span id="score">0</span></div>
  <div>BEST: <span id="best">0</span></div>
  <div>MAP: <span id="map">FOREST</span></div>
 </div>

 <button id="soundbtn">SOUND: ON</button>

 <div id="menu">
  <h1>FLYING CHALLENGE</h1>
  <p>Fly between the pillars.<br>Avoid obstacles and beat your best score!</p>
  <button id="start">START GAME</button>
  <p class="small">CLICK / TAP / SPACE TO FLY</p>
 </div>
</div>

<script>
const canvas=document.getElementById("game");
const ctx=canvas.getContext("2d");
const W=960,H=540;

const $=id=>document.getElementById(id);

const maps=[
 {name:"FOREST",sky:"#173c43",ground:"#263c2c",pipe:"#667c58",sun:"#e4d49a"},
 {name:"DESERT",sky:"#b77949",ground:"#88512d",pipe:"#a98b67",sun:"#ffe0a0"},
 {name:"MOUNTAINS",sky:"#293e5c",ground:"#34465b",pipe:"#8998a9",sun:"#dceaff"},
 {name:"NIGHT CITY",sky:"#10162e",ground:"#20263e",pipe:"#536b91",sun:"#c6cfff"},
 {name:"OCEAN",sky:"#17647b",ground:"#174a58",pipe:"#56818a",sun:"#b3f1ef"}
];

let bird,pipes,score,best=Number(localStorage.getItem("flyBest")||0);
let playing=false,dead=false;
let speed,gap,frame=0,spawnTimer=0,lastTime=0;
let audio=null,musicOn=true,musicTimer=null;
let musicTrack=0,lastTrackChange=0,musicStep=0;

$("best").textContent=best;

function currentMap(){
 return maps[Math.floor(score/12)%maps.length];
}

function reset(){
 bird={x:230,y:260,vy:0,r:16,rotation:0};
 pipes=[];
 score=0;speed=220;gap=180;
 frame=0;spawnTimer=0;
 updateHUD();
}

function updateHUD(){
 $("score").textContent=score;
 $("best").textContent=best;
 $("map").textContent=currentMap().name;
}

// AUDIO
function initAudio(){
 if(!audio){
  audio=new(window.AudioContext||window.webkitAudioContext)();
 }
 if(audio.state==="suspended")audio.resume();
 if(musicOn&&!musicTimer){
  lastTrackChange=Date.now();
  musicTimer=setInterval(playMusic,500);
 }
}

function tone(freq,duration,type="sine",volume=.035){
 if(!audio||!musicOn)return;
 const o=audio.createOscillator();
 const g=audio.createGain();
 o.type=type;
 o.frequency.setValueAtTime(freq,audio.currentTime);
 g.gain.setValueAtTime(volume,audio.currentTime);
 g.gain.exponentialRampToValueAtTime(.001,audio.currentTime+duration);
 o.connect(g);
 g.connect(audio.destination);
 o.start();
 o.stop(audio.currentTime+duration);
}

// Four original electronic background melodies.
// A new melody starts every 150 seconds.
const songs=[
 [196,247,294,330,294,247,220,247],
 [165,196,247,294,247,196,220,196],
 [220,261,330,392,330,294,261,220],
 [147,185,220,277,247,220,185,165]
];

function playMusic(){
 if(!audio||!musicOn||!playing)return;

 if(Date.now()-lastTrackChange>=150000){
  musicTrack=(musicTrack+1)%songs.length;
  lastTrackChange=Date.now();
  musicStep=0;
 }

 const note=songs[musicTrack][musicStep%songs[musicTrack].length];
 tone(note,.42,"triangle",.018);
 if(musicStep%4===0)tone(note/2,.6,"sine",.012);
 musicStep++;
}

function jumpSound(){
 tone(440,.09,"sine",.05);
}

function scoreSound(){
 tone(750,.12,"sine",.045);
 setTimeout(()=>tone(950,.13,"sine",.035),90);
}

function deathSound(){
 if(!audio||!musicOn)return;

 // Funny falling electronic "FHAAAAAA" sound
 const o=audio.createOscillator();
 const g=audio.createGain();
 const filter=audio.createBiquadFilter();

 o.type="sawtooth";
 o.frequency.setValueAtTime(650,audio.currentTime);
 o.frequency.exponentialRampToValueAtTime(75,audio.currentTime+1.35);

 filter.type="lowpass";
 filter.frequency.value=1100;

 g.gain.setValueAtTime(.13,audio.currentTime);
 g.gain.exponentialRampToValueAtTime(.001,audio.currentTime+1.4);

 o.connect(filter);
 filter.connect(g);
 g.connect(audio.destination);

 o.start();
 o.stop(audio.currentTime+1.4);
}

function startGame(){
 initAudio();
 reset();
 playing=true;
 dead=false;
 $("menu").style.display="none";
 bird.vy=-390;
 jumpSound();
 lastTime=performance.now();
 requestAnimationFrame(loop);
}

function jump(){
 if(!playing)return;
 bird.vy=-390;
 jumpSound();
}

// INPUT: MOUSE, TOUCH AND KEYBOARD
canvas.addEventListener("pointerdown",function(e){
 e.preventDefault();
 if(!playing)return;
 jump();
});

window.addEventListener("keydown",function(e){
 if(e.code==="Space"||e.code==="ArrowUp"){
  e.preventDefault();
  if(!playing){
   if(!dead)startGame();
  }else{
   jump();
  }
 }
});

$("start").addEventListener("click",startGame);

$("soundbtn").addEventListener("click",()=>{
 musicOn=!musicOn;
 $("soundbtn").textContent="SOUND: "+(musicOn?"ON":"OFF");

 if(musicOn){
  initAudio();
 }else if(musicTimer){
  clearInterval(musicTimer);
  musicTimer=null;
 }
});

// PILLARS
function spawnPipe(){
 const center=105+Math.random()*330;
 pipes.push({x:W+10,center:center,passed:false});
}

function update(dt){
 frame++;

 // Increasing difficulty
 speed=220+Math.min(score,100)*2;
 gap=Math.max(115,180-Math.min(score,100)*.55);

 // Gravity and flying
 bird.vy+=1050*dt;
 bird.y+=bird.vy*dt;
 bird.rotation=Math.max(-.5,Math.min(.9,bird.vy/500));

 spawnTimer+=dt;
 if(spawnTimer>1.65){
  spawnPipe();
  spawnTimer=0;
 }

 for(const p of pipes){
  p.x-=speed*dt;

  const top=p.center-gap/2;
  const bottom=p.center+gap/2;

  // Collision detection
  if(
   bird.x+bird.r>p.x &&
   bird.x-bird.r<p.x+85 &&
   (bird.y-bird.r<top||bird.y+bird.r>bottom)
  ){
   endGame();
   return;
  }

  // Score
  if(!p.passed&&p.x+85<bird.x){
   p.passed=true;
   score++;

   if(score>best){
    best=score;
    localStorage.setItem("flyBest",best);
   }

   scoreSound();
   updateHUD();
  }
 }

 pipes=pipes.filter(p=>p.x>-120);

 if(bird.y-bird.r<0||bird.y+bird.r>H-35){
  endGame();
 }
}

// BACKGROUND
function drawBackground(){
 const m=currentMap();

 const gradient=ctx.createLinearGradient(0,0,0,H);
 gradient.addColorStop(0,m.sky);
 gradient.addColorStop(1,m.ground);
 ctx.fillStyle=gradient;
 ctx.fillRect(0,0,W,H);

 // Sun / moon
 ctx.fillStyle=m.sun;
 ctx.globalAlpha=.7;
 ctx.beginPath();
 ctx.arc(770,100,42,0,Math.PI*2);
 ctx.fill();
 ctx.globalAlpha=1;

 // Distant mountains
 ctx.fillStyle=m.ground;
 for(let i=0;i<9;i++){
  let x=(i*150-frame*.12)%1100-70;
  ctx.beginPath();
  ctx.moveTo(x,H-35);
  ctx.lineTo(x+70,260+(i%3)*35);
  ctx.lineTo(x+160,H-35);
  ctx.fill();
 }

 // Ground
 ctx.fillStyle=m.ground;
 ctx.fillRect(0,H-35,W,35);
 ctx.fillStyle=m.sun;
 ctx.fillRect(0,H-35,W,3);
}

// PILLARS
function drawPipes(){
 const m=currentMap();

 for(const p of pipes){
  const top=p.center-gap/2;
  const bottom=p.center+gap/2;

  ctx.fillStyle=m.pipe;
  ctx.strokeStyle="#101923";
  ctx.lineWidth=4;

  // Upper pillar
  ctx.fillRect(p.x,0,85,top);
  ctx.strokeRect(p.x,0,85,top);
  ctx.fillRect(p.x-8,top-18,101,18);
  ctx.strokeRect(p.x-8,top-18,101,18);

  // Lower pillar
  ctx.fillRect(p.x,bottom,85,H-bottom);
  ctx.strokeRect(p.x,bottom,85,H-bottom);
  ctx.fillRect(p.x-8,bottom,101,18);
  ctx.strokeRect(p.x-8,bottom,101,18);

  // Pillar details
  ctx.strokeStyle="#ffffff35";
  ctx.lineWidth=2;

  for(let y=55;y<top-20;y+=65){
   ctx.beginPath();
   ctx.moveTo(p.x+10,y);
   ctx.lineTo(p.x+75,y);
   ctx.stroke();
  }

  for(let y=bottom+45;y<H-40;y+=65){
   ctx.beginPath();
   ctx.moveTo(p.x+10,y);
   ctx.lineTo(p.x+75,y);
   ctx.stroke();
  }
 }
}

// BIRD
function drawBird(){
 ctx.save();
 ctx.translate(bird.x,bird.y);
 ctx.rotate(bird.rotation);

 const g=ctx.createLinearGradient(-20,-15,20,15);
 g.addColorStop(0,"#f5e5c6");
 g.addColorStop(.5,"#aaa99f");
 g.addColorStop(1,"#555e68");

 ctx.fillStyle=g;
 ctx.beginPath();
 ctx.ellipse(0,0,22,15,0,0,Math.PI*2);
 ctx.fill();

 // Wing
 ctx.fillStyle="#727c83";
 ctx.beginPath();
 ctx.ellipse(-5,4,13,7,Math.sin(frame*.22)*.3,0,Math.PI*2);
 ctx.fill();

 // Beak
 ctx.fillStyle="#d99b47";
 ctx.beginPath();
 ctx.moveTo(17,-3);
 ctx.lineTo(31,2);
 ctx.lineTo(17,6);
 ctx.fill();

 // Eye
 ctx.fillStyle="white";
 ctx.beginPath();
 ctx.arc(10,-7,4,0,Math.PI*2);
 ctx.fill();

 ctx.fillStyle="#111";
 ctx.beginPath();
 ctx.arc(11,-7,2,0,Math.PI*2);
 ctx.fill();

 ctx.restore();
}

function draw(){
 drawBackground();
 drawPipes();
 drawBird();

 ctx.fillStyle="white";
 ctx.textAlign="center";
 ctx.font="bold 22px Arial";
 ctx.fillText(score,W/2,95);
}

// GAME LOOP
function loop(time){
 if(!playing)return;

 const dt=Math.min((time-lastTime)/1000,.04);
 lastTime=time;

 update(dt);
 if(!playing)return;

 draw();
 requestAnimationFrame(loop);
}

// GAME OVER
function endGame(){
 if(!playing)return;

 playing=false;
 dead=true;
 deathSound();
 draw();

 $("menu").style.display="flex";
 $("menu").innerHTML=`
  <h1>GAME OVER!</h1>
  <h2>FHAAAAAAAA!</h2>
  <p>YOUR SCORE: ${score}<br>BEST SCORE: ${best}</p>
  <button id="restart">PLAY AGAIN</button>
  <p class="small">Press SPACE to restart</p>
 `;

 $("restart").addEventListener("click",startGame);
}

reset();
draw();
</script>
</body>
</html>

Game Source: Flying Challenge

Creator: BlazeGalaxy19

Libraries: none

Complexity: complex (485 lines, 10.4 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: flying-challenge-blazegalaxy19" to link back to the original. Then publish at arcadelab.ai/publish.