<script src="https://ajax.googleapis.com/ajax/libs/threejs/r76/three.min.js"></script> <script class="myscript"> var overlay, paddle, ball, camera, scene, shadow, colliders, text; var blockheight = 20, blockdim = 2, speed = 5, dball; var min = new THREE.Vector3(-90, -50, -90); var max = new THREE.Vector3(90, 270, 90); var camstart = new THREE.Vector3(0, 110, max.z+500); var keys = {}; [onkeydown, onkeyup] = [e => keys[e.key] = 1, e => delete keys[e.key]]; var time = 0, loop = attractloop, rdr; var render = () => (time++, loop(), rdr.render(scene, camera), requestAnimationFrame(render)); document.body.onload = () => { rdr = new THREE.WebGLRenderer({antialias: true, canvas: cvs}); camera = new THREE.PerspectiveCamera(45, 1, 0.1, 1000); camera.position.copy(camstart); resetscene(); render(); }; function resetscene() { [colliders, scene] = [{}, new THREE.Scene()]; scene.add( paddle = new THREE.Mesh(new THREE.BoxGeometry(75, 10, 75), new THREE.MeshNormalMaterial()), ball = new THREE.Mesh(new THREE.SphereGeometry(10), new THREE.MeshBasicMaterial({color: 0xee0000})), shadow = new THREE.Mesh(new THREE.CircleGeometry(8), new THREE.MeshBasicMaterial({side: THREE.DoubleSide, color: 0x444444})), overlay = new THREE.Sprite(new THREE.SpriteMaterial({ map: new THREE.CanvasTexture(overlaycvs), depthTest: false, depthWrite: false}))); paddle.position.y = min.y; colliders[paddle.uuid] = paddle; ball.position.copy(min).lerp(max, 0.5); shadow.rotation.x = Math.PI/2; shadow.position.set(ball.position.x, min.y+8, ball.position.z); [overlay.hue, text] = [0, "click to play"]; for (var bx = min.x; bx <= max.x; bx+=(max.x - min.x)/blockdim) for (var by = max.y; by >= max.y-blockdim*(blockheight+1); by-=(blockheight+1)) for (var bz = min.z; bz <= max.z; bz+=(max.z - min.z)/blockdim) { var block = new THREE.Mesh(new THREE.BoxGeometry( (max.x - min.x)/blockdim-1, blockheight, (max.z - min.z)/blockdim-1), new THREE.MeshNormalMaterial()); block.position.set(bx, by, bz); scene.add(block); colliders[block.uuid] = block; } dball = new THREE.Vector3(Math.random()*3-1.5, -2, Math.random()*3-1.5); } function attractloop() { overlaycvs.width = 512; // easiset way to clear a canvas Object.assign(overlaycvs.getContext("2d"), { fillStyle: "hsl("+(overlay.hue++%360)+",100%,50%)", font: "italic small-caps 45px Georgia", textAlign: "center"}); overlaycvs.getContext("2d").fillText(text, 256, 310); overlay.material.map.needsUpdate = true; overlay.position.set(0, camera.position.y+Math.sin(time/40)*5, 0); var scale = (2 + Math.cos(time/10)/40) * Math.tan(camera.fov * Math.PI/360) * (overlay.position.distanceTo(camera.position)); overlay.scale.set(scale, scale, 2); spincam(); cvs.onclick = () => (cvs.onclick = 0, resetscene(), loop = function() { time += 2*Math.abs(Math.sin(time/100)); if (camera.position.z > camstart.z-.02)[time, loop] = [0, playloop]; spincam()}); } function spincam() { camera.position.set(Math.sin(time/100)*(camstart.z), camera.position.y, Math.cos(time/100)*(camstart.z)); camera.lookAt(new THREE.Vector3(0, camstart.y, 0)); } function playloop() { with(paddle) { rotation.set(0,0,0); Object.keys(keys).forEach(k => (({ w: () => {position.z -=speed; rotation.x -=.1}, a: () => {position.x -=speed; rotation.z +=.1}, s: () => {position.z +=speed; rotation.x +=.1}, d: () => {position.x +=speed; rotation.z -=.1}})[k] || (_=>_))()); position.clamp(min, max); } ball.position.add(dball); var old = ball.position.clone(); var hitnormal, hitbox, hitdist = 150; Object.values(colliders).forEach(box => box.geometry.faces.forEach(f => { var dist = (new THREE.Triangle()) .setFromPointsAndIndices(["a","b","c"].map( i => box.localToWorld(box.geometry.vertices[f[i]].clone())), 0, 1, 2) .closestPointToPoint(ball.position) .distanceToSquared(ball.position); if (dist > hitdist || f.normal.dot(dball) >= 0) return; hitnormal = f.normal.clone().applyEuler(box.rotation); [hitbox, hitdist] = [box, dist]; })); if (hitnormal) { dball.reflect(hitnormal); if (hitbox.uuid != paddle.uuid) { delete colliders[hitbox.uuid]; scene.remove(hitbox); }} ball.position.x = THREE.Math.clamp(ball.position.x, min.x, max.x); ball.position.z = THREE.Math.clamp(ball.position.z, min.z, max.z); ["x", "z"].forEach(d => old[d] != ball.position[d]? dball[d] *= -1 : 0); shadow.position.set(ball.position.x, min.y+8, ball.position.z); dball.y = dball.y + .001*(dball.y > 0? 1 : -1); text = ball.position.y < min.y - 50? "try again?" : ball.position.y > max.y + 50? "you win!" : 0; if (!text) return; scene.remove(shadow); [time, loop] = [0, attractloop]; } </script> <canvas id="cvs" width="512" height="512"></canvas> <canvas id="overlaycvs" width="512" height="512" style="display:none"></canvas>
Saturday, June 24, 2017
3D Breakout in ~120 lines
Controls are WASD; game appears after the jump!
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment