Island Smash Realism
by NovaShark68411 lines14.0 KB๐ ๏ธ Three.js (3D graphics)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
*{box-sizing:border-box}
body{margin:0;overflow:hidden;background:#87cde9;font-family:Arial,sans-serif}
canvas{display:block}
#hud{position:fixed;top:12px;left:12px;color:white;background:#10202ad9;padding:12px 15px;border-radius:10px;line-height:1.65;pointer-events:none}
#hud b{font-size:19px;color:#baffc7}
#status{color:#ffe9a3}
#cross{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);font-size:24px;color:white;text-shadow:0 1px 4px black;pointer-events:none}
#start{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:#06131d99;color:white;text-align:center;padding:20px}
#card{max-width:450px;background:#10252bf0;border:1px solid #8bbac766;border-radius:18px;padding:26px;box-shadow:0 15px 60px #0006}
#card h1{margin:0 0 10px;color:#aaffc1}
#card p{line-height:1.6}
button{border:0;border-radius:9px;background:#8cffaa;color:#10231b;padding:13px 22px;font-weight:bold;font-size:16px;cursor:pointer}
#tip{position:fixed;bottom:12px;left:50%;transform:translateX(-50%);background:#10202ad9;color:white;padding:10px 14px;border-radius:9px;text-align:center;white-space:nowrap;max-width:95%;font-size:13px;pointer-events:none}
</style>
</head>
<body>
<div id="hud"><b>ISLAND SMASH</b><br>
WASD โ Move<br>
Mouse โ Look around<br>
Click โ Swing hammer<br>
Space โ Jump<br>
<span id="status">Hits: 0 | Pieces broken: 0</span>
</div>
<div id="cross">+</div>
<div id="tip">Click PLAY, then click to swing at objects</div>
<div id="start"><div id="card">
<h1>ISLAND SMASH</h1>
<p>Explore the island in first person. Swing your hammer at wood, glass, stone, and metal. Different materials react differently to impacts.</p>
<p>WASD to move ยท Mouse to look ยท Click to smash ยท Space to jump ยท Esc to unlock mouse</p>
<button id="play">PLAY GAME</button>
</div></div>
<script>
(function(){
'use strict';
const msg=document.getElementById('status');
const overlay=document.getElementById('start');
const tip=document.getElementById('tip');
if(!window.THREE){
tip.textContent='Three.js did not load. Check the ArcadeLab preview settings.';
return;
}
const T=window.THREE;
const scene=new T.Scene();
scene.background=new T.Color(0x9bcfe8);
scene.fog=new T.Fog(0x9bcfe8,45,115);
const camera=new T.PerspectiveCamera(76,innerWidth/innerHeight,.05,180);
camera.rotation.order='YXZ';
const renderer=new T.WebGLRenderer({antialias:true});
renderer.setSize(innerWidth,innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio||1,1.5));
renderer.shadowMap.enabled=true;
renderer.shadowMap.type=T.PCFSoftShadowMap;
if('outputColorSpace' in renderer) renderer.outputColorSpace=T.SRGBColorSpace;
else if('outputEncoding' in renderer) renderer.outputEncoding=T.sRGBEncoding;
document.body.appendChild(renderer.domElement);
scene.add(new T.HemisphereLight(0xdaf4ff,0x526044,1.8));
const sun=new T.DirectionalLight(0xffedcf,2.7);
sun.position.set(-18,32,12);
sun.castShadow=true;
sun.shadow.mapSize.set(1024,1024);
sun.shadow.camera.left=-32;sun.shadow.camera.right=32;
sun.shadow.camera.top=32;sun.shadow.camera.bottom=-32;
scene.add(sun);
function material(color,roughness=.8,metalness=0){
return new T.MeshStandardMaterial({
color:color,roughness:roughness,metalness:metalness
});
}
const mats={
grass:material(0x4c8542),
sand:material(0xcbb887),
water:material(0x167eaa,.28,.1),
wood:material(0x87512c),
woodLight:material(0xb47a43),
leaf:material(0x28683b),
leaf2:material(0x43864a),
plaster:material(0xe2d6bd),
concrete:material(0x888a84),
roof:material(0x783c30),
glass:material(0x5bb4ca,.15,.15),
metal:material(0x7e8991,.32,.65),
rubber:material(0x202329),
red:material(0x96372c,.34,.25),
paint:material(0xb7c5cc,.3,.55),
dark:material(0x252c31),
gold:material(0xe6b85e,.35,.6)
};
function addMesh(geometry,mat,x,y,z,sx=1,sy=1,sz=1,parent=scene){
const o=new T.Mesh(geometry,mat);
o.position.set(x,y,z);
o.scale.set(sx,sy,sz);
o.castShadow=true;
o.receiveShadow=true;
parent.add(o);
return o;
}
function box(x,y,z,sx,sy,sz,mat,parent=scene){
return addMesh(new T.BoxGeometry(sx,sy,sz),mat,x,y,z,1,1,1,parent);
}
// Ocean, sandy shore, and grassy island
const ocean=addMesh(new T.PlaneGeometry(240,240),mats.water,0,-1.5,0);
ocean.rotation.x=-Math.PI/2;
addMesh(new T.CylinderGeometry(25,24,2.5,64),mats.sand,0,-.4,0);
addMesh(new T.CylinderGeometry(23.2,23.2,.55,64),mats.grass,0,.95,0);
const floorY=1.23;
// Destructible objects have material-specific strength and response.
const targets=[];
const debris=[];
let hits=0,broken=0;
function target(name,type,x,y,z,sx,sy,sz,mat,hp){
const o=box(x,y,z,sx,sy,sz,mat);
o.name=name;
o.userData={
target:true,name:name,type:type,hp:hp,maxHp:hp,
originalScale:new T.Vector3(sx,sy,sz)
};
targets.push(o);
return o;
}
// House: separate walls, timber supports, windows, and roof tiles.
for(let row=0;row<3;row++){
for(let col=0;col<4;col++){
const x=-2.25+col*1.5;
const y=floorY+.65+row*1.3;
if(!(row===1&&(col===1||col===2))){
target('Wood frame','wood',x,y,-3.0,.13,1.25,.18,mats.wood,2);
target('Front wall','plaster',x,y,-3.15,1.39,1.2,.25,mats.plaster,3);
}
target('Back wall','plaster',x,y,-8.5,1.4,1.2,.25,mats.plaster,3);
}
}
for(let row=0;row<3;row++){
for(let col=0;col<3;col++){
const z=-3.7-col*1.55,y=floorY+.65+row*1.3;
target('Left wall','plaster',-4.5,y,z,.25,1.2,1.43,mats.plaster,3);
target('Right wall','plaster',4.5,y,z,.25,1.2,1.43,mats.plaster,3);
}
}
for(let i=0;i<5;i++){
const tile=target('Roof tile','wood',-3.6+i*1.8,floorY+4.6,-5.75,1.8,.4,5.7,mats.roof,3);
tile.rotation.z=(i<2?-.04:.04);
}
target('Front door','wood',0,floorY+1,-2.78,1.05,2,.16,mats.woodLight,2);
target('Left window','glass',-2.15,floorY+2.5,-2.86,.9,.8,.12,mats.glass,1);
target('Right window','glass',2.15,floorY+2.5,-2.86,.9,.8,.12,mats.glass,1);
for(const x of [-2.15,2.15]){
box(x,floorY+2.5,-2.77,.98,.09,.09,mats.wood);
box(x,floorY+2.5,-2.77,.09,.88,.09,mats.wood);
}
// Car body and distinct parts.
target('Car body','metal',9,floorY+.82,1,4.5,.85,2.1,mats.red,5);
target('Car hood','metal',10.25,floorY+1.25,1,1.6,.22,1.9,mats.red,4);
target('Car cabin','glass',8.5,floorY+1.62,1,2.25,.85,1.65,mats.glass,2);
target('Front bumper','metal',11.3,floorY+.68,1,.3,.28,2.1,mats.paint,3);
target('Rear bumper','metal',6.7,floorY+.68,1,.3,.28,2.1,mats.paint,3);
for(const x of [7.5,10.5]){
for(const z of [-.15,2.15]){
addMesh(new T.CylinderGeometry(.45,.45,.3,24),mats.rubber,x,floorY+.45,z,1,1,1).rotation.z=Math.PI/2;
addMesh(new T.CylinderGeometry(.23,.23,.31,20),mats.paint,x,floorY+.45,z,1,1,1).rotation.z=Math.PI/2;
}
}
for(const z of [.0,2.0]){
box(11.31,floorY+.92,z,.06,.18,.3,mats.gold);
}
// Trees with trunks, layered foliage, and scattered rocks.
for(let i=0;i<25;i++){
const a=i*2.399,r=12+(i%5)*1.65;
const x=Math.cos(a)*r,z=Math.sin(a)*r;
addMesh(new T.CylinderGeometry(.17,.29,2.8,9),mats.wood,x,floorY+1.4,z);
for(let j=0;j<5;j++){
const ang=j*Math.PI*2/5;
const f=addMesh(new T.ConeGeometry(.42,2.3,8),j%2?mats.leaf:mats.leaf2,
x+Math.cos(ang)*.65,floorY+3,z+Math.sin(ang)*.65);
f.rotation.z=Math.cos(ang)*-.45;
f.rotation.x=Math.sin(ang)*.45;
}
addMesh(new T.SphereGeometry(.62,12,10),mats.leaf,x,floorY+3.5,z,1.15,.85,1.15);
}
for(let i=0;i<22;i++){
const a=i*2.4,r=18+(i%3);
addMesh(new T.DodecahedronGeometry(.45+(i%3)*.2),mats.concrete,
Math.cos(a)*r,floorY+.12,Math.sin(a)*r,1,.65,.8);
}
// First-person player and view.
const player={x:0,y:floorY,z:8,vy:0,onGround:true,yaw:0,pitch:0};
camera.position.set(player.x,player.y+1.7,player.z);
const weapon=new T.Group();
camera.add(weapon);
weapon.position.set(.43,-.38,-.72);
const handle=addMesh(new T.CylinderGeometry(.045,.065,.72,10),mats.woodLight,0,.15,0,1,1,1,weapon);
handle.rotation.z=-.23;
const hammerHead=box(0,.52,0,.5,.2,.23,mats.paint,weapon);
hammerHead.rotation.z=-.23;
const hand=addMesh(new T.SphereGeometry(.14,12,10),material(0x956345),-.04,-.14,.03,.85,1.1,.85,weapon);
const glove=box(-.02,-.1,.02,.22,.22,.22,material(0x313a42),weapon);
let swing=0,attackCooldown=0;
function makeDebris(pos,mat,type,scale,direction){
let geo;
if(type==='glass')geo=new T.TetrahedronGeometry(scale,0);
else if(type==='stone')geo=new T.DodecahedronGeometry(scale,0);
else geo=new T.BoxGeometry(scale*1.5,scale,scale);
const o=addMesh(geo,mat,pos.x,pos.y,pos.z);
const dir=direction.clone().add(new T.Vector3(
(Math.random()-.5)*1.5,Math.random()*.9,(Math.random()-.5)*1.5
)).normalize();
debris.push({
o:o,v:dir.multiplyScalar(2.5+Math.random()*4),
spin:new T.Vector3(Math.random()*7,Math.random()*7,Math.random()*7),
life:2.8+Math.random()*1.7
});
}
function particles(pos,color,count,dir){
for(let i=0;i<count;i++){
const m=material(color,.55);
makeDebris(pos,m,'stone',.06+Math.random()*.06,dir);
}
}
let audio=null;
function sound(type){
try{
if(!audio)audio=new (window.AudioContext||window.webkitAudioContext)();
if(audio.state==='suspended')audio.resume();
const osc=audio.createOscillator(),gain=audio.createGain();
osc.connect(gain);gain.connect(audio.destination);
const now=audio.currentTime;
if(type==='glass'){
osc.type='triangle';osc.frequency.setValueAtTime(1300,now);
osc.frequency.exponentialRampToValueAtTime(250,now+.15);
}else if(type==='metal'){
osc.type='square';osc.frequency.setValueAtTime(500,now);
osc.frequency.exponentialRampToValueAtTime(110,now+.13);
}else{
osc.type='sawtooth';osc.frequency.setValueAtTime(170,now);
osc.frequency.exponentialRampToValueAtTime(45,now+.12);
}
gain.gain.setValueAtTime(.13,now);
gain.gain.exponentialRampToValueAtTime(.001,now+.2);
osc.start(now);osc.stop(now+.21);
}catch(e){}
}
const raycaster=new T.Raycaster();
function smash(){
if(attackCooldown>0)return;
attackCooldown=.38;swing=1;
const direction=new T.Vector3();
camera.getWorldDirection(direction);
raycaster.setFromCamera(new T.Vector2(0,0),camera);
raycaster.far=5.5;
const hitsList=raycaster.intersectObjects(targets,false);
const hit=hitsList.find(h=>h.object.userData.target);
if(!hit){
tip.textContent='Swing! Get closer and aim at an object';
return;
}
const o=hit.object,d=o.userData;
hits++;
d.hp--;
sound(d.type==='glass'?'glass':d.type==='metal'?'metal':'wood');
const impact=hit.point.clone();
if(d.type==='metal'){
particles(impact,0xffbd55,7,direction);
}else if(d.type==='glass'){
particles(impact,0x94eaff,9,direction);
}else{
particles(impact,d.type==='plaster'?0xd8c5a6:0xb78148,4,direction);
}
if(d.hp<=0){
const index=targets.indexOf(o);
if(index>=0)targets.splice(index,1);
const size=d.originalScale;
const count=d.type==='glass'?10:d.type==='metal'?7:6;
for(let i=0;i<count;i++){
const type=d.type==='plaster'?'stone':d.type;
makeDebris(o.position.clone().add(new T.Vector3(
(Math.random()-.5)*size.x,
(Math.random()-.5)*size.y,
(Math.random()-.5)*size.z)),o.material,type,.14+Math.random()*.2,direction);
}
scene.remove(o);
o.geometry.dispose();
broken++;
tip.textContent=d.type==='glass'?'CRASH! Glass shattered!':
d.type==='metal'?'CLANG! Metal part broken!':
d.type==='plaster'?'CRACK! Wall crumbled!':'CRACK! Wood broke apart!';
}else{
tip.textContent=d.type==='glass'?'Glass cracked and shattered!':
d.type==='metal'?'Metal hit! Sparks fly!':
d.type==='plaster'?'Wall damaged!':
'Wood struck! '+d.hp+' hit(s) left';
}
msg.textContent='Hits: '+hits+' | Pieces broken: '+broken;
}
// Controls and pointer lock.
const keys={};
let started=false;
function start(){
started=true;
overlay.style.display='none';
renderer.domElement.requestPointerLock?.();
}
document.getElementById('play').addEventListener('click',start);
renderer.domElement.addEventListener('click',()=>{
if(!started){start();return;}
if(document.pointerLockElement!==renderer.domElement){
renderer.domElement.requestPointerLock?.();
}else smash();
});
window.addEventListener('keydown',e=>{
keys[e.code]=true;
if(['Space','ArrowUp','ArrowDown','ArrowLeft','ArrowRight'].includes(e.code))e.preventDefault();
if(e.code==='Space'&&!e.repeat&&player.onGround){
player.vy=6;player.onGround=false;
}
});
window.addEventListener('keyup',e=>keys[e.code]=false);
window.addEventListener('mousemove',e=>{
if(document.pointerLockElement!==renderer.domElement)return;
player.yaw-=e.movementX*.0022;
player.pitch-=e.movementY*.0022;
player.pitch=Math.max(-1.35,Math.min(1.35,player.pitch));
camera.rotation.set(player.pitch,player.yaw,0,'YXZ');
});
window.addEventListener('contextmenu',e=>e.preventDefault());
window.addEventListener('resize',()=>{
camera.aspect=innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth,innerHeight);
});
const clock=new T.Clock();
function animate(){
requestAnimationFrame(animate);
const dt=Math.min(clock.getDelta(),.04);
attackCooldown=Math.max(0,attackCooldown-dt);
swing=Math.max(0,swing-dt*3.2);
// Weapon visibly swings in front of the camera.
const amount=swing>0?Math.sin((1-swing)*Math.PI):0;
weapon.rotation.x=-amount*1.15;
weapon.rotation.y=amount*.65;
weapon.rotation.z=amount*.45;
weapon.position.set(.43+amount*.12,-.38-amount*.12,-.72+amount*.18);
// Move in the direction the camera faces.
let forward=(keys.KeyW||keys.ArrowUp?1:0)-(keys.KeyS||keys.ArrowDown?1:0);
let side=(keys.KeyD||keys.ArrowRight?1:0)-(keys.KeyA||keys.ArrowLeft?1:0);
const len=Math.hypot(forward,side);
if(len){
forward/=len;side/=len;
const speed=keys.ShiftLeft?8:5;
player.x+=(-Math.sin(player.yaw)*forward+Math.cos(player.yaw)*side)*speed*dt;
player.z+=(-Math.cos(player.yaw)*forward-Math.sin(player.yaw)*side)*speed*dt;
}
const radius=Math.hypot(player.x,player.z);
if(radius>21){player.x*=21/radius;player.z*=21/radius;}
player.vy-=15*dt;
player.y+=player.vy*dt;
if(player.y<floorY){player.y=floorY;player.vy=0;player.onGround=true;}
camera.position.set(player.x,player.y+1.7,player.z);
camera.rotation.set(player.pitch,player.yaw,0,'YXZ');
// Debris falls, spins, bounces, and eventually disappears.
for(let i=debris.length-1;i>=0;i--){
const p=debris[i];
p.life-=dt;
p.v.y-=13*dt;
p.o.position.addScaledVector(p.v,dt);
p.o.rotation.x+=p.spin.x*dt;
p.o.rotation.y+=p.spin.y*dt;
if(p.o.position.y<floorY+.08){
p.o.position.y=floorY+.08;
p.v.y=Math.abs(p.v.y)*.25;
p.v.x*=.78;p.v.z*=.78;
}
if(p.life<=0){
scene.remove(p.o);
p.o.geometry.dispose();
p.o.material.dispose();
debris.splice(i,1);
}
}
renderer.render(scene,camera);
}
animate();
})();
</script>
</body>
</html>
Game Source: Island Smash Realism
Creator: NovaShark68
Libraries: three
Complexity: complex (411 lines, 14.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: island-smash-realism-novashark68" to link back to the original. Then publish at arcadelab.ai/publish.