RUN! Monster Chase
by BraveMaker51568 lines24.8 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RUN! Monster Chase</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Creepster&family=Rajdhani:wght@400;700&display=swap');
* { margin:0; padding:0; box-sizing:border-box; }
body { background:#000; overflow:hidden; font-family:'Rajdhani',sans-serif; user-select:none; }
canvas { display:block; }
#ui {
position:fixed; top:0; left:0; right:0; z-index:10;
display:flex; justify-content:space-between; align-items:flex-start;
padding:16px 24px; pointer-events:none;
background:linear-gradient(to bottom, rgba(0,0,0,0.7), transparent);
}
#score-display { font-family:'Creepster',cursive; color:#ff3366; font-size:2rem; text-shadow:0 0 20px #ff003380; letter-spacing:2px; }
#lives-display { font-family:'Creepster',cursive; color:#cc44ff; font-size:1.8rem; }
#minimap { position:fixed; bottom:16px; right:16px; z-index:10; border:1px solid #44004480; border-radius:4px; opacity:0.75; }
#rage-bar-wrap { position:fixed; bottom:16px; left:50%; transform:translateX(-50%); z-index:10; display:flex; flex-direction:column; align-items:center; gap:4px; pointer-events:none; }
#rage-label { color:#ff3344; font-size:0.75rem; letter-spacing:3px; text-transform:uppercase; opacity:0.8; }
#rage-bar-bg { width:180px; height:8px; background:#1a001a; border-radius:4px; overflow:hidden; border:1px solid #44002280; }
#rage-bar { height:100%; width:0%; background:linear-gradient(90deg,#440000,#ff0000); border-radius:4px; transition:width 0.4s; box-shadow:0 0 8px #ff000080; }
#overlay { position:fixed; inset:0; z-index:20; display:flex; flex-direction:column; align-items:center; justify-content:center; background:radial-gradient(ellipse, #12000a 0%, #030003 100%); transition:opacity 0.5s; }
#overlay h1 { font-family:'Creepster',cursive; font-size:clamp(3rem,9vw,8rem); color:#cc0022; text-shadow:0 0 40px #ff000090, 0 0 80px #ff000040, 0 5px 0 #600010; letter-spacing:8px; animation:flicker 4s infinite; margin-bottom:8px; }
#overlay p { color:#aa66aa; font-size:1.2rem; margin-bottom:36px; letter-spacing:3px; text-transform:uppercase; opacity:0.8; }
#start-btn { font-family:'Creepster',cursive; font-size:1.8rem; letter-spacing:4px; color:#fff; background:linear-gradient(135deg,#770099,#bb0033); border:none; padding:14px 48px; border-radius:4px; cursor:pointer; box-shadow:0 0 30px #aa003380; transition:transform 0.1s, box-shadow 0.2s; pointer-events:all; }
#start-btn:hover { transform:scale(1.05); box-shadow:0 0 50px #cc004480; }
#start-btn:active { transform:scale(0.97); }
#monster-preview { margin-bottom:24px; animation:bob 2s ease-in-out infinite; }
#final-score { font-family:'Creepster',cursive; font-size:2rem; color:#ffaa00; text-shadow:0 0 20px #ffaa0080; margin-bottom:20px; display:none; }
#instructions { position:fixed; bottom:60px; left:50%; transform:translateX(-50%); z-index:10; color:#553355; font-size:0.8rem; letter-spacing:2px; text-transform:uppercase; pointer-events:none; white-space:nowrap; }
@keyframes flicker { 0%,100%{opacity:1} 92%{opacity:1} 93%{opacity:0.5} 94%{opacity:1} 96%{opacity:0.6} 97%{opacity:1} }
@keyframes bob { 0%,100%{transform:translateY(0) scale(1)} 50%{transform:translateY(-14px) scale(1.05)} }
</style>
</head>
<body>
<div id="ui">
<div id="score-display">SCORE: 0</div>
<div id="lives-display">❤️❤️❤️</div>
</div>
<div id="rage-bar-wrap">
<div id="rage-label">⚠ MONSTER RAGE</div>
<div id="rage-bar-bg"><div id="rage-bar"></div></div>
</div>
<canvas id="minimap" width="120" height="80"></canvas>
<div id="instructions">WASD / Arrow Keys — Collect crystals — Don't get caught</div>
<canvas id="c"></canvas>
<div id="overlay">
<canvas id="monster-preview" width="120" height="120"></canvas>
<h1>RUN!</h1>
<p id="tagline">IT IS ALWAYS HUNGRY...</p>
<div id="final-score"></div>
<button id="start-btn">BEGIN YOUR DOOM</button>
</div>
<script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const miniCanvas = document.getElementById('minimap');
const miniCtx = miniCanvas.getContext('2d');
const overlay = document.getElementById('overlay');
const scoreEl = document.getElementById('score-display');
const livesEl = document.getElementById('lives-display');
const startBtn = document.getElementById('start-btn');
const finalScoreEl = document.getElementById('final-score');
const taglineEl = document.getElementById('tagline');
const rageBar = document.getElementById('rage-bar');
const titleEl = document.querySelector('#overlay h1');
const previewCanvas = document.getElementById('monster-preview');
const previewCtx = previewCanvas.getContext('2d');
let VW, VH;
function resize(){ VW = canvas.width = window.innerWidth; VH = canvas.height = window.innerHeight; }
resize();
window.addEventListener('resize', resize);
const TILE = 48;
const MAP_COLS = 36;
const MAP_ROWS = 26;
const WORLD_W = MAP_COLS * TILE;
const WORLD_H = MAP_ROWS * TILE;
let cam = { x:0, y:0 };
const keys = {};
window.addEventListener('keydown', e => {
keys[e.key] = true;
if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight',' '].includes(e.key)) e.preventDefault();
});
window.addEventListener('keyup', e => { keys[e.key] = false; });
let touchDir = {x:0,y:0}, touchId=null, touchSX=0, touchSY=0;
canvas.addEventListener('touchstart', e=>{ const t=e.touches[0]; touchId=t.identifier; touchSX=t.clientX; touchSY=t.clientY; },{passive:true});
canvas.addEventListener('touchmove', e=>{
for(let t of e.touches) if(t.identifier===touchId){
const dx=t.clientX-touchSX,dy=t.clientY-touchSY,len=Math.hypot(dx,dy)||1,d=Math.min(len,70)/70;
touchDir.x=(dx/len)*d; touchDir.y=(dy/len)*d;
}
e.preventDefault();
},{passive:false});
canvas.addEventListener('touchend',()=>{touchDir.x=0;touchDir.y=0;},{passive:true});
let state='menu';
let score=0,lives=3,frame=0;
let gems=[],particles=[],bloodDrops=[],footprints=[];
let shake={mag:0,t:0,x:0,y:0};
let flashRed=0;
const PLAYER_SPEED=3.8;
let player={x:0,y:0,vx:0,vy:0,r:14,angle:0,invincible:0,trail:[]};
let monster={
x:0,y:0,vx:0,vy:0,r:28,speed:1.5,
wobble:0,rage:0,phase:0,roarTimer:300,
tentacleAngles:Array.from({length:6},(_,i)=>i*(Math.PI*2/6)),
eyePulse:0,slobber:0,lastFootX:0,lastFootY:0
};
let walls=[];
function buildMap(){
walls=[];
for(let c=0;c<MAP_COLS;c++){
walls.push({x:c*TILE,y:0,w:TILE,h:TILE});
walls.push({x:c*TILE,y:(MAP_ROWS-1)*TILE,w:TILE,h:TILE});
}
for(let r=1;r<MAP_ROWS-1;r++){
walls.push({x:0,y:r*TILE,w:TILE,h:TILE});
walls.push({x:(MAP_COLS-1)*TILE,y:r*TILE,w:TILE,h:TILE});
}
const grid=Array.from({length:MAP_ROWS},()=>new Array(MAP_COLS).fill(0));
const clusters=[
[3,2,2,3],[7,2,3,2],[13,2,2,4],[18,2,4,2],[24,2,3,3],[29,2,2,4],
[3,7,3,2],[8,6,2,4],[12,7,4,2],[18,6,2,3],[22,7,3,2],[27,6,2,4],[31,7,3,2],
[2,12,2,4],[6,12,3,2],[11,12,2,3],[15,11,4,2],[20,12,2,4],[25,12,3,2],[30,12,2,3],
[3,17,2,3],[7,17,4,2],[13,17,2,3],[18,17,3,2],[23,17,2,4],[28,17,2,3],[32,17,2,3],
[4,21,3,2],[9,21,2,3],[15,21,4,2],[20,21,2,2],[25,21,3,2],[30,21,2,3],
[16,10,4,6],
];
for(let [c,r,w,h] of clusters){
for(let dr=0;dr<h;dr++) for(let dc=0;dc<w;dc++){
const rr=r+dr,cc=c+dc;
if(rr>0&&rr<MAP_ROWS-1&&cc>0&&cc<MAP_COLS-1) grid[rr][cc]=1;
}
}
for(let r=1;r<MAP_ROWS-1;r++) for(let c=1;c<MAP_COLS-1;c++){
if(grid[r][c]===1) walls.push({x:c*TILE,y:r*TILE,w:TILE,h:TILE});
}
}
function wallAt(px,py,r=16){
for(let w of walls) if(px+r>w.x&&px-r<w.x+w.w&&py+r>w.y&&py-r<w.y+w.h) return true;
return false;
}
function resolveWalls(obj){
for(let w of walls){
const cx=Math.max(w.x,Math.min(obj.x,w.x+w.w));
const cy=Math.max(w.y,Math.min(obj.y,w.y+w.h));
const dx=obj.x-cx,dy=obj.y-cy,d=Math.hypot(dx,dy);
if(d<obj.r&&d>0){
const nx=dx/d,ny=dy/d,pen=obj.r-d;
obj.x+=nx*pen; obj.y+=ny*pen;
const dot=obj.vx*nx+obj.vy*ny;
if(dot<0){obj.vx-=dot*nx;obj.vy-=dot*ny;}
}
}
}
function spawnGem(){
let gx,gy,tries=0;
do{
gx=(Math.floor(Math.random()*(MAP_COLS-2))+1)*TILE+TILE/2;
gy=(Math.floor(Math.random()*(MAP_ROWS-2))+1)*TILE+TILE/2;
tries++;
} while(tries<40&&(wallAt(gx,gy,20)||Math.hypot(gx-player.x,gy-player.y)<120||Math.hypot(gx-monster.x,gy-monster.y)<100));
gems.push({x:gx,y:gy,r:11,t:Math.random()*Math.PI*2,collected:false});
}
function burst(x,y,color,n=14){
for(let i=0;i<n;i++){
const a=Math.random()*Math.PI*2,s=2+Math.random()*5;
particles.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s,life:1,color,r:3+Math.random()*4});
}
}
function bloodBurst(x,y,n=20){
for(let i=0;i<n;i++){
const a=Math.random()*Math.PI*2,s=1+Math.random()*6;
bloodDrops.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s-2,life:1,r:2+Math.random()*4,splat:false});
}
}
function startGame(){
score=0;lives=3;frame=0;gems=[];particles=[];bloodDrops=[];footprints=[];
buildMap();
player.x=WORLD_W/2;player.y=WORLD_H/2;
player.vx=0;player.vy=0;player.invincible=0;player.trail=[];
monster.x=TILE*2;monster.y=TILE*2;
monster.vx=0;monster.vy=0;monster.speed=1.5;monster.rage=0;monster.roarTimer=300;
monster.phase=0;monster.eyePulse=0;monster.slobber=0;
for(let i=0;i<8;i++) spawnGem();
updateUI();
overlay.style.opacity='0';
setTimeout(()=>overlay.style.display='none',500);
state='play';
}
startBtn.addEventListener('click',startGame);
function gameOver(){
state='over';
bloodBurst(player.x,player.y,40);
flashRed=1;
triggerShake(25,50);
setTimeout(()=>{
overlay.style.display='flex';overlay.style.opacity='1';
titleEl.textContent='DEVOURED.';titleEl.style.color='#cc0000';
taglineEl.textContent='It tasted your fear...';
finalScoreEl.style.display='block';finalScoreEl.textContent='You scored: '+score;
startBtn.textContent='FLEE AGAIN';
},900);
}
function triggerShake(mag,dur){shake.mag=mag;shake.t=dur;}
function updateUI(){
scoreEl.textContent='SCORE: '+score;
livesEl.textContent='❤️'.repeat(Math.max(0,lives));
rageBar.style.width=Math.min(100,monster.rage*100)+'%';
}
function loop(){
requestAnimationFrame(loop);
frame++;
if(shake.t>0){shake.t--;const m=shake.mag*(shake.t/50);shake.x=(Math.random()-.5)*m;shake.y=(Math.random()-.5)*m;}
else{shake.x=0;shake.y=0;}
flashRed=Math.max(0,flashRed-0.03);
ctx.save();
ctx.translate(shake.x,shake.y);
ctx.clearRect(-60,-60,VW+120,VH+120);
if(state==='play') update();
drawWorld();
drawFootprints();
drawBloodDrops();
drawGems();
drawParticles();
if(state==='play'||state==='over'){drawPlayer();drawMonster();}
drawDarkness();
if(flashRed>0){ctx.fillStyle=`rgba(180,0,0,${flashRed*0.35})`;ctx.fillRect(-60,-60,VW+120,VH+120);}
ctx.restore();
drawMinimap();
if(state==='menu') drawPreviewMonster();
}
function update(){
let dx=0,dy=0;
if(keys['ArrowLeft']||keys['a']||keys['A']) dx-=1;
if(keys['ArrowRight']||keys['d']||keys['D']) dx+=1;
if(keys['ArrowUp']||keys['w']||keys['W']) dy-=1;
if(keys['ArrowDown']||keys['s']||keys['S']) dy+=1;
dx+=touchDir.x;dy+=touchDir.y;
const plen=Math.hypot(dx,dy)||1;
if(dx||dy){player.vx=(dx/plen)*PLAYER_SPEED;player.vy=(dy/plen)*PLAYER_SPEED;}
else{player.vx*=0.78;player.vy*=0.78;}
if(Math.abs(player.vx)>0.1||Math.abs(player.vy)>0.1) player.angle=Math.atan2(player.vy,player.vx);
player.x+=player.vx;player.y+=player.vy;
player.x=Math.max(player.r,Math.min(WORLD_W-player.r,player.x));
player.y=Math.max(player.r,Math.min(WORLD_H-player.r,player.y));
resolveWalls(player);
if(frame%3===0) player.trail.push({x:player.x,y:player.y,life:1});
for(let t of player.trail) t.life-=0.08;
player.trail=player.trail.filter(t=>t.life>0);
const targetCX=player.x-VW/2,targetCY=player.y-VH/2;
cam.x+=(targetCX-cam.x)*0.1;cam.y+=(targetCY-cam.y)*0.1;
cam.x=Math.max(0,Math.min(WORLD_W-VW,cam.x));
cam.y=Math.max(0,Math.min(WORLD_H-VH,cam.y));
monster.phase+=0.06;monster.wobble+=0.05;monster.eyePulse+=0.08;monster.slobber+=0.04;
const toPX=player.x-monster.x,toPY=player.y-monster.y;
const toPDist=Math.hypot(toPX,toPY)||1;
if(toPDist<200) monster.rage=Math.min(1,monster.rage+0.004);
else if(toPDist<400) monster.rage=Math.min(1,monster.rage+0.001);
else monster.rage=Math.max(0,monster.rage-0.002);
const effectiveSpeed=monster.speed*(1+monster.rage*0.8);
monster.vx+=(toPX/toPDist)*0.2*(1+monster.rage*0.5);
monster.vy+=(toPY/toPDist)*0.2*(1+monster.rage*0.5);
const ms=Math.hypot(monster.vx,monster.vy);
if(ms>effectiveSpeed){monster.vx=(monster.vx/ms)*effectiveSpeed;monster.vy=(monster.vy/ms)*effectiveSpeed;}
monster.x+=monster.vx;monster.y+=monster.vy;
resolveWalls(monster);
if(Math.hypot(monster.x-monster.lastFootX,monster.y-monster.lastFootY)>50){
footprints.push({x:monster.x,y:monster.y,life:1,size:12+monster.rage*8});
monster.lastFootX=monster.x;monster.lastFootY=monster.y;
}
for(let f of footprints) f.life-=0.005;
footprints=footprints.filter(f=>f.life>0);
monster.roarTimer--;
if(monster.roarTimer<=0&&monster.rage>0.5){
triggerShake(6+monster.rage*10,20);
flashRed=0.3+monster.rage*0.3;
monster.roarTimer=180+Math.random()*200;
burst(monster.x,monster.y,'#ff0000',8);
}
monster.speed=1.5+Math.floor(score/60)*0.2;
for(let i=0;i<monster.tentacleAngles.length;i++) monster.tentacleAngles[i]+=0.03*(i%2===0?1:-1)*(1+monster.rage*0.5);
if(player.invincible<=0&&toPDist<player.r+monster.r-6){
lives--;triggerShake(18,35);flashRed=0.8;
bloodBurst(player.x,player.y,25);player.invincible=150;
updateUI();
if(lives<=0){gameOver();return;}
const ang=Math.atan2(player.y-monster.y,player.x-monster.x);
player.vx=Math.cos(ang)*10;player.vy=Math.sin(ang)*10;
}
if(player.invincible>0) player.invincible--;
for(let g of gems){
if(!g.collected&&Math.hypot(player.x-g.x,player.y-g.y)<player.r+g.r){
g.collected=true;score+=10;burst(g.x,g.y,'#00ffcc',16);updateUI();
}
g.t+=0.04;
}
gems=gems.filter(g=>!g.collected);
while(gems.length<6) spawnGem();
for(let p of particles){p.x+=p.vx;p.y+=p.vy;p.vx*=0.92;p.vy*=0.92;p.life-=0.022;}
particles=particles.filter(p=>p.life>0);
for(let b of bloodDrops){
if(!b.splat){b.x+=b.vx;b.y+=b.vy;b.vy+=0.3;b.life-=0.018;if(b.vy>0&&b.life<0.6)b.splat=true;}
else b.life-=0.008;
}
bloodDrops=bloodDrops.filter(b=>b.life>0);
}
function drawWorld(){
ctx.save();ctx.translate(-cam.x,-cam.y);
const groundGrad=ctx.createRadialGradient(WORLD_W/2,WORLD_H/2,100,WORLD_W/2,WORLD_H/2,WORLD_W*0.7);
groundGrad.addColorStop(0,'#0e0020');groundGrad.addColorStop(1,'#050008');
ctx.fillStyle=groundGrad;ctx.fillRect(0,0,WORLD_W,WORLD_H);
ctx.strokeStyle='rgba(40,0,60,0.3)';ctx.lineWidth=1;
for(let c=0;c<=MAP_COLS;c++){ctx.beginPath();ctx.moveTo(c*TILE,0);ctx.lineTo(c*TILE,WORLD_H);ctx.stroke();}
for(let r=0;r<=MAP_ROWS;r++){ctx.beginPath();ctx.moveTo(0,r*TILE);ctx.lineTo(WORLD_W,r*TILE);ctx.stroke();}
for(let w of walls){
ctx.save();
ctx.shadowColor=`hsl(${280+monster.rage*60},100%,30%)`;ctx.shadowBlur=12+monster.rage*8;
const wg=ctx.createLinearGradient(w.x,w.y,w.x+w.w,w.y+w.h);
wg.addColorStop(0,`hsl(${270+monster.rage*40},50%,10%)`);
wg.addColorStop(1,`hsl(${260+monster.rage*40},40%,7%)`);
ctx.fillStyle=wg;ctx.fillRect(w.x,w.y,w.w,w.h);
ctx.shadowBlur=0;
ctx.strokeStyle=`rgba(${80+monster.rage*100},0,${120+monster.rage*80},0.5)`;
ctx.lineWidth=1;ctx.strokeRect(w.x+0.5,w.y+0.5,w.w-1,w.h-1);
ctx.restore();
}
const monRadGrad=ctx.createRadialGradient(monster.x,monster.y,0,monster.x,monster.y,200+monster.rage*150);
monRadGrad.addColorStop(0,`rgba(180,0,0,${0.08+monster.rage*0.12})`);monRadGrad.addColorStop(1,'transparent');
ctx.fillStyle=monRadGrad;ctx.fillRect(0,0,WORLD_W,WORLD_H);
ctx.restore();
}
function drawFootprints(){
ctx.save();ctx.translate(-cam.x,-cam.y);
for(let f of footprints){
ctx.globalAlpha=f.life*0.5;ctx.fillStyle='#440000';
ctx.beginPath();ctx.ellipse(f.x,f.y,f.size*0.6,f.size,0,0,Math.PI*2);ctx.fill();
}
ctx.globalAlpha=1;ctx.restore();
}
function drawPlayer(){
if(player.invincible>0&&Math.floor(player.invincible/6)%2===0) return;
ctx.save();ctx.translate(-cam.x,-cam.y);
for(let t of player.trail){
ctx.globalAlpha=t.life*0.25;ctx.fillStyle='#4488ff';
ctx.beginPath();ctx.arc(t.x,t.y,8*t.life,0,Math.PI*2);ctx.fill();
}
ctx.globalAlpha=1;
ctx.translate(player.x,player.y);
ctx.shadowColor='#4488ff';ctx.shadowBlur=player.invincible>0?30:15;
ctx.strokeStyle='#aaccff';ctx.lineWidth=2.5;ctx.lineCap='round';
ctx.beginPath();ctx.arc(0,-18,7,0,Math.PI*2);ctx.stroke();
ctx.beginPath();ctx.moveTo(0,-11);ctx.lineTo(0,4);ctx.stroke();
const ro=Math.sin(frame*0.3)*6;
ctx.beginPath();ctx.moveTo(0,-5);ctx.lineTo(-10,2+ro);ctx.stroke();
ctx.beginPath();ctx.moveTo(0,-5);ctx.lineTo(10,2-ro);ctx.stroke();
ctx.beginPath();ctx.moveTo(0,4);ctx.lineTo(-8,16-ro*0.5);ctx.stroke();
ctx.beginPath();ctx.moveTo(0,4);ctx.lineTo(8,16+ro*0.5);ctx.stroke();
ctx.fillStyle='#fff';ctx.beginPath();ctx.arc(0,-18,3,0,Math.PI*2);ctx.fill();
ctx.fillStyle='#000';ctx.beginPath();ctx.arc(0.5,-18,1.5,0,Math.PI*2);ctx.fill();
ctx.restore();
}
function drawMonsterAt(cx,cy,scale,rage,ph,eyePulse,slobber,tentacleAngles,rctx){
rctx.save();rctx.translate(cx,cy);rctx.scale(scale,scale);
const breathe=Math.sin(ph)*4;
const rageGlow=`hsl(${rage*20},100%,${30+rage*20}%)`;
const aura=rctx.createRadialGradient(0,0,20,0,0,70+rage*30+breathe);
aura.addColorStop(0,`rgba(150,0,0,${0.2+rage*0.3})`);
aura.addColorStop(0.5,`rgba(80,0,20,${0.1+rage*0.15})`);
aura.addColorStop(1,'transparent');
rctx.fillStyle=aura;rctx.beginPath();rctx.arc(0,0,80+rage*30,0,Math.PI*2);rctx.fill();
for(let i=0;i<tentacleAngles.length;i++){
const ang=tentacleAngles[i],len=35+Math.sin(ph+i)*10+rage*15;
rctx.save();rctx.rotate(ang);
rctx.shadowColor=rageGlow;rctx.shadowBlur=8+rage*12;
rctx.strokeStyle=`hsl(${300+i*10},${40+rage*40}%,${15+rage*10}%)`;
rctx.lineWidth=6-i*0.3;rctx.lineCap='round';
rctx.beginPath();rctx.moveTo(0,16);
rctx.quadraticCurveTo(Math.sin(ph+i*1.5)*12,len*0.5,Math.cos(ph*0.7+i)*8,len);
rctx.stroke();
rctx.fillStyle=`hsl(0,${60+rage*40}%,${20+rage*20}%)`;
rctx.beginPath();rctx.arc(Math.cos(ph*0.7+i)*8,len,4+rage*3,0,Math.PI*2);rctx.fill();
rctx.restore();
}
rctx.shadowColor=rageGlow;rctx.shadowBlur=20+rage*25;
const bodyGrad=rctx.createRadialGradient(-5,-5,2,0,0,28+breathe);
bodyGrad.addColorStop(0,`hsl(${300+rage*20},${30+rage*30}%,${15+rage*15}%)`);
bodyGrad.addColorStop(0.6,`hsl(${280+rage*30},${20+rage*20}%,${8+rage*10}%)`);
bodyGrad.addColorStop(1,'hsl(260,10%,5%)');
rctx.fillStyle=bodyGrad;rctx.beginPath();
rctx.moveTo(0,-28-breathe);
rctx.bezierCurveTo(22,-22,28+breathe,0,22,20);
rctx.bezierCurveTo(10,30+breathe,-10,30+breathe,-22,20);
rctx.bezierCurveTo(-28+breathe,0,-22,-22,0,-28-breathe);
rctx.fill();
rctx.fillStyle='rgba(0,0,0,0.4)';
[[8,-10,5],[-10,-5,4],[4,8,3],[-6,12,3],[14,4,3],[-14,2,4]].forEach(([bx,by,br])=>{
rctx.beginPath();rctx.arc(bx,by,br,0,Math.PI*2);rctx.fill();
});
rctx.save();
const mouthOpen=0.3+rage*0.7+Math.sin(ph*2)*0.1;
rctx.fillStyle='#1a0000';
rctx.beginPath();rctx.ellipse(0,10+breathe*0.3,14+rage*6,8*mouthOpen,0,0,Math.PI*2);rctx.fill();
rctx.fillStyle=`rgba(200,200,180,${0.7+rage*0.3})`;
for(let t=-2;t<=2;t++){
rctx.beginPath();rctx.moveTo(t*5.5,10+breathe*0.3-2);
rctx.lineTo(t*5.5-3,10+breathe*0.3+7*mouthOpen);
rctx.lineTo(t*5.5+3,10+breathe*0.3+7*mouthOpen);
rctx.closePath();rctx.fill();
}
rctx.fillStyle=`rgba(180,180,160,${0.5+rage*0.3})`;
for(let t=-1;t<=1;t++){
rctx.beginPath();rctx.moveTo(t*7+3,10+breathe*0.3+6*mouthOpen);
rctx.lineTo(t*7,10+breathe*0.3+13*mouthOpen);
rctx.lineTo(t*7+6,10+breathe*0.3+13*mouthOpen);
rctx.closePath();rctx.fill();
}
const slob=Math.sin(slobber);
rctx.strokeStyle=`rgba(80,200,80,${0.4+rage*0.3})`;rctx.lineWidth=2;rctx.lineCap='round';
rctx.beginPath();rctx.moveTo(-4,10+breathe*0.3+6*mouthOpen);rctx.lineTo(-4+slob*3,10+breathe*0.3+14*mouthOpen);rctx.stroke();
rctx.beginPath();rctx.moveTo(5,10+breathe*0.3+5*mouthOpen);rctx.lineTo(5-slob*2,10+breathe*0.3+12*mouthOpen);rctx.stroke();
rctx.restore();
const eyePulseVal=0.5+Math.sin(eyePulse)*0.5;
[[-10,-10],[10,-10]].forEach(([ex,ey],i)=>{
rctx.fillStyle='rgba(0,0,0,0.8)';rctx.beginPath();rctx.arc(ex,ey+breathe*0.2,9,0,Math.PI*2);rctx.fill();
rctx.shadowColor=`hsl(${i*15},100%,${50+eyePulseVal*30}%)`;rctx.shadowBlur=15+eyePulseVal*20+rage*20;
rctx.fillStyle=`hsl(${i*15},100%,${50+eyePulseVal*30}%)`;
rctx.beginPath();rctx.arc(ex,ey+breathe*0.2,6,0,Math.PI*2);rctx.fill();
rctx.fillStyle='#000';rctx.beginPath();rctx.ellipse(ex,ey+breathe*0.2,2,5,0,0,Math.PI*2);rctx.fill();
rctx.shadowBlur=0;rctx.fillStyle='rgba(255,255,255,0.8)';
rctx.beginPath();rctx.arc(ex+2,ey+breathe*0.2-2,1.5,0,Math.PI*2);rctx.fill();
});
if(rage>0.3){
const ea=(rage-0.3)/0.7;
rctx.globalAlpha=ea;rctx.shadowColor='#ff0000';rctx.shadowBlur=25;
rctx.fillStyle=`hsl(0,100%,${50+eyePulseVal*40}%)`;
rctx.beginPath();rctx.arc(0,-22+breathe*0.3,5*ea,0,Math.PI*2);rctx.fill();
rctx.fillStyle='#000';rctx.beginPath();rctx.ellipse(0,-22+breathe*0.3,1.5,4,0,0,Math.PI*2);rctx.fill();
rctx.globalAlpha=1;
}
rctx.shadowColor=rageGlow;rctx.shadowBlur=8+rage*10;
rctx.fillStyle=`hsl(${290+rage*30},${30+rage*30}%,${12+rage*12}%)`;
[[-16,-22,6],[-6,-30,5],[6,-30,5],[16,-22,6],[0,-34,7]].forEach(([sx,sy,sh])=>{
rctx.save();rctx.translate(sx,sy+breathe*0.4);
rctx.beginPath();rctx.moveTo(0,-sh-rage*4);rctx.lineTo(-4,0);rctx.lineTo(4,0);rctx.closePath();rctx.fill();
rctx.restore();
});
rctx.restore();
}
function drawMonster(){
ctx.save();ctx.translate(-cam.x,-cam.y);
drawMonsterAt(monster.x,monster.y,1+monster.rage*0.3,monster.rage,monster.phase,monster.eyePulse,monster.slobber,monster.tentacleAngles,ctx);
ctx.restore();
}
function drawGems(){
ctx.save();ctx.translate(-cam.x,-cam.y);
for(let g of gems){
if(g.collected) continue;
ctx.save();ctx.translate(g.x,g.y+Math.sin(g.t)*5);
ctx.shadowColor='#00ffcc';ctx.shadowBlur=18+Math.sin(g.t*2)*8;
ctx.fillStyle='#00ffcc';
ctx.beginPath();ctx.moveTo(0,-13);ctx.lineTo(9,-3);ctx.lineTo(6,10);ctx.lineTo(-6,10);ctx.lineTo(-9,-3);ctx.closePath();ctx.fill();
ctx.fillStyle='rgba(255,255,255,0.5)';
ctx.beginPath();ctx.moveTo(0,-13);ctx.lineTo(-9,-3);ctx.lineTo(0,-2);ctx.closePath();ctx.fill();
ctx.restore();
}
ctx.restore();
}
function drawParticles(){
ctx.save();ctx.translate(-cam.x,-cam.y);
for(let p of particles){
ctx.globalAlpha=p.life;ctx.shadowColor=p.color;ctx.shadowBlur=10;ctx.fillStyle=p.color;
ctx.beginPath();ctx.arc(p.x,p.y,p.r*p.life,0,Math.PI*2);ctx.fill();
}
ctx.globalAlpha=1;ctx.restore();
}
function drawBloodDrops(){
ctx.save();ctx.translate(-cam.x,-cam.y);
for(let b of bloodDrops){
ctx.globalAlpha=b.life*0.9;ctx.fillStyle=b.splat?'#330000':'#cc0000';
ctx.shadowColor='#ff0000';ctx.shadowBlur=b.splat?0:5;
ctx.beginPath();
if(b.splat) ctx.ellipse(b.x,b.y,b.r*1.5,b.r*0.5,0,0,Math.PI*2);
else ctx.arc(b.x,b.y,b.r*b.life,0,Math.PI*2);
ctx.fill();
}
ctx.globalAlpha=1;ctx.restore();
}
function drawDarkness(){
const px=player.x-cam.x,py=player.y-cam.y;
const mx=monster.x-cam.x,my=monster.y-cam.y;
const darkGrad=ctx.createRadialGradient(px,py,60,px,py,280);
darkGrad.addColorStop(0,'transparent');darkGrad.addColorStop(0.5,'rgba(0,0,0,0.3)');darkGrad.addColorStop(1,'rgba(0,0,0,0.75)');
ctx.fillStyle=darkGrad;ctx.fillRect(0,0,VW,VH);
const monGrad=ctx.createRadialGradient(mx,my,0,mx,my,100+monster.rage*80);
monGrad.addColorStop(0,`rgba(180,0,0,${0.15+monster.rage*0.2})`);monGrad.addColorStop(1,'transparent');
ctx.fillStyle=monGrad;ctx.fillRect(0,0,VW,VH);
}
function drawMinimap(){
if(state!=='play') return;
const mw=miniCanvas.width,mh=miniCanvas.height;
const sx=mw/WORLD_W,sy=mh/WORLD_H;
miniCtx.clearRect(0,0,mw,mh);
miniCtx.fillStyle='#0a000f';miniCtx.fillRect(0,0,mw,mh);
miniCtx.fillStyle='#2a0044';
for(let w of walls) miniCtx.fillRect(w.x*sx,w.y*sy,w.w*sx,w.h*sy);
miniCtx.fillStyle='#00ffcc';
for(let g of gems) if(!g.collected){miniCtx.beginPath();miniCtx.arc(g.x*sx,g.y*sy,2,0,Math.PI*2);miniCtx.fill();}
miniCtx.fillStyle='#4488ff';miniCtx.beginPath();miniCtx.arc(player.x*sx,player.y*sy,3,0,Math.PI*2);miniCtx.fill();
miniCtx.fillStyle='#ff0000';miniCtx.beginPath();miniCtx.arc(monster.x*sx,monster.y*sy,4,0,Math.PI*2);miniCtx.fill();
miniCtx.strokeStyle='rgba(255,255,255,0.2)';miniCtx.lineWidth=1;
miniCtx.strokeRect(cam.x*sx,cam.y*sy,VW*sx,VH*sy);
}
let previewPhase=0;
const previewTentacles=Array.from({length:6},(_,i)=>i*(Math.PI*2/6));
function drawPreviewMonster(){
previewPhase+=0.04;
for(let i=0;i<previewTentacles.length;i++) previewTentacles[i]+=0.03*(i%2===0?1:-1);
previewCtx.clearRect(0,0,120,120);
const pr=0.5+Math.sin(previewPhase*0.5)*0.4;
drawMonsterAt(60,60,1.1,pr,previewPhase,previewPhase*1.2,previewPhase*0.8,previewTentacles,previewCtx);
}
loop();
</script>
</body>
</html>Game Source: RUN! Monster Chase
Creator: BraveMaker51
Libraries: none
Complexity: complex (568 lines, 24.8 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: run-monster-chase-bravemaker51" to link back to the original. Then publish at arcadelab.ai/publish.