Rendering the Map

Crossy Road with React Three Fiber ● Chapter 2

In this chapter, we will render the game map. The map consists of multiple rows, each described by metadata. Each row can be a forest, car, or truck lane. We go through each type and define the 3D objects that represent them.

The Different Row Types

The game map consists of multiple rows, each representing a distinct environment or obstacle.

  • The forest is a row of grass and trees.

  • The car lane contains cars.

  • The truck lane contains trucks.

Let’s define the types for the rows in types.ts. The RowType type is a union of the different row types. The Row type is a union of the different row objects. We will go through each row type in detail as we implement it.

src/types.ts
import * as THREE from "three";
export type RowType = "forest" | "car" | "truck";
export type Row =
| {
type: "forest";
trees: { tileIndex: number; height: number }[];
}
| {
type: "car";
direction: boolean;
speed: number;
vehicles: {
initialTileIndex: number;
color: THREE.ColorRepresentation;
}[];
}
| {
type: "truck";
direction: boolean;
speed: number;
vehicles: {
initialTileIndex: number;
color: THREE.ColorRepresentation;
}[];
};
src/types.js
export {};

The Different Row Types

The game map consists of multiple rows, each representing a distinct environment or obstacle.

  • The forest is a row of grass and trees.

  • The car lane contains cars.

  • The truck lane contains trucks.

Let’s define the types for the rows in types.ts. The RowType type is a union of the different row types. The Row type is a union of the different row objects. We will go through each row type in detail as we implement it.

src/types.ts
import * as THREE from "three";
export type RowType = "forest" | "car" | "truck";
export type Row =
| {
type: "forest";
trees: { tileIndex: number; height: number }[];
}
| {
type: "car";
direction: boolean;
speed: number;
vehicles: {
initialTileIndex: number;
color: THREE.ColorRepresentation;
}[];
}
| {
type: "truck";
direction: boolean;
speed: number;
vehicles: {
initialTileIndex: number;
color: THREE.ColorRepresentation;
}[];
};
src/types.js
export {};

Tiles

Each row consists of multiple tiles. The player moves from tile to tile. Trees are also placed on tiles. Cars and trucks do not relate to tiles; they move freely through the lane.

We define in a constants.js constants.ts file how many tiles each row has. In this case, we have 17 tiles per row, going from -8 to 8. The player will start in the middle at tile 0.

src/constants.ts
export const minTileIndex = -8;
export const maxTileIndex = 8;
export const tilesPerRow = maxTileIndex - minTileIndex + 1;
export const tileSize = 42;
src/constants.js
export const minTileIndex = -8;
export const maxTileIndex = 8;
export const tilesPerRow = maxTileIndex - minTileIndex + 1;
export const tileSize = 42;

Adding the Starting Row

In this section, we define some of the components we use to render the map, and we render the initial row.

The Map Component

Let’s create a new component called Map. This component will contain the 3D objects for every rows.

For now, let’s add a single Grass component as the starting row. To the Grass component we pass on the rowIndex. The Grass component will position itself based on this index.

We are going to extend this component with all the other rows, so let’s wrap the returned component into a Fragment element (using shorthand <> and </>).

Now, let’s define the Grass component.

src/components/Map.tsx
import { Grass } from "./Grass";
export function Map() {
return (
<>
<Grass rowIndex={0} />
</>
);
}
src/components/Map.jsx
import { Grass } from "./Grass";
export function Map() {
return (
<>
<Grass rowIndex={0} />
</>
);
}

The Grass Component

The grass component is the foundation and container of the forest rows, and it is also used for the starting row. Its content is a flat, wide green box.

The dimensions of this box are determined by the constants tileSize and tilesPerRow. It also has a bit of height, so it sticks out compared to the Road component, which will be completely flat.

The Grass component can serve as the container for the trees in the row. It has a children prop. The Forest component will pass in the trees as children.

We wrap the green box and the children into a group element. The group element is a container that can hold multiple 3D elements and apply transformations to all of them at once. We position it along the y-axis based on the rowIndex. This way, each row will have a different position.

src/components/Grass.tsx
import { tilesPerRow, tileSize } from "../constants";
type Props = {
rowIndex: number;
children?: React.ReactNode;
};
export function Grass({ rowIndex, children }: Props) {
return (
<group position-y={rowIndex * tileSize}>
<mesh>
<boxGeometry args={[tilesPerRow * tileSize, tileSize, 3]} />
<meshLambertMaterial color={0xbaf455} flatShading />
</mesh>
{children}
</group>
);
}
src/components/Grass.jsx
import { tilesPerRow, tileSize } from "../constants";
export function Grass({ rowIndex, children }) {
return (
<group position-y={rowIndex * tileSize}>
<mesh>
<boxGeometry args={[tilesPerRow * tileSize, tileSize, 3]} />
<meshLambertMaterial color={0xbaf455} flatShading />
</mesh>
{children}
</group>
);
}

Add the Map to the Scene

Let’s add a new Map component to the scene in the Game component. This will render the starting row.

Now, let’s add more rows to the map.

src/Game.tsx
import { Scene } from "./components/Scene";
import { Player } from "./components/Player";
import { Map } from "./components/Map";
export default function Game() {
return (
<Scene>
<Player />
<Map />
</Scene>
);
}
src/Game.jsx
import { Scene } from "./components/Scene";
import { Player } from "./components/Player";
import { Map } from "./components/Map";
export default function Game() {
return (
<Scene>
<Player />
<Map />
</Scene>
);
}

Adding Threes

It’s time to extend our map with a forest row. We define metadata for the map and render the rows based on it.

Defining the Map Metadata

Let’s define the metadata for the map. The metadata is an array of objects that contain information about each row. Each row object contains a type property that decides if we have a forest row, a car, or a truck lane. The rest of the properties depend on the row type.

For now, let’s hardcode the values for one single row. Later, we will generate the rows dynamically.

The metadata for a forest row has the type forest and a list of trees. Each tree has the following properties:

  • tileIndex: The tile number of the tree within the row. In this case, we have 17 tiles per row, going from -8 to 8
  • height: The height of the crown in units
src/metadata.ts
import type { Row } from "./types";
export const rows: Row[] = [
{
type: "forest",
trees: [
{ tileIndex: -3, height: 50 },
{ tileIndex: 2, height: 30 },
{ tileIndex: 5, height: 50 },
],
},
];
src/metadata.js
export const rows = [
{
type: "forest",
trees: [
{ tileIndex: -3, height: 50 },
{ tileIndex: 2, height: 30 },
{ tileIndex: 5, height: 50 },
],
},
];

Mapping Metadata to Rows

Let’s extend the Map component to render the rows based on the metadata. We import the metadata and map each row to a separate Row component.

Note that the rowIndex is off by one compared to the array index because the first item in the metadata array will become the second row (after the starting row).

Now, let’s define the Row component.

src/components/Map.tsx
import { rows } from "../metadata";
import { Grass } from "./Grass";
import { Row } from "./Row";
export function Map() {
return (
<>
<Grass rowIndex={0} />
{rows.map((rowData, index) => (
<Row key={index} rowIndex={index + 1} rowData={rowData} />
))}
</>
);
}
src/components/Map.jsx
import { rows } from "../metadata";
import { Grass } from "./Grass";
import { Row } from "./Row";
export function Map() {
return (
<>
<Grass rowIndex={0} />
{rows.map((rowData, index) => (
<Row key={index} rowIndex={index + 1} rowData={rowData} />
))}
</>
);
}

Rendering Row by Type

The Row component is essentially a switch case that renders the correct row based on the type property of the row. We only support the forest type for now, but we will extend this file later to support car and truck lanes.

src/components/Row.tsx
import type { Row } from "../types";
import { Forest } from "./Forest";
type Props = {
rowIndex: number;
rowData: Row;
};
export function Row({ rowIndex, rowData }: Props) {
switch (rowData.type) {
case "forest": {
return <Forest rowIndex={rowIndex} rowData={rowData} />;
}
}
}
src/components/Row.jsx
import { Forest } from "./Forest";
export function Row({ rowIndex, rowData }) {
switch (rowData.type) {
case "forest": {
return <Forest rowIndex={rowIndex} rowData={rowData} />;
}
}
}

The Forest Component

The Forest component contains the row’s foundation, a Grass component, and the trees in the row.

The Grass component can receive children. We map trees’ metadata to Tree components and pass them on as children to the Grass component. Each tree gets its tileIndex, which will be used for positioning the tree within the row, and its height.

Now, let’s see how to render a tree.

src/components/Forest.tsx
import type { Row } from "../types";
import { Grass } from "./Grass";
import { Tree } from "./Tree";
type Props = {
rowIndex: number;
rowData: Extract<Row, { type: "forest" }>;
};
export function Forest({ rowIndex, rowData }: Props) {
return (
<Grass rowIndex={rowIndex}>
{rowData.trees.map((tree, index) => (
<Tree
key={index}
tileIndex={tree.tileIndex}
height={tree.height}
/>
))}
</Grass>
);
}
src/components/Forest.jsx
import { Grass } from "./Grass";
import { Tree } from "./Tree";
export function Forest({ rowIndex, rowData }) {
return (
<Grass rowIndex={rowIndex}>
{rowData.trees.map((tree, index) => (
<Tree
key={index}
tileIndex={tree.tileIndex}
height={tree.height}
/>
))}
</Grass>
);
}

The Tree Component

A tree consists of a trunk and a crown. Both are simple boxes. The trunk is placed on top of the ground (lifted along the z-axis by half its height), and the crown is placed on top of the trunk. The crown’s height is based on the height property.

The two meshes are wrapped together in a group positioned along the x-axis based on the tileIndex attribute.

Grab the tree to see it from different angles.

src/components/Tree.tsx
import { tileSize } from "../constants";
type Props = {
tileIndex: number;
height: number;
};
export function Tree({ tileIndex, height }: Props) {
return (
<group position-x={tileIndex * tileSize}>
<mesh position-z={height / 2 + 20}>
<boxGeometry args={[30, 30, height]} />
<meshLambertMaterial color={0x7aa21d} flatShading />
</mesh>
<mesh position-z={10}>
<boxGeometry args={[15, 15, 20]} />
<meshLambertMaterial color={0x4d2926} flatShading />
</mesh>
</group>
);
}
src/components/Tree.jsx
import { tileSize } from "../constants";
export function Tree({ tileIndex, height }) {
return (
<group position-x={tileIndex * tileSize}>
<mesh position-z={height / 2 + 20}>
<boxGeometry args={[30, 30, height]} />
<meshLambertMaterial color={0x7aa21d} flatShading />
</mesh>
<mesh position-z={10}>
<boxGeometry args={[15, 15, 20]} />
<meshLambertMaterial color={0x4d2926} flatShading />
</mesh>
</group>
);
}

Adding Car Lanes

Adding car lanes follows a similar structure to the forest. We define metadata for the vehicles and then map them to 3D objects.

Metadata of a Car Lane

Let’s replace the forest row with a car lane. The car lane will contain a single red car moving to the left.

Each car lane object will have the following properties:

  • direction: A boolean that sets the direction of the vehicles. true means the cars move to the right, false means the vehicles move to the left
  • speed: The speed of the vehicles in units per second
  • vehicles: An array of objects that contain the metadata for each vehicle on the lane.

Each vehicle will have the following properties:

  • initialTileIndex: The tile number of the vehicle’s initial position within the lane. We have 17 tiles per lane, going from -8 to 8.
  • color: The color of the vehicle in hexadecimal format.
src/metadata.ts
import type { Row } from "./types";
export const rows: Row[] = [
{
type: "car",
direction: false,
speed: 1,
vehicles: [{ initialTileIndex: 2, color: 0xff0000 }],
},
];
src/metadata.js
export const rows = [
{
type: "car",
direction: false,
speed: 1,
vehicles: [{ initialTileIndex: 2, color: 0xff0000 }],
},
];

Extending the Row Component with Car Lanes

Let’s extend the Row Component with support for car lanes. If the type of a row is car we map it to a CarLane component.

src/components/Row.tsx
import type { Row } from "../types";
import { Forest } from "./Forest";
import { CarLane } from "./CarLane";
type Props = {
rowIndex: number;
rowData: Row;
};
export function Row({ rowIndex, rowData }: Props) {
switch (rowData.type) {
case "forest": {
return <Forest rowIndex={rowIndex} rowData={rowData} />;
}
case "car": {
return <CarLane rowIndex={rowIndex} rowData={rowData} />;
}
}
}
src/components/Row.jsx
import { Forest } from "./Forest";
import { CarLane } from "./CarLane";
export function Row({ rowIndex, rowData }) {
switch (rowData.type) {
case "forest": {
return <Forest rowIndex={rowIndex} rowData={rowData} />;
}
case "car": {
return <CarLane rowIndex={rowIndex} rowData={rowData} />;
}
}
}

Car Lanes

The CarLane component renders the cars on the road. It has a similar structure to the Forest component.

It receives a rowData object as a prop, which contains the cars to be rendered. It wraps the cars in a Road component and maps over the rowData.vehicles array to render each car.

The Road and Car components are new here. Let’s take a look at them next.

src/components/CarLane.tsx
import type { Row } from "../types";
import { Road } from "./Road";
import { Car } from "./Car";
type Props = {
rowIndex: number;
rowData: Extract<Row, { type: "car" }>;
};
export function CarLane({ rowIndex, rowData }: Props) {
return (
<Road rowIndex={rowIndex}>
{rowData.vehicles.map((vehicle, index) => (
<Car
key={index}
rowIndex={rowIndex}
initialTileIndex={vehicle.initialTileIndex}
direction={rowData.direction}
speed={rowData.speed}
color={vehicle.color}
/>
))}
</Road>
);
}
src/components/CarLane.jsx
import { Road } from "./Road";
import { Car } from "./Car";
export function CarLane({ rowIndex, rowData }) {
return (
<Road rowIndex={rowIndex}>
{rowData.vehicles.map((vehicle, index) => (
<Car
key={index}
rowIndex={rowIndex}
initialTileIndex={vehicle.initialTileIndex}
direction={rowData.direction}
speed={rowData.speed}
color={vehicle.color}
/>
))}
</Road>
);
}

The Road Component

The road component is the foundation and container for the car and truck lanes. It contains a gray plane.

Similar to the Grass component, its size is determined by the constants tileSize and tilesPerRow. Unlike the Grass component, though, it doesn’t have any height.

The Road component serves as a container for the cars on the road. It has a children prop. The CarLane and TruckLane components pass in the vehicles as props.

src/components/Road.tsx
import { tilesPerRow, tileSize } from "../constants";
type Props = {
rowIndex: number;
children?: React.ReactNode;
};
export function Road({ rowIndex, children }: Props) {
return (
<group position-y={rowIndex * tileSize}>
<mesh>
<planeGeometry args={[tilesPerRow * tileSize, tileSize]} />
<meshLambertMaterial color={0x454a59} flatShading />
</mesh>
{children}
</group>
);
}
src/components/Road.jsx
import { tilesPerRow, tileSize } from "../constants";
export function Road({ rowIndex, children }) {
return (
<group position-y={rowIndex * tileSize}>
<mesh>
<planeGeometry args={[tilesPerRow * tileSize, tileSize]} />
<meshLambertMaterial color={0x454a59} flatShading />
</mesh>
{children}
</group>
);
}

The Car Component

The Car component is a simplified 3D model of a car. It consists of a box for the body, a smaller box for the top, and two Wheel components for the wheels.

We group all these elements together, position them based on the initialTileIndex prop, and turn them based on the direction prop. If the car goes to the left, we rotate it 180 degrees, which is equivalent to Math.PI in radians.

There are two props that we don’t use yet: rowIndex and speed. We will use them later to implement the car movement and hit detection.

Grab the car to see it from different angles.

src/components/Car.tsx
import * as THREE from "three";
import { tileSize } from "../constants";
import { Wheel } from "./Wheel";
type Props = {
rowIndex: number;
initialTileIndex: number;
direction: boolean;
speed: number;
color: THREE.ColorRepresentation;
};
export function Car({
rowIndex,
initialTileIndex,
direction,
speed,
color,
}: Props) {
return (
<group
position-x={initialTileIndex * tileSize}
rotation-z={direction ? 0 : Math.PI}
>
<mesh position={[0, 0, 12]}>
<boxGeometry args={[60, 30, 15]} />
<meshLambertMaterial color={color} flatShading />
</mesh>
<mesh position={[-6, 0, 25.5]}>
<boxGeometry args={[33, 24, 12]} />
<meshLambertMaterial color={0xffffff} flatShading />
</mesh>
<Wheel x={-18} />
<Wheel x={18} />
</group>
);
}
src/components/Car.jsx
import { tileSize } from "../constants";
import { Wheel } from "./Wheel";
export function Car({
rowIndex,
initialTileIndex,
direction,
speed,
color,
}) {
return (
<group
position-x={initialTileIndex * tileSize}
rotation-z={direction ? 0 : Math.PI}
>
<mesh position={[0, 0, 12]}>
<boxGeometry args={[60, 30, 15]} />
<meshLambertMaterial color={color} flatShading />
</mesh>
<mesh position={[-6, 0, 25.5]}>
<boxGeometry args={[33, 24, 12]} />
<meshLambertMaterial color={0xffffff} flatShading />
</mesh>
<Wheel x={-18} />
<Wheel x={18} />
</group>
);
}

The Wheel Component

To continue with our boxy theme, the Wheel component is a simple box with a dark color. Because we never see the cars from under, we don’t need to separate the wheels into left and right. We can use one long box for the front wheels and another for the back wheels.

src/components/Wheel.tsx
export function Wheel({ x }: { x: number }) {
return (
<mesh position={[x, 0, 6]}>
<boxGeometry args={[12, 33, 12]} />
<meshLambertMaterial color={0x333333} flatShading />
</mesh>
);
}
src/components/Wheel.jsx
export function Wheel({ x }) {
return (
<mesh position={[x, 0, 6]}>
<boxGeometry args={[12, 33, 12]} />
<meshLambertMaterial color={0x333333} flatShading />
</mesh>
);
}

Adding Truck Lanes

Truck lanes are almost the same as car lanes. The metadata and the components follow the same structure. Except this time, we render trucks instead of cars.

Metadata of a Truck Lane

Let’s replace our car lane with a truck lane. The metadata structure is the same. We define the direction and speed properties and set a vehicles array. The only difference is that the type of the row is truck this time.

src/metadata.ts
import type { Row } from "./types";
export const rows: Row[] = [
{
type: "truck",
direction: true,
speed: 0,
vehicles: [{ initialTileIndex: -4, color: 0x00ff00 }],
},
];
src/metadata.js
export const rows = [
{
type: "truck",
direction: true,
speed: 0,
vehicles: [{ initialTileIndex: -4, color: 0x00ff00 }],
},
];

Extending the Row Component with Truck Lanes

Let’s extend the Row Component with support for truck lanes. If a row type is truck, we map it to a TruckLane component. With this, the Row component reached its final form.

src/components/Row.tsx
import type { Row } from "../types";
import { Forest } from "./Forest";
import { CarLane } from "./CarLane";
import { TruckLane } from "./TruckLane";
type Props = {
rowIndex: number;
rowData: Row;
};
export function Row({ rowIndex, rowData }: Props) {
switch (rowData.type) {
case "forest": {
return <Forest rowIndex={rowIndex} rowData={rowData} />;
}
case "car": {
return <CarLane rowIndex={rowIndex} rowData={rowData} />;
}
case "truck": {
return <TruckLane rowIndex={rowIndex} rowData={rowData} />;
}
}
}
src/components/Row.jsx
import { Forest } from "./Forest";
import { CarLane } from "./CarLane";
import { TruckLane } from "./TruckLane";
export function Row({ rowIndex, rowData }) {
switch (rowData.type) {
case "forest": {
return <Forest rowIndex={rowIndex} rowData={rowData} />;
}
case "car": {
return <CarLane rowIndex={rowIndex} rowData={rowData} />;
}
case "truck": {
return <TruckLane rowIndex={rowIndex} rowData={rowData} />;
}
}
}

Truck Lanes

Then, let’s define the TruckLane component. This follows the same structure as the CarLane component. We use the same Road component as a container and foundation, and this time, we map the vehicles array to Truck components.

src/components/TruckLane.tsx
import type { Row } from "../types";
import { Road } from "./Road";
import { Truck } from "./Truck";
type Props = {
rowIndex: number;
rowData: Extract<Row, { type: "truck" }>;
};
export function TruckLane({ rowIndex, rowData }: Props) {
return (
<Road rowIndex={rowIndex}>
{rowData.vehicles.map((vehicle, index) => (
<Truck
key={index}
rowIndex={rowIndex}
color={vehicle.color}
initialTileIndex={vehicle.initialTileIndex}
direction={rowData.direction}
speed={rowData.speed}
/>
))}
</Road>
);
}
src/components/TruckLane.jsx
import { Road } from "./Road";
import { Truck } from "./Truck";
export function TruckLane({ rowIndex, rowData }) {
return (
<Road rowIndex={rowIndex}>
{rowData.vehicles.map((vehicle, index) => (
<Truck
key={index}
rowIndex={rowIndex}
color={vehicle.color}
initialTileIndex={vehicle.initialTileIndex}
direction={rowData.direction}
speed={rowData.speed}
/>
))}
</Road>
);
}

The Truck Component

A truck is also built up from simple boxes. We have a long gray box for the cargo and a smaller box for the cabin. The cabin’s color depends on the color prop.

We position the truck on the lane using the initialTilePosition prop and turn it in the right direction using the direction prop.

We use the same Wheel component as the car did. This time we use three of them.

Grab the truck to see it from different angles.

src/components/Truck.tsx
import * as THREE from "three";
import { tileSize } from "../constants";
import { Wheel } from "./Wheel";
type Props = {
rowIndex: number;
initialTileIndex: number;
direction: boolean;
speed: number;
color: THREE.ColorRepresentation;
};
export function Truck({
rowIndex,
initialTileIndex,
direction,
speed,
color,
}: Props) {
return (
<group
position-x={initialTileIndex * tileSize}
rotation-z={direction ? 0 : Math.PI}
>
<mesh position={[-15, 0, 25]}>
<boxGeometry args={[70, 35, 35]} />
<meshLambertMaterial color={0xb4c6fc} flatShading />
</mesh>
<mesh position={[35, 0, 20]}>
<boxGeometry args={[30, 30, 30]} />
<meshLambertMaterial color={color} flatShading />
</mesh>
<Wheel x={-35} />
<Wheel x={5} />
<Wheel x={37} />
</group>
);
}
src/components/Truck.jsx
import { tileSize } from "../constants";
import { Wheel } from "./Wheel";
export function Truck({
rowIndex,
initialTileIndex,
direction,
speed,
color,
}) {
return (
<group
position-x={initialTileIndex * tileSize}
rotation-z={direction ? 0 : Math.PI}
>
<mesh position={[-15, 0, 25]}>
<boxGeometry args={[70, 35, 35]} />
<meshLambertMaterial color={0xb4c6fc} flatShading />
</mesh>
<mesh position={[35, 0, 20]}>
<boxGeometry args={[30, 30, 30]} />
<meshLambertMaterial color={color} flatShading />
</mesh>
<Wheel x={-35} />
<Wheel x={5} />
<Wheel x={37} />
</group>
);
}

Adding Rows Before the Player

Now, we can render a map with several rows based on metadata. For aesthetic reasons, let’s add a couple empty rows before the player.

As a finishing touch, let’s add a few empty grass rows in the Map component before the starting position.

src/components/Map.tsx
import { rows } from "../metadata";
import { Grass } from "./Grass";
import { Row } from "./Row";
export function Map() {
return (
<>
<Grass rowIndex={0} />
<Grass rowIndex={-1} />
<Grass rowIndex={-2} />
<Grass rowIndex={-3} />
<Grass rowIndex={-4} />
{rows.map((rowData, index) => (
<Row key={index} rowIndex={index + 1} rowData={rowData} />
))}
</>
);
}
src/components/Map.jsx
import { rows } from "../metadata";
import { Grass } from "./Grass";
import { Row } from "./Row";
export function Map() {
return (
<>
<Grass rowIndex={0} />
<Grass rowIndex={-1} />
<Grass rowIndex={-2} />
<Grass rowIndex={-3} />
<Grass rowIndex={-4} />
{rows.map((rowData, index) => (
<Row key={index} rowIndex={index + 1} rowData={rowData} />
))}
</>
);
}

Metadata of the Following Chapters

The metadata for the next few chapters and the demo above is as follows.

src/metadata.ts
import type { Row } from "./types";
export const rows: Row[] = [
{
type: "car",
direction: false,
speed: 188,
vehicles: [
{ initialTileIndex: -4, color: 0xbdb638 },
{ initialTileIndex: -1, color: 0x78b14b },
{ initialTileIndex: 4, color: 0xa52523 },
],
},
{
type: "forest",
trees: [
{ tileIndex: -5, height: 50 },
{ tileIndex: 0, height: 30 },
{ tileIndex: 3, height: 50 },
],
},
{
type: "truck",
direction: true,
speed: 125,
vehicles: [
{ initialTileIndex: -4, color: 0x78b14b },
{ initialTileIndex: 0, color: 0xbdb638 },
],
},
{
type: "forest",
trees: [
{ tileIndex: -8, height: 30 },
{ tileIndex: -3, height: 50 },
{ tileIndex: 2, height: 30 },
],
},
];
src/metadata.js
export const rows = [
{
type: "car",
direction: false,
speed: 188,
vehicles: [
{ initialTileIndex: -4, color: 0xbdb638 },
{ initialTileIndex: -1, color: 0x78b14b },
{ initialTileIndex: 4, color: 0xa52523 },
],
},
{
type: "forest",
trees: [
{ tileIndex: -5, height: 50 },
{ tileIndex: 0, height: 30 },
{ tileIndex: 3, height: 50 },
],
},
{
type: "truck",
direction: true,
speed: 125,
vehicles: [
{ initialTileIndex: -4, color: 0x78b14b },
{ initialTileIndex: 0, color: 0xbdb638 },
],
},
{
type: "forest",
trees: [
{ tileIndex: -8, height: 30 },
{ tileIndex: -3, height: 50 },
{ tileIndex: 2, height: 30 },
],
},
];
Previous:
Setting up the Game
Next:
Adding Shadows