Introduction

CARLA is an open-source simulator for autonomous driving research, offering realistic environments and sensor simulation. In this guide, I’ll walk through setting up CARLA 0.9.16 on Ubuntu 22.04 with ROS 2 Humble integration.

What We’ll Cover

  • Installing CARLA simulator locally and via Docker
  • Understanding CARLA’s client-server architecture
  • Spawning vehicles and attaching sensors
  • Getting sensor data into ROS 2 topics
  • Visualizing everything in RViz

Installing CARLA Locally on Ubuntu 22.04

Download the CARLA packages CARLA_0.9.16.tar.gz and AdditionalMaps_0.9.16.tar.gz from the GitHub releases page.

# Create and activate conda environment
conda create --name carla0916 python=3.10
conda activate carla0916

# Extract to your project-root folder
# If using additional maps, extract to '<project-root>/Import' and run the import script
cd <project-root>
./ImportAssets.sh

# Install the requirements
cd <project-root>/PythonAPI/examples/
python3 -m pip install -r requirements.txt

# Install CARLA python package
python3 -m pip install carla==0.9.16

# Start CARLA
cd <project-root>
./CarlaUE4.sh

# Force NVIDIA GPU usage if needed
DRI_PRIME=1 ./CarlaUE4.sh

# Reduce GPU load with quality flags
./CarlaUE4.sh -quality-level=Low
./CarlaUE4.sh -RenderOffScreen

If you see a “CARLA not responding” dialog, wait patiently—it’s normal during initial load.

CARLA not responding dialog

Once loaded, you’ll see the CARLA environment ready for simulation.

CARLA first launch

Spawning Traffic and Manual Control

# Spawn traffic
cd <project-root>/PythonAPI/examples
python3 generate_traffic.py

# Manual control (opens PyGame window, use WASD keys)
python3 manual_control.py

Tip: For IDE autocomplete support, check out carla-python-stubs.

Installing CARLA as a Docker Container

Docker is particularly useful for running CARLA on remote servers while keeping your AV stack local.

Prerequisites: Docker Engine and NVIDIA Container Toolkit

# Pull the image
docker pull carlasim/carla:0.9.16

# Start container with GPU support
docker run -it --entrypoint bash \
  --rm \
  --name carla0916 \
  --detach \
  --runtime=nvidia \
  --net=host \
  --user=$(id -u):$(id -g) \
  --env=DISPLAY=$DISPLAY \
  --env=NVIDIA_VISIBLE_DEVICES=all \
  --env=NVIDIA_DRIVER_CAPABILITIES=all \
  --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" \
  carlasim/carla:0.9.16

# Access the container
docker exec -it carla0916 bash

# Start CARLA inside container
./CarlaUE4.sh

# Stop container when done
docker stop carla0916

Understanding CARLA’s Architecture

CARLA uses a client-server model:

  • Server: Handles physics, collisions, sensor rendering
  • Client: Python/C++ libraries to control the simulation

Communication happens over TCP ports (default: 2000, 2001, 2002).

CARLA 0.9.16’s Native ROS 2 Support

Great news! CARLA 0.9.16 includes direct ROS 2 support—no more wrestling with the poorly maintained carla-ros-bridge. The communication flow is now:

CARLA Server <---> ROS 2 Topics

Instead of:

CARLA Server <--> Carla ROS Bridge <--> ROS

Coordinate System

CARLA uses a left-handed coordinate system, unlike ROS’s right-handed system. Keep this in mind when processing sensor data.

# Distance in meters
location = carla.Location(x=10, y=10, z=1)

# Angles in degrees (applied as yaw, pitch, roll)
rotation = carla.Rotation(pitch=10, yaw=90, roll=10)

# Combined transform
transform = carla.Transform(location, rotation)
vehicle = world.spawn_actor(vehicle_bp, transform)

Synchronous vs Asynchronous Mode

  • Asynchronous (default): Server runs as fast as possible, good for setup
  • Synchronous: Client controls simulation stepping, essential for data collection
# Enable synchronous mode at 50Hz
settings = world.get_settings()
settings.synchronous_mode = True
settings.fixed_delta_seconds = 0.05
world.apply_settings(settings)

# Manually step simulation
world.tick()

Setting Up ROS 2 Integration

CARLA 0.9.16 only works with rmw_fastrtps_cpp DDS implementation.

# Install FastRTPS
sudo apt update
sudo apt install ros-humble-rmw-fastrtps-cpp

# Set environment variable (add to .bashrc)
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp

# Verify
printenv RMW_IMPLEMENTATION

# Start CARLA with ROS 2 support
cd <project-root>
DRI_PRIME=1 ./CarlaUE4.sh --ros2

Spawning an Ego Vehicle with Sensors

Here’s the spawn_hero.py script that spawns a vehicle with sensors configured via a JSON file:

#!/usr/bin/env python

# Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de
# Barcelona (UAB).
#
# This work is licensed under the terms of the MIT license.
# For a copy, see <https://opensource.org/licenses/MIT>.

import argparse
import json
import logging

import carla


def _setup_vehicle(world, config):
    logging.debug("Spawning vehicle: {}".format(config.get("type")))

    bp_library = world.get_blueprint_library()
    map_ = world.get_map()

    bp = bp_library.filter(config.get("type"))[0]
    bp.set_attribute("role_name", config.get("id"))
    bp.set_attribute("ros_name", config.get("id")) 

    return  world.spawn_actor(
        bp,
        map_.get_spawn_points()[config.get("spawn_point")],
        attach_to=None)


def _setup_sensors(world, vehicle, sensors_config):
    bp_library = world.get_blueprint_library()

    sensors = []
    for sensor in sensors_config:
        logging.debug("Spawning sensor: {}".format(sensor))

        bp = bp_library.filter(sensor.get("type"))[0]
        bp.set_attribute("ros_name", sensor.get("id")) 
        bp.set_attribute("role_name", sensor.get("id")) 
        for key, value in sensor.get("attributes", {}).items():
            bp.set_attribute(str(key), str(value))

        wp = carla.Transform(
            location=carla.Location(x=sensor["spawn_point"]["x"], y=-sensor["spawn_point"]["y"], z=sensor["spawn_point"]["z"]),
            rotation=carla.Rotation(roll=sensor["spawn_point"]["roll"], pitch=-sensor["spawn_point"]["pitch"], yaw=-sensor["spawn_point"]["yaw"])
        )

        sensors.append(
            world.spawn_actor(
                bp,
                wp,
                attach_to=vehicle
            )
        )

        sensors[-1].enable_for_ros()

    return sensors


def main(args):

    world = None
    vehicle = None
    sensors = []
    original_settings = None

    try:
        client = carla.Client(args.host, args.port)
        client.set_timeout(20.0)

        world = client.get_world()

        original_settings = world.get_settings()
        settings = world.get_settings()
        settings.synchronous_mode = True
        settings.fixed_delta_seconds = 0.05
        world.apply_settings(settings)

        traffic_manager = client.get_trafficmanager()
        traffic_manager.set_synchronous_mode(True)

        with open(args.file) as f:
            config = json.load(f)

        vehicle = _setup_vehicle(world, config)
        sensors = _setup_sensors(world, vehicle, config.get("sensors", []))

        _ = world.tick()

        # vehicle.set_autopilot(True)

        logging.info("Running...")

        while True:
            _ = world.tick()

    except KeyboardInterrupt:
        print('\nCancelled by user. Bye!')

    finally:
        if original_settings:
            world.apply_settings(original_settings)

        for sensor in sensors:
            sensor.destroy()

        if vehicle:
            vehicle.destroy()


if __name__ == '__main__':
    argparser = argparse.ArgumentParser(description='CARLA ROS2 native')
    argparser.add_argument('--host', metavar='H', default='localhost', help='IP of the host CARLA Simulator (default: localhost)')
    argparser.add_argument('--port', metavar='P', default=2000, type=int, help='TCP port of CARLA Simulator (default: 2000)')
    argparser.add_argument('-f', '--file', default='', required=True, help='File to be executed')
    argparser.add_argument('-v', '--verbose', action='store_true', dest='debug', help='print debug information')

    args = argparser.parse_args()

    log_level = logging.DEBUG if args.debug else logging.INFO
    logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)

    logging.info('Listening to server %s:%s', args.host, args.port)

    main(args)

Create a JSON config file (e.g., hero_config.json) to define your vehicle and sensors:

{
  "id": "hero",
  "type": "vehicle.tesla.model3",
  "spawn_point": 0,
  "sensors": [
    {
      "id": "rgb_front",
      "type": "sensor.camera.rgb",
      "spawn_point": {"x": 1.5, "y": 0, "z": 2.4, "roll": 0, "pitch": 0, "yaw": 0},
      "attributes": {
        "image_size_x": "1920",
        "image_size_y": "1080",
        "fov": "110"
      }
    },
    {
      "id": "lidar",
      "type": "sensor.lidar.ray_cast",
      "spawn_point": {"x": 0, "y": 0, "z": 2.5, "roll": 0, "pitch": 0, "yaw": 0},
      "attributes": {
        "channels": "64",
        "range": "100",
        "rotation_frequency": "20",
        "points_per_second": "1200000"
      }
    },
    {
      "id": "gnss",
      "type": "sensor.other.gnss",
      "spawn_point": {"x": 0, "y": 0, "z": 0, "roll": 0, "pitch": 0, "yaw": 0}
    },
    {
      "id": "imu",
      "type": "sensor.other.imu",
      "spawn_point": {"x": 0, "y": 0, "z": 0, "roll": 0, "pitch": 0, "yaw": 0}
    }
  ]
}

Run the script:

python3 spawn_hero.py -f hero_config.json

Viewing ROS 2 Topics

With CARLA running with --ros2 flag and the above script running:

# List available topics
ros2 topic list

# You should see topics like:
# /carla/hero/rgb_front/image
# /carla/hero/lidar/point_cloud
# /carla/hero/gnss
# /carla/hero/imu

# Echo sensor data
ros2 topic echo /carla/hero/gnss

Visualizing in RViz

# Launch RViz
rviz2

# Add displays for:
# - Image: /carla/hero/rgb_front/image
# - PointCloud2: /carla/hero/lidar/point_cloud
# - Set fixed frame to 'hero'

Working with Maps

CARLA includes several pre-built maps. Switch between them using:

# List available maps
print(client.get_available_maps())

# Load a specific map
client.load_world('Town04')

# Get current map info
current_map = world.get_map()
print(f"Current map: {current_map.name}")

Tips and Troubleshooting

Performance Optimization

  • Use -quality-level=Low for development
  • Use -RenderOffScreen when only collecting sensor data
  • Consider synchronous mode with lower simulation frequency

Common Issues

  1. “CARLA not responding”: Wait—initial load takes time
  2. No GPU detected: Use DRI_PRIME=1 prefix
  3. ROS 2 topics not appearing: Verify RMW_IMPLEMENTATION=rmw_fastrtps_cpp
  4. Port conflicts: Ensure ports 2000-2002 are available

References

  1. CARLA Official Documentation
  2. Install CARLA Simulator on Linux - TeckBlick (YouTube)
  3. CARLA GitHub Repository
  4. ROS 2 Humble Documentation