logo

Babylon.js Market

By Lawrence

5 minutes

In the last lesson, the ship turned to face wherever WASD pushed it. Run left, and it points left. That feels right at first. But say you want to back away from an enemy while you keep shooting at it. You can't. Facing and firing are the same action. A twin-stick shooter splits them apart. One stick moves the body. The other points the gun. The two never have to agree.

A keyboard has no second analog stick. So this game uses the next best thing. The arrow keys become the second stick. Hold an arrow and the ship fires that way, in one of eight directions. It fires there no matter where WASD is pushing it. Everything you need is already in the TwinStickShooter component from last lesson: fireRate, bulletSpeed, bulletColor, bulletRadius, and muzzleFlash. The Player block does not change at all. This lesson turns on the firing half those settings were waiting for.

Aim with the arrow keys

The movement half read WASD into a (mvx, mvz) pair. The firing half reads the arrows into its own separate pair. Both read from the same set of keys the input listener fills:

src/components/TwinStickShooter/TwinStickShooter.ts
let shx = 0, shz = 0
if (this.keys.has('ArrowUp')) shz += 1
if (this.keys.has('ArrowDown')) shz -= 1
if (this.keys.has('ArrowLeft')) shx -= 1
if (this.keys.has('ArrowRight')) shx += 1
const slen = Math.hypot(shx, shz)

Hold the up and right arrows together. You get shx = 1, shz = 1, and slen = Math.hypot(1, 1) ≈ 1.414, a clean diagonal. Press nothing and slen is 0. That zero is the flag for "not firing this frame." Here is the key part. shx and shz come from ArrowLeft, ArrowRight, ArrowUp, and ArrowDown. But mvx and mvz came from KeyA, KeyD, KeyW, and KeyS. They use different keys, read in the same frame. So you can hold W+A and the down and right arrows at once. The ship will glide up-left while it shoots down-right. The two sticks are truly independent, because they never share an input.

The raw arrow axes are in screen space. Up-arrow means "up the screen." The system runs them through the same camera basis that movement uses. So the fire direction comes out in world coordinates, no matter how the top-down camera is angled:

const fireWx = fx * shz + rx * shx   // arrow axis → world X
const fireWz = fz * shz + rz * shx   // arrow axis → world Z

fx, fz is the camera's forward direction, flattened onto the floor. rx, rz is its right direction. The arrows pick how much forward and how much right. The basis turns that into a heading the bullet can travel along.

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