<!DOCTYPE html>
<html>
<head>
<script data-require="three.js@0.0.0-r61" data-semver="0.0.0-r61" src="//cdnjs.cloudflare.com/ajax/libs/three.js/r61/three.min.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h3 id="scoreBoard"></h3>
<div id="container"></div>
<script src="script.js"></script>
</body>
</html>
/**
* Copyright (c) 2013 Ben Lesh
* Pong ThreeJS demo
*/
console.clear();
(function (window, document, THREE){
// "constants"...
var WIDTH = 700,
HEIGHT = 500,
VIEW_ANGLE = 45,
ASPECT = WIDTH / HEIGHT,
NEAR = 0.1,
FAR = 10000,
FIELD_WIDTH = 1200,
FIELD_LENGTH = 3000,
BALL_RADIUS = 20,
PADDLE_WIDTH = 200,
PADDLE_HEIGHT = 30,
//get the scoreboard element.
scoreBoard = document.getElementById('scoreBoard'),
//declare members.
container, renderer, camera, mainLight,
scene, ball, paddle1, paddle2, field, running,
score = {
player1: 0,
player2: 0
};
function startBallMovement() {
var direction = Math.random() > 0.5 ? -1 : 1;
ball.$velocity = {
x: 0,
z: direction * 20
};
ball.$stopped = false;
}
function processCpuPaddle() {
var ballPos = ball.position,
cpuPos = paddle2.position;
if(cpuPos.x - 100 > ballPos.x) {
cpuPos.x -= Math.min(cpuPos.x - ballPos.x, 6);
}else if(cpuPos.x - 100 < ballPos.x) {
cpuPos.x += Math.min(ballPos.x - cpuPos.x, 6);
}
}
function processBallMovement() {
if(!ball.$velocity) {
startBallMovement();
}
if(ball.$stopped) {
return;
}
updateBallPosition();
if(isSideCollision()) {
ball.$velocity.x *= -1;
}
if(isPaddle1Collision()) {
hitBallBack(paddle1);
}
if(isPaddle2Collision()) {
hitBallBack(paddle2);
}
if(isPastPaddle1()) {
scoreBy('player2');
}
if(isPastPaddle2()) {
scoreBy('player1');
}
}
function isPastPaddle1() {
return ball.position.z > paddle1.position.z + 100;
}
function isPastPaddle2() {
return ball.position.z < paddle2.position.z - 100;
}
function updateBallPosition() {
var ballPos = ball.position;
//update the ball's position.
ballPos.x += ball.$velocity.x;
ballPos.z += ball.$velocity.z;
// add an arc to the ball's flight. Comment this out for boring, flat pong.
ballPos.y = -((ballPos.z - 1) * (ballPos.z - 1) / 5000) + 435;
}
function isSideCollision() {
var ballX = ball.position.x,
halfFieldWidth = FIELD_WIDTH / 2;
return ballX - BALL_RADIUS < -halfFieldWidth || ballX + BALL_RADIUS > halfFieldWidth;
}
function hitBallBack(paddle) {
ball.$velocity.x = (ball.position.x - paddle.position.x) / 5;
ball.$velocity.z *= -1;
}
function isPaddle2Collision() {
return ball.position.z - BALL_RADIUS <= paddle2.position.z &&
isBallAlignedWithPaddle(paddle2);
}
function isPaddle1Collision() {
return ball.position.z + BALL_RADIUS >= paddle1.position.z &&
isBallAlignedWithPaddle(paddle1);
}
function isBallAlignedWithPaddle(paddle) {
var halfPaddleWidth = PADDLE_WIDTH / 2,
paddleX = paddle.position.x,
ballX = ball.position.x;
return ballX > paddleX - halfPaddleWidth &&
ballX < paddleX + halfPaddleWidth;
}
function scoreBy(playerName) {
addPoint(playerName);
updateScoreBoard();
stopBall();
setTimeout(reset, 2000);
}
function updateScoreBoard() {
scoreBoard.innerHTML = 'Player 1: ' + score.player1 + ' Player 2: ' +
score.player2;
}
function stopBall(){
ball.$stopped = true;
}
function addPoint(playerName){
score[playerName]++;
console.log(score);
}
function startRender(){
running = true;
render();
}
function stopRender() {
running = false;
}
function render() {
if(running) {
requestAnimationFrame(render);
processBallMovement();
processCpuPaddle();
renderer.render(scene, camera);
}
}
function reset() {
ball.position.set(0,0,0);
ball.$velocity = null;
}
function init() {
container = document.getElementById('container');
renderer = new THREE.WebGLRenderer();
renderer.setSize(WIDTH, HEIGHT);
renderer.setClearColor(0x9999BB, 1);
container.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
camera.position.set(0, 100, FIELD_LENGTH / 2 + 500);
scene = new THREE.Scene();
scene.add(camera);
var fieldGeometry = new THREE.CubeGeometry(FIELD_WIDTH, 5, FIELD_LENGTH, 1, 1, 1),
fieldMaterial = new THREE.MeshLambertMaterial({ color: 0x003300 });
field = new THREE.Mesh(fieldGeometry, fieldMaterial);
field.position.set(0, -50, 0);
scene.add(field);
paddle1 = addPaddle();
paddle1.position.z = FIELD_LENGTH / 2;
paddle2 = addPaddle();
paddle2.position.z = -FIELD_LENGTH / 2;
var ballGeometry = new THREE.SphereGeometry(BALL_RADIUS, 16, 16),
ballMaterial = new THREE.MeshLambertMaterial({ color: 0xCC0000 });
ball = new THREE.Mesh(ballGeometry, ballMaterial);
scene.add(ball);
camera.lookAt(ball.position);
mainLight = new THREE.HemisphereLight(0xFFFFFF, 0x003300);
scene.add(mainLight);
camera.lookAt(ball.position);
updateScoreBoard();
startRender();
renderer.domElement.addEventListener('mousemove', containerMouseMove);
renderer.domElement.style.cursor = 'none';
}
function addPaddle() {
var paddleGeometry = new THREE.CubeGeometry(PADDLE_WIDTH, PADDLE_HEIGHT, 10, 1, 1, 1),
paddleMaterial = new THREE.MeshLambertMaterial({ color: 0xCCCCCC }),
paddle = new THREE.Mesh(paddleGeometry, paddleMaterial);
scene.add(paddle);
return paddle;
}
function containerMouseMove(e) {
var mouseX = e.clientX;
camera.position.x = paddle1.position.x = -((WIDTH - mouseX) / WIDTH * FIELD_WIDTH) + (FIELD_WIDTH / 2);
}
init();
})(window, window.document, window.THREE);
/* Styles go here */
Pong Three.js Demo
======
#MIT License
Started playing around with Three.js. Feel free to play with the code.
1. It has a few bugs, I'm sure. There are no tests, I was just playing.
2. It's not the best organized code. (Again, just playing around)
3. Lists look better with three things in them.
I hope this benefits someone.
## Some points about the code:
I started off with the `init()` function. Basically I created a renderer, a camera,
a scene, a light, and a ball. From there I thought, "This ball should do something!".
So I animated the ball with the `render()` function and the `processBallMovement()`
function. After that there were some tweaks for collisions, then scoring... THEN,
I realized I needed an opponent, so I added the `processCpuMovement()` function.
Then I decided to make the ball arc with a simple parabolic movement... because I
was bored.
... then a simple scoreboard. That's pretty much it.
#PLEASE: If you take this and make anything with it, I'd LOVE to see it, email me
at: ben@benlesh.com