Game
Anonymous
plain_text
02/07/2026 5:38 PM
30.0 KB
8
Indexable
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Spider-Man: Web Runner</title>
<style>
body {
margin: 0;
background-color: #050510;
overflow: hidden;
font-family: 'Arial', sans-serif;
touch-action: none; /* Prevent browser zooming/scrolling */
}
#game-container {
position: relative;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(to bottom, #000022, #001133);
}
/* The Game Canvas */
canvas {
image-rendering: pixelated;
box-shadow: 0 0 20px rgba(0, 150, 255, 0.3);
background: transparent; /* Let CSS gradient show through */
}
/* UI Overlays */
#ui-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.hud {
padding: 20px;
color: white;
font-size: 20px;
font-weight: bold;
text-shadow: 2px 2px 0 #000;
display: flex;
justify-content: space-between;
}
/* Mobile Controls */
#controls {
pointer-events: auto;
display: flex;
justify-content: space-between;
padding: 20px;
padding-bottom: 40px;
}
.btn-group {
display: flex;
gap: 15px;
}
.btn {
width: 70px;
height: 70px;
background: rgba(255, 255, 255, 0.15);
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
backdrop-filter: blur(4px);
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 24px;
user-select: none;
}
.btn:active {
background: rgba(255, 0, 0, 0.4);
transform: scale(0.95);
}
.btn-action {
background: rgba(255, 50, 50, 0.2);
border-color: rgba(255, 50, 50, 0.5);
}
.btn-jump {
background: rgba(50, 100, 255, 0.2);
border-color: rgba(50, 100, 255, 0.5);
}
/* Messages */
#message-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
color: white;
display: none; /* Hidden by default */
pointer-events: auto;
}
h1 { font-size: 40px; margin-bottom: 10px; color: #ff3333; text-shadow: 2px 2px #000; }
p { font-size: 18px; margin-bottom: 20px; }
button.start-btn {
padding: 15px 40px;
font-size: 20px;
background: #e62429;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
box-shadow: 0 4px #990000;
}
button.start-btn:active {
box-shadow: 0 0 #990000;
transform: translateY(4px);
}
</style>
</head>
<body>
<div id="game-container">
<canvas id="gameCanvas" width="800" height="450"></canvas>
<div id="ui-layer">
<div class="hud">
<span id="score-display">SCORE: 0</span>
<span id="level-display">LEVEL: 1</span>
</div>
<div id="message-overlay">
<h1 id="msg-title">SPIDER-MAN</h1>
<p id="msg-subtitle">Web Runner Prototype</p>
<button class="start-btn" onclick="startGame()">START MISSION</button>
</div>
<div id="controls">
<div class="btn-group">
<div class="btn" id="btn-left">⬅️</div>
<div class="btn" id="btn-right">➡️</div>
</div>
<div class="btn-group">
<div class="btn btn-action" id="btn-shoot">🕸️</div>
<div class="btn btn-jump" id="btn-jump">⬆️</div>
</div>
</div>
</div>
</div>
<script>
/**
* SENIOR GAMEPLAY ENGINEER - CORE ENGINE
* * Architecture:
* 1. Game Loop: Updates logic and renders frames.
* 2. Entity Component System (Simplified): Player, Enemies, Blocks, Projectiles.
* 3. Level Manager: ASCII map parsing.
* 4. Input Manager: Maps Touch/Keyboard to actions.
*/
// --- CONFIGURATION ---
const GRAVITY = 0.5;
const TERMINAL_VELOCITY = 12;
const FRICTION = 0.8;
const PLAYER_SPEED = 0.6;
const JUMP_FORCE = -11;
const BULLET_SPEED = 8;
// --- STATE MANAGEMENT ---
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let gameRunning = false;
let currentLevelIdx = 0;
let score = 0;
let cameraX = 0;
// Input State
const keys = { left: false, right: false, up: false, shoot: false };
// Game Objects Arrays
let platforms = [];
let enemies = [];
let bullets = [];
let particles = [];
let items = []; // Coins, etc.
// --- ASSETS (GENERATIVE DRAWING) ---
// We draw sprites via code to ensure functionality without external files.
// --- CLASSES ---
class Player {
constructor(x, y) {
this.x = x;
this.y = y;
this.w = 24;
this.h = 36;
this.vx = 0;
this.vy = 0;
this.grounded = false;
this.facingRight = true;
this.cooldown = 0;
}
update() {
// Horizontal Movement
if (keys.left) {
this.vx -= PLAYER_SPEED;
this.facingRight = false;
}
if (keys.right) {
this.vx += PLAYER_SPEED;
this.facingRight = true;
}
// Friction & Limits
this.vx *= FRICTION;
// Jump
if (keys.up && this.grounded) {
this.vy = JUMP_FORCE;
this.grounded = false;
}
// Gravity
this.vy += GRAVITY;
if(this.vy > TERMINAL_VELOCITY) this.vy = TERMINAL_VELOCITY;
// Apply Velocity
this.x += this.vx;
this.checkHorizontalCollisions();
this.y += this.vy;
this.grounded = false; // Assume falling until collision proves otherwise
this.checkVerticalCollisions();
// Screen Bounds (Death Logic)
if (this.y > canvas.height + 100) {
deathHandler("FELL TO DEATH");
}
// Shooting
if (keys.shoot && this.cooldown <= 0) {
bullets.push(new Bullet(
this.x + (this.facingRight ? this.w : 0),
this.y + 10,
this.facingRight ? BULLET_SPEED : -BULLET_SPEED,
true
));
this.cooldown = 20; // Frames between shots
}
if (this.cooldown > 0) this.cooldown--;
}
checkHorizontalCollisions() {
for (let p of platforms) {
if (rectIntersect(this.x, this.y, this.w, this.h, p.x, p.y, p.w, p.h)) {
// Collision detected, move back
if (this.vx > 0) this.x = p.x - this.w;
else if (this.vx < 0) this.x = p.x + p.w;
this.vx = 0;
}
}
}
checkVerticalCollisions() {
for (let p of platforms) {
if (rectIntersect(this.x, this.y, this.w, this.h, p.x, p.y, p.w, p.h)) {
if (this.vy > 0) {
// Landing on top
this.y = p.y - this.h;
this.vy = 0;
this.grounded = true;
// Kill enemy by jumping on them (Mario Logic) is handled in Enemy loop
} else if (this.vy < 0) {
// Hitting head on bottom
this.y = p.y + p.h;
this.vy = 0;
// Mystery Box Logic
if (p.type === 'mystery' && p.active) {
p.active = false;
spawnCoin(p.x, p.y - 30);
}
}
}
}
}
draw() {
// Spiderman Body (Red)
ctx.fillStyle = '#e62429';
ctx.fillRect(this.x - cameraX, this.y, this.w, this.h);
// Mask (Blue parts)
ctx.fillStyle = '#0066cc';
ctx.fillRect(this.x - cameraX + 4, this.y + 20, this.w - 8, 10);
// Eyes
ctx.fillStyle = 'white';
let eyeX = this.facingRight ? 12 : 4;
ctx.fillRect(this.x - cameraX + eyeX, this.y + 5, 8, 5);
}
}
class Enemy {
constructor(x, y, type) {
this.x = x;
this.y = y;
this.w = 30;
this.h = 30;
this.type = type; // 'walker', 'shooter'
this.vx = (type === 'walker') ? 2 : 0;
this.vy = 0;
this.patrolStart = x;
this.patrolDist = 100;
this.shootTimer = 0;
this.hp = 1;
}
update() {
// Gravity
this.vy += GRAVITY;
this.y += this.vy;
// Floor Collision
let grounded = false;
for (let p of platforms) {
if (rectIntersect(this.x, this.y, this.w, this.h, p.x, p.y, p.w, p.h)) {
if (this.vy > 0) {
this.y = p.y - this.h;
this.vy = 0;
grounded = true;
}
}
}
if (this.type === 'walker') {
this.x += this.vx;
// Turn around on edges or patrol limits
if (!grounded) {
this.vx *= -1; // Turn if about to fall
this.x += this.vx * 2; // Nudge back
}
// Simple Patrol logic
if (Math.abs(this.x - this.patrolStart) > this.patrolDist) {
this.vx *= -1;
}
}
if (this.type === 'shooter') {
// Look at player
let dx = player.x - this.x;
this.shootTimer++;
if (this.shootTimer > 120 && Math.abs(dx) < 400) { // Shoot every 2 seconds if close
let dir = dx > 0 ? 1 : -1;
bullets.push(new Bullet(this.x + 15, this.y + 10, dir * 5, false));
this.shootTimer = 0;
}
}
// Collision with Player (DEATH)
if (rectIntersect(this.x, this.y, this.w, this.h, player.x, player.y, player.w, player.h)) {
// Check if player jumped on top
if (player.vy > 0 && player.y + player.h < this.y + (this.h/2)) {
this.die();
player.vy = -6; // Bounce
} else {
deathHandler("TOUCHED ENEMY");
}
}
}
die() {
this.hp = 0;
score += 100;
createExplosion(this.x, this.y, '#00ff00');
}
draw() {
ctx.fillStyle = (this.type === 'walker') ? '#33cc33' : '#9900cc'; // Green (Goblin) or Purple (Venom-ish)
ctx.fillRect(this.x - cameraX, this.y, this.w, this.h);
// Eyes
ctx.fillStyle = 'yellow';
ctx.fillRect(this.x - cameraX + 5, this.y + 5, 5, 5);
ctx.fillRect(this.x - cameraX + 20, this.y + 5, 5, 5);
}
}
class Bullet {
constructor(x, y, vx, isPlayer) {
this.x = x;
this.y = y;
this.w = 8;
this.h = 8;
this.vx = vx;
this.isPlayer = isPlayer;
this.active = true;
}
update() {
this.x += this.vx;
// Collide with Walls
for (let p of platforms) {
if (rectIntersect(this.x, this.y, this.w, this.h, p.x, p.y, p.w, p.h)) {
this.active = false;
}
}
if (this.isPlayer) {
// Hit Enemy
enemies.forEach(e => {
if (rectIntersect(this.x, this.y, this.w, this.h, e.x, e.y, e.w, e.h)) {
e.die();
this.active = false;
}
});
} else {
// Hit Player (Instant Death)
if (rectIntersect(this.x, this.y, this.w, this.h, player.x, player.y, player.w, player.h)) {
deathHandler("SHOT BY ENEMY");
}
}
// Out of bounds cleanup
if (this.x - cameraX < -100 || this.x - cameraX > canvas.width + 100) this.active = false;
}
draw() {
ctx.fillStyle = this.isPlayer ? '#ffffff' : '#ff00ff'; // White web or Purple laser
ctx.beginPath();
ctx.arc(this.x - cameraX + 4, this.y + 4, 4, 0, Math.PI*2);
ctx.fill();
}
}
class Platform {
constructor(x, y, w, h, type) {
this.x = x; this.y = y; this.w = w; this.h = h; this.type = type;
this.active = true; // For mystery boxes
}
draw() {
if (this.type === 'mystery') {
ctx.fillStyle = this.active ? '#FFD700' : '#8B4513'; // Gold or Brown
ctx.fillRect(this.x - cameraX, this.y, this.w, this.h);
if(this.active) {
ctx.fillStyle = 'black';
ctx.fillText('?', this.x - cameraX + 10, this.y + 24);
}
} else if (this.type === 'ground') {
// Grass top, dirt body
ctx.fillStyle = '#228b22'; // Forest Green
ctx.fillRect(this.x - cameraX, this.y, this.w, this.h);
ctx.fillStyle = '#006400'; // Darker detail
ctx.fillRect(this.x - cameraX, this.y + 5, this.w, this.h - 5);
} else if (this.type === 'finish') {
ctx.fillStyle = '#fff';
ctx.fillRect(this.x - cameraX, this.y, 10, this.h);
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.moveTo(this.x - cameraX + 10, this.y);
ctx.lineTo(this.x - cameraX + 40, this.y + 15);
ctx.lineTo(this.x - cameraX + 10, this.y + 30);
ctx.fill();
}
}
}
class Item {
constructor(x, y, type) {
this.x = x; this.y = y; this.w = 16; this.h = 16; this.type = type;
this.vy = -5; // Pop up
this.active = true;
}
update() {
this.vy += GRAVITY;
this.y += this.vy;
// Simple ground check
for (let p of platforms) {
if (rectIntersect(this.x, this.y, this.w, this.h, p.x, p.y, p.w, p.h)) {
this.y = p.y - this.h;
this.vy = 0;
}
}
// Player collection
if (rectIntersect(this.x, this.y, this.w, this.h, player.x, player.y, player.w, player.h)) {
score += 50;
this.active = false;
}
}
draw() {
ctx.fillStyle = '#FFD700';
ctx.beginPath();
ctx.arc(this.x - cameraX + 8, this.y + 8, 8, 0, Math.PI*2);
ctx.fill();
}
}
// --- LEVEL DESIGN ---
// Maps are 20 tiles high. '#' = ground, 'M' = mystery, 'E' = walker, 'S' = shooter, 'F' = finish
const maps = [
// Level 1: The Basics
[
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" M M M ",
" ",
" E ",
"####################",
"####################",
" ",
" ", // Pit
" ### ######",
" ### ######",
" E ### ######",
"####### ### ######",
"####### ### F ######",
"####### ############"
],
// Level 2: Verticality
[
" ",
" ",
" S ",
" ##### ",
" ",
" ### ### ",
" # # ",
" M # E # M ",
" ### ####### ### ",
" # # ",
" # S # ",
"## ##### ## ",
" ## ## ",
" ## ## ",
" ## ## ",
" ## ## F",
" ## E ##########",
"########################",
"########################",
"########################"
],
// Level 3: The Gauntlet
[
" ",
" ",
" S S S ",
" ### ### ### ",
" ",
" M M ",
" ### ### ",
" ",
" E E E ",
"##### ##### ##### ",
" ",
" ",
" ## ## ",
" ## ## F",
" ## ## ## #######",
" ## #######",
" ## E E #######",
"#######################",
"#######################",
"#######################"
]
];
// --- UTILS ---
function rectIntersect(x1, y1, w1, h1, x2, y2, w2, h2) {
return x2 < x1 + w1 && x2 + w2 > x1 && y2 < y1 + h1 && y2 + h2 > y1;
}
function createExplosion(x, y, color) {
for(let i=0; i<8; i++) {
particles.push({
x: x, y: y,
vx: (Math.random() - 0.5) * 10,
vy: (Math.random() - 0.5) * 10,
life: 20,
color: color
});
}
}
function spawnCoin(x, y) {
items.push(new Item(x, y, 'coin'));
}
// --- CORE FUNCTIONS ---
let player;
function initLevel(levelIndex) {
currentLevelIdx = levelIndex;
platforms = [];
enemies = [];
bullets = [];
particles = [];
items = [];
const mapData = maps[levelIndex];
const tileSize = 40;
for (let row = 0; row < mapData.length; row++) {
let line = mapData[row];
for (let col = 0; col < line.length; col++) {
let char = line[col];
let x = col * tileSize;
let y = row * tileSize;
if (char === '#') platforms.push(new Platform(x, y, tileSize, tileSize, 'ground'));
if (char === 'M') platforms.push(new Platform(x, y, tileSize, tileSize, 'mystery'));
if (char === 'E') enemies.push(new Enemy(x, y, 'walker'));
if (char === 'S') enemies.push(new Enemy(x, y, 'shooter'));
if (char === 'F') platforms.push(new Platform(x, y, tileSize, tileSize * 2, 'finish'));
}
}
player = new Player(100, 100);
document.getElementById('level-display').innerText = "LEVEL: " + (levelIndex + 1);
}
function deathHandler(reason) {
gameRunning = false;
document.getElementById('msg-title').innerText = "GAME OVER";
document.getElementById('msg-subtitle').innerText = reason;
document.querySelector('.start-btn').innerText = "RETRY LEVEL";
document.getElementById('message-overlay').style.display = 'block';
}
function levelComplete() {
if (currentLevelIdx < maps.length - 1) {
initLevel(currentLevelIdx + 1);
} else {
gameRunning = false;
document.getElementById('msg-title').innerText = "VICTORY!";
document.getElementById('msg-subtitle').innerText = "All Levels Cleared";
document.querySelector('.start-btn').innerText = "PLAY AGAIN";
document.getElementById('message-overlay').style.display = 'block';
currentLevelIdx = -1; // Reset to 0 next time
}
}
function startGame() {
document.getElementById('message-overlay').style.display = 'none';
if(currentLevelIdx === -1) score = 0;
initLevel(currentLevelIdx === -1 ? 0 : currentLevelIdx);
gameRunning = true;
gameLoop();
}
function gameLoop() {
if (!gameRunning) return;
// 1. Clear
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 2. Logic Update
player.update();
// Camera Logic (Keep player in center)
cameraX = player.x - 300;
if (cameraX < 0) cameraX = 0;
// Entity Updates
enemies.forEach((e, i) => {
if (e.hp <= 0) enemies.splice(i, 1);
else e.update();
});
bullets.forEach((b, i) => {
if (!b.active) bullets.splice(i, 1);
else b.update();
});
items.forEach((item, i) => {
if (!item.active) items.splice(i, 1);
else item.update();
});
particles.forEach((p, i) => {
p.x += p.vx;
p.y += p.vy;
p.life--;
if (p.life <= 0) particles.splice(i, 1);
});
// Check Level Complete
platforms.forEach(p => {
if (p.type === 'finish' && rectIntersect(player.x, player.y, player.w, player.h, p.x, p.y, p.w, p.h)) {
levelComplete();
}
});
// 3. Render
// Draw Stars (Background)
ctx.fillStyle = '#ffffff';
for(let i=0; i<50; i++) {
let starX = (i * 100 - cameraX * 0.5) % canvas.width;
if (starX < 0) starX += canvas.width;
ctx.fillRect(starX, (Math.sin(i)*200 + 200), 2, 2);
}
platforms.forEach(p => p.draw());
enemies.forEach(e => e.draw());
items.forEach(i => i.draw());
bullets.forEach(b => b.draw());
player.draw();
// Draw Particles
particles.forEach(p => {
ctx.fillStyle = p.color;
ctx.fillRect(p.x - cameraX, p.y, 4, 4);
});
// UI Update
document.getElementById('score-display').innerText = "SCORE: " + score;
requestAnimationFrame(gameLoop);
}
// --- INPUT HANDLING ---
function setKey(key, val) {
if (key === 'left') keys.left = val;
if (key === 'right') keys.right = val;
if (key === 'up') keys.up = val;
if (key === 'shoot') keys.shoot = val;
}
// Keyboard
window.addEventListener('keydown', e => {
if (e.code === 'ArrowLeft') setKey('left', true);
if (e.code === 'ArrowRight') setKey('right', true);
if (e.code === 'ArrowUp') setKey('up', true);
if (e.code === 'Space') setKey('shoot', true);
});
window.addEventListener('keyup', e => {
if (e.code === 'ArrowLeft') setKey('left', false);
if (e.code === 'ArrowRight') setKey('right', false);
if (e.code === 'ArrowUp') setKey('up', false);
if (e.code === 'Space') setKey('shoot', false);
});
// Touch (Mobile)
const setupTouch = (id, key) => {
const el = document.getElementById(id);
el.addEventListener('touchstart', (e) => { e.preventDefault(); setKey(key, true); });
el.addEventListener('touchend', (e) => { e.preventDefault(); setKey(key, false); });
};
setupTouch('btn-left', 'left');
setupTouch('btn-right', 'right');
setupTouch('btn-jump', 'up');
setupTouch('btn-shoot', 'shoot');
// Initial Setup
document.getElementById('message-overlay').style.display = 'block';
</script>
</body>
</html>
Editor is loading...
Leave a Comment