Liberty City 3D
by ApexPanther55538 lines17.5 KB🛠️ Three.js (3D graphics)
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Liberty City 3D</title>
<style>
*{box-sizing:border-box}
body{margin:0;overflow:hidden;background:#111;font-family:Arial}
canvas{display:block}
#hud{position:fixed;top:12px;right:15px;color:white;font-size:17px;line-height:1.8;text-shadow:0 2px 5px black;pointer-events:none}
#mission{position:fixed;top:12px;left:15px;background:#07151ccc;color:white;padding:10px;border-radius:10px}
#hint{position:fixed;bottom:18px;left:50%;transform:translateX(-50%);color:white;background:#111c;padding:10px 18px;border-radius:20px;white-space:nowrap}
#map{position:fixed;bottom:15px;right:15px;border:2px solid white;border-radius:50%;background:#222;width:120px;height:120px}
#menu{position:fixed;inset:0;background:#07121deF;display:flex;align-items:center;justify-content:center;z-index:10;color:white}
.panel{background:#152b3a;padding:25px;border-radius:20px;width:min(90%,450px);text-align:center;max-height:90vh;overflow:auto}
button{border:0;border-radius:12px;padding:12px 18px;margin:5px;background:#16a085;color:white;font-weight:bold;font-size:15px}
input,select{accent-color:#16a085}
#mobile{display:none}
.pad{position:fixed;bottom:20px;display:grid;gap:7px;z-index:4}
#move{left:15px;grid-template-columns:48px 48px 48px}
#acts{right:15px;grid-template-columns:60px 60px}
.pad button{width:48px;height:48px;padding:0;background:#152d3bcc;border:1px solid #fff8}
#acts button{width:60px;height:52px;font-size:12px}
#rotate{display:none;position:fixed;inset:0;background:#07121d;color:white;z-index:20;align-items:center;justify-content:center;text-align:center;font-size:24px}
@media(orientation:portrait) and (max-width:800px){
#rotate{display:flex}
#map{width:85px;height:85px}
}
</style>
<script type="importmap">
{"imports":{"three":"https://unpkg.com/three@0.160.0/build/three.module.js"}}
</script>
</head>
<body>
<div id="rotate">↻<br>أدر هاتفك إلى الوضع الأفقي لتلعب</div>
<div id="hud">💰 $0<br>❤️ 100<br>⭐ 0</div>
<div id="mission">المهمة: استكشف المدينة</div>
<div id="hint">WASD للحركة · F للمركبة · Shift للجري</div>
<canvas id="map" width="160" height="160"></canvas>
<div id="mobile">
<div class="pad" id="move">
<button data-key="KeyW" style="grid-column:2">▲</button>
<button data-key="KeyA">◀</button>
<button data-key="KeyS">▼</button>
<button data-key="KeyD">▶</button>
</div>
<div class="pad" id="acts">
<button data-key="ShiftLeft">جري</button>
<button id="enter">ركوب</button>
<button data-key="Space">قفز</button>
<button id="horn">بوق</button>
</div>
</div>
<div id="menu">
<div class="panel">
<h1 id="title">LIBERTY CITY</h1>
<p>عالم مفتوح ثلاثي الأبعاد. استكشف المدينة وقُد المركبات واكتشف المهام.</p>
<div id="settings" style="display:none">
<p>جودة الرسومات</p>
<select id="quality">
<option value="1">عالية</option>
<option value=".7">متوسطة</option>
<option value=".45">منخفضة</option>
</select>
<p>حساسية الكاميرا</p>
<input id="sensitivity" type="range" min=".5" max="2" step=".1" value="1">
<p>مستوى الصوت</p>
<input id="volume" type="range" min="0" max="1" step=".1" value=".5">
</div>
<button id="start">ابدأ اللعب</button>
<button id="settingsBtn">الإعدادات</button>
<button id="resume" style="display:none">متابعة</button>
</div>
</div>
<script type="module">
import * as THREE from 'three';
// إعداد العالم
const scene=new THREE.Scene();
scene.background=new THREE.Color(0x86b9df);
scene.fog=new THREE.Fog(0x86b9df,100,500);
const camera=new THREE.PerspectiveCamera(70,innerWidth/innerHeight,.1,1000);
const renderer=new THREE.WebGLRenderer({antialias:true});
renderer.setSize(innerWidth,innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio,1.5));
renderer.shadowMap.enabled=true;
renderer.shadowMap.type=THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
scene.add(new THREE.HemisphereLight(0xffffff,0x52604a,1.6));
const sun=new THREE.DirectionalLight(0xffe4bd,2);
sun.position.set(100,150,50);
sun.castShadow=true;
sun.shadow.mapSize.set(1024,1024);
scene.add(sun);
const N=8,B=70,W=N*B;
const mat=c=>new THREE.MeshStandardMaterial({color:c,roughness:.8});
const box=(w,h,d,c,x,y,z)=>{
const m=new THREE.Mesh(new THREE.BoxGeometry(w,h,d),mat(c));
m.position.set(x,y,z);m.castShadow=true;m.receiveShadow=true;
return m;
};
// الأرض والطرق
const ground=new THREE.Mesh(
new THREE.PlaneGeometry(W+200,W+200),mat(0x497b43)
);
ground.rotation.x=-Math.PI/2;
ground.position.set(W/2,-.1,W/2);
scene.add(ground);
for(let i=0;i<=N;i++){
const r1=box(23,.12,W+23,0x303438,i*B,0,W/2);
const r2=box(W+23,.12,23,0x303438,W/2,0,i*B);
for(let j=0;j<N;j++){
box(.2,.03,7,0xe8d27d,i*B,.1,j*B+35);
box(7,.03,.2,0xe8d27d,j*B+35,.1,i*B);
}
}
// المباني
const buildings=[];
const colors=[0x9ba8ac,0xb8a58d,0x788e99,0xb2b5b0,0x8d8078];
for(let i=0;i<N;i++)for(let j=0;j<N;j++){
for(let sx of [-1,1])for(let sz of [-1,1]){
if(Math.random()<.12)continue;
let w=14+Math.random()*5;
let d=14+Math.random()*5;
let h=12+Math.random()*45;
let x=(i+.5)*B+sx*12;
let z=(j+.5)*B+sz*12;
let b=box(w,h,d,colors[Math.floor(Math.random()*colors.length)],x,h/2,z);
buildings.push({x,z,w,d});
scene.add(b);
// نوافذ المباني
for(let y=3;y<h-2;y+=4){
let win=box(w*.65,.8,.12,0x5794ae,x,y,z+d/2+.08);
scene.add(win);
}
}
}
// الأشجار
for(let i=0;i<100;i++){
let x=Math.random()*W,z=Math.random()*W;
let trunk=new THREE.Mesh(new THREE.CylinderGeometry(.2,.3,2,8),mat(0x755033));
trunk.position.set(x,1,z);scene.add(trunk);
let leaves=new THREE.Mesh(new THREE.SphereGeometry(1.7,8,8),mat(0x28703d));
leaves.position.set(x,3,z);leaves.castShadow=true;scene.add(leaves);
}
// الشخصيات المتحركة
function makePerson(color=0x2255aa){
const g=new THREE.Group();
const torso=box(.65,1,.35,color,0,1.25,0);
const head=new THREE.Mesh(new THREE.SphereGeometry(.24,12,12),mat(0xd8a27e));
head.position.y=2;g.add(torso,head);
const limbs=[];
for(let side of [-1,1]){
let arm=new THREE.Group();
arm.position.set(side*.43,1.65,0);
arm.add(box(.19,.65,.2,0xb78c70,0,-.3,0));
g.add(arm);limbs.push(arm);
let leg=new THREE.Group();
leg.position.set(side*.18,.85,0);
leg.add(box(.23,.8,.25,0x202a36,0,-.35,0));
g.add(leg);limbs.push(leg);
}
return {mesh:g,limbs,phase:Math.random()*6};
}
const people=[];
for(let i=0;i<32;i++){
const person=makePerson([0x2255aa,0x993333,0x228855,0xaaaa44,0xeeeeee][i%5]);
person.mesh.position.set(Math.random()*W,0,Math.random()*W);
scene.add(person.mesh);
people.push({
...person,
direction:Math.random()*Math.PI*2,
speed:1+Math.random()*1.2,
change:Math.random()*5
});
}
// المركبات
function makeVehicle(type,color){
const g=new THREE.Group();
if(type==="car"){
g.add(box(2,.8,4,color,0,.8,0));
g.add(box(1.65,.7,2,0x85b7d1,0,1.5,-.2));
for(let x of [-1,1])for(let z of [-1.3,1.3]){
let w=new THREE.Mesh(new THREE.CylinderGeometry(.4,.4,.25,16),mat(0x111111));
w.rotation.z=Math.PI/2;w.position.set(x,.4,z);g.add(w);
}
}else if(type==="bike"){
g.add(box(.35,.55,1.4,color,0,.8,0));
for(let z of [-.7,.7]){
let w=new THREE.Mesh(new THREE.TorusGeometry(.36,.08,8,16),mat(0x111111));
w.position.set(0,.4,z);g.add(w);
}
}else{
g.add(box(.08,.08,1.4,color,0,.7,0));
for(let z of [-.65,.65]){
let w=new THREE.Mesh(new THREE.TorusGeometry(.34,.045,8,16),mat(0x111111));
w.position.set(0,.4,z);g.add(w);
}
g.add(box(.5,.08,.08,0x222222,0,1.1,.5));
}
return g;
}
const vehicles=[];
function spawnVehicle(type,x,z){
const mesh=makeVehicle(type,[0xcc2222,0x2255cc,0xeeeeee,0x22aa55,0xe6bd35][Math.floor(Math.random()*5)]);
mesh.position.set(x,0,z);scene.add(mesh);
vehicles.push({mesh,type,speed:0,heading:Math.random()*6.28});
}
spawnVehicle("car",B+5,B);
spawnVehicle("car",B*2+8,B*2);
spawnVehicle("bike",B+10,B+12);
spawnVehicle("bicycle",B-8,B+15);
for(let i=0;i<18;i++){
spawnVehicle(Math.random()<.7?"car":Math.random()<.5?"bike":"bicycle",
Math.random()*W,Math.random()*W);
}
// هليكوبتر متحركة
const heli=new THREE.Group();
heli.add(box(2,.7,4,0x334b5d,0,0,0));
heli.add(box(1.6,.6,1.5,0x78aaca,0,.3,-.2));
heli.add(box(.12,.12,5,0x222222,0,.6,0));
const rotor=new THREE.Group();
rotor.add(box(7,.07,.16,0x222222,0,0,0));
rotor.add(box(.16,.07,7,0x222222,0,0,0));
rotor.position.y=.8;heli.add(rotor);
heli.position.set(200,55,200);scene.add(heli);
// اللاعب
const player=makePerson(0x2255aa);
scene.add(player.mesh);
player.mesh.position.set(B,0,B);
let activeVehicle=null;
let money=0,missionCount=0;
let yaw=0,pitch=.3,paused=true;
let time=0,velocityY=0;
const keys={};
addEventListener('keydown',e=>{
keys[e.code]=true;
if(e.code==="KeyF")toggleVehicle();
if(e.code==="Escape")openMenu();
});
addEventListener('keyup',e=>keys[e.code]=false);
renderer.domElement.addEventListener('click',()=>{
if(!paused)renderer.domElement.requestPointerLock?.();
});
addEventListener('mousemove',e=>{
if(document.pointerLockElement&&!paused){
let s=+$("sensitivity").value;
yaw-=e.movementX*.002*s;
pitch=Math.max(.05,Math.min(1.1,pitch+e.movementY*.001*s));
}
});
function $(id){return document.getElementById(id)}
function nearestVehicle(){
let best=null,distance=7;
for(let v of vehicles){
if(v===activeVehicle)continue;
let d=v.mesh.position.distanceTo(player.mesh.position);
if(d<distance){distance=d;best=v}
}
return best;
}
function toggleVehicle(){
if(activeVehicle){
player.mesh.position.copy(activeVehicle.mesh.position);
player.mesh.position.x+=3;
player.mesh.visible=true;
activeVehicle.speed=0;
activeVehicle=null;
}else{
let v=nearestVehicle();
if(v){activeVehicle=v;player.mesh.visible=false;yaw=v.heading}
}
}
function animatePerson(p,dt,moving){
p.phase+=dt*(moving?9:2);
let a=Math.sin(p.phase)*.55*(moving?1:.08);
p.limbs[0].rotation.x=a;
p.limbs[2].rotation.x=-a;
p.limbs[1].rotation.x=-a;
p.limbs[3].rotation.x=a;
p.mesh.position.y=moving?Math.abs(Math.sin(p.phase*2))*.06:0;
}
function walk(dt){
let f=(keys.KeyW?1:0)-(keys.KeyS?1:0);
let s=(keys.KeyD?1:0)-(keys.KeyA?1:0);
let x=Math.sin(yaw)*f-Math.cos(yaw)*s;
let z=Math.cos(yaw)*f+Math.sin(yaw)*s;
let len=Math.hypot(x,z);
if(len){
x/=len;z/=len;
let speed=keys.ShiftLeft?9:5;
player.mesh.position.x+=x*speed*dt;
player.mesh.position.z+=z*speed*dt;
player.mesh.rotation.y=Math.atan2(x,z);
}
if(keys.Space&&player.mesh.position.y<=.01)velocityY=7;
velocityY-=18*dt;
player.mesh.position.y+=velocityY*dt;
if(player.mesh.position.y<0){player.mesh.position.y=0;velocityY=0}
animatePerson(player,dt,!!len);
}
function drive(v,dt){
let acceleration=(keys.KeyW?1:0)-(keys.KeyS?1:0);
let steering=(keys.KeyA?1:0)-(keys.KeyD?1:0);
let max=v.type==="car"?35:v.type==="bike"?48:12;
v.speed+=acceleration*(v.speed<0?25:17)*dt;
if(!acceleration)v.speed*=Math.max(0,1-1.7*dt);
if(keys.Space)v.speed*=Math.max(0,1-5*dt);
v.speed=Math.max(-10,Math.min(max,v.speed));
v.heading+=steering*2*dt*Math.sign(v.speed);
v.mesh.position.x+=Math.sin(v.heading)*v.speed*dt;
v.mesh.position.z+=Math.cos(v.heading)*v.speed*dt;
v.mesh.rotation.y=v.heading;
}
// مؤثرات الجسيمات
const particles=[];
function burst(position,color=0xffcc66,count=10){
for(let i=0;i<count;i++){
let m=new THREE.Mesh(new THREE.SphereGeometry(.08,5,5),new THREE.MeshBasicMaterial({color,transparent:true}));
m.position.copy(position);
scene.add(m);
particles.push({mesh:m,life:1,
velocity:new THREE.Vector3((Math.random()-.5)*4,Math.random()*4,(Math.random()-.5)*4)});
}
}
function updateParticles(dt){
for(let i=particles.length-1;i>=0;i--){
let p=particles[i];
p.life-=dt;
p.mesh.position.addScaledVector(p.velocity,dt);
p.velocity.y-=5*dt;
p.mesh.material.opacity=Math.max(0,p.life);
p.mesh.scale.setScalar(Math.max(.01,p.life));
if(p.life<=0){scene.remove(p.mesh);particles.splice(i,1)}
}
}
// أصوات مولدة دون ملفات خارجية
let audio=null,engineOsc=null,engineGain=null;
function startAudio(){
if(audio)return;
audio=new (window.AudioContext||window.webkitAudioContext)();
engineOsc=audio.createOscillator();
engineGain=audio.createGain();
engineOsc.type="sawtooth";
engineOsc.frequency.value=55;
engineGain.gain.value=0;
engineOsc.connect(engineGain);
engineGain.connect(audio.destination);
engineOsc.start();
}
function sound(freq=440,duration=.12){
if(!audio)return;
let o=audio.createOscillator(),g=audio.createGain();
o.frequency.value=freq;
g.gain.setValueAtTime(+$("volume").value*.12,audio.currentTime);
g.gain.exponentialRampToValueAtTime(.001,audio.currentTime+duration);
o.connect(g);g.connect(audio.destination);
o.start();o.stop(audio.currentTime+duration);
}
// عناصر قابلة للجمع
const coins=[];
for(let i=0;i<35;i++){
let c=new THREE.Mesh(new THREE.TorusGeometry(.65,.16,8,18),new THREE.MeshStandardMaterial({color:0xffd32a,metalness:.7,emissive:0x553300}));
c.position.set(Math.random()*W,1.5,Math.random()*W);
scene.add(c);coins.push(c);
}
const map=$("map").getContext("2d");
function update(dt){
time+=dt;
if(paused)return;
if(activeVehicle){
drive(activeVehicle,dt);
player.mesh.position.copy(activeVehicle.mesh.position);
yaw+=(activeVehicle.heading-yaw)*Math.min(1,dt*2);
if(engineGain){
engineGain.gain.setTargetAtTime(Math.abs(activeVehicle.speed)*.0007, audio.currentTime,.1);
engineOsc.frequency.setTargetAtTime(45+Math.abs(activeVehicle.speed)*3,audio.currentTime,.1);
}
}else{
walk(dt);
if(engineGain)engineGain.gain.setTargetAtTime(0,audio.currentTime,.1);
}
// الشخصيات تتجول وتتفاعل مع محيطها
for(let p of people){
p.change-=dt;
if(p.change<=0){p.direction+=Math.random()*2-1;p.change=2+Math.random()*5}
let dx=Math.sin(p.direction),dz=Math.cos(p.direction);
p.mesh.position.x+=dx*p.speed*dt;
p.mesh.position.z+=dz*p.speed*dt;
p.mesh.rotation.y=Math.atan2(dx,dz);
if(p.mesh.position.x<2||p.mesh.position.x>W-2)p.direction=Math.PI-p.direction;
if(p.mesh.position.z<2||p.mesh.position.z>W-2)p.direction=-p.direction;
animatePerson(p,dt,true);
// تغيير اتجاه الشخصيات عند الاقتراب من اللاعب
let distance=p.mesh.position.distanceTo(player.mesh.position);
if(distance<3)p.direction=Math.atan2(p.mesh.position.x-player.mesh.position.x,p.mesh.position.z-player.mesh.position.z);
}
// الهليكوبتر والمروحة
heli.position.x=200+Math.sin(time*.12)*110;
heli.position.z=200+Math.cos(time*.12)*110;
heli.position.y=55+Math.sin(time*.8)*2;
heli.rotation.y=time*.15;
rotor.rotation.y+=dt*18;
// العملات
for(let c of coins){
c.rotation.y+=dt*2;
c.position.y=1.5+Math.sin(time*3+c.position.x)*.25;
if(c.visible&&c.position.distanceTo(player.mesh.position)<2.5){
c.visible=false;money+=100;missionCount++;
burst(c.position,0xffd32a,16);sound(850,.2);
$("mission").textContent="الرموز المجمعة: "+missionCount;
}
}
// الجسيمات
updateParticles(dt);
// الكاميرا
let target=player.mesh.position.clone();
target.y+=1.5;
let dist=activeVehicle?10:6;
camera.position.set(
target.x-Math.sin(yaw)*Math.cos(pitch)*dist,
target.y+Math.sin(pitch)*dist+1,
target.z-Math.cos(yaw)*Math.cos(pitch)*dist
);
camera.lookAt(target);
// واجهة اللعبة
$("hud").innerHTML=`💰 $${money}<br>❤️ 100<br>⭐ 0`;
$("hint").textContent=activeVehicle?"F للنزول · WASD للقيادة":
nearestVehicle()?"F لركوب المركبة القريبة":"WASD للحركة · Shift للجري · Space للقفز";
// الخريطة
map.fillStyle="#263b37";map.fillRect(0,0,160,160);
map.fillStyle="#65716d";
for(let i=0;i<=N;i++){
map.fillRect(i*160/N-2,0,4,160);
map.fillRect(0,i*160/N-2,160,4);
}
for(let v of vehicles){
map.fillStyle="#48b9ed";
map.fillRect(v.mesh.position.x/W*160-2,v.mesh.position.z/W*160-2,4,4);
}
map.fillStyle="white";
map.beginPath();
map.arc(player.mesh.position.x/W*160,player.mesh.position.z/W*160,4,0,Math.PI*2);
map.fill();
}
let last=performance.now();
function loop(now){
requestAnimationFrame(loop);
let dt=Math.min((now-last)/1000,.04);
last=now;
update(dt);
renderer.render(scene,camera);
}
requestAnimationFrame(loop);
// القائمة والإعدادات
function openMenu(){
paused=true;
document.exitPointerLock?.();
$("menu").style.display="flex";
$("start").style.display="none";
$("resume").style.display="inline-block";
}
$("start").onclick=()=>{
startAudio();
paused=false;
$("menu").style.display="none";
};
$("resume").onclick=()=>{
paused=false;
$("menu").style.display="none";
};
$("settingsBtn").onclick=()=>{
$("settings").style.display=$("settings").style.display==="none"?"block":"none";
};
$("quality").onchange=()=>{
renderer.setPixelRatio(devicePixelRatio*+$("quality").value);
renderer.setSize(innerWidth,innerHeight);
};
$("volume").oninput=()=>{};
$("enter").onclick=toggleVehicle;
$("horn").onclick=()=>sound(180,.4);
document.querySelectorAll("[data-key]").forEach(b=>{
const k=b.dataset.key;
b.addEventListener("pointerdown",e=>{e.preventDefault();keys[k]=true});
["pointerup","pointerleave","pointercancel"].forEach(ev=>
b.addEventListener(ev,()=>keys[k]=false));
});
if("ontouchstart" in window)$("mobile").style.display="block";
addEventListener("resize",()=>{
camera.aspect=innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth,innerHeight);
});
openMenu();
</script>
</body>
</html>Game Source: Liberty City 3D
Creator: ApexPanther55
Libraries: three
Complexity: complex (538 lines, 17.5 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: liberty-city-3d-apexpanther55" to link back to the original. Then publish at arcadelab.ai/publish.