Vice Coast — Realistic Open World
by ChromeBuilder441258 lines18.8 KB🛠️ Three.js (3D graphics)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Vice Coast — Realistic Open World</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#05070a}
body{font-family:Arial,sans-serif}
canvas{display:block}
#hud{
position:fixed;
left:20px;
top:20px;
color:white;
z-index:10;
pointer-events:none;
text-shadow:0 2px 5px #000;
}
#title{font-size:22px;font-weight:bold;letter-spacing:3px}
#status{margin-top:8px;font-size:14px}
#mission{
position:fixed;
top:20px;
right:20px;
color:white;
text-align:right;
z-index:10;
text-shadow:0 2px 5px #000;
}
#controls{
position:fixed;
bottom:18px;
left:20px;
color:#ddd;
z-index:10;
font-size:13px;
text-shadow:0 2px 5px #000;
}
#vignette{
position:fixed;
inset:0;
pointer-events:none;
z-index:8;
box-shadow:inset 0 0 180px rgba(0,0,0,.65);
}
#rain{
position:fixed;
inset:0;
pointer-events:none;
z-index:9;
opacity:0;
}
</style>
</head>
<body>
<div id="hud">
<div id="title">VICE COAST</div>
<div id="status">Loading realistic world...</div>
</div>
<div id="mission">
<b>FREE ROAM</b><br>
Reach the glowing checkpoint
</div>
<div id="controls">
WASD — Move | Shift — Sprint |
N — Night | R — Rain | E — Cinematic Camera
</div>
<div id="vignette"></div>
<div id="rain"></div>
<script type="module">
import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.186.0/build/three.module.js";
import { OrbitControls } from
"https://cdn.jsdelivr.net/npm/three@0.186.0/examples/jsm/controls/OrbitControls.js";
/* =========================================================
BASIC SETUP
========================================================= */
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x8fb7d9);
scene.fog = new THREE.FogExp2(0x9bbbd1,0.0018);
const camera = new THREE.PerspectiveCamera(
65,
innerWidth/innerHeight,
0.1,
2500
);
camera.position.set(0,8,14);
const 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;
renderer.outputColorSpace=THREE.SRGBColorSpace;
renderer.toneMapping=THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure=1.15;
document.body.appendChild(renderer.domElement);
/* =========================================================
LIGHTING
========================================================= */
const sun = new THREE.DirectionalLight(0xffffff,4.0);
sun.position.set(
180,
260,
100
);
sun.castShadow=true;
sun.shadow.mapSize.width=4096;
sun.shadow.mapSize.height=4096;
sun.shadow.camera.left=-500;
sun.shadow.camera.right=500;
sun.shadow.camera.top=500;
sun.shadow.camera.bottom=-500;
sun.shadow.bias=-0.0002;
scene.add(sun);
const hemi = new THREE.HemisphereLight(
0xb9d8ff,
0x26301f,
1.6
);
scene.add(hemi);
const streetAmbient = new THREE.AmbientLight(
0xffffff,
0.35
);
scene.add(streetAmbient);
/* =========================================================
WORLD
========================================================= */
const world = new THREE.Group();
scene.add(world);
const CITY_SIZE=900;
/* =========================================================
GROUND
========================================================= */
const groundMat = new THREE.MeshStandardMaterial({
color:0x59685c,
roughness:0.95,
metalness:0
});
const ground = new THREE.Mesh(
new THREE.PlaneGeometry(
CITY_SIZE,
CITY_SIZE,
80,
80
),
groundMat
);
ground.rotation.x=-Math.PI/2;
ground.receiveShadow=true;
world.add(ground);
/* =========================================================
ROAD MATERIALS
========================================================= */
const roadMat = new THREE.MeshStandardMaterial({
color:0x181b1d,
roughness:0.38,
metalness:0.05
});
const wetRoadMat = new THREE.MeshStandardMaterial({
color:0x111417,
roughness:0.12,
metalness:0.45
});
/* =========================================================
ROADS
========================================================= */
const roads=[];
function createRoad(x,z,w,d){
const road=new THREE.Mesh(
new THREE.BoxGeometry(w,0.08,d),
roadMat
);
road.position.set(x,0.04,z);
road.receiveShadow=true;
world.add(road);
roads.push(road);
}
/* Main road grid */
for(let x=-400;x<=400;x+=100){
createRoad(x,0,18,900);
}
for(let z=-400;z<=400;z+=100){
createRoad(0,z,900,18);
}
/* =========================================================
ROAD MARKINGS
========================================================= */
const markingMat=new THREE.MeshBasicMaterial({
color:0xd8c98a
});
function marking(x,z,vertical){
const m=new THREE.Mesh(
new THREE.BoxGeometry(
vertical?0.25:5,
0.025,
vertical?5:0.25
),
markingMat
);
m.position.set(x,0.095,z);
world.add(m);
}
for(let x=-400;x<=400;x+=100){
for(let z=-420;z<=420;z+=20){
marking(x,z,true);
}
}
for(let z=-400;z<=400;z+=100){
for(let x=-420;x<=420;x+=20){
marking(x,z,false);
}
}
/* =========================================================
BUILDINGS
========================================================= */
const buildingMats=[];
const buildingColors=[
0xaaa49a,
0x7d8790,
0x927c69,
0x626b73,
0xb5ad9b,
0x77715f,
0x4c5961
];
for(const c of buildingColors){
buildingMats.push(
new THREE.MeshStandardMaterial({
color:c,
roughness:.72,
metalness:.08
})
);
}
function createBuilding(x,z,w,d,h){
const mat=
buildingMats[
Math.floor(Math.random()*buildingMats.length)
];
const building=new THREE.Mesh(
new THREE.BoxGeometry(w,h,d),
mat
);
building.position.set(
x,
h/2,
z
);
building.castShadow=true;
building.receiveShadow=true;
world.add(building);
/* windows */
const windowMat=new THREE.MeshStandardMaterial({
color:0x86a9bb,
emissive:0x172c36,
emissiveIntensity:.25,
roughness:.25,
metalness:.35
});
const rows=Math.max(2,Math.floor(h/4));
const cols=Math.max(2,Math.floor(w/4));
for(let r=1;r<rows;r++){
for(let c=0;c<cols;c++){
if(Math.random()<.2) continue;
const windowMesh=new THREE.Mesh(
new THREE.BoxGeometry(.8,.65,.05),
windowMat
);
windowMesh.position.set(
x-w/2+2+c*3,
r*3,
z-d/2-.03
);
world.add(windowMesh);
}
}
}
/* city blocks */
for(let x=-400;x<=400;x+=100){
for(let z=-400;z<=400;z+=100){
/* leave roads clear */
const bx=x+30+(Math.random()*15);
const bz=z+30+(Math.random()*15);
const w=45+Math.random()*28;
const d=45+Math.random()*28;
const h=12+Math.random()*75;
createBuilding(
bx,
bz,
w,
d,
h
);
}
}
/* =========================================================
PALM TREES
========================================================= */
function createPalm(x,z,scale=1){
const palm=new THREE.Group();
const trunkMat=new THREE.MeshStandardMaterial({
color:0x79553b,
roughness:.9
});
const leafMat=new THREE.MeshStandardMaterial({
color:0x1f683c,
roughness:.75
});
const trunk=new THREE.Mesh(
new THREE.CylinderGeometry(
.35,
.55,
7,
10
),
trunkMat
);
trunk.position.y=3.5;
trunk.castShadow=true;
palm.add(trunk);
for(let i=0;i<9;i++){
const leaf=new THREE.Mesh(
new THREE.ConeGeometry(
.38,
5,
7
),
leafMat
);
leaf.position.y=7;
leaf.rotation.z=Math.PI/2;
leaf.rotation.y=
i*Math.PI*2/9;
leaf.translateX(2.2);
leaf.castShadow=true;
palm.add(leaf);
}
palm.position.set(x,0,z);
palm.scale.setScalar(scale);
world.add(palm);
}
for(let i=0;i<180;i++){
const x=(Math.random()-.5)*850;
const z=(Math.random()-.5)*850;
/* avoid center roads */
if(
Math.abs(x%100)<14 ||
Math.abs(z%100)<14
) continue;
createPalm(
x,
z,
.8+Math.random()*.7
);
}
/* =========================================================
BEACH + OCEAN
========================================================= */
const beachMat=new THREE.MeshStandardMaterial({
color:0xd5bd8c,
roughness:.95
});
const beach=new THREE.Mesh(
new THREE.PlaneGeometry(900,90),
beachMat
);
beach.rotation.x=-Math.PI/2;
beach.position.set(0,.02,-485);
world.add(beach);
const oceanMat=new THREE.MeshPhysicalMaterial({
color:0x167da3,
roughness:.08,
metalness:.05,
transmission:.05,
transparent:true,
opacity:.92
});
const ocean=new THREE.Mesh(
new THREE.PlaneGeometry(
1200,
300,
100,
40
),
oceanMat
);
ocean.rotation.x=-Math.PI/2;
ocean.position.set(0,-.1,-620);
world.add(ocean);
/* =========================================================
PLAYER
========================================================= */
const player=new THREE.Group();
const skinMat=new THREE.MeshStandardMaterial({
color:0xb98262,
roughness:.7
});
const shirtMat=new THREE.MeshStandardMaterial({
color:0x252c35,
roughness:.7
});
const body=new THREE.Mesh(
new THREE.CapsuleGeometry(
.55,
1.4,
8,
16
),
shirtMat
);
body.position.y=1.35;
body.castShadow=true;
player.add(body);
const head=new THREE.Mesh(
new THREE.SphereGeometry(.43,24,24),
skinMat
);
head.position.y=2.55;
head.castShadow=true;
player.add(head);
player.position.set(0,0,30);
world.add(player);
/* =========================================================
VEHICLES
========================================================= */
const cars=[];
const carColors=[
0xb52d2d,
0x22262b,
0xd6d6d2,
0x315d88,
0xb8892f,
0x4c7654
];
function createCar(x,z,angle=0){
const car=new THREE.Group();
const bodyMat=new THREE.MeshPhysicalMaterial({
color:
carColors[
Math.floor(Math.random()*carColors.length)
],
roughness:.2,
metalness:.75,
clearcoat:1,
clearcoatRoughness:.08
});
const body=new THREE.Mesh(
new THREE.BoxGeometry(4.4,1.1,2),
bodyMat
);
body.position.y=.9;
body.castShadow=true;
car.add(body);
const cabinMat=new THREE.MeshPhysicalMaterial({
color:0x172b38,
roughness:.08,
metalness:.15,
transmission:.05,
transparent:true,
opacity:.9
});
const cabin=new THREE.Mesh(
new THREE.BoxGeometry(2.2,.85,1.65),
cabinMat
);
cabin.position.set(-.1,1.65,0);
cabin.castShadow=true;
car.add(cabin);
const wheelMat=new THREE.MeshStandardMaterial({
color:0x101010,
roughness:.75
});
for(const wx of [-1.5,1.5]){
for(const wz of [-1.05,1.05]){
const wheel=new THREE.Mesh(
new THREE.CylinderGeometry(
.42,.42,.3,20
),
wheelMat
);
wheel.rotation.z=Math.PI/2;
wheel.position.set(
wx,
.48,
wz
);
wheel.castShadow=true;
car.add(wheel);
}
}
car.position.set(x,.1,z);
car.rotation.y=angle;
world.add(car);
cars.push({
mesh:car,
speed:.15+Math.random()*.25
});
}
/* traffic */
for(let i=0;i<70;i++){
const vertical=Math.random()<.5;
if(vertical){
createCar(
Math.round(
(Math.random()*8-4)
)*100+8,
(Math.random()-.5)*800,
Math.PI/2
);
}else{
createCar(
(Math.random()-.5)*800,
Math.round(
(Math.random()*8-4)
)*100-8,
0
);
}
}
/* =========================================================
CHECKPOINT
========================================================= */
const checkpoint=new THREE.Group();
const ringMat=new THREE.MeshBasicMaterial({
color:0x00ffff,
transparent:true,
opacity:.8
});
const ring=new THREE.Mesh(
new THREE.TorusGeometry(5,.25,12,48),
ringMat
);
ring.rotation.x=Math.PI/2;
checkpoint.add(ring);
const beamMat=new THREE.MeshBasicMaterial({
color:0x00ffff,
transparent:true,
opacity:.12,
side:THREE.DoubleSide
});
const beam=new THREE.Mesh(
new THREE.CylinderGeometry(
4.8,4.8,20,32,1,true
),
beamMat
);
beam.position.y=10;
checkpoint.add(beam);
checkpoint.position.set(
300,
0,
-300
);
world.add(checkpoint);
/* =========================================================
STREET LIGHTS
========================================================= */
function createStreetLight(x,z){
const poleMat=new THREE.MeshStandardMaterial({
color:0x25282b,
metalness:.7,
roughness:.35
});
const pole=new THREE.Mesh(
new THREE.CylinderGeometry(
.12,.16,8,10
),
poleMat
);
pole.position.set(x,4,z);
pole.castShadow=true;
world.add(pole);
const bulb=new THREE.PointLight(
0xffc982,
0,
35,
2
);
bulb.position.set(x,7.7,z);
world.add(bulb);
return bulb;
}
const streetLights=[];
for(let x=-400;x<=400;x+=100){
for(let z=-400;z<=400;z+=100){
streetLights.push(
createStreetLight(
x+8,
z+8
)
);
}
}
/* =========================================================
WEATHER
========================================================= */
let raining=false;
const rainCount=2500;
const rainGeo=new THREE.BufferGeometry();
const rainPositions=new Float32Array(
rainCount*3
);
for(let i=0;i<rainCount;i++){
rainPositions[i*3]=
(Math.random()-.5)*900;
rainPositions[i*3+1]=
Math.random()*200;
rainPositions[i*3+2]=
(Math.random()-.5)*900;
}
rainGeo.setAttribute(
"position",
new THREE.BufferAttribute(
rainPositions,
3
)
);
const rainMat=new THREE.PointsMaterial({
color:0xb9dfff,
size:.16,
transparent:true,
opacity:.7
});
const rain=new THREE.Points(
rainGeo,
rainMat
);
rain.visible=false;
scene.add(rain);
/* =========================================================
NIGHT MODE
========================================================= */
let night=false;
function setNight(value){
night=value;
if(night){
scene.background.set(0x071426);
scene.fog.color.set(0x071426);
sun.intensity=.35;
hemi.intensity=.45;
streetAmbient.intensity=.12;
for(const light of streetLights)
light.intensity=5;
}else{
scene.background.set(0x8fb7d9);
scene.fog.color.set(0x9bbbd1);
sun.intensity=4;
hemi.intensity=1.6;
streetAmbient.intensity=.35;
for(const light of streetLights)
light.intensity=0;
}
}
/* =========================================================
INPUT
========================================================= */
const keys={};
addEventListener(
"keydown",
e=>{
keys[e.key.toLowerCase()]=true;
if(e.key.toLowerCase()==="n")
setNight(!night);
if(e.key.toLowerCase()==="r")
setRain(!raining);
if(e.key.toLowerCase()==="e")
cinematic=!cinematic;
}
);
addEventListener(
"keyup",
e=>{
keys[e.key.toLowerCase()]=false;
}
);
/* =========================================================
RAIN
========================================================= */
function setRain(value){
raining=value;
rain.visible=raining;
if(raining){
rainGeo.attributes.position.needsUpdate=true;
for(const road of roads)
road.material=wetRoadMat;
}else{
for(const road of roads)
road.material=roadMat;
}
}
/* =========================================================
PLAYER MOVEMENT
========================================================= */
const clock=new THREE.Clock();
let cinematic=false;
function updatePlayer(dt){
let dx=0;
let dz=0;
if(keys["w"]) dz-=1;
if(keys["s"]) dz+=1;
if(keys["a"]) dx-=1;
if(keys["d"]) dx+=1;
const length=Math.hypot(dx,dz);
if(length>0){
dx/=length;
dz/=length;
const speed=
keys["shift"]?18:9;
player.position.x+=
dx*speed*dt;
player.position.z+=
dz*speed*dt;
player.rotation.y=
Math.atan2(dx,dz);
}
player.position.x=
THREE.MathUtils.clamp(
player.position.x,
-440,
440
);
player.position.z=
THREE.MathUtils.clamp(
player.position.z,
-440,
440
);
}
/* =========================================================
TRAFFIC
========================================================= */
function updateTraffic(dt){
for(const car of cars){
const obj=car.mesh;
if(Math.abs(obj.rotation.y)<.1){
obj.position.x+=
car.speed*dt*60;
if(obj.position.x>460)
obj.position.x=-460;
}else{
obj.position.z+=
car.speed*dt*60;
if(obj.position.z>460)
obj.position.z=-460;
}
}
}
/* =========================================================
RAIN UPDATE
========================================================= */
function updateRain(dt){
if(!raining)return;
const p=
rainGeo.attributes.position.array;
for(let i=0;i<rainCount;i++){
p[i*3+1]-=
55*dt;
if(p[i*3+1]<0)
p[i*3+1]=200;
}
rainGeo.attributes.position.needsUpdate=true;
}
/* =========================================================
WATER ANIMATION
========================================================= */
let waterTime=0;
function updateOcean(dt){
waterTime+=dt;
ocean.position.y=
-.1+
Math.sin(waterTime*.7)*.03;
}
/* =========================================================
CINEMATIC CAMERA
========================================================= */
function updateCamera(dt){
if(cinematic){
const t=clock.elapsedTime;
camera.position.x=
player.position.x+
Math.sin(t*.22)*15;
camera.position.z=
player.position.z+
Math.cos(t*.22)*15;
camera.position.y=
8+
Math.sin(t*.5)*1.5;
}else{
const desired=
new THREE.Vector3(
player.position.x,
player.position.y+6,
player.position.z+12
);
camera.position.lerp(
desired,
1-Math.pow(.001,dt)
);
}
const target=
new THREE.Vector3(
player.position.x,
player.position.y+1.8,
player.position.z
);
camera.lookAt(target);
}
/* =========================================================
CHECKPOINT
========================================================= */
function updateCheckpoint(){
const distance=
player.position.distanceTo(
checkpoint.position
);
ring.rotation.z=
clock.elapsedTime;
beam.rotation.y=
clock.elapsedTime*.3;
if(distance<7){
checkpoint.position.set(
(Math.random()-.5)*700,
0,
(Math.random()-.5)*700
);
}
}
/* =========================================================
HUD
========================================================= */
function updateHUD(){
const distance=
player.position.distanceTo(
checkpoint.position
);
document.getElementById("status").textContent=
`${night?"NIGHT":"DAY"} • ${
raining?"RAIN":"CLEAR"
} • CHECKPOINT ${
Math.round(distance)
}m`;
}
/* =========================================================
RESIZE
========================================================= */
addEventListener(
"resize",
()=>{
camera.aspect=
innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(
innerWidth,
innerHeight
);
}
);
/* =========================================================
MAIN LOOP
========================================================= */
setNight(false);
function animate(){
requestAnimationFrame(animate);
const dt=Math.min(
clock.getDelta(),
.033
);
updatePlayer(dt);
updateTraffic(dt);
updateRain(dt);
updateOcean(dt);
updateCamera(dt);
updateCheckpoint();
updateHUD();
renderer.render(
scene,
camera
);
}
animate();
</script>
</body>
</html>Game Source: Vice Coast — Realistic Open World
Creator: ChromeBuilder44
Libraries: three
Complexity: complex (1258 lines, 18.8 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: vice-coast-realistic-open-world-chromebuilder44" to link back to the original. Then publish at arcadelab.ai/publish.