Super Bear Adventure 3D
by NovaNinja74712 lines24.4 KB🛠️ Three.js (3D graphics)
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#1a1a2e">
<title>Super Bear Adventure 3D</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; -webkit-tap-highlight-color:transparent;
user-select:none; -webkit-user-select:none; }
html, body { width:100%; height:100%; overflow:hidden; background:#1a1a2e;
font-family:-apple-system,'Arial',sans-serif; touch-action:none;
position:fixed; overscroll-behavior:none; }
canvas { display:block; touch-action:none; }
/* HUD */
#hud {
position:fixed; top:calc(8px + env(safe-area-inset-top,0px)); left:10px;
color:#fff; font-size:14px; font-weight:bold; text-shadow:2px 2px 4px #000;
pointer-events:none; z-index:100; background:rgba(0,0,0,0.35);
padding:6px 12px; border-radius:12px;
backdrop-filter:blur(4px); -webkit-backdrop-filter:blur(4px);
}
#hud div { margin-bottom:3px; line-height:1.3; }
#zoneName {
position:fixed; top:45%; left:50%; transform:translate(-50%,-50%);
color:#fff; font-size:28px; font-weight:bold; text-shadow:3px 3px 8px #000;
pointer-events:none; opacity:0; transition:opacity 0.5s; z-index:200;
text-align:center; padding:0 20px;
}
/* Джойстик */
#joystick {
position:fixed; bottom:calc(25px + env(safe-area-inset-bottom,0px)); left:20px;
width:130px; height:130px; border-radius:50%;
background:rgba(255,255,255,0.15);
border:2px solid rgba(255,255,255,0.35);
z-index:150; touch-action:none;
backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px);
}
#stick {
position:absolute; width:60px; height:60px; border-radius:50%;
background:radial-gradient(circle at 30% 30%,#fff,#999);
top:50%; left:50%; transform:translate(-50%,-50%);
box-shadow:0 4px 12px rgba(0,0,0,0.5); pointer-events:none;
}
.btn {
position:fixed; border-radius:50%; background:rgba(255,255,255,0.2);
border:2px solid rgba(255,255,255,0.5); color:#fff;
display:flex; justify-content:center; align-items:center;
z-index:150; touch-action:none;
backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px);
font-size:30px; transition:transform 0.1s, background 0.1s;
}
.btn.pressed { transform:scale(0.9); background:rgba(255,255,255,0.45); }
#jumpBtn {
bottom:calc(115px + env(safe-area-inset-bottom,0px)); right:20px;
width:85px; height:85px;
background:rgba(100,200,100,0.35); border-color:rgba(100,255,100,0.6);
}
#attackBtn {
bottom:calc(25px + env(safe-area-inset-bottom,0px)); right:20px;
width:85px; height:85px;
background:rgba(200,100,100,0.35); border-color:rgba(255,100,100,0.6);
}
#hint {
position:fixed; bottom:calc(160px + env(safe-area-inset-bottom,0px));
left:50%; transform:translateX(-50%);
color:#fff; background:rgba(0,0,0,0.55); padding:6px 14px;
border-radius:20px; font-size:12px; z-index:100; white-space:nowrap;
opacity:1; transition:opacity 1s; pointer-events:none;
}
@media (max-height:450px) { #hint { display:none; } }
/* Победа / старт */
.overlay {
position:fixed; inset:0; display:none;
justify-content:center; align-items:center; flex-direction:column;
z-index:500; text-align:center; padding:20px;
}
#startScreen {
background:linear-gradient(180deg,#87CEEB,#4a90e2); display:flex;
}
#startScreen h1 { color:#fff; font-size:30px; text-shadow:3px 3px 6px #000; margin-bottom:10px; }
#startScreen p { color:#fff; font-size:15px; text-shadow:1px 1px 3px #000; margin-bottom:25px; line-height:1.5; }
#startScreen button, #win button {
padding:18px 50px; font-size:22px; font-weight:bold;
background:linear-gradient(135deg,#FFD700,#FFA500); color:#fff;
border:none; border-radius:50px; cursor:pointer;
box-shadow:0 8px 20px rgba(0,0,0,0.4);
text-shadow:2px 2px 4px rgba(0,0,0,0.3);
}
#startScreen button:active, #win button:active { transform:scale(0.95); }
#win { background:rgba(0,0,0,0.85); color:#FFD700; font-size:34px; font-weight:bold;
text-shadow:3px 3px 6px #000; }
#win small { color:#fff; font-size:16px; margin-top:15px; }
</style>
</head>
<body>
<div id="hud">
<div>🍯 <span id="coins">0</span> / 125</div>
<div>🐻 Спасено: <span id="bearsSaved">0</span> / 3</div>
<div>📍 <span id="zoneLabel">Turtletown</span></div>
</div>
<div id="zoneName"></div>
<div id="hint">Джойстик — движение • 🦘 прыжок • ⚡ удар</div>
<div id="joystick"><div id="stick"></div></div>
<div class="btn" id="jumpBtn">🦘</div>
<div class="btn" id="attackBtn">⚡</div>
<div id="win" class="overlay">
🐻 ПОБЕДА!
<small>Королевство спасено от фиолетового мёда!</small>
<button id="winBtn">🔄 Играть снова</button>
</div>
<div id="startScreen" class="overlay">
<h1>🐻 Super Bear Adventure</h1>
<p>Спаси 3 медведей — откроется босс!<br>Победи всех 6 боссов и спаси королевство.</p>
<button id="startBtn">▶ ИГРАТЬ</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// ============================================================
// SUPER BEAR ADVENTURE 3D — Mobile (full working version)
// ============================================================
const ZONES = [
{ name:'Turtletown', boss:'Turtle Golem', bg:0x87CEEB, ground:0x7cb342 },
{ name:'Snow Valley', boss:'Yeti', bg:0xB0D4F1, ground:0xE8F4F8 },
{ name:'Beemothep Desert', boss:'The Guardian', bg:0xFFD59E, ground:0xE8C170 },
{ name:'Giant House', boss:'Test Subject No.1706', bg:0x8B7355, ground:0xA0522D },
{ name:'The Hive', boss:'Queen Beeatrice', bg:0x4B0082, ground:0x8B4513 },
{ name:'Bear Village', boss:"Baaren's Brother", bg:0x87CEEB, ground:0x7cb342 }
];
let scene, camera, renderer, clock;
let player, playerMesh;
let platforms = [], enemies = [], coinList = [], bearList = [], boss = null, particles = [], trees = [];
let ground = null;
const joy = { x:0, y:0, active:false };
let jumpBtnDown = false, attackBtnDown = false, attackCooldown = 0;
let keys = {};
const state = {
zone: 0, coins: 0, bearsSaved: 0, bearsTotal: 3,
playing: false, won: false, bossActive: false,
camAngle: 0, camDist: 11, camHeight: 5.5,
bossHp: 3, bossHpMax: 3, transition: false
};
const $ = id => document.getElementById(id);
// ============================================================
// ИНИЦИАЛИЗАЦИЯ СЦЕНЫ
// ============================================================
function initScene() {
scene = new THREE.Scene();
scene.background = new THREE.Color(ZONES[0].bg);
scene.fog = new THREE.Fog(ZONES[0].bg, 35, 110);
camera = new THREE.PerspectiveCamera(65, innerWidth/innerHeight, 0.1, 400);
camera.position.set(0, 6, 12);
renderer = new THREE.WebGLRenderer({ antialias:true, powerPreference:'high-performance' });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
clock = new THREE.Clock();
scene.add(new THREE.HemisphereLight(0xffffff, 0x444444, 0.9));
const sun = new THREE.DirectionalLight(0xffffff, 1.0);
sun.position.set(20, 40, 20);
sun.castShadow = true;
sun.shadow.mapSize.set(1024, 1024);
sun.shadow.camera.left = -45;
sun.shadow.camera.right = 45;
sun.shadow.camera.top = 45;
sun.shadow.camera.bottom = -45;
scene.add(sun);
// Стартовая земля — постоянная (не пересоздаётся)
ground = new THREE.Mesh(
new THREE.PlaneGeometry(200, 200),
new THREE.MeshStandardMaterial({ color: ZONES[0].ground, roughness: 0.9 })
);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
createPlayer();
}
// ============================================================
// ИГРОК (Баарен)
// ============================================================
function createPlayer() {
playerMesh = new THREE.Group();
const body = new THREE.Mesh(
new THREE.SphereGeometry(0.8, 14, 14),
new THREE.MeshStandardMaterial({ color:0x8B4513, roughness:0.8 })
);
body.castShadow = true; body.position.y = 0.8;
playerMesh.add(body);
const head = new THREE.Mesh(
new THREE.SphereGeometry(0.55, 14, 14),
new THREE.MeshStandardMaterial({ color:0xA0522D })
);
head.castShadow = true; head.position.set(0, 1.7, 0.1);
playerMesh.add(head);
const earGeo = new THREE.SphereGeometry(0.2, 8, 8);
const earMat = new THREE.MeshStandardMaterial({ color:0x6B3E1E });
const earL = new THREE.Mesh(earGeo, earMat); earL.position.set(-0.35, 2.15, 0);
const earR = new THREE.Mesh(earGeo, earMat); earR.position.set( 0.35, 2.15, 0);
playerMesh.add(earL, earR);
const eyeMat = new THREE.MeshStandardMaterial({ color:0x000000 });
const eyeL = new THREE.Mesh(new THREE.SphereGeometry(0.12, 8, 8), eyeMat);
eyeL.position.set(-0.2, 1.75, 0.5);
const eyeR = new THREE.Mesh(new THREE.SphereGeometry(0.12, 8, 8), eyeMat);
eyeR.position.set( 0.2, 1.75, 0.5);
playerMesh.add(eyeL, eyeR);
const nose = new THREE.Mesh(
new THREE.SphereGeometry(0.1, 8, 8),
new THREE.MeshStandardMaterial({ color:0x000000 })
);
nose.position.set(0, 1.6, 0.55);
playerMesh.add(nose);
const pawGeo = new THREE.SphereGeometry(0.25, 8, 8);
const pawMat = new THREE.MeshStandardMaterial({ color:0x6B3E1E });
const pawL = new THREE.Mesh(pawGeo, pawMat); pawL.position.set(-0.5, 0.3, 0.4);
const pawR = new THREE.Mesh(pawGeo, pawMat); pawR.position.set( 0.5, 0.3, 0.4);
playerMesh.add(pawL, pawR);
scene.add(playerMesh);
player = {
mesh: playerMesh,
vx:0, vy:0, vz:0,
speed:7,
jumpPower:11,
gravity:26,
grounded:false,
canDouble:true,
jumpCooldown:0
};
}
// ============================================================
// УРОВЕНЬ
// ============================================================
function clearLevel() {
platforms.forEach(p => scene.remove(p.mesh));
enemies.forEach(e => scene.remove(e.mesh));
coinList.forEach(c => scene.remove(c.mesh));
bearList.forEach(b => scene.remove(b.mesh));
trees.forEach(t => scene.remove(t));
if (boss) { scene.remove(boss.mesh); boss = null; }
platforms = []; enemies = []; coinList = []; bearList = []; trees = [];
particles.forEach(p => scene.remove(p.mesh)); particles = [];
}
function buildLevel(idx) {
clearLevel();
state.zone = idx;
state.coins = 0;
state.bearsSaved = 0;
state.bossActive = false;
state.won = false;
state.transition = false;
state.bossHp = 3;
const z = ZONES[idx];
scene.background = new THREE.Color(z.bg);
scene.fog = new THREE.Fog(z.bg, 35, 110);
ground.material.color.setHex(z.ground);
// Стартовая площадка
addPlatform(0, -0.5, 0, 16, 1, 16, z.ground);
// Платформы
const layout = [
[0, 0, -14, 6, 1, 6],
[6, 2, -21, 5, 1, 5],
[-2, 4, -29, 5, 1, 5],
[8, 6, -37, 5, 1, 5],
[0, 8, -46, 7, 1, 7],
[-10, 10, -54, 5, 1, 5],
[-4, 12, -62, 5, 1, 5],
[10, 14, -72, 12, 1, 12] // Арена босса
];
layout.forEach(p => addPlatform(p[0], p[1], p[2], p[3], p[4], p[5], 0x9C6A3A));
// Монеты — по платформам и в воздухе
for (let i = 0; i < 125; i++) {
const px = (Math.random() - 0.5) * 60;
const pz = -10 - Math.random() * 65;
const py = 1.5 + Math.random() * 13;
addCoin(px, py, pz);
}
// Медведи (3 для спасения)
const bearZ = [-18, -40, -60];
for (let i = 0; i < 3; i++) {
addBear((Math.random() - 0.5) * 40, 1, bearZ[i]);
}
// Враги
for (let i = 0; i < 4; i++) {
addEnemy((Math.random() - 0.5) * 40, 1, -20 - i * 14);
}
// Босс
createBoss(z.boss, 10, 15.2, -72);
// Деревья
for (let i = 0; i < 12; i++) {
const tx = (Math.random() - 0.5) * 70;
const tz = -10 - Math.random() * 65;
if (Math.abs(tx) > 8) addTree(tx, tz, z.name === 'Snow Valley');
}
// Респавн игрока
player.mesh.position.set(0, 1.5, 0);
player.vx = player.vy = player.vz = 0;
player.grounded = false;
player.canDouble = true;
state.camAngle = 0;
updateHUD();
showZoneName(z.name);
}
// ============================================================
// ХЕЛПЕРЫ УРОВНЯ
// ============================================================
function addPlatform(x, y, z, w, h, d, color) {
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(w, h, d),
new THREE.MeshStandardMaterial({ color, roughness: 0.85 })
);
mesh.position.set(x, y, z);
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
platforms.push({ mesh, x, y, z, w, h, d });
}
function addCoin(x, y, z) {
const mesh = new THREE.Mesh(
new THREE.SphereGeometry(0.32, 8, 8),
new THREE.MeshStandardMaterial({ color:0xFFD700, emissive:0xFFD700, emissiveIntensity:0.5 })
);
mesh.position.set(x, y, z);
mesh.castShadow = true;
scene.add(mesh);
coinList.push({ mesh, taken:false, baseY:y, bob: Math.random() * 6.28 });
}
function addBear(x, y, z) {
const g = new THREE.Group();
const body = new THREE.Mesh(
new THREE.BoxGeometry(1.2, 1.4, 1.2),
new THREE.MeshStandardMaterial({ color:0x8B4513 })
);
body.position.y = 0.7; body.castShadow = true;
g.add(body);
const head = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 8, 8),
new THREE.MeshStandardMaterial({ color:0xA0522D })
);
head.position.y = 1.7; head.castShadow = true;
g.add(head);
// Клетка — прутья
const barMat = new THREE.MeshStandardMaterial({ color:0x654321, metalness:0.6 });
for (let i = -1; i <= 1; i++) {
const bar = new THREE.Mesh(new THREE.BoxGeometry(0.1, 2.5, 0.1), barMat);
bar.position.set(i * 0.7, 1.25, 0.7);
g.add(bar);
}
g.position.set(x, y, z);
scene.add(g);
bearList.push({ mesh: g, saved:false, bob: Math.random() * 6.28 });
}
function addEnemy(x, y, z) {
const mesh = new THREE.Mesh(
new THREE.SphereGeometry(0.6, 10, 10),
new THREE.MeshStandardMaterial({ color:0x8B008B, emissive:0x4B0055, emissiveIntensity:0.5 })
);
mesh.position.set(x, y + 0.6, z);
mesh.castShadow = true;
scene.add(mesh);
enemies.push({ mesh, baseX:x, range:4, speed:1.6, dir:1 });
}
function addTree(x, z, snowy) {
const trunk = new THREE.Mesh(
new THREE.CylinderGeometry(0.3, 0.4, 3, 6),
new THREE.MeshStandardMaterial({ color:0x5D4037 })
);
trunk.position.set(x, 1.5, z);
trunk.castShadow = true;
scene.add(trunk);
const leaves = new THREE.Mesh(
new THREE.SphereGeometry(1.5, 8, 8),
new THREE.MeshStandardMaterial({ color: snowy ? 0xFFFFFF : 0x2E7D32 })
);
leaves.position.set(x, 4, z);
leaves.castShadow = true;
scene.add(leaves);
trees.push(trunk, leaves);
}
function createBoss(name, x, y, z) {
const g = new THREE.Group();
const body = new THREE.Mesh(
new THREE.BoxGeometry(4, 4, 4),
new THREE.MeshStandardMaterial({ color:0x800080, emissive:0x400040, emissiveIntensity:0.3 })
);
body.position.y = 2; body.castShadow = true;
g.add(body);
const eyeMat = new THREE.MeshStandardMaterial({ color:0xFF0000, emissive:0xFF0000, emissiveIntensity:1 });
const eyeL = new THREE.Mesh(new THREE.SphereGeometry(0.3, 8, 8), eyeMat);
eyeL.position.set(-0.8, 3, 2);
const eyeR = new THREE.Mesh(new THREE.SphereGeometry(0.3, 8, 8), eyeMat);
eyeR.position.set( 0.8, 3, 2);
g.add(eyeL, eyeR);
g.position.set(x, y, z);
g.visible = false;
scene.add(g);
boss = { mesh: g, name, active:false, baseY: y };
}
// ============================================================
// UI
// ============================================================
function updateHUD() {
$('coins').textContent = state.coins;
$('bearsSaved').textContent = state.bearsSaved;
$('zoneLabel').textContent = ZONES[state.zone].name;
}
function showZoneName(name) {
const el = $('zoneName');
el.textContent = name;
el.style.opacity = '1';
clearTimeout(showZoneName._t);
showZoneName._t = setTimeout(() => { el.style.opacity = '0'; }, 1800);
}
function showBossName(name) {
const el = $('zoneName');
el.innerHTML = '⚔️ БОСС<br><span style="font-size:22px">' + name + '</span>';
el.style.opacity = '1';
clearTimeout(showZoneName._t);
showZoneName._t = setTimeout(() => { el.style.opacity = '0'; }, 2200);
}
// ============================================================
// УПРАВЛЕНИЕ (мульти-тач)
// ============================================================
function setupControls() {
// ----- Кнопка старт -----
$('startBtn').addEventListener('click', () => {
$('startScreen').style.display = 'none';
state.playing = true;
clock.start();
});
$('winBtn').addEventListener('click', () => {
$('win').style.display = 'none';
buildLevel(0);
state.playing = true;
});
// ----- ДЖОЙСТИК (мульти-тач) -----
const joyEl = $('joystick');
const stickEl = $('stick');
const JOY_MAX = 60;
let joyTouchId = null;
function setStick(dx, dy) {
const dist = Math.min(Math.hypot(dx, dy), JOY_MAX);
const ang = Math.atan2(dy, dx);
const px = Math.cos(ang) * dist;
const py = Math.sin(ang) * dist;
stickEl.style.transform = `translate(calc(-50% + ${px}px), calc(-50% + ${py}px))`;
joy.x = px / JOY_MAX;
joy.y = py / JOY_MAX;
}
function resetStick() {
stickEl.style.transform = 'translate(-50%, -50%)';
joy.x = joy.y = 0;
joyTouchId = null;
}
joyEl.addEventListener('touchstart', e => {
e.preventDefault();
const t = e.changedTouches[0];
joyTouchId = t.identifier;
const r = joyEl.getBoundingClientRect();
const cx = r.left + r.width / 2;
const cy = r.top + r.height / 2;
setStick(t.clientX - cx, t.clientY - cy);
}, { passive:false });
joyEl.addEventListener('touchmove', e => {
e.preventDefault();
if (joyTouchId === null) return;
for (const t of e.changedTouches) {
if (t.identifier !== joyTouchId) continue;
const r = joyEl.getBoundingClientRect();
const cx = r.left + r.width / 2;
const cy = r.top + r.height / 2;
setStick(t.clientX - cx, t.clientY - cy);
}
}, { passive:false });
joyEl.addEventListener('touchend', e => {
for (const t of e.changedTouches) {
if (t.identifier === joyTouchId) resetStick();
}
}, { passive:false });
joyEl.addEventListener('touchcancel', resetStick, { passive:false });
// Мышь для теста на ПК
let joyMouse = false;
joyEl.addEventListener('mousedown', e => {
joyMouse = true;
const r = joyEl.getBoundingClientRect();
setStick(e.clientX - (r.left + r.width/2), e.clientY - (r.top + r.height/2));
});
document.addEventListener('mousemove', e => {
if (!joyMouse) return;
const r = joyEl.getBoundingClientRect();
setStick(e.clientX - (r.left + r.width/2), e.clientY - (r.top + r.height/2));
});
document.addEventListener('mouseup', () => { if (joyMouse) { joyMouse = false; resetStick(); } });
// ----- КНОПКИ ПРЫЖКА И АТАКИ (мульти-тач) -----
function bindBtn(el, setter) {
let id = null;
el.addEventListener('touchstart', e => {
e.preventDefault();
const t = e.changedTouches[0];
id = t.identifier;
setter(true);
el.classList.add('pressed');
}, { passive:false });
el.addEventListener('touchend', e => {
for (const t of e.changedTouches) {
if (t.identifier === id) {
setter(false);
el.classList.remove('pressed');
id = null;
}
}
}, { passive:false });
el.addEventListener('touchcancel', () => { setter(false); el.classList.remove('pressed'); id = null; }, { passive:false });
// Мышь
el.addEventListener('mousedown', e => { e.preventDefault(); setter(true); el.classList.add('pressed'); });
el.addEventListener('mouseup', e => { e.preventDefault(); setter(false); el.classList.remove('pressed'); });
el.addEventListener('mouseleave',() => { setter(false); el.classList.remove('pressed'); });
}
bindBtn($('jumpBtn'), v => { jumpBtnDown = v; });
bindBtn($('attackBtn'), v => { attackBtnDown = v; });
// ----- КАМЕРА СВАЙПОМ (по середине/правой части, кроме UI) -----
let camTouchId = null, camLastX = 0;
function isOverUI(x, y) {
const els = [joyEl, $('jumpBtn'), $('attackBtn')];
for (const el of els) {
const r = el.getBoundingClientRect();
if (x >= r.left - 15 && x <= r.right + 15 && y >= r.top - 15 && y <= r.bottom + 15) return true;
}
return false;
}
document.addEventListener('touchstart', e => {
if (!state.playing) return;
for (const t of e.changedTouches) {
if (camTouchId !== null) continue;
if (isOverUI(t.clientX, t.clientY)) continue;
camTouchId = t.identifier;
camLastX = t.clientX;
}
}, { passive:true });
document.addEventListener('touchmove', e => {
if (camTouchId === null) return;
for (const t of e.changedTouches) {
if (t.identifier !== camTouchId) continue;
const dx = t.clientX - camLastX;
state.camAngle -= dx * 0.008;
camLastX = t.clientX;
}
}, { passive:true });
document.addEventListener('touchend', e => {
for (const t of e.changedTouches) {
if (t.identifier === camTouchId) camTouchId = null;
}
}, { passive:true });
// ----- КЛАВИАТУРА (для ПК) -----
document.addEventListener('keydown', e => {
keys[e.code] = true;
if (e.code === 'Space') { jumpBtnDown = true; e.preventDefault(); }
if (e.code === 'KeyE') attackBtnDown = true;
});
document.addEventListener('keyup', e => {
keys[e.code] = false;
if (e.code === 'Space') jumpBtnDown = false;
if (e.code === 'KeyE') attackBtnDown = false;
});
// Колесо мыши — зум (ПК)
document.addEventListener('wheel', e => {
state.camDist = Math.max(7, Math.min(18, state.camDist + e.deltaY * 0.01));
}, { passive:true });
// Скрыть подсказку
setTimeout(() => { $('hint').style.opacity = '0'; }, 6000);
}
// ============================================================
// ФИЗИКА И ЛОГИКА
// ============================================================
function updatePhysics(dt) {
if (!state.playing || state.won || state.transition) return;
// ----- Ввод -----
const forward = new THREE.Vector3(Math.sin(state.camAngle), 0, Math.cos(state.camAngle));
const right = new THREE.Vector3(forward.z, 0, -forward.x);
let mx = 0, mz = 0;
if (Math.hypot(joy.x, joy.y) > 0.18) {
mx = forward.x * (-joy.y) + right.x * joy.x;
mz = forward.z * (-joy.y) + right.z * joy.x;
}
if (keys['KeyW'] || keys['ArrowUp']) { mx += forward.x; mz += forward.z; }
if (keys['KeyS'] || keys['ArrowDown']) { mx -= forward.x; mz -= forward.z; }
if (keys['KeyA'] || keys['ArrowLeft']) { mx -= right.x; mz -= right.z; }
if (keys['KeyD'] || keys['ArrowRight']) { mx += right.x; mz += right.z; }
const len = Math.hypot(mx, mz);
if (len > 0.01) {
mx /= len; mz /= len;
player.vx = mx * player.speed;
player.vz = mz * player.speed;
// Поворот морды в сторону движения
const targetRot = Math.atan2(mx, mz);
let diff = targetRot - player.mesh.rotation.y;
while (diff > Math.PI) diff -= Math.PI * 2;
while (diff < -Math.PI) diff += Math.PI * 2;
player.mesh.rotation.y += diff * Math.min(1, dt * 12);
} else {
player.vx *= 0.75;
player.vz *= 0.75;
}
// ----- Прыжок -----
player.jumpCooldown = Math.max(0, player.jumpCooldown - dt);
if (jumpBtnDown && player.jumpCooldown <= 0) {
if (player.grounded) {
player.vy = player.jumpPower;
player.grounded = false;
player.canDouble = true;
player.jumpCooldown = 0.28;
burst(player.mesh.position.x, player.mesh.position.y, player.mesh.position.z, 0xffffff, 6);
} else if (player.canDouble) {
player.vy = player.jumpPower * 0.85;
player.canDouble = false;
player.jumpCooldown = 0.28;
burst(player.mesh.position.x, player.mesh.position.y, player.mesh.position.z, 0xffff00, 10);
}
}
// ----- Атака (Belly Stomp) -----
attackCooldown = Math.max(0, attackGame Source: Super Bear Adventure 3D
Creator: NovaNinja74
Libraries: three
Complexity: complex (712 lines, 24.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: super-bear-adventure-3d-novaninja74" to link back to the original. Then publish at arcadelab.ai/publish.