C50BARZ's picture
please make a full screen 16-bit style mario style game, can shoot fireballs and jump on to kill enemies witch cool illustrated background
ff3dd11 verified
(() => {
// Game Config
const WORLD = { width: 4200, height: 180 };
const GRAVITY = 0.35;
const FRICTION = 0.80;
const MAX_FALL = 8.5;
const PLAYER = { w: 14, h: 16, speed: 1.25, jump: 6.5, fireCooldown: 280 };
const CAMERA = { lerp: 0.12, shake: 0 };
const TILE = 16;
// Colors (choose pleasing 16-bit palette)
const COLORS = {
sky: "#14213d",
sky2: "#0f1a35",
cloud: "#e9f0ff",
cloud2: "#d6e4ff",
hill: "#274c77",
hill2: "#1d3a5b",
grass: "#41a55d",
dirt: "#6b4f3a",
dirt2: "#5a4130",
pipe: "#2f9e44",
pipeDark: "#237a34",
enemy: "#8b5e34",
enemyDark: "#704a2a",
playerHat: "#e63946",
playerBody: "#1d3557",
playerFace: "#ffe0bd",
fire1: "#ff9f1c",
fire2: "#ffd166",
coin1: "#ffd166",
coin2: "#fca311",
uiText: "#ffffff",
overlay: "rgba(0,0,0,0.55)"
};
// DOM
const bgCanvas = document.getElementById("bg");
const gameCanvas = document.getElementById("game");
const bgCtx = bgCanvas.getContext("2d");
const ctx = gameCanvas.getContext("2d");
ctx.imageSmoothingEnabled = false;
bgCtx.imageSmoothingEnabled = false;
const scoreEl = document.getElementById("score");
const coinsEl = document.getElementById("coins");
const livesEl = document.getElementById("lives");
const messageBox = document.getElementById("messageBox");
const messageTitle = document.getElementById("messageTitle");
const messageText = document.getElementById("messageText");
// Input
const keys = {
left: false, right: false, up: false, fire: false,
pause: false, mute: false
};
// Touch controls
const btnLeft = document.getElementById("btnLeft");
const btnRight = document.getElementById("btnRight");
const btnJump = document.getElementById("btnJump");
const btnFire = document.getElementById("btnFire");
// Camera
const camera = { x: 0, y: 0, w: 320, h: 180 };
// Game State
let state = "playing"; // playing, paused, gameover, win
let score = 0;
let coins = 0;
let lives = 3;
let time = 0;
let muted = false;
// Entities
const player = {
x: 32, y: 0, w: PLAYER.w, h: PLAYER.h,
vx: 0, vy: 0, dir: 1, onGround: false,
canFireAt: 0, spawnX: 32, spawnY: 0
};
/** Platforms and Pipes (simple AABB rectangles) */
const solids = [
// Ground
{ x: 0, y: 150, w: WORLD.width, h: 60 },
// Small hills / platforms
{ x: 240, y: 136, w: 90, h: 8 },
{ x: 420, y: 122, w: 90, h: 8 },
{ x: 650, y: 142, w: 80, h: 8 },
{ x: 980, y: 120, w: 120, h: 8 },
{ x: 1270, y: 128, w: 100, h: 8 },
{ x: 1550, y: 110, w: 80, h: 8 },
{ x: 1800, y: 140, w: 140, h: 8 },
{ x: 2150, y: 118, w: 100, h: 8 },
{ x: 2450, y: 130, w: 90, h: 8 },
{ x: 2780, y: 115, w: 120, h: 8 },
{ x: 3120, y: 140, w: 120, h: 8 },
{ x: 3450, y: 120, w: 100, h: 8 },
{ x: 3720, y: 135, w: 90, h: 8 },
];
// Pipes
const pipes = [
{ x: 600, y: 150 - 24, w: 24, h: 24 },
{ x: 1320, y: 150 - 32, w: 24, h: 32 },
{ x: 2000, y: 150 - 40, w: 24, h: 40 },
{ x: 2680, y: 150 - 28, w: 24, h: 28 },
{ x: 3300, y: 150 - 36, w: 24, h: 36 },
];
solids.push(...pipes);
/** Enemies */
const enemies = [];
const enemySpawn = [300, 480, 700, 1060, 1300, 1700, 2100, 2450, 2890, 3230, 3600, 3920];
enemySpawn.forEach((x, i) => {
enemies.push({
x, y: 0, w: 14, h: 12,
vx: (i % 2 === 0 ? -0.6 : -0.8),
vy: 0,
dir: -1, onGround: false, dead: false, squash: 0
});
});
/** Fireballs */
const fireballs = [];
/** Coins */
const coinsArr = [];
const coinSpawn = [
[260, 120], [440, 108], [680, 128], [1000, 100], [1280, 110],
[1560, 95], [1810, 120], [2160, 100], [2460, 110], [2790, 95],
[3140, 120], [3460, 100], [3740, 116]
];
coinSpawn.forEach(([x, y]) => {
coinsArr.push({ x, y, w: 6, h: 8, t: Math.random() * Math.PI * 2, collected: false });
});
// Resize handling
function resize() {
bgCanvas.width = window.innerWidth;
bgCanvas.height = window.innerHeight;
gameCanvas.width = window.innerWidth;
gameCanvas.height = window.innerHeight;
camera.w = gameCanvas.width;
camera.h = gameCanvas.height;
}
window.addEventListener("resize", resize);
resize();
// Input: Keyboard
const onKey = (e, down) => {
const k = e.key.toLowerCase();
if (["arrowleft","arrowright","arrowup"," "].includes(e.key.toLowerCase())) e.preventDefault();
if (k === "arrowleft" || k === "a") keys.left = down;
if (k === "arrowright" || k === "d") keys.right = down;
if (k === "arrowup" || k === "w" || e.key === " ") keys.up = down;
if (k === "f") keys.fire = down;
if (k === "p" && down) togglePause();
if (k === "m" && down) muted = !muted;
if (k === "r" && down) resetGame();
};
window.addEventListener("keydown", (e)=>onKey(e, true));
window.addEventListener("keyup", (e)=>onKey(e, false));
// Touch helpers
const press = (setter) => (down) => { setter(down); };
btnLeft.addEventListener("pointerdown", press(v => keys.left = v));
btnLeft.addEventListener("pointerup", press(v => keys.left = v));
btnLeft.addEventListener("pointerleave", press(v => keys.left = v));
btnRight.addEventListener("pointerdown", press(v => keys.right = v));
btnRight.addEventListener("pointerup", press(v => keys.right = v));
btnRight.addEventListener("pointerleave", press(v => keys.right = v));
btnJump.addEventListener("pointerdown", press(v => keys.up = v));
btnJump.addEventListener("pointerup", press(v => keys.up = v));
btnJump.addEventListener("pointerleave", press(v => keys.up = v));
btnFire.addEventListener("pointerdown", press(v => keys.fire = v));
btnFire.addEventListener("pointerup", press(v => keys.fire = v));
btnFire.addEventListener("pointerleave", press(v => keys.fire = v));
// Audio (simple blips)
const AudioCtx = window.AudioContext || window.webkitAudioContext;
const actx = new AudioCtx();
function beep(freq = 440, dur = 0.06, type = "square", vol = 0.05) {
if (muted) return;
const o = actx.createOscillator();
const g = actx.createGain();
o.type = type;
o.frequency.value = freq;
g.gain.value = vol;
o.connect(g).connect(actx.destination);
o.start();
o.stop(actx.currentTime + dur);
}
// Utils
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
function rectsOverlap(a, b) {
return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
}
function collideAxis(entity, dx, dy) {
// Move X
if (dx !== 0) {
entity.x += dx;
for (const s of solids) {
if (rectsOverlap(entity, s)) {
if (dx > 0) entity.x = s.x - entity.w;
else entity.x = s.x + s.w;
entity.vx = 0;
}
}
}
// Move Y
let onGround = false;
if (dy !== 0) {
entity.y += dy;
for (const s of solids) {
if (rectsOverlap(entity, s)) {
if (dy > 0) { // falling
entity.y = s.y - entity.h;
entity.vy = 0;
onGround = true;
} else { // jumping
entity.y = s.y + s.h;
entity.vy = 0;
}
}
}
}
return onGround;
}
// Update player
function updatePlayer(dt) {
// Horizontal
if (keys.left) player.vx -= PLAYER.speed * dt;
if (keys.right) player.vx += PLAYER.speed * dt;
if (!(keys.left || keys.right)) player.vx *= FRICTION;
player.vx = clamp(player.vx, -2.4, 2.4);
if (Math.abs(player.vx) > 0.05) player.dir = player.vx > 0 ? 1 : -1;
// Jump
if (keys.up && player.onGround) {
player.vy = -PLAYER.jump;
player.onGround = false;
beep(600, 0.07, "square", 0.05);
CAMERA.shake = Math.max(CAMERA.shake, 2);
}
// Gravity
player.vy += GRAVITY * dt;
player.vy = clamp(player.vy, -99, MAX_FALL);
// Move and collide
const onGroundNow = collideAxis(player, player.vx * dt, 0) === true;
const prevVy = player.vy;
const landed = collideAxis(player, 0, player.vy * dt) === true;
if (landed) {
player.onGround = true;
if (prevVy > 4.5) {
CAMERA.shake = Math.max(CAMERA.shake, prevVy * 0.3);
beep(120, 0.05, "sawtooth", 0.03);
}
} else {
player.onGround = onGroundNow;
}
// Fire
if (keys.fire && performance.now() > player.canFireAt) {
shootFireball();
player.canFireAt = performance.now() + PLAYER.fireCooldown;
}
// Clamp world
player.x = clamp(player.x, 0, WORLD.width - player.w);
if (player.y > WORLD.height + 100) {
// fell off world
loseLife(true);
}
}
function shootFireball() {
const fb = {
x: player.x + player.w / 2 + player.dir * 8,
y: player.y + 6,
w: 5, h: 5,
vx: 3.2 * player.dir,
vy: -0.2,
life: 1400
};
fireballs.push(fb);
beep(900, 0.05, "square", 0.04);
}
function updateFireballs(dt) {
for (let i = fireballs.length - 1; i >= 0; i--) {
const f = fireballs[i];
f.vy += 0.1 * dt;
f.x += f.vx * dt;
f.y += f.vy * dt;
f.life -= dt;
let remove = false;
// Collide with solids
for (const s of solids) {
if (rectsOverlap(f, s)) { remove = true; break; }
}
// Hit enemies
if (!remove) {
for (const e of enemies) {
if (!e.dead && rectsOverlap(f, e)) {
e.dead = true;
e.squash = 0.3;
addScore(100);
beep(300, 0.06, "triangle", 0.05);
remove = true;
break;
}
}
}
if (remove || f.life <= 0) fireballs.splice(i, 1);
}
}
function updateEnemies(dt) {
for (const e of enemies) {
if (e.dead) {
e.squash -= 0.02 * dt;
if (e.squash <= -0.2) e.y += 0.2 * dt;
continue;
}
// Basic AI: walk, turn on edge or hit wall
const ahead = { x: e.x + (e.vx > 0 ? e.w + 1 : -1), y: e.y + e.h + 1, w: 1, h: 1 };
// Edge detection
let onGround = false;
for (const s of solids) {
if (rectsOverlap(ahead, s)) { onGround = true; break; }
}
if (!onGround) e.vx = -e.vx;
// Gravity
e.vy += GRAVITY * dt;
e.vy = clamp(e.vy, -99, MAX_FALL);
// Move with collision
collideAxis(e, e.vx * dt, 0);
const landed = collideAxis(e, 0, e.vy * dt);
if (landed) e.onGround = true;
// Reverse on solid hit
for (const s of solids) {
if (rectsOverlap(e, s)) {
if (e.x + e.w > s.x && e.x < s.x) e.vx = Math.abs(e.vx);
else if (e.x < s.x + s.w && e.x + e.w > s.x + s.w) e.vx = -Math.abs(e.vx);
}
}
// Player interaction
if (rectsOverlap(player, e) && !e.dead) {
// Stomp if falling and above enemy
const playerBottom = player.y + player.h;
const enemyTop = e.y;
if (player.vy > 0.2 && playerBottom - enemyTop < 10) {
e.dead = true;
e.squash = 0.3;
player.vy = -4.8; // bounce
addScore(100);
beep(340, 0.07, "triangle", 0.05);
} else {
// Player hit
loseLife(false);
}
}
}
}
function updateCoins(dt) {
for (const c of coinsArr) {
c.t += 0.05 * dt;
if (!c.collected && rectsOverlap(player, c)) {
c.collected = true;
coins += 1;
addScore(200);
beep(880, 0.05, "sine", 0.05);
}
}
}
function addScore(n) {
score += n;
scoreEl.textContent = score.toString().padStart(6, "0");
}
function loseLife(fell) {
if (state !== "playing") return;
lives -= 1;
livesEl.textContent = lives.toString();
beep(160, 0.25, "sawtooth", 0.08);
CAMERA.shake = 10;
if (lives < 0) {
state = "gameover";
showMessage("GAME OVER", "Press R to Restart");
} else {
// Respawn
player.x = player.spawnX;
player.y = player.spawnY;
player.vx = 0;
player.vy = 0;
}
}
function checkWin() {
if (player.x > WORLD.width - 220 && state === "playing") {
state = "win";
showMessage("YOU WIN!", "Press R to Play Again");
addScore(1000);
}
}
function showMessage(title, text) {
messageTitle.textContent = title;
messageText.textContent = text;
messageBox.classList.remove("hidden");
}
function hideMessage() {
messageBox.classList.add("hidden");
}
function togglePause() {
if (state === "playing") {
state = "paused";
showMessage("PAUSED", "Press P to Resume");
} else if (state === "paused") {
state = "playing";
hideMessage();
}
}
function resetGame() {
// Reset state
score = 0; coins = 0; lives = 3; time = 0;
player.x = 32; player.y = 0; player.vx = 0; player.vy = 0;
enemies.forEach((e, i) => {
e.dead = false; e.squash = 0;
e.x = enemySpawn[i]; e.y = 0; e.vx = (i % 2 === 0 ? -0.6 : -0.8); e.vy = 0;
});
coinsArr.forEach(c => c.collected = false);
fireballs.length = 0;
scoreEl.textContent = "000000";
coinsEl.textContent = "0";
livesEl.textContent = "3";
state = "playing";
hideMessage();
}
// Camera follows player with easing
function updateCamera(dt) {
const targetX = clamp(player.x + player.w / 2 - camera.w / 2, 0, WORLD.width - camera.w);
camera.x += (targetX - camera.x) * CAMERA.lerp * dt;
// Parallax background offset
}
// Background drawing (parallax)
function drawBackground() {
// Sky gradient is via CSS; draw clouds/hills on bg canvas
const w = bgCanvas.width, h = bgCanvas.height;
bgCtx.clearRect(0, 0, w, h);
// Parallax offsets
const camX = camera.x * 0.3;
const hillX = camera.x * 0.5;
// Clouds
function cloud(x, y, s, c1, c2) {
bgCtx.fillStyle = c1;
bgCtx.beginPath();
bgCtx.ellipse(x, y, 18*s, 12*s, 0, 0, Math.PI*2);
bgCtx.ellipse(x+16*s, y+2*s, 16*s, 10*s, 0, 0, Math.PI*2);
bgCtx.ellipse(x-16*s, y+3*s, 14*s, 9*s, 0, 0, Math.PI*2);
bgCtx.fill();
bgCtx.fillStyle = c2;
bgCtx.beginPath();
bgCtx.ellipse(x+2, y+5, 14*s, 8*s, 0, 0, Math.PI*2);
bgCtx.fill();
}
for (let i=0;i<6;i++){
const cx = ((i*280 - camX*0.8) % (w+300)) - 150;
cloud(cx, 80 + (i%2)*12, 1 + (i%3)*0.2, COLORS.cloud, COLORS.cloud2);
}
// Hills
function hill(x, baseY, r, color) {
bgCtx.fillStyle = color;
bgCtx.beginPath();
bgCtx.arc(x, baseY, r, Math.PI, 0);
bgCtx.lineTo(x+r, baseY+200);
bgCtx.lineTo(x-r, baseY+200);
bgCtx.closePath();
bgCtx.fill();
}
for (let i=0;i<8;i++) {
const hx = ((i*280 - hillX) % (w+400)) - 200;
hill(hx, h-40, 140 + (i%3)*20, COLORS.hill);
hill(hx+140, h-36, 120 + ((i+1)%3)*16, COLORS.hill2);
}
}
// Draw entities and tiles
function draw() {
// Camera shake
if (CAMERA.shake > 0) CAMERA.shake *= 0.9;
const shakeX = (Math.random() - 0.5) * CAMERA.shake;
const shakeY = (Math.random() - 0.5) * CAMERA.shake;
// Clear
ctx.clearRect(0, 0, gameCanvas.width, gameCanvas.height);
ctx.save();
ctx.translate(-Math.floor(camera.x) + shakeX, -Math.floor(camera.y) + shakeY);
// Ground tiles
for (let x = 0; x < WORLD.width; x += TILE) {
const baseY = 150;
ctx.fillStyle = COLORS.dirt;
ctx.fillRect(x, baseY, TILE, WORLD.height - baseY);
// top grass
ctx.fillStyle = COLORS.grass;
ctx.fillRect(x, baseY - 2, TILE, 2);
// speckles
ctx.fillStyle = COLORS.dirt2;
for (let i = 0; i < 3; i++) {
const sx = x + (i * 5 % 12) + 2;
const sy = baseY + 6 + (i * 7 % 10);
ctx.fillRect(sx, sy, 2, 2);
}
}
// Platforms
for (const s of solids) {
if (s.y < 150) {
ctx.fillStyle = COLORS.dirt;
ctx.fillRect(s.x, s.y, s.w, s.h);
ctx.fillStyle = COLORS.grass;
ctx.fillRect(s.x, s.y - 2, s.w, 2);
}
}
// Pipes
for (const p of pipes) {
ctx.fillStyle = COLORS.pipe;
ctx.fillRect(p.x, p.y, p.w, p.h);
ctx.fillStyle = COLORS.pipeDark;
ctx.fillRect(p.x + 2, p.y + 2, p.w - 4, p.h - 4);
// lip
ctx.fillStyle = COLORS.pipe;
ctx.fillRect(p.x - 4, p.y - 4, p.w + 8, 4);
}
// Coins
for (const c of coinsArr) {
if (c.collected) continue;
const bob = Math.sin(c.t) * 2;
ctx.fillStyle = COLORS.coin2;
ctx.fillRect(c.x, c.y + bob, c.w, c.h);
ctx.fillStyle = COLORS.coin1;
ctx.fillRect(c.x + 1, c.y + 1 + bob, c.w - 2, c.h - 2);
// highlight
ctx.fillStyle = "rgba(255,255,255,0.5)";
ctx.fillRect(c.x + 1, c.y + 2 + bob, 1, c.h - 4);
}
// Enemies
for (const e of enemies) {
if (e.dead) {
// Squash effect
ctx.fillStyle = COLORS.enemyDark;
ctx.fillRect(e.x, e.y + e.h - 4, e.w, 4);
continue;
}
ctx.fillStyle = COLORS.enemy;
ctx.fillRect(e.x, e.y, e.w, e.h);
ctx.fillStyle = COLORS.enemyDark;
ctx.fillRect(e.x + 2, e.y + e.h - 3, e.w - 4, 3);
// Eyes
ctx.fillStyle = "#111";
ctx.fillRect(e.x + 3, e.y + 3, 2, 2);
ctx.fillRect(e.x + e.w - 5, e.y + 3, 2, 2);
}
// Fireballs
for (const f of fireballs) {
ctx.fillStyle = COLORS.fire1;
ctx.fillRect(f.x - 2, f.y - 2, 4, 4);
ctx.fillStyle = COLORS.fire2;
ctx.fillRect(f.x - 1, f.y - 1, 2, 2);
}
// Player
drawPlayer();
// Goal flag near end
const flagX = WORLD.width - 160, flagY = 110;
ctx.fillStyle = "#444";
ctx.fillRect(flagX, flagY - 40, 3, 60);
ctx.fillStyle = "#e63946";
ctx.fillRect(flagX + 3, flagY - 38, 18, 12);
ctx.fillStyle = "#fff";
ctx.fillRect(flagX + 9, flagY - 35, 6, 3);
ctx.restore();
// HUD update
coinsEl.textContent = coins.toString();
}
function drawPlayer() {
const p = player;
// Shadow
ctx.fillStyle = "rgba(0,0,0,0.25)";
ctx.fillRect(p.x - 2, p.y + p.h - 2, p.w + 4, 3);
// Body
ctx.fillStyle = COLORS.playerBody;
ctx.fillRect(p.x, p.y + 4, p.w, p.h - 4);
// Hat
ctx.fillStyle = COLORS.playerHat;
ctx.fillRect(p.x, p.y, p.w, 4);
// Face
ctx.fillStyle = COLORS.playerFace;
ctx.fillRect(p.x + 3, p.y + 5, 8, 6);
// Eye
ctx.fillStyle = "#111";
if (p.dir >= 0) ctx.fillRect(p.x + 8, p.y + 6, 2, 2);
else ctx.fillRect(p.x + 4, p.y + 6, 2, 2);
// Shoes
ctx.fillStyle = "#3d3d3d";
ctx.fillRect(p.x + 2, p.y + p.h - 2, 4, 2);
ctx.fillRect(p.x + p.w - 6, p.y + p.h - 2, 4, 2);
}
// Main loop
let last = performance.now();
function loop(now) {
const dtMs = now - last;
last = now;
const dt = clamp(dtMs / (1000 / 60), 0.2, 3); // normalize to 60fps units
time += dtMs;
if (state === "playing") {
updatePlayer(dt);
updateFireballs(dt);
updateEnemies(dt);
updateCoins(dt);
checkWin();
updateCamera(dt);
}
// Redraw bg and fg
drawBackground();
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
// Initialize
resetGame();
// Prevent context menu on touch
window.addEventListener("contextmenu", e => e.preventDefault(), { passive: false });
})();