Generating the Map

Crossy Road with Three.js ● Chapter 8

So far, we have rendered the map based on static metadata. Now, it’s time to generate the map randomly and dynamically expand the level as the player moves forward. In this chapter, we add utility functions that generate metadata and use them to render the map.

Generating Metadata

Generating Metadata for Map

Let’s create a utility file to generate metadata. In this, we export the generateRows function, which returns the metadata for n amount of rows.

The generateRows function calls the generateRow function, which generates the metadata for one single row. It randomly selects a row type: car, truck, or forest.

It uses a helper function randomElement to pick a random element from an array.

Now, let’s look into the functions that generate the metadata for each row type.

src/utilities/generateRows.ts
import * as THREE from "three";
import { minTileIndex, maxTileIndex } from "../constants";
import { type Row, type RowType } from "../types";
export function generateRows(amount: number): Row[] {
const rows: Row[] = [];
for (let i = 0; i < amount; i++) {
const rowData = generateRow();
rows.push(rowData);
}
return rows;
}
function generateRow(): Row {
const type: RowType = randomElement(["car", "truck", "forest"]);
if (type === "car") return generateCarLaneMetadata();
if (type === "truck") return generateTruckLaneMetadata();
return generateForesMetadata();
}
function randomElement<T>(array: T[]): T {
return array[Math.floor(Math.random() * array.length)];
}
src/utilities/generateRows.js
import * as THREE from "three";
import { minTileIndex, maxTileIndex } from "../constants";
export function generateRows(amount) {
const rows = [];
for (let i = 0; i < amount; i++) {
const rowData = generateRow();
rows.push(rowData);
}
return rows;
}
function generateRow() {
const type = randomElement(["car", "truck", "forest"]);
if (type === "car") return generateCarLaneMetadata();
if (type === "truck") return generateTruckLaneMetadata();
return generateForesMetadata();
}
function randomElement(array) {
return array[Math.floor(Math.random() * array.length)];
}

Generating Metadata for a Forest

Let’s continue with the same file and add the generateForesMetadata function, which returns metadata for a forest row.

To generate metadata for a forest, we need to generate an object with a type property set to forest and an array of trees. Each tree has a tileIndex and a height.

We generate four trees in a forest. We pick a random tileIndex for each based on the available range. We use THREE.MathUtils.randInt to pick a random integer number between the minimum and maximum tile index. To avoid overlapping trees, we keep track of the occupied tiles in the occupiedTiles set. If the generated position is already occupied, we keep generating a new tile index until we find one that is free.

Let’s say we pick tile -3 for the first tree. We mark the tile in the set as occupied, and the following trees won’t be placed there.

We also pick a random height for each tree from the array [20, 45, 60].

src/utilities/generateRows.ts
23 collapsed lines
import * as THREE from "three";
import { minTileIndex, maxTileIndex } from "../constants";
import { type Row, type RowType } from "../types";
export function generateRows(amount: number): Row[] {
const rows: Row[] = [];
for (let i = 0; i < amount; i++) {
const rowData = generateRow();
rows.push(rowData);
}
return rows;
}
function generateRow(): Row {
const type: RowType = randomElement(["car", "truck", "forest"]);
if (type === "car") return generateCarLaneMetadata();
if (type === "truck") return generateTruckLaneMetadata();
return generateForesMetadata();
}
function randomElement<T>(array: T[]): T {
return array[Math.floor(Math.random() * array.length)];
}
function generateForesMetadata(): Row {
const occupiedTiles = new Set<number>();
const trees = Array.from({ length: 4 }, () => {
let tileIndex;
do {
tileIndex = THREE.MathUtils.randInt(minTileIndex, maxTileIndex);
} while (occupiedTiles.has(tileIndex));
occupiedTiles.add(tileIndex);
const height = randomElement([20, 45, 60]);
return { tileIndex, height };
});
return { type: "forest", trees };
}
src/utilities/generateRows.js
22 collapsed lines
import * as THREE from "three";
import { minTileIndex, maxTileIndex } from "../constants";
export function generateRows(amount) {
const rows = [];
for (let i = 0; i < amount; i++) {
const rowData = generateRow();
rows.push(rowData);
}
return rows;
}
function generateRow() {
const type = randomElement(["car", "truck", "forest"]);
if (type === "car") return generateCarLaneMetadata();
if (type === "truck") return generateTruckLaneMetadata();
return generateForesMetadata();
}
function randomElement(array) {
return array[Math.floor(Math.random() * array.length)];
}
function generateForesMetadata() {
const occupiedTiles = new Set();
const trees = Array.from({ length: 4 }, () => {
let tileIndex;
do {
tileIndex = THREE.MathUtils.randInt(minTileIndex, maxTileIndex);
} while (occupiedTiles.has(tileIndex));
occupiedTiles.add(tileIndex);
const height = randomElement([20, 45, 60]);
return { tileIndex, height };
});
return { type: "forest", trees };
}

Generating Metadata for a Car Lane

Generating car lanes is very similar. Car lanes also have a direction and speed property. The direction is a boolean value determining whether the cars move left or right. The speed property tells us how much distance each car moves in one second. We pick a random value for both properties with our utility function.

Then, we need to generate an array of vehicles with the initialTileIndex and color properties. We generate three cars in a lane. The logic here is similar to before, except that cars are longer. We mark three tiles as occupied for each vehicle.

src/utilities/generateRows.ts
40 collapsed lines
import * as THREE from "three";
import { minTileIndex, maxTileIndex } from "../constants";
import { type Row, type RowType } from "../types";
export function generateRows(amount: number): Row[] {
const rows: Row[] = [];
for (let i = 0; i < amount; i++) {
const rowData = generateRow();
rows.push(rowData);
}
return rows;
}
function generateRow(): Row {
const type: RowType = randomElement(["car", "truck", "forest"]);
if (type === "car") return generateCarLaneMetadata();
if (type === "truck") return generateTruckLaneMetadata();
return generateForesMetadata();
}
function randomElement<T>(array: T[]): T {
return array[Math.floor(Math.random() * array.length)];
}
function generateForesMetadata(): Row {
const occupiedTiles = new Set<number>();
const trees = Array.from({ length: 4 }, () => {
let tileIndex;
do {
tileIndex = THREE.MathUtils.randInt(minTileIndex, maxTileIndex);
} while (occupiedTiles.has(tileIndex));
occupiedTiles.add(tileIndex);
const height = randomElement([20, 45, 60]);
return { tileIndex, height };
});
return { type: "forest", trees };
}
function generateCarLaneMetadata(): Row {
const direction = randomElement([true, false]);
const speed = randomElement([125, 156, 188]);
const occupiedTiles = new Set<number>();
const vehicles = Array.from({ length: 3 }, () => {
let initialTileIndex;
do {
initialTileIndex = THREE.MathUtils.randInt(
minTileIndex,
maxTileIndex
);
} while (occupiedTiles.has(initialTileIndex));
occupiedTiles.add(initialTileIndex - 1);
occupiedTiles.add(initialTileIndex);
occupiedTiles.add(initialTileIndex + 1);
const color: THREE.ColorRepresentation = randomElement([
0xa52523, 0xbdb638, 0x78b14b,
]);
return { initialTileIndex, color };
});
return { type: "car", direction, speed, vehicles };
}
src/utilities/generateRows.js
39 collapsed lines
import * as THREE from "three";
import { minTileIndex, maxTileIndex } from "../constants";
export function generateRows(amount) {
const rows = [];
for (let i = 0; i < amount; i++) {
const rowData = generateRow();
rows.push(rowData);
}
return rows;
}
function generateRow() {
const type = randomElement(["car", "truck", "forest"]);
if (type === "car") return generateCarLaneMetadata();
if (type === "truck") return generateTruckLaneMetadata();
return generateForesMetadata();
}
function randomElement(array) {
return array[Math.floor(Math.random() * array.length)];
}
function generateForesMetadata() {
const occupiedTiles = new Set();
const trees = Array.from({ length: 4 }, () => {
let tileIndex;
do {
tileIndex = THREE.MathUtils.randInt(minTileIndex, maxTileIndex);
} while (occupiedTiles.has(tileIndex));
occupiedTiles.add(tileIndex);
const height = randomElement([20, 45, 60]);
return { tileIndex, height };
});
return { type: "forest", trees };
}
function generateCarLaneMetadata() {
const direction = randomElement([true, false]);
const speed = randomElement([125, 156, 188]);
const occupiedTiles = new Set();
const vehicles = Array.from({ length: 3 }, () => {
let initialTileIndex;
do {
initialTileIndex = THREE.MathUtils.randInt(
minTileIndex,
maxTileIndex
);
} while (occupiedTiles.has(initialTileIndex));
occupiedTiles.add(initialTileIndex - 1);
occupiedTiles.add(initialTileIndex);
occupiedTiles.add(initialTileIndex + 1);
const color = randomElement([0xa52523, 0xbdb638, 0x78b14b]);
return { initialTileIndex, color };
});
return { type: "car", direction, speed, vehicles };
}

Generating Metadata for a Truck Lane

Generating metadata for a truck lane is essentially the same as generating metadata for a car lane. The only difference is that a truck lane has only two vehicles, and because trucks are longer than cars, they occupy more tiles.

src/utilities/generateRows.ts
68 collapsed lines
import * as THREE from "three";
import { minTileIndex, maxTileIndex } from "../constants";
import { type Row, type RowType } from "../types";
export function generateRows(amount: number): Row[] {
const rows: Row[] = [];
for (let i = 0; i < amount; i++) {
const rowData = generateRow();
rows.push(rowData);
}
return rows;
}
function generateRow(): Row {
const type: RowType = randomElement(["car", "truck", "forest"]);
if (type === "car") return generateCarLaneMetadata();
if (type === "truck") return generateTruckLaneMetadata();
return generateForesMetadata();
}
function randomElement<T>(array: T[]): T {
return array[Math.floor(Math.random() * array.length)];
}
function generateForesMetadata(): Row {
const occupiedTiles = new Set<number>();
const trees = Array.from({ length: 4 }, () => {
let tileIndex;
do {
tileIndex = THREE.MathUtils.randInt(minTileIndex, maxTileIndex);
} while (occupiedTiles.has(tileIndex));
occupiedTiles.add(tileIndex);
const height = randomElement([20, 45, 60]);
return { tileIndex, height };
});
return { type: "forest", trees };
}
function generateCarLaneMetadata(): Row {
const direction = randomElement([true, false]);
const speed = randomElement([125, 156, 188]);
const occupiedTiles = new Set<number>();
const vehicles = Array.from({ length: 3 }, () => {
let initialTileIndex;
do {
initialTileIndex = THREE.MathUtils.randInt(
minTileIndex,
maxTileIndex
);
} while (occupiedTiles.has(initialTileIndex));
occupiedTiles.add(initialTileIndex - 1);
occupiedTiles.add(initialTileIndex);
occupiedTiles.add(initialTileIndex + 1);
const color: THREE.ColorRepresentation = randomElement([
0xa52523, 0xbdb638, 0x78b14b,
]);
return { initialTileIndex, color };
});
return { type: "car", direction, speed, vehicles };
}
function generateTruckLaneMetadata(): Row {
const direction = randomElement([true, false]);
const speed = randomElement([125, 156, 188]);
const occupiedTiles = new Set<number>();
const vehicles = Array.from({ length: 2 }, () => {
let initialTileIndex;
do {
initialTileIndex = THREE.MathUtils.randInt(
minTileIndex,
maxTileIndex
);
} while (occupiedTiles.has(initialTileIndex));
occupiedTiles.add(initialTileIndex - 2);
occupiedTiles.add(initialTileIndex - 1);
occupiedTiles.add(initialTileIndex);
occupiedTiles.add(initialTileIndex + 1);
occupiedTiles.add(initialTileIndex + 2);
const color: THREE.ColorRepresentation = randomElement([
0xa52523, 0xbdb638, 0x78b14b,
]);
return { initialTileIndex, color };
});
return { type: "truck", direction, speed, vehicles };
}
src/utilities/generateRows.js
65 collapsed lines
import * as THREE from "three";
import { minTileIndex, maxTileIndex } from "../constants";
export function generateRows(amount) {
const rows = [];
for (let i = 0; i < amount; i++) {
const rowData = generateRow();
rows.push(rowData);
}
return rows;
}
function generateRow() {
const type = randomElement(["car", "truck", "forest"]);
if (type === "car") return generateCarLaneMetadata();
if (type === "truck") return generateTruckLaneMetadata();
return generateForesMetadata();
}
function randomElement(array) {
return array[Math.floor(Math.random() * array.length)];
}
function generateForesMetadata() {
const occupiedTiles = new Set();
const trees = Array.from({ length: 4 }, () => {
let tileIndex;
do {
tileIndex = THREE.MathUtils.randInt(minTileIndex, maxTileIndex);
} while (occupiedTiles.has(tileIndex));
occupiedTiles.add(tileIndex);
const height = randomElement([20, 45, 60]);
return { tileIndex, height };
});
return { type: "forest", trees };
}
function generateCarLaneMetadata() {
const direction = randomElement([true, false]);
const speed = randomElement([125, 156, 188]);
const occupiedTiles = new Set();
const vehicles = Array.from({ length: 3 }, () => {
let initialTileIndex;
do {
initialTileIndex = THREE.MathUtils.randInt(
minTileIndex,
maxTileIndex
);
} while (occupiedTiles.has(initialTileIndex));
occupiedTiles.add(initialTileIndex - 1);
occupiedTiles.add(initialTileIndex);
occupiedTiles.add(initialTileIndex + 1);
const color = randomElement([0xa52523, 0xbdb638, 0x78b14b]);
return { initialTileIndex, color };
});
return { type: "car", direction, speed, vehicles };
}
function generateTruckLaneMetadata() {
const direction = randomElement([true, false]);
const speed = randomElement([125, 156, 188]);
const occupiedTiles = new Set();
const vehicles = Array.from({ length: 2 }, () => {
let initialTileIndex;
do {
initialTileIndex = THREE.MathUtils.randInt(
minTileIndex,
maxTileIndex
);
} while (occupiedTiles.has(initialTileIndex));
occupiedTiles.add(initialTileIndex - 2);
occupiedTiles.add(initialTileIndex - 1);
occupiedTiles.add(initialTileIndex);
occupiedTiles.add(initialTileIndex + 1);
occupiedTiles.add(initialTileIndex + 2);
const color = randomElement([0xa52523, 0xbdb638, 0x78b14b]);
return { initialTileIndex, color };
});
return { type: "truck", direction, speed, vehicles };
}

Using the Metadata Generators

After defining the utility functions to generate metadata, let’s use them to create rows.

Initializing the Metadata

Now that the metadata will be dynamic, let’s make sure that it is empty initially.

src/components/Map.ts
7 collapsed lines
import type { Row } from "../types";
import * as THREE from "three";
import { Grass } from "./Grass";
import { Road } from "./Road";
import { Tree } from "./Tree";
import { Car } from "./Car";
import { Truck } from "./Truck";
export const metadata: Row[] = [];
58 collapsed lines
export const map = new THREE.Group();
export function initializeMap() {
for (let rowIndex = 0; rowIndex > -5; rowIndex--) {
const grass = Grass(rowIndex);
map.add(grass);
}
addRows();
}
export function addRows() {
metadata.forEach((rowData, index) => {
const rowIndex = index + 1;
if (rowData.type === "forest") {
const row = Grass(rowIndex);
rowData.trees.forEach(({ tileIndex, height }) => {
const three = Tree(tileIndex, height);
row.add(three);
});
map.add(row);
}
if (rowData.type === "car") {
const row = Road(rowIndex);
rowData.vehicles.forEach((vehicle) => {
const car = Car(
vehicle.initialTileIndex,
rowData.direction,
vehicle.color
);
vehicle.ref = car;
row.add(car);
});
map.add(row);
}
if (rowData.type === "truck") {
const row = Road(rowIndex);
rowData.vehicles.forEach((vehicle) => {
const truck = Truck(
vehicle.initialTileIndex,
rowData.direction,
vehicle.color
);
vehicle.ref = truck;
row.add(truck);
});
map.add(row);
}
});
}
src/components/Map.js
6 collapsed lines
import * as THREE from "three";
import { Grass } from "./Grass";
import { Road } from "./Road";
import { Tree } from "./Tree";
import { Car } from "./Car";
import { Truck } from "./Truck";
export const metadata = [];
58 collapsed lines
export const map = new THREE.Group();
export function initializeMap() {
for (let rowIndex = 0; rowIndex > -5; rowIndex--) {
const grass = Grass(rowIndex);
map.add(grass);
}
addRows();
}
export function addRows() {
metadata.forEach((rowData, index) => {
const rowIndex = index + 1;
if (rowData.type === "forest") {
const row = Grass(rowIndex);
rowData.trees.forEach(({ tileIndex, height }) => {
const three = Tree(tileIndex, height);
row.add(three);
});
map.add(row);
}
if (rowData.type === "car") {
const row = Road(rowIndex);
rowData.vehicles.forEach((vehicle) => {
const car = Car(
vehicle.initialTileIndex,
rowData.direction,
vehicle.color
);
vehicle.ref = car;
row.add(car);
});
map.add(row);
}
if (rowData.type === "truck") {
const row = Road(rowIndex);
rowData.vehicles.forEach((vehicle) => {
const truck = Truck(
vehicle.initialTileIndex,
rowData.direction,
vehicle.color
);
vehicle.ref = truck;
row.add(truck);
});
map.add(row);
}
});
}

Rendering Random Rows

Let’s return to our addRows function now. This function will now generate the map’s metadata and add the 3D objects based on it to the map.

We call the previously defined generateRows function to generate metadata for 20 rows. We push the result into the metadata so other parts of the game can access it. Then, we create the 3D objects and add them to the map as we did before.

src/components/Map.ts
import type { Row } from "../types";
import * as THREE from "three";
import { generateRows } from "../utilities/generateRows";
import { Grass } from "./Grass";
import { Road } from "./Road";
import { Tree } from "./Tree";
import { Car } from "./Car";
import { Truck } from "./Truck";
export const metadata: Row[] = [];
9 collapsed lines
export const map = new THREE.Group();
export function initializeMap() {
for (let rowIndex = 0; rowIndex > -5; rowIndex--) {
const grass = Grass(rowIndex);
map.add(grass);
}
addRows();
}
export function addRows() {
const newMetadata = generateRows(20);
metadata.push(...newMetadata);
metadata.forEach((rowData, index) => {
44 collapsed lines
const rowIndex = index + 1;
if (rowData.type === "forest") {
const row = Grass(rowIndex);
rowData.trees.forEach(({ tileIndex, height }) => {
const three = Tree(tileIndex, height);
row.add(three);
});
map.add(row);
}
if (rowData.type === "car") {
const row = Road(rowIndex);
rowData.vehicles.forEach((vehicle) => {
const car = Car(
vehicle.initialTileIndex,
rowData.direction,
vehicle.color
);
vehicle.ref = car;
row.add(car);
});
map.add(row);
}
if (rowData.type === "truck") {
const row = Road(rowIndex);
rowData.vehicles.forEach((vehicle) => {
const truck = Truck(
vehicle.initialTileIndex,
rowData.direction,
vehicle.color
);
vehicle.ref = truck;
row.add(truck);
});
map.add(row);
}
});
}
src/components/Map.js
import * as THREE from "three";
import { generateRows } from "../utilities/generateRows";
import { Grass } from "./Grass";
import { Road } from "./Road";
import { Tree } from "./Tree";
import { Car } from "./Car";
import { Truck } from "./Truck";
export const metadata = [];
9 collapsed lines
export const map = new THREE.Group();
export function initializeMap() {
for (let rowIndex = 0; rowIndex > -5; rowIndex--) {
const grass = Grass(rowIndex);
map.add(grass);
}
addRows();
}
export function addRows() {
const newMetadata = generateRows(20);
metadata.push(...newMetadata);
metadata.forEach((rowData, index) => {
44 collapsed lines
const rowIndex = index + 1;
if (rowData.type === "forest") {
const row = Grass(rowIndex);
rowData.trees.forEach(({ tileIndex, height }) => {
const three = Tree(tileIndex, height);
row.add(three);
});
map.add(row);
}
if (rowData.type === "car") {
const row = Road(rowIndex);
rowData.vehicles.forEach((vehicle) => {
const car = Car(
vehicle.initialTileIndex,
rowData.direction,
vehicle.color
);
vehicle.ref = car;
row.add(car);
});
map.add(row);
}
if (rowData.type === "truck") {
const row = Road(rowIndex);
rowData.vehicles.forEach((vehicle) => {
const truck = Truck(
vehicle.initialTileIndex,
rowData.direction,
vehicle.color
);
vehicle.ref = truck;
row.add(truck);
});
map.add(row);
}
});
}

Expanding the Map as the Player Moves Forward

Now that we can generate rows dynamically, let’s add new rows as the player moves forward.

Update the Map to Support Extension

Let’s change the addRows function again to support two scenarios: generating an initial set of rows and generating rows that extend the current map.

We change two things. When adding rows to the map, we process only the new metadata that represents the rows to be added. The old rows are still part of the scene.

Before we push the new rows into the metadata, we get how many rows we already have there. This will be the startIndex variable that we use to calculate the rowIndex. As this function can extend the existing map, we need to know from which position we can add the new rows.

src/components/Map.ts
import type { Row } from "../types";
import * as THREE from "three";
import { generateRows } from "../utilities/generateRows";
import { Grass } from "./Grass";
import { Road } from "./Road";
import { Tree } from "./Tree";
import { Car } from "./Car";
import { Truck } from "./Truck";
export const metadata: Row[] = [];
9 collapsed lines
export const map = new THREE.Group();
export function initializeMap() {
for (let rowIndex = 0; rowIndex > -5; rowIndex--) {
const grass = Grass(rowIndex);
map.add(grass);
}
addRows();
}
export function addRows() {
const newMetadata = generateRows(20);
const startIndex = metadata.length;
metadata.push(...newMetadata);
newMetadata.forEach((rowData, index) => {
const rowIndex = startIndex + index + 1;
42 collapsed lines
if (rowData.type === "forest") {
const row = Grass(rowIndex);
rowData.trees.forEach(({ tileIndex, height }) => {
const three = Tree(tileIndex, height);
row.add(three);
});
map.add(row);
}
if (rowData.type === "car") {
const row = Road(rowIndex);
rowData.vehicles.forEach((vehicle) => {
const car = Car(
vehicle.initialTileIndex,
rowData.direction,
vehicle.color
);
vehicle.ref = car;
row.add(car);
});
map.add(row);
}
if (rowData.type === "truck") {
const row = Road(rowIndex);
rowData.vehicles.forEach((vehicle) => {
const truck = Truck(
vehicle.initialTileIndex,
rowData.direction,
vehicle.color
);
vehicle.ref = truck;
row.add(truck);
});
map.add(row);
}
});
}
src/components/Map.js
import * as THREE from "three";
import { generateRows } from "../utilities/generateRows";
import { Grass } from "./Grass";
import { Road } from "./Road";
import { Tree } from "./Tree";
import { Car } from "./Car";
import { Truck } from "./Truck";
export const metadata = [];
9 collapsed lines
export const map = new THREE.Group();
export function initializeMap() {
for (let rowIndex = 0; rowIndex > -5; rowIndex--) {
const grass = Grass(rowIndex);
map.add(grass);
}
addRows();
}
export function addRows() {
const newMetadata = generateRows(20);
const startIndex = metadata.length;
metadata.push(...newMetadata);
newMetadata.forEach((rowData, index) => {
const rowIndex = startIndex + index + 1;
42 collapsed lines
if (rowData.type === "forest") {
const row = Grass(rowIndex);
rowData.trees.forEach(({ tileIndex, height }) => {
const three = Tree(tileIndex, height);
row.add(three);
});
map.add(row);
}
if (rowData.type === "car") {
const row = Road(rowIndex);
rowData.vehicles.forEach((vehicle) => {
const car = Car(
vehicle.initialTileIndex,
rowData.direction,
vehicle.color
);
vehicle.ref = car;
row.add(car);
});
map.add(row);
}
if (rowData.type === "truck") {
const row = Road(rowIndex);
rowData.vehicles.forEach((vehicle) => {
const truck = Truck(
vehicle.initialTileIndex,
rowData.direction,
vehicle.color
);
vehicle.ref = truck;
row.add(truck);
});
map.add(row);
}
});
}

Adding Rows as the Player Moves

Once the player completes a step, the stepCompleted function gets called. This is a good place to check if the player is running out of the current map. We check if the player is less than 10 rows away from the end of the current map. If so, we call the addRows function to add new rows.

As a result, we have an infinite number of rows. As the player moves forward, more rows are added to the map.

src/components/Player.ts
import * as THREE from "three";
import { endsUpInValidPosition } from "../utilities/endsUpInValidPosition";
import { metadata as rows, addRows } from "./Map";
import type { MoveDirection } from "../types";
58 collapsed lines
export const player = Player();
function Player() {
const player = new THREE.Group();
const body = new THREE.Mesh(
new THREE.BoxGeometry(15, 15, 20),
new THREE.MeshLambertMaterial({
color: "white",
flatShading: true,
})
);
body.castShadow = true;
body.receiveShadow = true;
body.position.z = 10;
player.add(body);
const cap = new THREE.Mesh(
new THREE.BoxGeometry(2, 4, 2),
new THREE.MeshLambertMaterial({
color: 0xf0619a,
flatShading: true,
})
);
cap.position.z = 21;
cap.castShadow = true;
cap.receiveShadow = true;
player.add(cap);
const playerContainer = new THREE.Group();
playerContainer.add(player);
return playerContainer;
}
export const position: {
currentRow: number;
currentTile: number;
} = {
currentRow: 0,
currentTile: 0,
};
export const movesQueue: MoveDirection[] = [];
export function queueMove(direction: MoveDirection) {
const isValidMove = endsUpInValidPosition(
{
rowIndex: position.currentRow,
tileIndex: position.currentTile,
},
[...movesQueue, direction]
);
if (!isValidMove) return;
movesQueue.push(direction);
}
export function stepCompleted() {
const direction = movesQueue.shift();
if (direction === "forward") position.currentRow += 1;
if (direction === "backward") position.currentRow -= 1;
if (direction === "left") position.currentTile -= 1;
if (direction === "right") position.currentTile += 1;
// Add new rows if the player is running out of them
if (position.currentRow > rows.length - 10) addRows();
}
src/components/Player.js
import * as THREE from "three";
import { endsUpInValidPosition } from "../utilities/endsUpInValidPosition";
import { metadata as rows, addRows } from "./Map";
55 collapsed lines
export const player = Player();
function Player() {
const player = new THREE.Group();
const body = new THREE.Mesh(
new THREE.BoxGeometry(15, 15, 20),
new THREE.MeshLambertMaterial({
color: "white",
flatShading: true,
})
);
body.castShadow = true;
body.receiveShadow = true;
body.position.z = 10;
player.add(body);
const cap = new THREE.Mesh(
new THREE.BoxGeometry(2, 4, 2),
new THREE.MeshLambertMaterial({
color: 0xf0619a,
flatShading: true,
})
);
cap.position.z = 21;
cap.castShadow = true;
cap.receiveShadow = true;
player.add(cap);
const playerContainer = new THREE.Group();
playerContainer.add(player);
return playerContainer;
}
export const position = {
currentRow: 0,
currentTile: 0,
};
export const movesQueue = [];
export function queueMove(direction) {
const isValidMove = endsUpInValidPosition(
{
rowIndex: position.currentRow,
tileIndex: position.currentTile,
},
[...movesQueue, direction]
);
if (!isValidMove) return;
movesQueue.push(direction);
}
export function stepCompleted() {
const direction = movesQueue.shift();
if (direction === "forward") position.currentRow += 1;
if (direction === "backward") position.currentRow -= 1;
if (direction === "left") position.currentTile -= 1;
if (direction === "right") position.currentTile += 1;
// Add new rows if the player is running out of them
if (position.currentRow > rows.length - 10) addRows();
}
Previous:
Following the Player
Next:
Score Indicator