#StackBounty: #javascript #html #game #ecmascript-6 #dom Arkanoid/Breakout clone with ThreeJS

Bounty: 100

Here’s my attempt at an Arkanoid/Breakout clone using JavaScript with ThreeJS. The feedback I’m looking for is more on the code side; the game itself is a work in progress. You can take it for a spin by running the code snippet below.

(function() {
    "use strict";

    const paddleStates = {
        MOVING_LEFT : 0,
        MOVING_RIGHT : 1,
        STATIONARY : 2
    };

    function createMeshAtPosition(meshProperties, position) {
        let mesh = new THREE.Mesh(meshProperties.geometry, meshProperties.material);
        mesh.position.copy(position);
        return mesh;
    }

    function createFullScreenRenderer(elementId, settings) {
        let renderer = new THREE.WebGLRenderer({
            canvas: document.getElementById(elementId)
        });
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(settings.backgroundColor);
        return renderer;
    }

    function createCamera() {
        let camera = new THREE.PerspectiveCamera(
            90,
            window.innerWidth / window.innerHeight,
            0.1,
            3000);
        camera.position.set(0.0, 10.0, 0.0);
        camera.lookAt(0.0, 0.0, -10.0);
        return camera;
    }

    function makeResizeCallback(camera, renderer) {
        return function() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        };
    }

    function makeKeyDownCallback(paddle, speed) {
        return function(event) {
            if (paddle.state === paddleStates.STATIONARY) {
                if (event.key === "ArrowLeft") {
                    paddle.velocity.x = -speed;
                    paddle.state = paddleStates.MOVING_LEFT;
                } else if (event.key === "ArrowRight") {
                    paddle.velocity.x = speed;
                    paddle.state = paddleStates.MOVING_RIGHT;
                }
            }
        };
    }

    function makeKeyUpCallback(paddle) {
        return function(event) {
            if (paddle.state === paddleStates.MOVING_LEFT && event.key === "ArrowLeft") {
                paddle.velocity.x = 0.0;
                paddle.state = paddleStates.STATIONARY;
            } else if (paddle.state === paddleStates.MOVING_RIGHT && event.key === "ArrowRight") {
                paddle.velocity.x = 0.0;
                paddle.state = paddleStates.STATIONARY;
            }
        };
    }

    function updatePosition(gameObject) {
        gameObject.mesh.position.add(gameObject.velocity);
    }

    function resolveBallBlockCollision(ball, blockMesh, blockProperties, callback) {
        if ((ball.mesh.position.z + ball.radius > blockMesh.position.z - blockProperties.height / 2 &&
            (ball.mesh.position.z < blockMesh.position.z)) &&
            (ball.mesh.position.x > blockMesh.position.x - blockProperties.width / 2) &&
            (ball.mesh.position.x < blockMesh.position.x + blockProperties.width / 2) &&
            (ball.velocity.z > 0.0)) 
        {
            ball.velocity.z *= -1.0;
            callback();
            return true;
        }

        if ((ball.mesh.position.z - ball.radius < blockMesh.position.z + blockProperties.height / 2 &&
            (ball.mesh.position.z > blockMesh.position.z)) &&
            (ball.mesh.position.x > blockMesh.position.x - blockProperties.width / 2) &&
            (ball.mesh.position.x < blockMesh.position.x + blockProperties.width / 2) &&
            (ball.velocity.z < 0.0)) 
        {
            ball.velocity.z *= -1.0;
            callback();
            return true;
        }

        if ((ball.mesh.position.x + ball.radius > blockMesh.position.x - blockProperties.width / 2 &&
            (ball.mesh.position.x < blockMesh.position.x)) &&
            (ball.mesh.position.z > blockMesh.position.z - blockProperties.height / 2) &&
            (ball.mesh.position.z < blockMesh.position.z + blockProperties.height / 2) &&
            (ball.velocity.x > 0.0)) 
        {
            ball.velocity.x *= -1.0;
            callback();
            return true;
        }

        if ((ball.mesh.position.x - ball.radius < blockMesh.position.x + blockProperties.width / 2 &&
            (ball.mesh.position.x > blockMesh.position.x)) &&
            (ball.mesh.position.z > blockMesh.position.z - blockProperties.height / 2) &&
            (ball.mesh.position.z < blockMesh.position.z + blockProperties.height / 2) &&
            (ball.velocity.x < 0.0)) 
        {
            ball.velocity.x *= -1.0;
            callback();
            return true;
        }

        return false;
    }

    function main() {
        // Hard-coded "settings"
        let settings = {
            backgroundColor : 0x008888,
            paddleSpeed : 0.3,
            ballSpeed: 0.2
        };

        let paddle = {
            width : 4,
            height : 1,
            depth : 1,
            color : 0xffffff,
            velocity : new THREE.Vector3(0.0, 0.0, 0.0),
            state : paddleStates.STATIONARY,
            startPosition : new THREE.Vector3(0.0, 0.0, -4.0)
        };

        let ball = {
            radius : 0.5,
            color : 0xffff00,
            velocity : new THREE.Vector3(settings.ballSpeed, 0.0, -settings.ballSpeed),
            startPosition : new THREE.Vector3(0.0, 0.0, -9.0),
            segments : {
                width : 16,
                height : 16
            }
        };

        const levelBounds = {
            top : -35.0,
            right : 17.0,
            left : -17.0,
            bottom : 0.0
        };

        const bricks = {
            rows : 11,
            columns : 11,
            distanceFromEdges : 1.0,
            distanceFromTop : 13.0,
            spacing : 0.2,
            color : 0xff00ff,
            depth : 1.0
        };

        const lights = [
            new THREE.AmbientLight(0xffffff, 0.5), 
            new THREE.PointLight(0xffffff, 0.5)
        ];

        // Game
        let renderer = createFullScreenRenderer("game-window", settings);

        let scene = new THREE.Scene();
        let camera = createCamera();
        scene.add(camera);

        paddle.mesh = createMeshAtPosition({
            geometry : new THREE.BoxGeometry(paddle.width, paddle.depth, paddle.height),
            material : new THREE.MeshLambertMaterial({ color : paddle.color })
        }, paddle.startPosition);
        scene.add(paddle.mesh);

        ball.mesh = createMeshAtPosition({
            geometry : new THREE.SphereGeometry(ball.radius, ball.segments.width, ball.segments.height),
            material : new THREE.MeshLambertMaterial({ color: ball.color })
        }, ball.startPosition);
        scene.add(ball.mesh);

        lights.forEach(light => scene.add(light));

        const levelWidth = levelBounds.right - levelBounds.left;
        const brick = {
            width : (levelWidth - 2 * bricks.distanceFromEdges + bricks.spacing * (1 - bricks.columns)) / bricks.columns,
            height : (bricks.distanceFromTop - bricks.distanceFromEdges) / bricks.rows,
            depth : bricks.depth
        };

        let visibleBricks = [];
        for (let row = 0; row < bricks.rows; row += 1) {
            for (let column = 0; column < bricks.columns; column += 1) {
                let position = new THREE.Vector3(
                    levelBounds.left + bricks.distanceFromEdges + column * (brick.width + bricks.spacing) + 0.5 * brick.width,
                    0.0,
                    levelBounds.top + bricks.distanceFromEdges + row * (brick.height + bricks.spacing) + 0.5 * brick.height);
                let mesh = createMeshAtPosition({
                    geometry : new THREE.BoxGeometry(brick.width, brick.depth, brick.height),
                    material : new THREE.MeshLambertMaterial({ color : bricks.color })
                }, position);
                let name = `${row},${column}`;
                mesh.name = name;
                scene.add(mesh);
                visibleBricks.push({
                    position : position,
                    name : name
                });
            }
        }  

        requestAnimationFrame(render);
        function render() {
            // update paddle position
            // ball-level collision
            if ((ball.mesh.position.z - ball.radius < levelBounds.top && ball.velocity.z < 0.0) ||
                (ball.mesh.position.z + ball.radius > levelBounds.bottom && ball.velocity.z > 0.0)) 
            {
                ball.velocity.z *= -1.0;
            }

            if ((ball.mesh.position.x + ball.radius > levelBounds.right && ball.velocity.x > 0.0) ||
                (ball.mesh.position.x - ball.radius < levelBounds.left && ball.velocity.x < 0.0))
            {
                ball.velocity.x *= -1.0;
            }

            resolveBallBlockCollision(ball, paddle.mesh, paddle, function() {});

            // ball-brick collision
            for (let i = 0; i < visibleBricks.length; i += 1) {
                let visibleBrick = visibleBricks[i];
                let isCollided = resolveBallBlockCollision(ball, visibleBrick, brick, function() {
                    let selectedObject = scene.getObjectByName(visibleBrick.name);
                    scene.remove(selectedObject);
                    visibleBricks.splice(i, 1);
                });
                if (isCollided) {
                    break;
                }
            }

            updatePosition(paddle);
            updatePosition(ball);
            renderer.render(scene, camera);
            requestAnimationFrame(render);
        }

        window.addEventListener("resize", makeResizeCallback(camera, renderer), false);
        window.addEventListener("keydown", makeKeyDownCallback(paddle, settings.paddleSpeed), false);
        window.addEventListener("keyup", makeKeyUpCallback(paddle), false);
    }

    window.addEventListener("load", main, false);
})();
<!DOCTYPE html>
<html>
    <head>
        <title>Arkanoid</title>
        <style>
            body {
                padding: 0px;
                margin: 0px;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
        <canvas id="game-window"></canvas>
        https://cdnjs.cloudflare.com/ajax/libs/three.js/95/three.js
    </body>
</html>


Get this bounty!!!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.