V12 Engine Simulator – тахометр, троение, тряска двигателя
by PrismBolt10568 lines22.0 KB
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>V12 Engine Simulator – тахометр, троение, тряска двигателя</title>
<style>
* {
user-select: none;
-webkit-tap-highlight-color: transparent;
}
body {
background: radial-gradient(circle at 20% 30%, #0a0f1a, #03060c);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Orbitron', 'Courier New', monospace;
padding: 16px;
margin: 0;
}
/* приборная панель */
.dashboard {
max-width: 800px;
width: 100%;
background: #0b0e16dd;
backdrop-filter: blur(4px);
border-radius: 64px;
padding: 24px 20px 30px;
box-shadow: 0 20px 40px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.08);
border: 1px solid #2a3a5a;
}
h1 {
text-align: center;
font-size: 1.5rem;
letter-spacing: 4px;
color: #ffd966;
text-shadow: 0 0 6px #ff9900;
margin-bottom: 10px;
}
.engine-badge {
text-align: center;
font-size: 0.7rem;
color: #7f8fa4;
margin-bottom: 20px;
}
/* тахометр (canvas) */
.tacho-container {
background: #000000aa;
border-radius: 60px;
padding: 10px;
margin-bottom: 20px;
}
canvas#tachometer {
display: block;
width: 100%;
background: #10141f;
border-radius: 40px;
box-shadow: inset 0 0 10px #00000055, 0 5px 12px black;
}
.rpm-value {
text-align: center;
font-size: 2.1rem;
font-weight: bold;
margin-top: 8px;
color: #ffaa44;
background: #00000077;
border-radius: 36px;
padding: 6px;
letter-spacing: 3px;
}
/* визуальный блок двигателя (тряска) */
.engine-visual {
background: #1a1f2c;
border-radius: 48px;
padding: 16px;
margin-bottom: 24px;
transition: transform 0.02s linear;
box-shadow: 0 5px 15px black;
border-bottom: 3px solid #ff8800;
}
.engine-title {
display: flex;
justify-content: space-between;
font-size: 0.85rem;
color: #bbccff;
margin-bottom: 12px;
}
.v12-block {
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: center;
}
.cylinder {
width: 22px;
height: 44px;
background: linear-gradient(145deg, #3a445e, #1f2538);
border-radius: 12px;
box-shadow: inset -1px -1px 0 #0f111c, inset 1px 1px 0 #5b6d96;
transition: all 0.03s linear;
}
.cylinder.firing {
background: #ffaa33;
box-shadow: 0 0 10px #ff7700;
}
.status-lights {
display: flex;
gap: 14px;
margin-top: 12px;
font-size: 0.7rem;
justify-content: center;
}
.light {
padding: 4px 12px;
border-radius: 50px;
background: #202433;
color: #8b98b5;
}
.light.warning {
background: #441111;
color: #ff8888;
box-shadow: 0 0 4px red;
}
/* элементы управления */
.controls {
display: flex;
flex-direction: column;
gap: 20px;
}
.gas-panel {
background: #0c0f18;
border-radius: 80px;
padding: 12px 18px;
}
.gas-slider {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
}
input#throttleSlider {
flex: 3;
height: 8px;
border-radius: 10px;
background: #2a2f3e;
}
.gas-value {
background: #00000088;
padding: 4px 12px;
border-radius: 20px;
font-weight: bold;
width: 65px;
text-align: center;
color: #ffb347;
}
.button-group {
display: flex;
gap: 16px;
justify-content: space-between;
flex-wrap: wrap;
}
button {
background: #1f2538;
border: none;
padding: 12px 24px;
border-radius: 60px;
font-family: inherit;
font-weight: bold;
color: #eef5ff;
font-size: 1rem;
cursor: pointer;
flex: 1;
transition: 0.05s linear;
box-shadow: 0 3px 0 #0b0e16;
}
button:active {
transform: translateY(2px);
box-shadow: none;
}
.ignite-btn {
background: #2c6e2c;
color: #e0ffc0;
}
.break-btn {
background: #8b3c2c;
}
.fix-btn {
background: #2a5f6e;
}
.misfire-indicator {
font-size: 0.9rem;
text-align: center;
margin-top: 16px;
background: #00000066;
padding: 6px;
border-radius: 30px;
}
@keyframes shake {
0% { transform: translate(1px, 0px);}
25% { transform: translate(-1px, 1px);}
50% { transform: translate(1px, -1px);}
75% { transform: translate(-1px, 0px);}
100% { transform: translate(0, 1px);}
}
.shake-engine {
animation: shake 0.06s infinite;
}
footer {
font-size: 0.6rem;
text-align: center;
margin-top: 20px;
color: #5f6b87;
}
</style>
</head>
<body>
<div class="dashboard">
<h1>⚡ V12 BITURBO ⚡</h1>
<div class="engine-badge">симулятор двигателя | тахометр | троение</div>
<div class="tacho-container">
<canvas id="tachometer" width="600" height="180" style="width:100%; height:auto; max-width:600px; margin:0 auto; display:block;"></canvas>
<div class="rpm-value" id="rpmDisplay">0 RPM</div>
</div>
<!-- двигатель (визуальный блок с тряской) -->
<div class="engine-visual" id="engineBlock">
<div class="engine-title">
<span>🔧 V12 - 48 клапанов</span>
<span id="engineStateLabel">⛔ ЗАГЛУШЕН</span>
</div>
<div class="v12-block" id="cylinderBank">
<!-- 12 цилиндров добавим js -->
</div>
<div class="status-lights">
<div class="light" id="ignitionLight">🔌 ЗАЖИГАНИЕ</div>
<div class="light" id="misfireLight">⚠️ ТРОИТ</div>
<div class="light" id="checkEngine">🧰 CHECK</div>
</div>
</div>
<div class="controls">
<div class="gas-panel">
<div class="gas-slider">
<span style="color:#ccc;">🎛️ ГАЗ (0-100%)</span>
<input type="range" id="throttleSlider" min="0" max="100" value="0" step="1" disabled>
<span class="gas-value" id="throttlePercent">0%</span>
</div>
</div>
<div class="button-group">
<button id="startStopBtn" class="ignite-btn">🔑 ЗАВЕСТИ / ЗАГЛУШИТЬ</button>
<button id="breakBtn" class="break-btn">⚠️ ВЫЗВАТЬ ТРОЕНИЕ</button>
<button id="fixBtn" class="fix-btn">🔧 ПОЧИНИТЬ</button>
</div>
<div class="misfire-indicator" id="misfireMsg">⚙️ Двигатель работает ровно</div>
</div>
<footer>нажмите газ (ползунок) — двигатель V12 оживает, троение вызывает пропуски и тряску</footer>
</div>
<script>
(function(){
// ---------- состояние двигателя ----------
let engineRunning = false; // заведён ли
let throttle = 0; // 0..100
let rpm = 0; // текущие обороты
let isBroken = false; // режим "троит" (пропуски воспламенения)
let misfireSeverity = 0; // 0..1 (интенсивность тряски/потери мощности)
// физические параметры
let idleRPM = 850;
let maxRPM = 7800;
let healthFactor = 1.0; // при троении снижается максимальная тяга
// для анимации цилиндров и тряски
let cylinders = 12;
let cylinderElements = [];
let lastTimestamp = 0;
let animationId = null;
// случайные пропуски зажигания (для визуального эффекта)
let misfireRandom = 0;
// DOM элементы
const tachoCanvas = document.getElementById('tachometer');
const ctx = tachoCanvas.getContext('2d');
const rpmSpan = document.getElementById('rpmDisplay');
const engineBlockDiv = document.getElementById('engineBlock');
const throttleSlider = document.getElementById('throttleSlider');
const throttlePercentSpan = document.getElementById('throttlePercent');
const startStopBtn = document.getElementById('startStopBtn');
const breakBtn = document.getElementById('breakBtn');
const fixBtn = document.getElementById('fixBtn');
const misfireMsgSpan = document.getElementById('misfireMsg');
const engineStateLabel = document.getElementById('engineStateLabel');
const misfireLight = document.getElementById('misfireLight');
const checkEngineLight = document.getElementById('checkEngine');
const ignitionLight = document.getElementById('ignitionLight');
// создаём 12 цилиндров
const cylinderContainer = document.getElementById('cylinderBank');
for(let i=0; i<cylinders; i++) {
let div = document.createElement('div');
div.classList.add('cylinder');
cylinderContainer.appendChild(div);
cylinderElements.push(div);
}
// отрисовка тахометра (стрелка, шкала)
function drawTachometer(rpmVal) {
const w = tachoCanvas.width = 600;
const h = tachoCanvas.height = 180;
ctx.clearRect(0, 0, w, h);
// фон
ctx.fillStyle = "#0c1020";
ctx.fillRect(0, 0, w, h);
// градиентная шкала
let percent = Math.min(1, Math.max(0, rpmVal / maxRPM));
let angle = -Math.PI/2 + percent * Math.PI; // 0..180 градусов
let centerX = w/2, centerY = h - 22;
let radius = 70;
// дуга тахометра
ctx.beginPath();
ctx.arc(centerX, centerY, radius, -Math.PI/2, Math.PI/2, false);
ctx.lineWidth = 18;
ctx.strokeStyle = "#2e3b4e";
ctx.stroke();
// цветная дуга (обороты)
let grad = ctx.createLinearGradient(0,0,w,0);
grad.addColorStop(0, "#22ff88");
grad.addColorStop(0.7, "#ffcc33");
grad.addColorStop(1, "#ff5533");
ctx.beginPath();
ctx.arc(centerX, centerY, radius, -Math.PI/2, -Math.PI/2 + (percent * Math.PI), false);
ctx.lineWidth = 18;
ctx.strokeStyle = grad;
ctx.stroke();
// рисование рисок
for(let i=0; i<=10; i++) {
let p = i/10;
let rAngle = -Math.PI/2 + p * Math.PI;
let x1 = centerX + (radius-12)*Math.cos(rAngle);
let y1 = centerY + (radius-12)*Math.sin(rAngle);
let x2 = centerX + (radius+2)*Math.cos(rAngle);
let y2 = centerY + (radius+2)*Math.sin(rAngle);
ctx.beginPath();
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.strokeStyle = "#bbddff";
ctx.lineWidth = 2;
ctx.stroke();
}
// стрелка
let arrowLen = radius+6;
let arrowX = centerX + arrowLen * Math.cos(angle);
let arrowY = centerY + arrowLen * Math.sin(angle);
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.lineTo(arrowX, arrowY);
ctx.lineWidth = 4;
ctx.strokeStyle = "#ffaa44";
ctx.stroke();
ctx.fillStyle = "#ffaa44";
ctx.beginPath();
ctx.arc(centerX, centerY, 8, 0, Math.PI*2);
ctx.fill();
// текст оборотов
ctx.font = "bold 24px 'Orbitron'";
ctx.fillStyle = "#ffe5a3";
ctx.shadowBlur = 0;
ctx.fillText(Math.floor(rpmVal), centerX-45, centerY-35);
ctx.font = "12px monospace";
ctx.fillStyle = "#bfbfdf";
ctx.fillText("x100 RPM", centerX+20, centerY-28);
}
// обновление модели двигателя (физика оборотов)
function updateEnginePhysics(deltaSec) {
if(!engineRunning) {
// заглушен -> обороты падают к 0
rpm = Math.max(0, rpm - 600 * deltaSec);
if(rpm < 10) rpm = 0;
return;
}
let targetRPM = idleRPM;
let effectiveThrottle = throttle;
// если троит, снижаем мощность
let powerMulti = isBroken ? 0.45 + (Math.random() * 0.2) : 1.0;
if(isBroken) {
// хаотичность оборотов при троении
effectiveThrottle = throttle * (0.5 + Math.random() * 0.3);
}
// зависимость целевых оборотов от газа
let rpmFromGas = idleRPM + (maxRPM - idleRPM) * (effectiveThrottle / 100) * powerMulti;
targetRPM = Math.min(maxRPM, Math.max(idleRPM, rpmFromGas));
// инерция двигателя
let diff = targetRPM - rpm;
let acceleration = diff * 2.8 * deltaSec;
rpm += acceleration;
if(rpm < 0) rpm = 0;
if(rpm > maxRPM) rpm = maxRPM;
// эффект троения – случайные провалы оборотов если isBroken
if(isBroken && engineRunning && rpm > 1000) {
if(Math.random() < 0.08) {
rpm = Math.max(idleRPM, rpm - (Math.random() * 800));
}
}
}
// анимация цилиндров (вспышки) и тряска двигателя
let lastSpark = 0;
function animateCylindersAndShake(nowSec) {
if(!engineRunning) {
cylinderElements.forEach(c => c.classList.remove('firing'));
engineBlockDiv.classList.remove('shake-engine');
return;
}
// скорость вспышек зависит от оборотов и троения
let flashesPerSec = Math.min(30, 10 + (rpm / 800));
if(isBroken) flashesPerSec *= 0.6; // часть пропусков
let threshold = 1/flashesPerSec;
if(nowSec - lastSpark > threshold) {
lastSpark = nowSec;
// случайные цилиндры
let numFiring = isBroken ? (Math.random() > 0.6 ? 1 : Math.floor(Math.random() * 3) + 1) : 2;
cylinderElements.forEach(c => c.classList.remove('firing'));
for(let i=0; i<numFiring; i++) {
let idx = Math.floor(Math.random() * cylinders);
cylinderElements[idx].classList.add('firing');
setTimeout(() => {
if(cylinderElements[idx]) cylinderElements[idx].classList.remove('firing');
}, 70);
}
}
// тряска двигателя: зависит от оборотов и особенно от троения
let shakeIntensity = 0;
if(engineRunning) {
let rpmFactor = Math.min(1, rpm / 5000);
shakeIntensity = rpmFactor * 0.8;
if(isBroken) shakeIntensity += 0.7;
if(rpm > 2000 && isBroken) shakeIntensity += 0.5;
}
if(shakeIntensity > 0.15) {
engineBlockDiv.classList.add('shake-engine');
let intensityPx = Math.min(3, shakeIntensity * 2);
engineBlockDiv.style.animation = `shake ${0.04 + (shakeIntensity*0.02)}s infinite`;
} else {
engineBlockDiv.classList.remove('shake-engine');
engineBlockDiv.style.animation = '';
}
}
// обновление UI (тахометр, индикаторы, текст)
function updateUI() {
drawTachometer(rpm);
rpmSpan.innerText = Math.floor(rpm) + " RPM";
throttlePercentSpan.innerText = throttle + "%";
throttleSlider.value = throttle;
if(engineRunning) {
engineStateLabel.innerHTML = "🔥 V12 РАБОТАЕТ 🔥";
ignitionLight.style.background = "#2c9e2c";
ignitionLight.style.color = "white";
ignitionLight.style.boxShadow = "0 0 6px #00ff66";
} else {
engineStateLabel.innerHTML = "⛔ ЗАГЛУШЕН";
ignitionLight.style.background = "#202433";
ignitionLight.style.color = "#8b98b5";
ignitionLight.style.boxShadow = "none";
}
if(isBroken) {
misfireLight.classList.add('warning');
checkEngineLight.classList.add('warning');
misfireMsgSpan.innerHTML = "⚠️ ДВИГАТЕЛЬ ТРОИТ! ПРОПУСКИ ВОСПЛАМЕНЕНИЯ, ТРЯСКА ⚠️";
} else {
misfireLight.classList.remove('warning');
checkEngineLight.classList.remove('warning');
misfireMsgSpan.innerHTML = "✅ Двигатель работает ровно, V12 звучит мощно.";
}
}
// ---------- обработчики управления ----------
function setThrottle(value) {
if(!engineRunning) return;
throttle = Math.min(100, Math.max(0, value));
throttlePercentSpan.innerText = throttle + "%";
throttleSlider.value = throttle;
}
function toggleEngine() {
if(engineRunning) {
engineRunning = false;
throttle = 0;
setThrottle(0);
throttleSlider.disabled = true;
// снимаем тряску
engineBlockDiv.classList.remove('shake-engine');
} else {
engineRunning = true;
throttleSlider.disabled = false;
rpm = idleRPM;
// если была поломка, оставляем её, но мотор заведётся с троением
if(isBroken) {
// двигатель заведётся но будет троить
}
}
updateUI();
}
function causeMisfire() {
if(!engineRunning) {
// можно сломать и после запуска будет троить
}
isBroken = true;
updateUI();
}
function fixEngine() {
isBroken = false;
updateUI();
}
// привязка событий
startStopBtn.addEventListener('click', toggleEngine);
breakBtn.addEventListener('click', causeMisfire);
fixBtn.addEventListener('click', fixEngine);
throttleSlider.addEventListener('input', (e) => {
setThrottle(parseInt(e.target.value));
});
// первоначальное состояние
throttleSlider.disabled = true;
engineRunning = false;
rpm = 0;
isBroken = false;
updateUI();
// главный цикл симуляции с delta time
let lastFrameTime = 0;
let nowSeconds = 0;
function simulationLoop(nowMs) {
requestAnimationFrame(simulationLoop);
if(!lastFrameTime) { lastFrameTime = nowMs; return; }
let delta = Math.min(0.033, (nowMs - lastFrameTime) / 1000);
if(delta < 0.005) { lastFrameTime = nowMs; return; }
updateEnginePhysics(delta);
updateUI();
let nowSec = nowMs / 1000;
animateCylindersAndShake(nowSec);
lastFrameTime = nowMs;
}
// запуск цикла
simulationLoop(performance.now());
// дополнительный эффект: если поломка и газ высокий — показать сообщение
setInterval(() => {
if(engineRunning && isBroken && throttle > 40) {
misfireMsgSpan.style.background = "#3a1a1a";
setTimeout(() => { if(misfireMsgSpan) misfireMsgSpan.style.background = ""; }, 300);
}
}, 800);
})();
</script>
</body>
</html>Game Source: V12 Engine Simulator – тахометр, троение, тряска двигателя
Creator: PrismBolt10
Libraries: none
Complexity: complex (568 lines, 22.0 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: v12-engine-simulator-prismbolt10" to link back to the original. Then publish at arcadelab.ai/publish.