Car-arab-code-BMG
htmlv1MITنُشر ٢٧ سبتمبر ٢٠٢٦· ♥ 0 · ⬇ 1 · 💬 0
لعبه سيارات بعبصه اخر اليل - BMG
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<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="theme-color" content="#050510" />
<title>3D ROAD RUSH</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 100%; height: 100%;
overflow: hidden;
background: #050510;
color: #fff;
font-family: 'Segoe UI', system-ui, -apple-system, 'Helvetica Neue', Arial, sans-serif;
user-select: none;
-webkit-user-select: none;
-webkit-tap-highlight-color: transparent;
touch-action: none;
overscroll-behavior: none;
}
#game { position: fixed; inset: 0; z-index: 0; }
#game canvas { display: block; width: 100% !important; height: 100% !important; }
/* ===== Speed vignette ===== */
#speedOverlay {
position: fixed; inset: 0;
pointer-events: none;
z-index: 5;
background: radial-gradient(ellipse at 50% 45%, rgba(0,0,0,0) 30%, rgba(20,10,50,0.55) 75%, rgba(0,0,0,0.85) 100%);
opacity: 0.15;
transition: opacity 0.25s linear;
}
/* ===== HUD ===== */
#hud {
position: fixed;
top: 0; left: 0; right: 0;
padding: max(12px, env(safe-area-inset-top)) 12px 0;
display: flex;
justify-content: center;
gap: 8px;
z-index: 10;
pointer-events: none;
flex-wrap: wrap;
}
.hud-panel {
background: rgba(10, 14, 32, 0.55);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(120, 180, 255, 0.18);
border-radius: 12px;
padding: 7px 14px;
min-width: 84px;
text-align: center;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255,255,255,0.06);
}
.hud-label {
font-size: 9px;
letter-spacing: 2px;
color: #7aa8ff;
font-weight: 700;
margin-bottom: 1px;
}
.hud-value {
font-size: 19px;
font-weight: 800;
color: #fff;
font-variant-numeric: tabular-nums;
text-shadow: 0 0 14px rgba(100, 180, 255, 0.55);
line-height: 1.1;
}
.hud-value small {
font-size: 9px;
color: #8899bb;
margin-inline-start: 3px;
font-weight: 700;
letter-spacing: 1px;
}
/* ===== Touch controls ===== */
#controls {
position: fixed;
bottom: 0; left: 0; right: 0;
padding: 0 18px max(20px, env(safe-area-inset-bottom));
display: flex;
justify-content: space-between;
align-items: flex-end;
z-index: 10;
pointer-events: none;
}
.ctrl-btn {
width: 88px; height: 88px;
border-radius: 50%;
background: rgba(24, 34, 68, 0.42);
border: 2px solid rgba(120, 180, 255, 0.32);
color: #cfe4ff;
font-size: 34px;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
pointer-events: auto;
cursor: pointer;
transition: transform 0.08s ease, background 0.15s ease, border-color 0.15s ease;
box-shadow: 0 6px 24px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.1);
user-select: none;
-webkit-user-select: none;
touch-action: none;
-webkit-appearance: none;
appearance: none;
outline: none;
font-family: inherit;
padding-bottom: 4px;
}
.ctrl-btn:active, .ctrl-btn.pressed {
background: rgba(80, 140, 255, 0.55);
transform: scale(0.93);
border-color: rgba(170, 215, 255, 0.9);
color: #fff;
}
@media (max-height: 520px) {
.ctrl-btn { width: 72px; height: 72px; font-size: 28px; }
}
/* ===== Overlays ===== */
.overlay {
position: fixed;
inset: 0;
z-index: 20;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: radial-gradient(ellipse at center, rgba(14,20,50,0.88) 0%, rgba(2,3,12,0.98) 100%);
backdrop-filter: blur(7px);
-webkit-backdrop-filter: blur(7px);
gap: 18px;
padding: 24px;
text-align: center;
transition: opacity 0.3s ease;
}
.overlay.hidden { display: none; }
.game-title {
font-size: clamp(34px, 10vw, 76px);
font-weight: 900;
letter-spacing: 5px;
line-height: 1;
background: linear-gradient(180deg, #8fd4ff 0%, #3a7bff 48%, #b44bff 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
filter: drop-shadow(0 0 30px rgba(80, 140, 255, 0.55));
margin-bottom: 4px;
}
.subtitle {
font-size: 12px;
letter-spacing: 5px;
color: #7d92bb;
font-weight: 600;
text-transform: uppercase;
margin-bottom: 6px;
}
.go-title {
font-size: clamp(38px, 11vw, 78px);
font-weight: 900;
letter-spacing: 4px;
color: #ff4d6d;
text-shadow: 0 0 30px rgba(255, 60, 100, 0.6), 0 0 60px rgba(255, 60, 100, 0.3);
margin-bottom: 4px;
}
.stat-card {
display: flex;
flex-direction: column;
gap: 12px;
background: rgba(14, 20, 44, 0.6);
border: 1px solid rgba(120, 180, 255, 0.2);
border-radius: 16px;
padding: 20px 34px;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.06), 0 10px 40px rgba(0,0,0,0.5);
}
.stat-row {
display: flex;
justify-content: space-between;
gap: 26px;
align-items: baseline;
}
.stat-key {
font-size: 12px;
letter-spacing: 2px;
color: #7aa8ff;
font-weight: 700;
}
.stat-val {
font-size: 24px;
font-weight: 900;
color: #fff;
font-variant-numeric: tabular-nums;
text-shadow: 0 0 14px rgba(120, 190, 255, 0.5);
}
.stat-val.gold { color: #ffd24a; text-shadow: 0 0 14px rgba(255, 210, 74, 0.6); }
.primary-btn {
padding: 16px 46px;
font-size: 18px;
font-weight: 800;
letter-spacing: 2.5px;
color: #fff;
background: linear-gradient(135deg, #3a7bff 0%, #8b3aff 100%);
border: none;
border-radius: 14px;
cursor: pointer;
box-shadow: 0 10px 34px rgba(80, 100, 255, 0.5), inset 0 1px 0 rgba(255,255,255,0.28);
transition: transform 0.12s ease, box-shadow 0.2s ease, filter 0.2s;
font-family: inherit;
text-transform: uppercase;
-webkit-appearance: none;
appearance: none;
outline: none;
touch-action: manipulation;
}
.primary-btn:hover { filter: brightness(1.1); }
.primary-btn:active {
transform: scale(0.95);
box-shadow: 0 5px 18px rgba(80, 100, 255, 0.55), inset 0 1px 0 rgba(255,255,255,0.28);
}
.hint {
font-size: 12px;
color: #6b7fa5;
letter-spacing: 1px;
line-height: 1.9;
margin-top: 6px;
max-width: 420px;
}
.hint b { color: #9ab8e8; font-weight: 700; }
#errorBox {
position: fixed; inset: 0;
z-index: 99;
display: none;
align-items: center;
justify-content: center;
background: #05050f;
color: #ff6b6b;
font-size: 16px;
padding: 30px;
text-align: center;
line-height: 1.8;
font-weight: 600;
}
</style>
</head>
<body>
<div id="game"></div>
<div id="speedOverlay"></div>
<!-- HUD -->
<div id="hud">
<div class="hud-panel">
<div class="hud-label">SPEED</div>
<div class="hud-value"><span id="speedVal">0</span><small>KM/H</small></div>
</div>
<div class="hud-panel">
<div class="hud-label">DISTANCE</div>
<div class="hud-value"><span id="distVal">0</span><small>M</small></div>
</div>
<div class="hud-panel">
<div class="hud-label">BEST</div>
<div class="hud-value"><span id="bestVal">0</span><small>M</small></div>
</div>
</div>
<!-- Touch controls -->
<div id="controls">
<button class="ctrl-btn" id="btnLeft" aria-label="يسار">◀</button>
<button class="ctrl-btn" id="btnRight" aria-label="يمين">▶</button>
</div>
<!-- Start screen -->
<div id="startScreen" class="overlay">
<div class="game-title">3D ROAD RUSH</div>
<div class="subtitle">Endless Highway Racing</div>
<button id="startBtn" class="primary-btn">START GAME</button>
<div class="hint">
<b>A / D</b> أو <b>← →</b> لتغيير المسار · <b>Space</b> للفرامل<br>
على الهاتف استخدم أزرار <b>◀ ▶</b> أسفل الشاشة
</div>
</div>
<!-- Game over screen -->
<div id="gameOverScreen" class="overlay hidden">
<div class="go-title">GAME OVER</div>
<div class="stat-card">
<div class="stat-row">
<span class="stat-key">DISTANCE</span>
<span class="stat-val"><span id="goDist">0</span> M</span>
</div>
<div class="stat-row">
<span class="stat-key">BEST</span>
<span class="stat-val gold"><span id="goBest">0</span> M</span>
</div>
</div>
<button id="restartBtn" class="primary-btn">PLAY AGAIN</button>
</div>
<div id="errorBox">
⚠️ تعذّر تحميل مكتبة Three.js.<br>
تأكد من اتصالك بالإنترنت ثم أعد تحميل الصفحة.
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
(function () {
'use strict';
/* =========================================================
التحقق من تحميل Three.js
========================================================= */
if (typeof THREE === 'undefined') {
document.getElementById('errorBox').style.display = 'flex';
return;
}
/* =========================================================
الثوابت
========================================================= */
const LANES = [-3, 0, 3]; // مواضع المسارات على المحور X
const BASE_SPEED = 11; // م/ث ≈ 40 كم/س
const MAX_SPEED = 60; // م/ث ≈ 216 كم/س
const SPEED_GROWTH = 0.018; // زيادة السرعة لكل متر
const ROAD_LENGTH = 420;
const ROAD_WIDTH = 11.4;
const DASH_SPACING = 8;
const DASH_PER_LANE = 46;
const DASH_TOTAL = DASH_SPACING * DASH_PER_LANE;
const PILLAR_SPACING = 24;
const PILLAR_PAIRS = 16;
const PILLAR_TOTAL = PILLAR_SPACING * PILLAR_PAIRS;
const SPAWN_Z = -175;
const DESPAWN_Z = 16;
const FOG_COLOR = 0x1a0f33;
const NPC_COLORS = [
0xff3b30, 0xffcc00, 0x34c759, 0x007aff,
0xff9500, 0xaf52de, 0xff2d55, 0x5ac8fa,
0x00d9a3, 0xff7a00
];
/* =========================================================
حالة اللعبة
========================================================= */
const state = {
running: false,
started: false,
playerLane: 1,
playerX: 0,
speed: BASE_SPEED,
displaySpeed: 0,
distance: 0,
best: 0,
lastSpawnTimer: 1.4,
elapsed: 0,
braking: false,
shakeX: 0,
shakeY: 0
};
try {
const stored = parseInt(localStorage.getItem('roadRushBest'), 10);
if (!isNaN(stored) && stored > 0) state.best = stored;
} catch (e) { /* ignore */ }
/* =========================================================
المشهد والكاميرا والعارض
========================================================= */
const container = document.getElementById('game');
const scene = new THREE.Scene();
scene.fog = new THREE.Fog(FOG_COLOR, 55, 190);
const camera = new THREE.PerspectiveCamera(68, window.innerWidth / window.innerHeight, 0.1, 600);
camera.position.set(0, 4.1, 8.2);
camera.lookAt(0, 0.9, -14);
let renderer;
try {
renderer = new THREE.WebGLRenderer({
antialias: window.devicePixelRatio < 2,
powerPreference: 'high-performance',
alpha: false
});
} catch (e) {
document.getElementById('errorBox').style.display = 'flex';
document.getElementById('errorBox').innerHTML = '⚠️ متصفحك لا يدعم WebGL.';
return;
}
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.15;
container.appendChild(renderer.domElement);
/* =========================================================
الإضاءة
========================================================= */
scene.add(new THREE.AmbientLight(0x6a7ba8, 0.85));
const hemi = new THREE.HemisphereLight(0x8099ff, 0x221133, 0.7);
scene.add(hemi);
const dirLight = new THREE.DirectionalLight(0xffe0b0, 1.25);
dirLight.position.set(-14, 26, -10);
dirLight.target.position.set(0, 0, -30);
scene.add(dirLight);
scene.add(dirLight.target);
const rimLight = new THREE.DirectionalLight(0x7a5cff, 0.55);
rimLight.position.set(12, 8, 20);
scene.add(rimLight);
/* =========================================================
السماء (تدرّج لوني عبر Shader)
========================================================= */
(function createSky() {
const geo = new THREE.SphereGeometry(420, 24, 16);
const mat = new THREE.ShaderMaterial({
side: THREE.BackSide,
depthWrite: false,
uniforms: {
topColor: { value: new THREE.Color(0x04040f) },
midColor: { value: new THREE.Color(0x1a0f33) },
bottomColor: { value: new THREE.Color(0x2e1a52) }
},
vertexShader: `
varying vec3 vWorldPos;
void main() {
vec4 wp = modelMatrix * vec4(position, 1.0);
vWorldPos = wp.xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform vec3 topColor;
uniform vec3 midColor;
uniform vec3 bottomColor;
varying vec3 vWorldPos;
void main() {
float h = normalize(vWorldPos).y;
vec3 col;
if (h > 0.0) {
col = mix(midColor, topColor, clamp(h * 1.6, 0.0, 1.0));
} else {
col = mix(midColor, bottomColor, clamp(-h * 2.2, 0.0, 1.0));
}
gl_FragColor = vec4(col, 1.0);
}
`
});
const sky = new THREE.Mesh(geo, mat);
sky.frustumCulled = false;
scene.add(sky);
})();
/* =========================================================
الأرضية
========================================================= */
(function createGround() {
const geo = new THREE.PlaneGeometry(520, 620);
const mat = new THREE.MeshStandardMaterial({
color: 0x160b28,
roughness: 1.0,
metalness: 0.0
});
const ground = new THREE.Mesh(geo, mat);
ground.rotation.x = -Math.PI / 2;
ground.position.set(0, -0.08, -200);
scene.add(ground);
})();
/* =========================================================
الطريق
========================================================= */
(function createRoad() {
const geo = new THREE.PlaneGeometry(ROAD_WIDTH, ROAD_LENGTH);
const mat = new THREE.MeshStandardMaterial({
color: 0x181822,
roughness: 0.85,
metalness: 0.15
});
const road = new THREE.Mesh(geo, mat);
road.rotation.x = -Math.PI / 2;
road.position.set(0, 0, -ROAD_LENGTH / 2 + 30);
scene.add(road);
// حواف الطريق المتوهجة
const edgeMat = new THREE.MeshBasicMaterial({ color: 0x2f6fff });
const edgeGeo = new THREE.PlaneGeometry(0.14, ROAD_LENGTH);
[-ROAD_WIDTH / 2 + 0.1, ROAD_WIDTH / 2 - 0.1].forEach(x => {
const edge = new THREE.Mesh(edgeGeo, edgeMat);
edge.rotation.x = -Math.PI / 2;
edge.position.set(x, 0.012, -ROAD_LENGTH / 2 + 30);
scene.add(edge);
});
})();
/* =========================================================
خطوط الطريق (Dashes) — مع إعادة تدوير
========================================================= */
const dashGeo = new THREE.PlaneGeometry(0.22, 3.4);
const dashMat = new THREE.MeshBasicMaterial({ color: 0xffffff });
const dashes = [];
for (let i = 0; i < DASH_PER_LANE; i++) {
for (let l = 0; l < 2; l++) {
const dash = new THREE.Mesh(dashGeo, dashMat);
dash.rotation.x = -Math.PI / 2;
const x = l === 0 ? -1.5 : 1.5;
const z = -i * DASH_SPACING;
dash.position.set(x, 0.015, z);
dash.userData.initialZ = z;
scene.add(dash);
dashes.push(dash);
}
}
/* =========================================================
أعمدة الإضاءة الجانبية
========================================================= */
const pillars = [];
(function createPillars() {
const geo = new THREE.BoxGeometry(0.18, 2.6, 0.18);
const capGeo = new THREE.BoxGeometry(0.34, 0.34, 0.34);
for (let i = 0; i < PILLAR_PAIRS; i++) {
const z = -i * PILLAR_SPACING;
for (let s = 0; s < 2; s++) {
const side = s === 0 ? -1 : 1;
const x = side * (ROAD_WIDTH / 2 + 0.85);
const pillar = new THREE.Mesh(geo, new THREE.MeshStandardMaterial({
color: 0x22263a, roughness: 0.6, metalness: 0.5
}));
pillar.position.set(x, 1.3, z);
pillar.userData.initialZ = z;
const capMat = new THREE.MeshBasicMaterial({
color: s === 0 ? 0x00e5ff : 0xff2fb9
});
const cap = new THREE.Mesh(capGeo, capMat);
cap.position.set(x, 2.75, z);
cap.userData.isCap = true;
scene.add(pillar);
scene.add(cap);
pillars.push({ pillar, cap, initialZ: z });
}
}
})();
/* =========================================================
بناء السيارة
========================================================= */
const SHARED = {
bodyGeo: new THREE.BoxGeometry(1.78, 0.52, 3.9),
chassisGeo: new THREE.BoxGeometry(1.9, 0.26, 4.05),
cabinGeo: new THREE.BoxGeometry(1.42, 0.58, 1.95),
roofGeo: new THREE.BoxGeometry(1.36, 0.14, 1.85),
wheelGeo: new THREE.CylinderGeometry(0.42, 0.42, 0.34, 14),
rimGeo: new THREE.CylinderGeometry(0.2, 0.2, 0.36, 10),
headGeo: new THREE.BoxGeometry(0.34, 0.16, 0.1),
tailGeo: new THREE.BoxGeometry(0.42, 0.14, 0.1),
chassisMat: new THREE.MeshStandardMaterial({ color: 0x141418, roughness: 0.95, metalness: 0.15 }),
glassMat: new THREE.MeshStandardMaterial({ color: 0x0a0d18, roughness: 0.12, metalness: 0.85 }),
wheelMat: new THREE.MeshStandardMaterial({ color: 0x0c0c0e, roughness: 1.0 }),
rimMat: new THREE.MeshStandardMaterial({ color: 0x9aa4bb, roughness: 0.28, metalness: 0.95 }),
headMat: new THREE.MeshBasicMaterial({ color: 0xfff6cc }),
tailMat: new THREE.MeshBasicMaterial({ color: 0xff1f2e })
};
const shadowGeo = new THREE.CircleGeometry(1.35, 14);
const shadowMat = new THREE.MeshBasicMaterial({
color: 0x000000, transparent: true, opacity: 0.42, depthWrite: false
});
/**
* ينشئ مجسّم سيارة.
* @param {number} color اللون الأساسي
* @param {boolean} detailed هل نضيف تفاصيل إضافية (للسيارة اللاعبة)
*/
function createCarMesh(color, detailed) {
const car = new THREE.Group();
const bodyMat = new THREE.MeshStandardMaterial({
color: color, roughness: 0.32, metalness: 0.72
});
// الهيكل السفلي
const chassis = new THREE.Mesh(SHARED.chassisGeo, SHARED.chassisMat);
chassis.position.y = 0.34;
car.add(chassis);
// الجسم الرئيسي
const body = new THREE.Mesh(SHARED.bodyGeo, bodyMat);
body.position.y = 0.66;
car.add(body);
// الكابينة
const cabin = new THREE.Mesh(SHARED.cabinGeo, SHARED.glassMat);
cabin.position.set(0, 1.18, 0.12);
car.add(cabin);
// السقف
const roofMat = new THREE.MeshStandardMaterial({
color: color, roughness: 0.32, metalness: 0.72
});
const roof = new THREE.Mesh(SHARED.roofGeo, roofMat);
roof.position.set(0, 1.48, 0.12);
car.add(roof);
// العجلات
const wheelPositions = [
[-0.93, 0.42, 1.32], [0.93, 0.42, 1.32],
[-0.93, 0.42, -1.32], [0.93, 0.42, -1.32]
];
for (let i = 0; i < wheelPositions.length; i++) {
const p = wheelPositions[i];
const wheel = new THREE.Mesh(SHARED.wheelGeo, SHARED.wheelMat);
wheel.rotation.z = Math.PI / 2;
wheel.position.set(p[0], p[1], p[2]);
car.add(wheel);
const rim = new THREE.Mesh(SHARED.rimGeo, SHARED.rimMat);
rim.rotation.z = Math.PI / 2;
rim.position.set(p[0], p[1], p[2]);
car.add(rim);
}
// المصابيح الخلفية (تظهر دائماً لأننا نرى السيارة من الخلف)
for (let i = 0; i < 2; i++) {
const t = new THREE.Mesh(SHARED.tailGeo, SHARED.tailMat);
t.position.set(i === 0 ? -0.58 : 0.58, 0.78, 1.98);
car.add(t);
}
// المصابيح الأمامية للسيارة اللاعبة فقط
if (detailed) {
for (let i = 0; i < 2; i++) {
const h = new THREE.Mesh(SHARED.headGeo, SHARED.headMat);
h.position.set(i === 0 ? -0.55 : 0.55, 0.72, -1.98);
car.add(h);
}
// شريط أمامي مضيء
const stripMat = new THREE.MeshBasicMaterial({ color: 0x3affff });
const strip = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.05, 0.06), stripMat);
strip.position.set(0, 0.5, -1.99);
car.add(strip);
}
// ظل وهمي
const shadow = new THREE.Mesh(shadowGeo, shadowMat);
shadow.rotation.x = -Math.PI / 2;
shadow.position.y = 0.025;
car.add(shadow);
car.userData.bodyMat = bodyMat;
car.userData.roofMat = roofMat;
return car;
}
/* =========================================================
السيارة اللاعبة
========================================================= */
const playerCar = createCarMesh(0x2f7bff, true);
playerCar.position.set(0, 0, 0);
scene.add(playerCar);
// هالة ضوئية أسفل السيارة اللاعبة
const glowLight = new THREE.PointLight(0x3a7bff, 0.9, 10, 2);
glowLight.position.set(0, 0.4, 0);
playerCar.add(glowLight);
/* =========================================================
نظام تجميع السيارات (Object Pooling)
========================================================= */
const npcPool = [];
const activeNPCs = [];
function acquireNPC() {
let npc = npcPool.pop();
if (!npc) {
npc = createCarMesh(0xff3b30, false);
npc.visible = false;
scene.add(npc);
}
return npc;
}
function releaseNPC(npc) {
npc.visible = false;
npcPool.push(npc);
}
function spawnNPC(laneIndex, color) {
const npc = acquireNPC();
npc.position.set(LANES[laneIndex], 0, SPAWN_Z);
npc.rotation.set(0, 0, 0);
if (npc.userData.bodyMat) npc.userData.bodyMat.color.setHex(color);
if (npc.userData.roofMat) npc.userData.roofMat.color.setHex(color);
npc.visible = true;
activeNPCs.push(npc);
}
/* =========================================================
الصوت
========================================================= */
let audioCtx = null;
let engineNodes = null;
function initAudio() {
if (audioCtx) {
if (audioCtx.state === 'suspended') audioCtx.resume();
return;
}
try {
const Ctx = window.AudioContext || window.webkitAudioContext;
if (!Ctx) return;
audioCtx = new Ctx();
const osc1 = audioCtx.createOscillator();
osc1.type = 'sawtooth';
osc1.frequency.value = 70;
const osc2 = audioCtx.createOscillator();
osc2.type = 'square';
osc2.frequency.value = 35;
const filter = audioCtx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 320;
filter.Q.value = 3;
const gain = audioCtx.createGain();
gain.gain.value = 0.0;
osc1.connect(filter);
osc2.connect(filter);
filter.connect(gain);
gain.connect(audioCtx.destination);
osc1.start();
osc2.start();
engineNodes = { osc1, osc2, filter, gain };
} catch (e) {
audioCtx = null;
engineNodes = null;
}
}
function updateEngineSound() {
if (!audioCtx || !engineNodes) return;
const t = audioCtx.currentTime;
const ratio = Math.min(state.displaySpeed / (MAX_SPEED * 3.6), 1);
const base = 58 + ratio * 170;
try {
engineNodes.osc1.frequency.setTargetAtTime(base, t, 0.12);
engineNodes.osc2.frequency.setTargetAtTime(base * 0.5, t, 0.12);
engineNodes.filter.frequency.setTargetAtTime(260 + ratio * 780, t, 0.15);
engineNodes.gain.gain.setTargetAtTime(
state.running ? (0.025 + ratio * 0.055) : 0.0,
t, 0.2
);
} catch (e) { /* ignore */ }
}
function stopEngineSound() {
if (!audioCtx || !engineNodes) return;
try {
engineNodes.gain.gain.setTargetAtTime(0.0, audioCtx.currentTime, 0.12);
} catch (e) { /* ignore */ }
}
function playCrashSound() {
if (!audioCtx) return;
try {
const dur = 0.55;
const len = Math.floor(audioCtx.sampleRate * dur);
const buffer = audioCtx.createBuffer(1, len, audioCtx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < len; i++) {
const env = Math.pow(1 - i / len, 2.2);
data[i] = (Math.random() * 2 - 1) * env;
}
const src = audioCtx.createBufferSource();
src.buffer = buffer;
const filter = audioCtx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 1400;
filter.Q.value = 1.2;
const gain = audioCtx.createGain();
gain.gain.value = 0.55;
src.connect(filter);
filter.connect(gain);
gain.connect(audioCtx.destination);
src.start();
// نغمة هابطة
const osc = audioCtx.createOscillator();
osc.type = 'sawtooth';
const g2 = audioCtx.createGain();
osc.frequency.setValueAtTime(220, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(45, audioCtx.currentTime + 0.4);
g2.gain.setValueAtTime(0.18, audioCtx.currentTime);
g2.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.45);
osc.connect(g2);
g2.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.5);
} catch (e) { /* ignore */ }
}
/* =========================================================
عناصر واجهة المستخدم
========================================================= */
const ui = {
speedVal: document.getElementById('speedVal'),
distVal: document.getElementById('distVal'),
bestVal: document.getElementById('bestVal'),
startScreen: document.getElementById('startScreen'),
gameOver: document.getElementById('gameOverScreen'),
goDist: document.getElementById('goDist'),
goBest: document.getElementById('goBest'),
startBtn: document.getElementById('startBtn'),
restartBtn: document.getElementById('restartBtn'),
btnLeft: document.getElementById('btnLeft'),
btnRight: document.getElementById('btnRight'),
speedOverlay: document.getElementById('speedOverlay')
};
ui.bestVal.textContent = Math.floor(state.best);
/* =========================================================
أدوات مساعدة
========================================================= */
function clamp(v, a, b) { return v < a ? a : (v > b ? b : v); }
function lerp(a, b, t) { return a + (b - a) * t; }
function getLevel() {
return Math.floor(state.distance / 380) + 1;
}
/* =========================================================
التحكم بالمسارات
========================================================= */
function moveLane(dir) {
if (!state.running) return;
const next = state.playerLane + dir;
if (next < 0 || next > 2) return;
state.playerLane = next;
}
/* أزرار اللمس مع الاستمرار */
function bindHoldButton(el, onDown, onUp) {
let active = false;
function down(e) {
if (e) e.preventDefault();
if (active) return;
active = true;
el.classList.add('pressed');
onDown();
}
function up(e) {
if (e) e.preventDefault();
if (!active) return;
active = false;
el.classList.remove('pressed');
if (onUp) onUp();
}
el.addEventListener('pointerdown', function (e) {
try { el.setPointerCapture(e.pointerId); } catch (err) { /* ignore */ }
down(e);
});
el.addEventListener('pointerup', up);
el.addEventListener('pointercancel', up);
el.addEventListener('pointerleave', up);
el.addEventListener('contextmenu', function (e) { e.preventDefault(); });
}
let leftRepeat = null;
let rightRepeat = null;
const REPEAT_DELAY = 260;
bindHoldButton(
ui.btnLeft,
function () {
moveLane(-1);
if (leftRepeat) clearInterval(leftRepeat);
leftRepeat = setInterval(function () { moveLane(-1); }, REPEAT_DELAY);
},
function () {
if (leftRepeat) { clearInterval(leftRepeat); leftRepeat = null; }
}
);
bindHoldButton(
ui.btnRight,
function () {
moveLane(1);
if (rightRepeat) clearInterval(rightRepeat);
rightRepeat = setInterval(function () { moveLane(1); }, REPEAT_DELAY);
},
function () {
if (rightRepeat) { clearInterval(rightRepeat); rightRepeat = null; }
}
);
/* =========================================================
لوحة المفاتيح
========================================================= */
window.addEventListener('keydown', function (e) {
const k = e.key;
if (k === 'ArrowLeft' || k === 'a' || k === 'A') {
e.preventDefault();
if (!e.repeat) moveLane(-1);
} else if (k === 'ArrowRight' || k === 'd' || k === 'D') {
e.preventDefault();
if (!e.repeat) moveLane(1);
} else if (k === ' ' || k === 'Spacebar') {
e.preventDefault();
state.braking = true;
} else if (k === 'Enter') {
if (!state.running) {
if (!ui.startScreen.classList.contains('hidden')) startGame();
else if (!ui.gameOver.classList.contains('hidden')) startGame();
}
}
}, { passive: false });
window.addEventListener('keyup', function (e) {
const k = e.key;
if (k === ' ' || k === 'Spacebar') {
state.braking = false;
}
});
/* =========================================================
توليد حركة المرور
========================================================= */
function spawnTraffic() {
const level = getLevel();
// تحديد المسارات المشغولة في منطقة الظهور
const blocked = [false, false, false];
for (let i = 0; i < activeNPCs.length; i++) {
const npc = activeNPCs[i];
if (npc.position.z > SPAWN_Z - 42 && npc.position.z < SPAWN_Z + 48) {
const lane = LANES.indexOf(npc.position.x);
if (lane >= 0) blocked[lane] = true;
}
}
const free = [];
for (let i = 0; i < 3; i++) if (!blocked[i]) free.push(i);
if (free.length <= 1) return; // لا تحجب كل المسارات
// أقصى عدد سيارات في المرة الواحدة
let maxSpawn = Math.min(free.length - 1, 1 + Math.floor(level / 3), 2);
if (maxSpawn <= 0) return;
let count = 1 + (Math.random() < 0.38 ? 1 : 0);
count = Math.min(count, maxSpawn);
// خلط المسارات الحرة
for (let i = free.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const tmp = free[i]; free[i] = free[j]; free[j] = tmp;
}
for (let i = 0; i < count; i++) {
const color = NPC_COLORS[(Math.random() * NPC_COLORS.length) | 0];
spawnNPC(free[i], color);
}
}
/* =========================================================
كشف التصادم
========================================================= */
const PLAYER_HALF_W = 0.78;
const PLAYER_HALF_L = 1.75;
const NPC_HALF_W = 0.86;
const NPC_HALF_L = 1.9;
function checkCollision() {
const px = playerCar.position.x;
const pz = playerCar.position.z;
for (let i = 0; i < activeNPCs.length; i++) {
const npc = activeNPCs[i];
if (!npc.visible) continue;
const dx = Math.abs(px - npc.position.x);
const dz = Math.abs(pz - npc.position.z);
if (dx < (PLAYER_HALF_W + NPC_HALF_W) && dz < (PLAYER_HALF_L + NPC_HALF_L)) {
return true;
}
}
return false;
}
/* =========================================================
تحديث الكاميرا
========================================================= */
function updateCamera(dt) {
const ratio = clamp(state.displaySpeed / (MAX_SPEED * 3.6), 0, 1);
// FOV يتغير مع السرعة
const targetFov = 66 + ratio * 14;
camera.fov += (targetFov - camera.fov) * Math.min(dt * 2.2, 1);
camera.updateProjectionMatrix();
// متابعة سلسة لمحور X
const targetCamX = playerCar.position.x * 0.72;
camera.position.x += (targetCamX - camera.position.x) * Math.min(dt * 4.2, 1);
// ارتفاع وزاوية خلفية
const targetY = 4.05 - ratio * 0.35;
camera.position.y += (targetY - camera.position.y) * Math.min(dt * 2.4, 1);
camera.position.z = 8.2 + ratio * 0.7;
// اهتزاز الكاميرا
const shake = ratio * ratio * 0.075;
state.shakeX = (Math.random() - 0.5) * shake;
state.shakeY = (Math.random() - 0.5) * shake;
const lookX = playerCar.position.x * 0.5 + state.shakeX * 0.5;
camera.position.x += state.shakeX * 0.35;
camera.position.y += state.shakeY * 0.35;
camera.lookAt(lookX, 0.85 - ratio * 0.1, -14);
}
/* =========================================================
تحديث واجهة HUD
========================================================= */
let hudAccum = 0;
function updateHUD(dt) {
hudAccum += dt;
if (hudAccum < 0.06) return;
hudAccum = 0;
ui.speedVal.textContent = Math.round(state.displaySpeed);
ui.distVal.textContent = Math.floor(state.distance);
ui.bestVal.textContent = Math.floor(state.best);
// تأثير Vignette مع السرعة
const ratio = clamp(state.displaySpeed / (MAX_SPEED * 3.6), 0, 1);
ui.speedOverlay.style.opacity = (0.12 + ratio * 0.5).toFixed(3);
}
/* =========================================================
التحديث الرئيسي للعبة
========================================================= */
function update(dt) {
state.elapsed += dt;
/* --- السرعة --- */
if (state.braking) {
state.speed = Math.max(7, state.speed - 34 * dt);
} else {
const targetSpeed = Math.min(MAX_SPEED, BASE_SPEED + state.distance * SPEED_GROWTH);
state.speed += (targetSpeed - state.speed) * Math.min(dt * 0.9, 1);
}
/* --- المسافة --- */
state.distance += state.speed * dt;
/* --- عرض السرعة بالكم/س --- */
const targetDisplay = state.speed * 3.6;
state.displaySpeed += (targetDisplay - state.displaySpeed) * Math.min(dt * 6, 1);
/* --- حركة السيارة بين المسارات --- */
const targetX = LANES[state.playerLane];
const diffX = targetX - state.playerX;
state.playerX += diffX * Math.min(dt * 7.5, 1);
playerCar.position.x = state.playerX;
// ميلان خفيف أثناء الانتقال
const roll = clamp(-diffX * 0.11, -0.16, 0.16);
const yaw = clamp(diffX * 0.055, -0.09, 0.09);
playerCar.rotation.z += (roll - playerCar.rotation.z) * Math.min(dt * 9, 1);
playerCar.rotation.y += (yaw - playerCar.rotation.y) * Math.min(dt * 9, 1);
// اهتزاز بسيط للسيارة عند السرعات العالية
const vib = clamp(state.displaySpeed / (MAX_SPEED * 3.6), 0, 1) * 0.012;
playerCar.position.y = Math.sin(state.elapsed * 42) * vib;
/* --- تحريك خطوط الطريق --- */
const moveZ = state.speed * dt;
for (let i = 0; i < dashes.length; i++) {
const d = dashes[i];
d.position.z += moveZ;
if (d.position.z > DESPAWN_Z) {
d.position.z -= DASH_TOTAL;
}
}
/* --- تحريك الأعمدة الجانبية --- */
for (let i = 0; i < pillars.length; i++) {
const p = pillars[i];
p.pillar.position.z += moveZ;
p.cap.position.z += moveZ;
if (p.pillar.position.z > DESPAWN_Z) {
p.pillar.position.z -= PILLAR_TOTAL;
p.cap.position.z -= PILLAR_TOTAL;
}
}
/* --- تحريك سيارات المرور --- */
for (let i = activeNPCs.length - 1; i >= 0; i--) {
const npc = activeNPCs[i];
npc.position.z += moveZ;
if (npc.position.z > DESPAWN_Z) {
releaseNPC(npc);
activeNPCs.splice(i, 1);
}
}
/* --- توليد سيارات جديدة --- */
state.lastSpawnTimer -= dt;
if (state.lastSpawnTimer <= 0) {
spawnTraffic();
const level = getLevel();
const base = Math.max(0.42, 1.55 - level * 0.11);
state.lastSpawnTimer = base + Math.random() * 0.45;
}
/* --- التصادم --- */
if (checkCollision()) {
triggerGameOver();
return;
}
/* --- الكاميرا والصوت والواجهة --- */
updateCamera(dt);
updateHUD(dt);
updateEngineSound();
}
/* =========================================================
بدء / إعادة تشغيل / نهاية اللعبة
========================================================= */
function resetWorld() {
// إعادة كل سيارات المرور
for (let i = activeNPCs.length - 1; i >= 0; i--) {
releaseNPC(activeNPCs[i]);
}
activeNPCs.length = 0;
// إعادة خطوط الطريق
for (let i = 0; i < dashes.length; i++) {
dashes[i].position.z = dashes[i].userData.initialZ;
}
// إعادة الأعمدة
for (let i = 0; i < pillars.length; i++) {
pillars[i].pillar.position.z = pillars[i].initialZ;
pillars[i].cap.position.z = pillars[i].initialZ;
}
// إعادة السيارة اللاعبة
state.playerLane = 1;
state.playerX = 0;
state.speed = BASE_SPEED;
state.displaySpeed = 0;
state.distance = 0;
state.lastSpawnTimer = 1.2;
state.elapsed = 0;
state.braking = false;
playerCar.position.set(0, 0, 0);
playerCar.rotation.set(0, 0, 0);
camera.position.set(0, 4.1, 8.2);
camera.fov = 68;
camera.updateProjectionMatrix();
camera.lookAt(0, 0.9, -14);
ui.speedOverlay.style.opacity = '0.12';
ui.speedVal.textContent = '0';
ui.distVal.textContent = '0';
ui.bestVal.textContent = Math.floor(state.best);
}
function startGame() {
initAudio();
ui.startScreen.classList.add('hidden');
ui.gameOver.classList.add('hidden');
resetWorld();
state.running = true;
state.started = true;
}
function triggerGameOver() {
state.running = false;
if (state.distance > state.best) {
state.best = Math.floor(state.distance);
try { localStorage.setItem('roadRushBest', String(state.best)); } catch (e) { /* ignore */ }
}
ui.goDist.textContent = Math.floor(state.distance);
ui.goBest.textContent = Math.floor(state.best);
ui.bestVal.textContent = Math.floor(state.best);
ui.gameOver.classList.remove('hidden');
playCrashSound();
stopEngineSound();
// تأثير اهتزاز سريع للكاميرا
let shakeCount = 0;
const shakeInterval = setInterval(function () {
camera.position.x += (Math.random() - 0.5) * 0.5;
camera.position.y += (Math.random() - 0.5) * 0.4;
shakeCount++;
if (shakeCount > 10) clearInterval(shakeInterval);
}, 28);
}
/* =========================================================
ربط الأزرار
========================================================= */
ui.startBtn.addEventListener('click', function (e) {
e.preventDefault();
startGame();
});
ui.restartBtn.addEventListener('click', function (e) {
e.preventDefault();
startGame();
});
// منع السلوك الافتراضي للمس على الأزرار
[ui.startBtn, ui.restartBtn].forEach(function (btn) {
btn.addEventListener('touchstart', function (e) {
e.preventDefault();
btn.click();
}, { passive: false });
});
/* =========================================================
الاستجابة لتغيير حجم الشاشة
========================================================= */
function onResize() {
const w = window.innerWidth;
const h = window.innerHeight;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
renderer.setSize(w, h);
}
window.addEventListener('resize', onResize);
window.addEventListener('orientationchange', function () {
setTimeout(onResize, 200);
});
// إيقاف الفرامل عند فقدان التركيز
window.addEventListener('blur', function () {
state.braking = false;
if (leftRepeat) { clearInterval(leftRepeat); leftRepeat = null; }
if (rightRepeat) { clearInterval(rightRepeat); rightRepeat = null; }
ui.btnLeft.classList.remove('pressed');
ui.btnRight.classList.remove('pressed');
});
/* =========================================================
حلقة اللعبة
========================================================= */
let lastTime = 0;
let loopHandle = null;
function loop(now) {
loopHandle = requestAnimationFrame(loop);
if (!lastTime) lastTime = now;
let dt = (now - lastTime) / 1000;
lastTime = now;
// حماية من القفزات الكبيرة (تبديل التبويب)
if (dt > 0.1) dt = 0.1;
if (dt < 0) dt = 0;
if (state.running) {
update(dt);
} else {
// مشهد ساكن خلف شاشة البداية
if (!state.started) {
state.elapsed += dt;
for (let i = 0; i < dashes.length; i++) {
const d = dashes[i];
d.position.z += 6 * dt;
if (d.position.z > DESPAWN_Z) d.position.z -= DASH_TOTAL;
}
for (let i = 0; i < pillars.length; i++) {
const p = pillars[i];
p.pillar.position.z += 6 * dt;
p.cap.position.z += 6 * dt;
if (p.pillar.position.z > DESPAWN_Z) {
p.pillar.position.z -= PILLAR_TOTAL;
p.cap.position.z -= PILLAR_TOTAL;
}
}
// دوران بطيء للسيارة في شاشة البداية
playerCar.rotation.y = Math.sin(state.elapsed * 0.5) * 0.12;
playerCar.position.y = Math.sin(state.elapsed * 1.6) * 0.04;
}
}
renderer.render(scene, camera);
}
/* =========================================================
الإقلاع
========================================================= */
resetWorld();
loopHandle = requestAnimationFrame(loop);
// تنظيف عند إغلاق الصفحة
window.addEventListener('beforeunload', function () {
if (loopHandle) cancelAnimationFrame(loopHandle);
if (leftRepeat) clearInterval(leftRepeat);
if (rightRepeat) clearInterval(rightRepeat);
try {
if (renderer) {
renderer.dispose();
if (renderer.domElement && renderer.domElement.parentNode) {
renderer.domElement.parentNode.removeChild(renderer.domElement);
}
}
} catch (e) { /* ignore */ }
});
})();
</script>
</body>
</html>
التعليقات (1)
الكود جميل ما شاء الله لكن في نقطة مهمه الازرار معكوسه ممكن تعدلها عند #controls في الستايل وتضيف السطر ده في الاخر direction: ltr; وبس كده وهتبقي افضل لو عملت دانا بيز تتحفظ فيها السكور بس ده هيحتاج منك api بس عاااااش
سجّل الدخول للتعليق.