logo

Babylon.js Market

By Lawrence

5 minutes

A soccer match needs a pitch. Right now the canvas is black. There is no grass, no goalposts, and no light to see by. Before anything can move, the pitch has to exist. You need something to look at first.

Five terms carry the whole course. An entity is a named thing in the world, like a wall, a light, or the ball. A component is data attached to an entity. A MeshPrimitive holds a shape and colour. A Goal holds a box. A System reads one kind of component every frame and acts on it. The World holds them all and ticks once per frame. A scene is a JSON file. It lists the starting entities and their components. You build this game by writing that file, not a render loop.

Scaffold a project with create-arcade. Then open the empty scene at src/scenes/soccer.json. Every component this course uses lives under src/components/. That is the path printed on each code chip below.

The scene file, lights, and camera

A scene file starts with a few headers and an entities map. Add the sun, the ambient fill light, and the overhead camera:

src/scenes/soccer.json
{
  "name": "SoccerPitch",
  "gameTitle": "AI Soccer",
  "sceneTitle": "Red vs Blue — Push, Strike, Score",
  "clearColor": [0.04, 0.12, 0.07, 1],
  "entities": {
    "Sun": {
      "tags": ["sun"],
      "components": {
        "DirectionalLight": {
          "direction": [-0.4, -0.85, -0.3],
          "intensity": 0.9,
          "diffuse": [1.0, 0.95, 0.85],
          "shadowEnabled": true,
          "shadowMapSize": 2048,
          "shadowMinZ": 0.1,
          "shadowMaxZ": 90
        }
      }
    },
    "Ambient": {
      "components": {
        "HemisphericLight": { "intensity": 0.6, "diffuse": [0.75, 0.85, 0.9], "groundColor": [0.12, 0.22, 0.14] }
      }
    },
    "Camera": {
      "components": {
        "ArcCamera": { "distance": 34, "alpha": -1.5708, "beta": 0.9, "controlsEnabled": false }
      }
    }
  }
}

The DirectionalLight is the sun. It is one angled beam that lights up surfaces. Because shadowEnabled is true, it also casts shadows. The toSpec() method packs its fields into a renderer spec:

src/components/DirectionalLight/DirectionalLight.core.ts
toSpec(): DirectionalLightSpec {
  return {
    direction: cloneVec3(this.direction),
    position: cloneVec3(this.position),
    intensity: this.intensity,
    diffuse: cloneVec3(this.diffuse),
    specular: cloneVec3(this.specular),
    shadow: {
      enabled: this.shadowEnabled,
      mapSize: this.shadowMapSize,
      minZ: this.shadowMinZ,
      maxZ: this.shadowMaxZ,
    },
  };
}

The HemisphericLight is the fill light. It is a soft glow from the sky, tinted by the ground. It keeps shadowed sides from turning pure black:

src/components/HemisphericLight/HemisphericLight.core.ts
toSpec(): HemisphericLightSpec {
  return {
    direction: cloneVec3(this.direction),
    intensity: this.intensity,
    diffuse: cloneVec3(this.diffuse),
    groundColor: cloneVec3(this.groundColor),
    specular: cloneVec3(this.specular),
  };
}

The ArcCamera sits almost straight overhead. alpha: -1.5708 is −π/2, which looks down the −Z axis. beta: 0.9 tips it forward off vertical. controlsEnabled: false locks the view, so a stray mouse drag can never move it. The System builds the camera through the adapter:

src/components/ArcCamera/ArcCamera.ts
const spec = arc.toSpec();
spec.target = seedTarget;

rt.handle = r.createArcCamera(entity.id, spec);
rt.initialized = true;
const angles = r.getCameraAngles(rt.handle);
arc.lastAlpha = angles.alpha;
arc.lastBeta = angles.beta;
arc.lastRadius = angles.radius;

Continue reading

Unlock the Full Course

Every lesson, the runnable examples, and the finished build — yours to keep.

$9one-time

Was this page helpful?

We read every note — tell us what's working and what isn't.

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search