# Recipe: drive with a joystick

> Copy-paste on-screen thumbstick that writes throttle and steering into shared values on the UI thread, wired to a Vehicle and a chase camera.

Drive a car with an on-screen thumbstick and a chase camera. **Copy `Joystick.tsx` into your app**, then:

```tsx title="DriveScreen.tsx"
import { Car, ContactShadow, Ground, Trees } from "@apricot/assets";
import { FollowCamera, Scene, type SceneObject, Vehicle } from "@apricot/react";
import { useRef } from "react";
import { View } from "react-native";
import { Joystick, useJoystickValues } from "./Joystick";

export function DriveScreen() {
  const car = useRef<SceneObject>(null);
  const { throttle, steer } = useJoystickValues();
  return (
    <View style={{ flex: 1 }}>
      <Scene look="soft" fog style={{ flex: 1 }}>
        <Ground />
        <Trees />
        <Vehicle ref={car} throttle={throttle} steer={steer} bounds={[-90, -90, 90, 90]}>
          <Car />
          <ContactShadow width={1.5} length={2.5} />
        </Vehicle>
        <FollowCamera target={car} />
      </Scene>
      <Joystick throttle={throttle} steer={steer} style={{ position: "absolute", bottom: 40, alignSelf: "center" }} />
    </View>
  );
}
```

The app root must be wrapped in `GestureHandlerRootView` (see [Installation](https://matthiasbrandolin.dev/apricot/installation)).

## The component

```tsx title="Joystick.tsx"
// Apricot recipe: drive-joystick
// Copy this file into your app. An on-screen thumbstick that writes throttle
// (up = forward) and steering (right = right) into Reanimated shared values,
// on the UI thread. Pass them to <Vehicle throttle steer>.
import { StyleSheet, View, type ViewStyle } from "react-native";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import Animated, {
  type SharedValue,
  useAnimatedStyle,
  useSharedValue,
  withSpring,
} from "react-native-reanimated";

export interface JoystickValues {
  /** -1 (back) to 1 (forward). */
  throttle: SharedValue<number>;
  /** -1 (left) to 1 (right). */
  steer: SharedValue<number>;
}

/** Creates the shared values a <Joystick> writes and a <Vehicle> reads. */
export function useJoystickValues(): JoystickValues {
  return { throttle: useSharedValue(0), steer: useSharedValue(0) };
}

export interface JoystickProps extends JoystickValues {
  /** Radius of the knob's travel, in points (default 56). */
  radius?: number;
  style?: ViewStyle;
}

export function Joystick({ throttle, steer, radius = 56, style }: JoystickProps) {
  const x = useSharedValue(0);
  const y = useSharedValue(0);
  const gesture = Gesture.Pan()
    .minDistance(0)
    .onChange((event) => {
      // Keep the knob inside the ring.
      const length = Math.hypot(event.translationX, event.translationY);
      const scale = length > radius ? radius / length : 1;
      x.value = event.translationX * scale;
      y.value = event.translationY * scale;
      steer.value = x.value / radius;
      throttle.value = -y.value / radius;
    })
    .onFinalize(() => {
      x.value = withSpring(0);
      y.value = withSpring(0);
      steer.value = 0;
      throttle.value = 0;
    });
  const knob = useAnimatedStyle(() => ({ transform: [{ translateX: x.value }, { translateY: y.value }] }));
  const size = radius * 2 + 48;
  return (
    <GestureDetector gesture={gesture}>
      <View style={[styles.ring, { width: size, height: size, borderRadius: size / 2 }, style]}>
        <Animated.View style={[styles.knob, knob]} />
      </View>
    </GestureDetector>
  );
}

const styles = StyleSheet.create({
  ring: {
    alignItems: "center",
    justifyContent: "center",
    backgroundColor: "rgba(255,255,255,0.35)",
    borderWidth: 2,
    borderColor: "rgba(255,255,255,0.7)",
  },
  knob: {
    width: 56,
    height: 56,
    borderRadius: 28,
    backgroundColor: "#ffffff",
    boxShadow: "0 2px 6px rgba(0, 0, 0, 0.2)",
  },
});
```

It needs `react-native-gesture-handler` and `react-native-reanimated`.

## API

`useJoystickValues()` returns `{ throttle, steer }`: two shared values starting at 0, for the joystick to write and `<Vehicle>` to read.

| `<Joystick>` prop | Default | Meaning |
| --- | --- | --- |
| `throttle` | required | Shared value: −1 (back) to 1 (forward). Up is forward |
| `steer` | required | Shared value: −1 (left) to 1 (right) |
| `radius` | `56` | How far the knob travels, in points |
| `style` | | Style for the ring (position it here) |

## Notes

- **Input:** the joystick writes Reanimated shared values on the UI thread; `<Vehicle>` reads them each frame without re-rendering React. Releasing the stick springs the knob back and sets both values to 0.
- **Your own car model:** any `.glb` works in `<Vehicle>`. Name the wheel nodes `wheel_fl`, `wheel_fr` (front: steer and spin) and `wheel_rl`, `wheel_rr` (rear: spin), and make the car face +z with its wheels on y = 0. Run `npx apricot-models <folder>` (from `@apricot/metro`) on it first. See [Vehicles](https://matthiasbrandolin.dev/apricot/guides/vehicles#the-car).
- **Battery:** when the car stops and the joystick is released, the scene stops rendering.
- **Camera:** put `<FollowCamera>` after the `<Vehicle>` so it follows this frame's position, not the last one.
- **Keep the car on the ground:** `<Ground>` is 400 units wide and `<Trees>` covers 160 × 160, so `bounds={[-90, -90, 90, 90]}` keeps the car among the trees.

## Variations

Two buttons instead of a stick (plain numbers work too):

```tsx title="Pedals.tsx"
import { useSharedValue } from "react-native-reanimated";
import { Pressable, Text, View } from "react-native";

export function usePedals() {
  const throttle = useSharedValue(0);
  const steer = useSharedValue(0);
  const pedals = (
    <View style={{ position: "absolute", bottom: 40, left: 0, right: 0, flexDirection: "row", justifyContent: "space-around" }}>
      <Pressable onPressIn={() => (throttle.value = -1)} onPressOut={() => (throttle.value = 0)}>
        <Text style={{ fontSize: 24 }}>Brake</Text>
      </Pressable>
      <Pressable onPressIn={() => (throttle.value = 1)} onPressOut={() => (throttle.value = 0)}>
        <Text style={{ fontSize: 24 }}>Gas</Text>
      </Pressable>
    </View>
  );
  return { throttle, steer, pedals };
}
```

## Related

- [Vehicles guide](https://matthiasbrandolin.dev/apricot/guides/vehicles).
- [Cameras reference](https://matthiasbrandolin.dev/apricot/reference/cameras#followcamera).
