Create a Crossy Road Game Clone with JavaScript

codinglabsolution

Discover the process of creating your own Crossy Road game clone from scratch using HTML, CSS, and JavaScript. Follow a detailed step-by-step guide enriched with examples and code snippets to master the art of game development.


Create a Crossy Road Game Clone with HTML, CSS, and JavaScript






Get ready to dive into the world of game development as we guide you through the creation of a Crossy Road game clone using HTML, CSS, and JavaScript. Crossy Road is a beloved arcade-style game known for its simple yet addictive gameplay, colorful pixel art graphics, and challenging obstacles.


Throughout this tutorial, you'll learn how to recreate the core mechanics of Crossy Road, including character movement, obstacle generation, scoring, and game over conditions. We'll start by setting up the basic structure of the game using HTML, then add visual styling and animations with CSS to bring the game to life. Finally, we'll implement the game logic and interactivity using JavaScript, allowing players to control the character and navigate through the obstacles.


By the end of this tutorial, you'll not only have a functional Crossy Road game clone but also a deeper understanding of game development concepts such as collision detection, sprite animation, and user input handling. So get ready to unleash your creativity and embark on this exciting game development journey!

Source Code

Step 1 (HTML Code):


 This HTML code sets up the structure for a Crossy Road game clone:


1. `<!DOCTYPE html>`: Declares the document type and version of HTML being used.


2. `<html lang="en">`: Defines the root element of the HTML document and specifies the language as English.


3. `<head>`: Contains meta-information and external resources related to the web page.


   - `<title>`: Sets the title of the web page.

   - `<meta charset="UTF-8" />`: Specifies the character encoding for the HTML document as UTF-8.

   - `<meta name="viewport" content="width=device-width" />`: Defines the viewport properties for responsive web design.

   - `<link rel="stylesheet" href="styles.css" />`: Links an external CSS file called "styles.css" to style the HTML elements.


4. `<body>`: Represents the visible content of the web page.


   - `<div id="counter">0</div>`: Displays the current score or counter of the game.

   - `<div id="controlls">`: Contains the game controls.

     - `<button id="forward">`: Button for moving the character forward.

     - `<button id="left">`: Button for moving the character left.

     - `<button id="backward">`: Button for moving the character backward.

     - `<button id="right">`: Button for moving the character right.

   - `<div id="end">`: Appears at the end of the game.

     - `<button id="retry">Retry</button>`: Button to restart the game.

   - `<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/99/three.min.js'></script>`: Links an external JavaScript library called "three.js" for 3D graphics rendering.

   - `<script src="script.js"></script>`: Links an external JavaScript file called "script.js" that contains the game logic and functionality.


This HTML structure sets up the foundation for creating a Crossy Road game clone and includes elements for displaying the score, controlling the character's movements, and restarting the game.

<!DOCTYPE html> <html lang="en"> <head> <title>Crossy Road Game Clone</title> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width" /> <link rel="stylesheet" href="styles.css" /> </head> <body> <div id="counter">0</div> <div id="controlls"> <div> <button id="forward"> <svg width="30" height="30" viewBox="0 0 10 10"> <g transform="rotate(0, 5,5)"> <path d="M5,4 L7,6 L3,6 L5,4" /> </g> </svg> </button> <button id="left"> <svg width="30" height="30" viewBox="0 0 10 10"> <g transform="rotate(-90, 5,5)"> <path d="M5,4 L7,6 L3,6 L5,4" /> </g> </svg> </button> <button id="backward"> <svg width="30" height="30" viewBox="0 0 10 10"> <g transform="rotate(180, 5,5)"> <path d="M5,4 L7,6 L3,6 L5,4" /> </g> </svg> </button> <button id="right"> <svg width="30" height="30" viewBox="0 0 10 10"> <g transform="rotate(90, 5,5)"> <path d="M5,4 L7,6 L3,6 L5,4" /> </g> </svg> </button> </div> </div> <div id="end"> <button id="retry">Retry</button> </div> <script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/99/three.min.js'></script> <script src="script.js"></script> </body> </html>

Read Also :Create a Maze Game with JavaScript 


Step 2 (CSS Code):


Here's the breakdown of the CSS code provided:


1. `@import url('https://fonts.googleapis.com/css?family=Press+Start+2P');`: Imports the "Press Start 2P" font from Google Fonts to use for the text in the game.


2. `body`: Styles applied to the entire body of the HTML document.

   - `margin: 0;`: Removes default margin.

   - `font-family: 'Press Start 2P', cursive;`: Sets the font family to "Press Start 2P" for a retro gaming style.

   - `font-size: 2em;`: Sets the font size to 2 times the default size.

   - `color: white;`: Sets the text color to white.


3. `button`: Styles applied to all button elements.

   - `outline: none;`: Removes default outline.

   - `cursor: pointer;`: Displays the cursor as a pointer on hover.

   - `border: none;`: Removes default border.

   - `box-shadow: 3px 5px 0px 0px rgba(0,0,0,0.75);`: Adds a subtle shadow effect to buttons.


4. `#counter`: Styles applied to the counter element displaying the score.

   - `position: absolute;`: Positions the element absolutely within its containing element.

   - `top: 20px; right: 20px;`: Positions the element 20 pixels from the top and right edges of the viewport.


5. `#end`: Styles applied to the end screen element.

   - `position: absolute;`: Positions the element absolutely within its containing element.

   - `min-width: 100%; min-height: 100%;`: Sets the minimum width and height to cover the entire viewport.

   - `display: flex; align-items: center; justify-content: center;`: Centers the content vertically and horizontally.

   - `visibility: hidden;`: Hides the element initially.


6. `#end button`: Styles applied to the retry button in the end screen.

   - `background-color: red;`: Sets the background color to red.

   - `padding: 20px 50px 20px 50px;`: Adds padding to the button.

   - `font-family: inherit; font-size: inherit;`: Inherits font properties from the parent element.


7. `#controlls`: Styles applied to the game controls section.

   - `position: absolute;`: Positions the element absolutely within its containing element.

   - `min-width: 100%; min-height: 100%;`: Sets the minimum width and height to cover the entire viewport.

   - `display: flex; align-items: flex-end; justify-content: center;`: Aligns the content to the bottom and centers horizontally.


8. `#controlls div`: Styles applied to the grid container for the control buttons.

   - `display: grid;`: Specifies that the element is a grid container.

   - `grid-template-columns: 50px 50px 50px;`: Sets the columns of the grid to a fixed width of 50 pixels.

   - `grid-template-rows: auto auto;`: Sets the rows of the grid to auto height.

   - `grid-column-gap: 10px; grid-row-gap: 10px;`: Adds gaps between grid items.

   - `margin-bottom: 20px;`: Adds margin at the bottom.


9. `#controlls button`: Styles applied to all control buttons.

   - `width: 100%;`: Sets the width of the buttons to 100%.

   - `background-color: white;`: Sets the background color to white.

   - `border: 1px solid lightgray;`: Adds a light gray border to the buttons.


10. `#controlls button:first-of-type`: Styles applied to the first control button (forward button).

    - `grid-column: 1/-1;`: Spans the button across all columns.

@import url('https://fonts.googleapis.com/css?family=Press+Start+2P'); body { margin: 0; font-family: 'Press Start 2P', cursive; font-size: 2em; color: white; } button { outline: none; cursor: pointer; border: none; box-shadow: 3px 5px 0px 0px rgba(0,0,0,0.75); } #counter { position: absolute; top: 20px; right: 20px; } #end { position: absolute; min-width: 100%; min-height: 100%; display: flex; align-items: center; justify-content: center; visibility: hidden; } #end button { background-color: red; padding: 20px 50px 20px 50px; font-family: inherit; font-size: inherit; } #controlls { position: absolute; min-width: 100%; min-height: 100%; display: flex; align-items: flex-end; justify-content: center; } #controlls div { display: grid; grid-template-columns: 50px 50px 50px; grid-template-rows: auto auto; grid-column-gap: 10px; grid-row-gap: 10px; margin-bottom: 20px; } #controlls button { width: 100%; background-color: white; border: 1px solid lightgray; } #controlls button:first-of-type { grid-column: 1/-1; }


Step 3 (JavaScript Code):


This JavaScript code defines the functionality and behavior of a Crossy Road game clone. Here's a breakdown of its key components:


1. Initialization

   - It sets up the initial scene using the Three.js library.

   - Defines camera settings, such as position and rotation.

   - Sets up initial values for variables used throughout the game.


2. Lane Generation:

   - Defines different lane types: field, forest, car, and truck.

   - Creates lanes with randomly selected types and adds corresponding objects (grass, trees, vehicles) to each lane.


3. Object Creation:

   - Defines classes for creating game objects such as cars, trucks, trees, grass, and the chicken player character.

   - Each object is created with its own mesh and textures.


4. Game Logic:

   - Handles player input (button clicks and keyboard events) to move the chicken character.

   - Manages the movement of vehicles on the lanes.

   - Performs hit testing to detect collisions between the chicken and vehicles.


5. Rendering:

   - Sets up the rendering loop using Three.js to continuously update and render the scene.

   - Renders the scene from the camera's perspective.


6. Event Listeners:

   - Listens for button clicks and keyboard events to trigger player movement.


7. Retry Functionality:

   - Resets the game when the player clicks the retry button.


This code provides the core functionality for a Crossy Road game clone using HTML, CSS, and JavaScript, leveraging the Three.js library for 3D rendering.

const counterDOM = document.getElementById('counter');
const endDOM = document.getElementById('end');

const scene = new THREE.Scene();

const distance = 500;
const camera = new THREE.OrthographicCamera( window.innerWidth/-2, window.innerWidth/2, window.innerHeight / 2, window.innerHeight / -2, 0.1, 10000 );

camera.rotation.x = 50*Math.PI/180;
camera.rotation.y = 20*Math.PI/180;
camera.rotation.z = 10*Math.PI/180;

const initialCameraPositionY = -Math.tan(camera.rotation.x)*distance;
const initialCameraPositionX = Math.tan(camera.rotation.y)*Math.sqrt(distance**2 + initialCameraPositionY**2);
camera.position.y = initialCameraPositionY;
camera.position.x = initialCameraPositionX;
camera.position.z = distance;

const zoom = 2;

const chickenSize = 15;

const positionWidth = 42;
const columns = 17;
const boardWidth = positionWidth*columns;

const stepTime = 200; // Miliseconds it takes for the chicken to take a step forward, backward, left or right

let lanes;
let currentLane;
let currentColumn;

let previousTimestamp;
let startMoving;
let moves;
let stepStartTimestamp;

const carFrontTexture = new Texture(40,80,[{x: 0, y: 10, w: 30, h: 60 }]);
const carBackTexture = new Texture(40,80,[{x: 10, y: 10, w: 30, h: 60 }]);
const carRightSideTexture = new Texture(110,40,[{x: 10, y: 0, w: 50, h: 30 }, {x: 70, y: 0, w: 30, h: 30 }]);
const carLeftSideTexture = new Texture(110,40,[{x: 10, y: 10, w: 50, h: 30 }, {x: 70, y: 10, w: 30, h: 30 }]);

const truckFrontTexture = new Texture(30,30,[{x: 15, y: 0, w: 10, h: 30 }]);
const truckRightSideTexture = new Texture(25,30,[{x: 0, y: 15, w: 10, h: 10 }]);
const truckLeftSideTexture = new Texture(25,30,[{x: 0, y: 5, w: 10, h: 10 }]);

const generateLanes = () => [-9,-8,-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9].map((index) => {
  const lane = new Lane(index);
  lane.mesh.position.y = index*positionWidth*zoom;
  scene.add( lane.mesh );
  return lane;
}).filter((lane) => lane.index >= 0);

const addLane = () => {
  const index = lanes.length;
  const lane = new Lane(index);
  lane.mesh.position.y = index*positionWidth*zoom;
  scene.add(lane.mesh);
  lanes.push(lane);
}

const chicken = new Chicken();
scene.add( chicken );

hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.6);
scene.add(hemiLight)

const initialDirLightPositionX = -100;
const initialDirLightPositionY = -100;
dirLight = new THREE.DirectionalLight(0xffffff, 0.6);
dirLight.position.set(initialDirLightPositionX, initialDirLightPositionY, 200);
dirLight.castShadow = true;
dirLight.target = chicken;
scene.add(dirLight);

dirLight.shadow.mapSize.width = 2048;
dirLight.shadow.mapSize.height = 2048;
var d = 500;
dirLight.shadow.camera.left = - d;
dirLight.shadow.camera.right = d;
dirLight.shadow.camera.top = d;
dirLight.shadow.camera.bottom = - d;

// var helper = new THREE.CameraHelper( dirLight.shadow.camera );
// var helper = new THREE.CameraHelper( camera );
// scene.add(helper)

backLight = new THREE.DirectionalLight(0x000000, .4);
backLight.position.set(200, 200, 50);
backLight.castShadow = true;
scene.add(backLight)

const laneTypes = ['car', 'truck', 'forest'];
const laneSpeeds = [2, 2.5, 3];
const vechicleColors = [0xa52523, 0xbdb638, 0x78b14b];
const threeHeights = [20,45,60];

const initaliseValues = () => {
  lanes = generateLanes()

  currentLane = 0;
  currentColumn = Math.floor(columns/2);

  previousTimestamp = null;

  startMoving = false;
  moves = [];
  stepStartTimestamp;

  chicken.position.x = 0;
  chicken.position.y = 0;

  camera.position.y = initialCameraPositionY;
  camera.position.x = initialCameraPositionX;

  dirLight.position.x = initialDirLightPositionX;
  dirLight.position.y = initialDirLightPositionY;
}

initaliseValues();

const renderer = new THREE.WebGLRenderer({
  alpha: true,
  antialias: true
});
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

function Texture(width, height, rects) {
  const canvas = document.createElement( "canvas" );
  canvas.width = width;
  canvas.height = height;
  const context = canvas.getContext( "2d" );
  context.fillStyle = "#ffffff";
  context.fillRect( 0, 0, width, height );
  context.fillStyle = "rgba(0,0,0,0.6)";
  rects.forEach(rect => {
    context.fillRect(rect.x, rect.y, rect.w, rect.h);
  });
  return new THREE.CanvasTexture(canvas);
}

function Wheel() {
  const wheel = new THREE.Mesh(
    new THREE.BoxBufferGeometry( 12*zoom, 33*zoom, 12*zoom ),
    new THREE.MeshLambertMaterial( { color: 0x333333, flatShading: true } )
  );
  wheel.position.z = 6*zoom;
  return wheel;
}

function Car() {
  const car = new THREE.Group();
  const color = vechicleColors[Math.floor(Math.random() * vechicleColors.length)];

  const main = new THREE.Mesh(
    new THREE.BoxBufferGeometry( 60*zoom, 30*zoom, 15*zoom ),
    new THREE.MeshPhongMaterial( { color, flatShading: true } )
  );
  main.position.z = 12*zoom;
  main.castShadow = true;
  main.receiveShadow = true;
  car.add(main)

  const cabin = new THREE.Mesh(
    new THREE.BoxBufferGeometry( 33*zoom, 24*zoom, 12*zoom ),
    [
      new THREE.MeshPhongMaterial( { color: 0xcccccc, flatShading: true, map: carBackTexture } ),
      new THREE.MeshPhongMaterial( { color: 0xcccccc, flatShading: true, map: carFrontTexture } ),
      new THREE.MeshPhongMaterial( { color: 0xcccccc, flatShading: true, map: carRightSideTexture } ),
      new THREE.MeshPhongMaterial( { color: 0xcccccc, flatShading: true, map: carLeftSideTexture } ),
      new THREE.MeshPhongMaterial( { color: 0xcccccc, flatShading: true } ), // top
      new THREE.MeshPhongMaterial( { color: 0xcccccc, flatShading: true } ) // bottom
    ]
  );
  cabin.position.x = 6*zoom;
  cabin.position.z = 25.5*zoom;
  cabin.castShadow = true;
  cabin.receiveShadow = true;
  car.add( cabin );

  const frontWheel = new Wheel();
  frontWheel.position.x = -18*zoom;
  car.add( frontWheel );

  const backWheel = new Wheel();
  backWheel.position.x = 18*zoom;
  car.add( backWheel );

  car.castShadow = true;
  car.receiveShadow = false;

  return car;
}

function Truck() {
  const truck = new THREE.Group();
  const color = vechicleColors[Math.floor(Math.random() * vechicleColors.length)];


  const base = new THREE.Mesh(
    new THREE.BoxBufferGeometry( 100*zoom, 25*zoom, 5*zoom ),
    new THREE.MeshLambertMaterial( { color: 0xb4c6fc, flatShading: true } )
  );
  base.position.z = 10*zoom;
  truck.add(base)

  const cargo = new THREE.Mesh(
    new THREE.BoxBufferGeometry( 75*zoom, 35*zoom, 40*zoom ),
    new THREE.MeshPhongMaterial( { color: 0xb4c6fc, flatShading: true } )
  );
  cargo.position.x = 15*zoom;
  cargo.position.z = 30*zoom;
  cargo.castShadow = true;
  cargo.receiveShadow = true;
  truck.add(cargo)

  const cabin = new THREE.Mesh(
    new THREE.BoxBufferGeometry( 25*zoom, 30*zoom, 30*zoom ),
    [
      new THREE.MeshPhongMaterial( { color, flatShading: true } ), // back
      new THREE.MeshPhongMaterial( { color, flatShading: true, map: truckFrontTexture } ),
      new THREE.MeshPhongMaterial( { color, flatShading: true, map: truckRightSideTexture } ),
      new THREE.MeshPhongMaterial( { color, flatShading: true, map: truckLeftSideTexture } ),
      new THREE.MeshPhongMaterial( { color, flatShading: true } ), // top
      new THREE.MeshPhongMaterial( { color, flatShading: true } ) // bottom
    ]
  );
  cabin.position.x = -40*zoom;
  cabin.position.z = 20*zoom;
  cabin.castShadow = true;
  cabin.receiveShadow = true;
  truck.add( cabin );

  const frontWheel = new Wheel();
  frontWheel.position.x = -38*zoom;
  truck.add( frontWheel );

  const middleWheel = new Wheel();
  middleWheel.position.x = -10*zoom;
  truck.add( middleWheel );

  const backWheel = new Wheel();
  backWheel.position.x = 30*zoom;
  truck.add( backWheel );

  return truck;
}

function Three() {
  const three = new THREE.Group();

  const trunk = new THREE.Mesh(
    new THREE.BoxBufferGeometry( 15*zoom, 15*zoom, 20*zoom ),
    new THREE.MeshPhongMaterial( { color: 0x4d2926, flatShading: true } )
  );
  trunk.position.z = 10*zoom;
  trunk.castShadow = true;
  trunk.receiveShadow = true;
  three.add(trunk);

  height = threeHeights[Math.floor(Math.random()*threeHeights.length)];

  const crown = new THREE.Mesh(
    new THREE.BoxBufferGeometry( 30*zoom, 30*zoom, height*zoom ),
    new THREE.MeshLambertMaterial( { color: 0x7aa21d, flatShading: true } )
  );
  crown.position.z = (height/2+20)*zoom;
  crown.castShadow = true;
  crown.receiveShadow = false;
  three.add(crown);

  return three;
}

function Chicken() {
  const chicken = new THREE.Group();

  const body = new THREE.Mesh(
    new THREE.BoxBufferGeometry( chickenSize*zoom, chickenSize*zoom, 20*zoom ),
    new THREE.MeshPhongMaterial( { color: 0xffffff, flatShading: true } )
  );
  body.position.z = 10*zoom;
  body.castShadow = true;
  body.receiveShadow = true;
  chicken.add(body);

  const rowel = new THREE.Mesh(
    new THREE.BoxBufferGeometry( 2*zoom, 4*zoom, 2*zoom ),
    new THREE.MeshLambertMaterial( { color: 0xF0619A, flatShading: true } )
  );
  rowel.position.z = 21*zoom;
  rowel.castShadow = true;
  rowel.receiveShadow = false;
  chicken.add(rowel);

  return chicken;
}

function Road() {
  const road = new THREE.Group();

  const createSection = color => new THREE.Mesh(
    new THREE.PlaneBufferGeometry( boardWidth*zoom, positionWidth*zoom ),
    new THREE.MeshPhongMaterial( { color } )
  );

  const middle = createSection(0x454A59);
  middle.receiveShadow = true;
  road.add(middle);

  const left = createSection(0x393D49);
  left.position.x = - boardWidth*zoom;
  road.add(left);

  const right = createSection(0x393D49);
  right.position.x = boardWidth*zoom;
  road.add(right);

  return road;
}

function Grass() {
  const grass = new THREE.Group();

  const createSection = color => new THREE.Mesh(
    new THREE.BoxBufferGeometry( boardWidth*zoom, positionWidth*zoom, 3*zoom ),
    new THREE.MeshPhongMaterial( { color } )
  );

  const middle = createSection(0xbaf455);
  middle.receiveShadow = true;
  grass.add(middle);

  const left = createSection(0x99C846);
  left.position.x = - boardWidth*zoom;
  grass.add(left);

  const right = createSection(0x99C846);
  right.position.x = boardWidth*zoom;
  grass.add(right);

  grass.position.z = 1.5*zoom;
  return grass;
}

function Lane(index) {
  this.index = index;
  this.type = index <= 0 ? 'field' : laneTypes[Math.floor(Math.random()*laneTypes.length)];

  switch(this.type) {
    case 'field': {
      this.type = 'field';
      this.mesh = new Grass();
      break;
    }
    case 'forest': {
      this.mesh = new Grass();

      this.occupiedPositions = new Set();
      this.threes = [1,2,3,4].map(() => {
        const three = new Three();
        let position;
        do {
          position = Math.floor(Math.random()*columns);
        }while(this.occupiedPositions.has(position))
          this.occupiedPositions.add(position);
        three.position.x = (position*positionWidth+positionWidth/2)*zoom-boardWidth*zoom/2;
        this.mesh.add( three );
        return three;
      })
      break;
    }
    case 'car' : {
      this.mesh = new Road();
      this.direction = Math.random() >= 0.5;

      const occupiedPositions = new Set();
      this.vechicles = [1,2,3].map(() => {
        const vechicle = new Car();
        let position;
        do {
          position = Math.floor(Math.random()*columns/2);
        }while(occupiedPositions.has(position))
          occupiedPositions.add(position);
        vechicle.position.x = (position*positionWidth*2+positionWidth/2)*zoom-boardWidth*zoom/2;
        if(!this.direction) vechicle.rotation.z = Math.PI;
        this.mesh.add( vechicle );
        return vechicle;
      })

      this.speed = laneSpeeds[Math.floor(Math.random()*laneSpeeds.length)];
      break;
    }
    case 'truck' : {
      this.mesh = new Road();
      this.direction = Math.random() >= 0.5;

      const occupiedPositions = new Set();
      this.vechicles = [1,2].map(() => {
        const vechicle = new Truck();
        let position;
        do {
          position = Math.floor(Math.random()*columns/3);
        }while(occupiedPositions.has(position))
          occupiedPositions.add(position);
        vechicle.position.x = (position*positionWidth*3+positionWidth/2)*zoom-boardWidth*zoom/2;
        if(!this.direction) vechicle.rotation.z = Math.PI;
        this.mesh.add( vechicle );
        return vechicle;
      })

      this.speed = laneSpeeds[Math.floor(Math.random()*laneSpeeds.length)];
      break;
    }
  }
}

document.querySelector("#retry").addEventListener("click", () => {
  lanes.forEach(lane => scene.remove( lane.mesh ));
  initaliseValues();
  endDOM.style.visibility = 'hidden';
});

document.getElementById('forward').addEventListener("click", () => move('forward'));

document.getElementById('backward').addEventListener("click", () => move('backward'));

document.getElementById('left').addEventListener("click", () => move('left'));

document.getElementById('right').addEventListener("click", () => move('right'));

window.addEventListener("keydown", event => {
  if (event.keyCode == '38') {
    // up arrow
    move('forward');
  }
  else if (event.keyCode == '40') {
    // down arrow
    move('backward');
  }
  else if (event.keyCode == '37') {
    // left arrow
    move('left');
  }
  else if (event.keyCode == '39') {
    // right arrow
    move('right');
  }
});

function move(direction) {
  const finalPositions = moves.reduce((position,move) => {
    if(move === 'forward') return {lane: position.lane+1, column: position.column};
    if(move === 'backward') return {lane: position.lane-1, column: position.column};
    if(move === 'left') return {lane: position.lane, column: position.column-1};
    if(move === 'right') return {lane: position.lane, column: position.column+1};
  }, {lane: currentLane, column: currentColumn})

  if (direction === 'forward') {
    if(lanes[finalPositions.lane+1].type === 'forest' && lanes[finalPositions.lane+1].occupiedPositions.has(finalPositions.column)) return;
    if(!stepStartTimestamp) startMoving = true;
    addLane();
  }
  else if (direction === 'backward') {
    if(finalPositions.lane === 0) return;
    if(lanes[finalPositions.lane-1].type === 'forest' && lanes[finalPositions.lane-1].occupiedPositions.has(finalPositions.column)) return;
    if(!stepStartTimestamp) startMoving = true;
  }
  else if (direction === 'left') {
    if(finalPositions.column === 0) return;
    if(lanes[finalPositions.lane].type === 'forest' && lanes[finalPositions.lane].occupiedPositions.has(finalPositions.column-1)) return;
    if(!stepStartTimestamp) startMoving = true;
  }
  else if (direction === 'right') {
    if(finalPositions.column === columns - 1 ) return;
    if(lanes[finalPositions.lane].type === 'forest' && lanes[finalPositions.lane].occupiedPositions.has(finalPositions.column+1)) return;
    if(!stepStartTimestamp) startMoving = true;
  }
  moves.push(direction);
}

function animate(timestamp) {
  requestAnimationFrame( animate );

  if(!previousTimestamp) previousTimestamp = timestamp;
  const delta = timestamp - previousTimestamp;
  previousTimestamp = timestamp;

  // Animate cars and trucks moving on the lane
  lanes.forEach(lane => {
    if(lane.type === 'car' || lane.type === 'truck') {
      const aBitBeforeTheBeginingOfLane = -boardWidth*zoom/2 - positionWidth*2*zoom;
      const aBitAfterTheEndOFLane = boardWidth*zoom/2 + positionWidth*2*zoom;
      lane.vechicles.forEach(vechicle => {
        if(lane.direction) {
          vechicle.position.x = vechicle.position.x < aBitBeforeTheBeginingOfLane ? aBitAfterTheEndOFLane : vechicle.position.x -= lane.speed/16*delta;
        }else{
          vechicle.position.x = vechicle.position.x > aBitAfterTheEndOFLane ? aBitBeforeTheBeginingOfLane : vechicle.position.x += lane.speed/16*delta;
        }
      });
    }
  });

  if(startMoving) {
    stepStartTimestamp = timestamp;
    startMoving = false;
  }

  if(stepStartTimestamp) {
    const moveDeltaTime = timestamp - stepStartTimestamp;
    const moveDeltaDistance = Math.min(moveDeltaTime/stepTime,1)*positionWidth*zoom;
    const jumpDeltaDistance = Math.sin(Math.min(moveDeltaTime/stepTime,1)*Math.PI)*8*zoom;
    switch(moves[0]) {
      case 'forward': {
        const positionY = currentLane*positionWidth*zoom + moveDeltaDistance;
        camera.position.y = initialCameraPositionY + positionY;
        dirLight.position.y = initialDirLightPositionY + positionY;
        chicken.position.y = positionY; // initial chicken position is 0

        chicken.position.z = jumpDeltaDistance;
        break;
      }
      case 'backward': {
        positionY = currentLane*positionWidth*zoom - moveDeltaDistance
        camera.position.y = initialCameraPositionY + positionY;
        dirLight.position.y = initialDirLightPositionY + positionY;
        chicken.position.y = positionY;

        chicken.position.z = jumpDeltaDistance;
        break;
      }
      case 'left': {
        const positionX = (currentColumn*positionWidth+positionWidth/2)*zoom -boardWidth*zoom/2 - moveDeltaDistance;
        camera.position.x = initialCameraPositionX + positionX;
        dirLight.position.x = initialDirLightPositionX + positionX;
        chicken.position.x = positionX; // initial chicken position is 0
        chicken.position.z = jumpDeltaDistance;
        break;
      }
      case 'right': {
        const positionX = (currentColumn*positionWidth+positionWidth/2)*zoom -boardWidth*zoom/2 + moveDeltaDistance;
        camera.position.x = initialCameraPositionX + positionX;
        dirLight.position.x = initialDirLightPositionX + positionX;
        chicken.position.x = positionX;

        chicken.position.z = jumpDeltaDistance;
        break;
      }
    }
    // Once a step has ended
    if(moveDeltaTime > stepTime) {
      switch(moves[0]) {
        case 'forward': {
          currentLane++;
          counterDOM.innerHTML = currentLane;
          break;
        }
        case 'backward': {
          currentLane--;
          counterDOM.innerHTML = currentLane;
          break;
        }
        case 'left': {
          currentColumn--;
          break;
        }
        case 'right': {
          currentColumn++;
          break;
        }
      }
      moves.shift();
      // If more steps are to be taken then restart counter otherwise stop stepping
      stepStartTimestamp = moves.length === 0 ? null : timestamp;
    }
  }

  // Hit test
  if(lanes[currentLane].type === 'car' || lanes[currentLane].type === 'truck') {
    const chickenMinX = chicken.position.x - chickenSize*zoom/2;
    const chickenMaxX = chicken.position.x + chickenSize*zoom/2;
    const vechicleLength = { car: 60, truck: 105}[lanes[currentLane].type];
    lanes[currentLane].vechicles.forEach(vechicle => {
      const carMinX = vechicle.position.x - vechicleLength*zoom/2;
      const carMaxX = vechicle.position.x + vechicleLength*zoom/2;
      if(chickenMaxX > carMinX && chickenMinX < carMaxX) {
        endDOM.style.visibility = 'visible';
      }
    });

  }
  renderer.render( scene, camera );
}

requestAnimationFrame( animate );

Final Output:

Crossy Road Game Clone HTML CSS JavaScript.gif





Source Code : Download files

Conclusion:


The JavaScript code provided implements the core functionality of a Crossy Road game clone. It includes features such as lane generation, object creation, player movement, vehicle movement, collision detection, and rendering. By leveraging the Three.js library for 3D rendering, the game achieves a visually engaging experience reminiscent of the original Crossy Road game. With further enhancements and additions, such as additional obstacles, power-ups, and scoring mechanics, this clone has the potential to become a fully-fledged and entertaining game for players to enjoy.


Tags: #CrossyRoad #GameClone #JavaScript #Threejs #HTML #CSS #GameDevelopment

إرسال تعليق

ملفات تعريف الارتباط
نستخدم ملفات تعريف الارتباط (Cookies) لفهم كيفية استخدامك لموقعنا وتحسين تجربتك في المحتوى والإعلانات. باستمرارك في تصفح الموقع، فإنك توافق على استخدامنا لملفات تعريف الارتباط وفقًا لسياسة الخصوصية لدينا.
أُووبس!
يبدو أن هناك خطأ ما في اتصالك بالإنترنت. يرجى الاتصال بالإنترنت وبدء التصفح مرة أخرى.
AdBlock Detected!
We have detected that you are using adblocking plugin in your browser.
The revenue we earn by the advertisements is used to manage this website, we request you to whitelist our website in your adblocking plugin.
Site is Blocked
Sorry! This site is not available in your country.