Log In  


<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>MunchRex — Playable HTML</title>
<style>
html,body{height:100%;margin:0;background:#0b1220;display:flex;align-items:center;justify-content:center}
canvas{image-rendering:pixelated;border:8px solid #081018; background:#7ec850}

ui{position:fixed;left:10px;top:10px;color:#cfe;border-radius:6px;padding:6px;font-family:monospace}

a{color:#9ff}
</style>
</head>
<body>
<canvas id="game" width="128" height="96"></canvas>
<div id="ui">MunchRex — Save as HTML & open. Controls: ← → | Z jump | X gulp | C tail/stomp<br/>Press Enter to Start</div>

<script>
/*
MunchRex — compact browser export

  • 128x96 logical resolution (scaled by browser)
  • Player: little T-rex (16x12 pixel sprite drawn from arrays)
  • Mechanics: move, jump, gulp (collect orbs), tail + stomp
  • Title screen, 3 short levels, simple chiptune
    */

const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const SCALE = Math.floor(Math.min(window.innerWidth0.7 / canvas.width, window.innerHeight0.8 / canvas.height));
canvas.style.width = (canvas.width SCALE) + 'px';
canvas.style.height = (canvas.height
SCALE) + 'px';

const W = canvas.width, H = canvas.height;
let keys = {};
window.addEventListener('keydown', e => { keys[e.key.toLowerCase()] = true; if(e.key==='Enter'){ if(gameState==='title') startGame(); }});
window.addEventListener('keyup', e => { keys[e.key.toLowerCase()] = false; });

let gameState = 'title'; // title, play, win
let frame = 0;

// simple palette (PICO-ish)
const pal = {
bg: '#7ec850', // grass
sky: '#8fd3ff',
dirt: '#8a5a3b',
stone: '#6b6b6b',
rex: '#FFB86B',
rex_dark: '#c07a38',
rex_eye: '#000',
orb: '#ffd86b',
ui: '#e8f7ff'
};

// minimal level tiles (8x8)
const tileSize = 8;
const levels = [
// level 0 - meadow (wide string rows)
[
'................................................................',
'................................................................',
'................................................................',
'................................................................',
'................................................................',
'................................o...............................',
'.....................o..........................................',
'...............................................o................',
'############################################################....',
'############################################################....',
'############################################################....',
'############################################################....'
],
// level 1 - cave (gaps)
[
'................................................................',
'................................................................',
'...........................o....................................',
'................................................................',
'..............................................o.................',
'................................................................',
'...............######...................#######..................',
'...............#....#...................#.....#..................',
'#######...########....####......#########.....######....######..',
'######....................................................####..',
'############################################################....',
'############################################################....'
],
// level 2 - sky ruins (platforms)
[
'................................................................',
'................................................................',
'................................o...............................',
'......====......====...............====.......====..............',
'...............................................................o',
'................................................................',
'............====............=====...........======..............',
'................................................................',
'....................o...........................................',
'...............................................................',
'############################################################....',
'############################################################....'
]
];

// convert level chars to tile codes
// '.' empty, '#' ground, '=' floating platform, 'o' orb
function tileAt(lvl,x,y){
if(y<0 || y>=levels[lvl].length) return '#';
const row = levels[lvl][y];
if(x<0 || x>=row.length) return '.';
return row[x];
}

let levelIndex = 0;

const player = {
x:16, y:64, w:12, h:10,
vx:0, vy:0, dir:1,
onGround:false,
gulpCd:0, ability:0, abilityTimer:0,
hp:3, gems:0,
anim:0
};

const GRAV = 0.28;
const JUMP_V = -3.6;
const WALK_SPD = 1.2;
const MAX_FALL = 3.2;

// orbs list (we will scan level for 'o' tiles)
function scanOrbs(lvl){
let arr=[];
for(let ty=0;ty<levels[lvl].length;ty++){
const row=levels[lvl][ty];
for(let tx=0;tx<row.length;tx++){
if(row[tx]==='o') arr.push({x:tx8+4, y:ty8+4, r:3, alive:true, bob: Math.random()*20});
}
}
return arr;
}
let orbs = scanOrbs(levelIndex);

// audio: tiny chiptune using WebAudio
let audioCtx = null;
function startAudio(){
if(audioCtx) return;
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// simple looped arpeggio
const seq = [523.25, 659.25, 783.99, 987.77, 783.99, 659.25];
let t=0;
const master = audioCtx.createGain(); master.gain.value=0.08; master.connect(audioCtx.destination);
function playStep(){
const now = audioCtx.currentTime;
for(let i=0;i<2;i++){
const osc = audioCtx.createOscillator();
osc.type = 'square';
osc.frequency.value = seq[(t+i)%seq.length] * (i?0.5:1);
const a = audioCtx.createGain();
a.gain.value = 0.0001;
osc.connect(a); a.connect(master);
osc.start(now);
a.gain.linearRampToValueAtTime(0.06, now+0.01);
a.gain.exponentialRampToValueAtTime(0.0001, now+0.18);
osc.stop(now+0.2);
}
t=(t+1)%seq.length;
setTimeout(playStep, 220);
}
playStep();
}

// very small sfx
function sfx(type){
if(!audioCtx) startAudio();
const now = audioCtx.currentTime;
const o = audioCtx.createOscillator();
const g = audioCtx.createGain();
o.connect(g); g.connect(audioCtx.destination);
g.gain.value = 0.0001;
if(type===0){ // gulp
o.type='sawtooth'; o.frequency.setValueAtTime(800,now);
o.frequency.exponentialRampToValueAtTime(220, now+0.12);
g.gain.linearRampToValueAtTime(0.08, now+0.01);
g.gain.exponentialRampToValueAtTime(0.0001, now+0.12);
o.start(now); o.stop(now+0.16);
} else if(type===1){ // miss
o.type='square'; o.frequency.setValueAtTime(120,now);
g.gain.linearRampToValueAtTime(0.04, now+0.01);
g.gain.exponentialRampToValueAtTime(0.0001, now+0.18);
o.start(now); o.stop(now+0.18);
} else { // tail
o.type='triangle'; o.frequency.setValueAtTime(400,now);
g.gain.linearRampToValueAtTime(0.06, now+0.01);
g.gain.exponentialRampToValueAtTime(0.0001, now+0.08);
o.start(now); o.stop(now+0.1);
}
}

// helpers: tile collision with rectangle
function isSolidAt(worldX, worldY){
const tx = Math.floor(worldX / tileSize);
const ty = Math.floor(worldY / tileSize);
const t = tileAt(levelIndex, tx, ty);
return (t === '#' || t === '=');
}
function rectCollidesTile(px,py,w,h){
const left = Math.floor(px / tileSize);
const right = Math.floor((px + w - 1) / tileSize);
const top = Math.floor(py / tileSize);
const bottom = Math.floor((py + h - 1) / tileSize);
for(let ty=top; ty<=bottom; ty++){
for(let tx=left; tx<=right; tx++){
if(isSolidAt(txtileSize, tytileSize)) return true;
}
}
return false;
}

// update player physics & input
function updatePlayer(){
// input
const left = keys['arrowleft'] || keys['a'];
const right = keys['arrowright'] || keys['d'];
const jump = keys['z'] || keys[' '];
const gulp = keys['x'];
const tail = keys['c'];
const down = keys['arrowdown'] || keys['s'];

// horizontal
if(left){ player.vx = -WALK_SPD; player.dir=-1; player.anim = (player.anim+0.2)%2; }
else if(right){ player.vx = WALK_SPD; player.dir=1; player.anim = (player.anim+0.2)%2; }
else { player.vx = 0; player.anim = 0; }

// gravity
player.vy = Math.min(player.vy + GRAV, MAX_FALL);

// jump
if(jump && player.onGround){ player.vy = JUMP_V; player.onGround=false; }

// apply horizontal movement
player.x += player.vx;
if(rectCollidesTile(player.x, player.y, player.w, player.h)){
// nudge back
player.x -= player.vx;
}

// apply vertical
player.y += player.vy;
if(rectCollidesTile(player.x, player.y, player.w, player.h)){
// landed or hit ceiling
player.y -= player.vy;
if(player.vy > 0){ player.onGround = true; player.vy = 0; }
else player.vy = 0;
} else { player.onGround = false; }

// stomp: down + tail button in air
if(tail && down && !player.onGround){
player.vy = 4.0;
sfx(2);
}

// tail (tap) for close damage (we'll simply play sfx)
if(tail && !down){
sfx(2);
}

// gulp
if(gulp && player.gulpCd<=0){
player.gulpCd = 25;
let grabbed=false;
for(let o of orbs){
if(!o.alive) continue;
const dx = (o.x - (player.x + player.w/2));
const dy = (o.y - (player.y + player.h/2));
if(Math.hypot(dx,dy) < 14){
o.alive=false; player.gems++; grabbed=true;
sfx(0);
// random ability placeholder
player.ability = Math.floor(Math.random()*3)+1;
player.abilityTimer = 180;
break;
}
}
if(!grabbed) sfx(1);
}
if(player.gulpCd>0) player.gulpCd--;

// ability timer
if(player.abilityTimer>0) player.abilityTimer--;
else player.ability = 0;

// keep player inside level width
const maxX = levels[levelIndex][0].length * tileSize - player.w - 2;
player.x = Math.max(0, Math.min(player.x, maxX));
}

// draw tiny pixel T-rex using scaled rectangles
function drawRex(x,y,dir,animState){
// basic 12x10 pixel design (scale up by 1; canvas is 128x96)
// draw body pixels using small rectangles to emulate pixel art
const px = Math.round(x), py=Math.round(y);
// body layout rows (12x10) using characters:
// ' ' empty, 'b' body, 'd' dark, 'e' eye, 't' tail
const sprite = [
" bbbbb ",
" bbbbbbb ",
" bbbbbbbbb ",
" bbbbd bbb ",
" bbbbbbbbb ",
" bbbbbbbbb ",
" bbbbbb ",
" b b b ",
" b b ",
" b b "
];
// flip horizontally if dir == -1
for(let ry=0; ry<sprite.length; ry++){
for(let rx=0; rx<sprite[ry].length; rx++){
const ch = sprite[ry][(dir===1) ? rx : sprite[ry].length-1-rx];
if(ch === 'b'){
ctx.fillStyle = pal.rex;
ctx.fillRect(px + rx, py + ry, 1,1);
} else if(ch === 'd'){
ctx.fillStyle = pal.rex_dark;
ctx.fillRect(px + rx, py + ry, 1,1);
} else if(ch === 'e'){
ctx.fillStyle = pal.rex_eye;
ctx.fillRect(px + rx, py + ry, 1,1);
}
}
}
// simple tail swing when animState>0.5 draw a 1-pixel tail
if(animState>0.6){
const tx = (dir===1) ? px-1 : px + sprite[0].length;
ctx.fillStyle = pal.rex_dark;
ctx.fillRect(tx, py+3, 1, 2);
}
}

// draw frame
function draw(){
// background sky & ground
ctx.fillStyle = pal.sky;
ctx.fillRect(0,0,W,H);
// draw tiles
const cols = levels[levelIndex][0].length;
for(let ty=0; ty<levels[levelIndex].length; ty++){
for(let tx=0; tx<levels[levelIndex][ty].length; tx++){
const t = tileAt(levelIndex, tx, ty);
const sx = txtileSize, sy = tytileSize;
if(t === '#'){
ctx.fillStyle = pal.dirt;
ctx.fillRect(sx, sy, tileSize, tileSize);
// stone pattern
ctx.fillStyle = pal.stone;
ctx.fillRect(sx+1, sy+1, 2,2);
ctx.fillRect(sx+4, sy+3, 2,2);
} else if(t === '='){
ctx.fillStyle = pal.dirt;
ctx.fillRect(sx, sy, tileSize, tileSize/2);
}
}
}
// draw orbs
for(let o of orbs){
if(!o.alive) continue;
o.bob += 0.08;
const oy = o.y + Math.sin(o.bob)2;
// glow
ctx.fillStyle = pal.orb;
ctx.beginPath();
ctx.arc(o.x, oy, o.r, 0, Math.PI
2);
ctx.fill();
ctx.fillStyle = '#fff7cc';
ctx.fillRect(o.x-1, oy-1, 2, 2);
}
// draw player (scale 1px = 1 screen pixel)
drawRex(player.x, player.y, player.dir, player.anim);
// HUD
ctx.fillStyle = pal.ui;
ctx.font = '7px monospace';
ctx.fillText('GEMS: '+player.gems, 4, 8);
ctx.fillText('HP: '+player.hp, 4, 16);
if(player.ability) ctx.fillText('ABL:'+player.ability+' ('+Math.ceil(player.abilityTimer/60)+'s)', 60, 8);
}

// title screen
function drawTitle(){
ctx.fillStyle = pal.sky;
ctx.fillRect(0,0,W,H);
ctx.fillStyle = '#120b1a';
ctx.font = '9px monospace';
ctx.fillText('MUNCHREX', 36, 24);
ctx.font = '6px monospace';
ctx.fillText('A tiny original 8-bit platformer', 14, 36);
ctx.fillText('Controls: ← → Z jump X gulp C tail/stomp', 6, 48);
ctx.fillText('Press ENTER to start', 26, 68);
}

// simple camera: center on player (but here canvas is small; we can keep static)
function gameLoop(){
frame++;
if(gameState === 'title'){
drawTitle();
} else if(gameState === 'play'){
updatePlayer();
// check orb collisions are handled in gulp; also auto-collect if touch
for(let o of orbs){
if(!o.alive) continue;
const dx = o.x - (player.x + player.w/2);
const dy = o.y - (player.y + player.h/2);
if(Math.hypot(dx,dy) < 8){
o.alive=false; player.gems++; sfx(0);
}
}
// if all orbs collected -> next level
const remaining = orbs.filter(o=>o.alive).length;
if(remaining === 0){
levelIndex++;
if(levelIndex >= levels.length){ gameState = 'win'; }
else {
// load next
orbs = scanOrbs(levelIndex);
player.x = 16; player.y = 40;
}
}
draw();
} else if(gameState === 'win'){
ctx.fillStyle = pal.sky;
ctx.fillRect(0,0,W,H);
ctx.fillStyle = '#081018';
ctx.font = '9px monospace';
ctx.fillText('YOU WIN!', 40, 36);
ctx.font = '6px monospace';
ctx.fillText('Thanks for playing MunchRex', 12, 52);
ctx.fillText('Refresh to play again', 20, 68);
}
requestAnimationFrame(gameLoop);
}

// start game
function startGame(){
startAudio();
gameState = 'play';
levelIndex = 0;
orbs = scanOrbs(levelIndex);
player.x=16; player.y=40; player.vx=0; player.vy=0;
document.getElementById('ui').style.display='none';
}

// start main loop
requestAnimationFrame(gameLoop);

// touch / mobile controls (simple)
let touchStartX = null;
canvas.addEventListener('touchstart', e=>{
const t = e.touches[0];
touchStartX = t.clientX;
// simple: left half = left, right half = right
if(t.clientX < window.innerWidth/2) keys['arrowleft']=true;
else keys['arrowright']=true;
// jump on second finger
if(e.touches.length>1) keys['z']=true;
});
canvas.addEventListener('touchend', e=>{
keys['arrowleft']=false; keys['arrowright']=false; keys['z']=false;
touchStartX = null;
});

// resume audio on first user interaction if needed
document.addEventListener('pointerdown', ()=>{ if(!audioCtx) startAudio(); }, {once:true});
</script>
</body>
</html>




[Please log in to post a comment]