Flying Challenge
by BlazeGalaxy19485 lines10.4 KB
<!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.