<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