Orbit camera

Drag with the left button to swing the camera around the centerpiece, and use the wheel to zoom. Yaw, pitch and distance are the whole state.

The editor above is running this project. Change a line and it reloads.

Godot classes used

Camera3DInputEventMouseMotionInputEventMouseButton

The code

scripts/main.gd
extends Node

const TARGET: Vector3 = Vector3(0, 1.4, 0)
const SENSITIVITY: float = 0.007
const PITCH_LIMIT: float = 1.35
const ZOOM_STEP: float = 0.7

var camera: Camera3D
var yaw: float = 0.7
var pitch: float = 0.4
var distance: float = 9.8


func _ready() -> void:
	_add_environment()
	_add_light()
	_add_ground()
	_add_centerpiece()
	_add_pillars()

	camera = Camera3D.new()
	add_child(camera)
	camera.current = true
	_place_camera()

	print('drag with the left button to orbit the torus, and the wheel zooms in and out')


func _unhandled_input(event: InputEvent) -> void:
	if event is InputEventMouseMotion:
		var motion: InputEventMouseMotion = event

		if motion.button_mask & MOUSE_BUTTON_MASK_LEFT:
			yaw -= motion.relative.x * SENSITIVITY
			# beyond 90 degrees the orbit direction flips and look_at goes singular against Vector3.UP
			pitch = clamp(pitch - motion.relative.y * SENSITIVITY, -PITCH_LIMIT, PITCH_LIMIT)
			_place_camera()

	if event is InputEventMouseButton:
		var click: InputEventMouseButton = event

		if not click.pressed:
			return

		if click.button_index == MOUSE_BUTTON_WHEEL_UP:
			distance = clamp(distance - ZOOM_STEP, 4.0, 24.0)
			_place_camera()
		elif click.button_index == MOUSE_BUTTON_WHEEL_DOWN:
			distance = clamp(distance + ZOOM_STEP, 4.0, 24.0)
			_place_camera()


func _place_camera() -> void:
	var offset: Vector3 = Vector3(cos(pitch) * sin(yaw), sin(pitch), cos(pitch) * cos(yaw))

	camera.position = TARGET + offset * distance
	camera.look_at(TARGET, Vector3.UP)


func _add_centerpiece() -> void:
	var base: CylinderMesh = CylinderMesh.new()
	base.top_radius = 1.1
	base.bottom_radius = 1.5
	base.height = 1.0

	var pedestal: MeshInstance3D = MeshInstance3D.new()
	pedestal.mesh = base
	pedestal.material_override = _make_material('#3a4152', 0.6)
	pedestal.position = Vector3(0, 0.5, 0)
	add_child(pedestal)

	var ring: TorusMesh = TorusMesh.new()
	ring.inner_radius = 0.7
	ring.outer_radius = 1.25

	var material: StandardMaterial3D = _make_material('#f3d48d', 0.25)
	material.metallic = 0.7
	material.emission_enabled = true
	material.emission = Color('#f3d48d')
	material.emission_energy_multiplier = 0.5

	var torus: MeshInstance3D = MeshInstance3D.new()
	torus.mesh = ring
	torus.material_override = material
	torus.position = Vector3(0, 2.0, 0)
	torus.rotation_degrees = Vector3(-25, 0, 0)
	add_child(torus)


func _add_pillars() -> void:
	var colors: Array[String] = ['#8da5f3', '#8eef97', '#fc7f7f', '#8da5f3', '#8eef97', '#fc7f7f']

	for i in colors.size():
		var turn: float = TAU * i / colors.size()
		var height: float = 1.4 + float(i % 3) * 0.8

		var mesh: BoxMesh = BoxMesh.new()
		mesh.size = Vector3(0.9, height, 0.9)

		var pillar: MeshInstance3D = MeshInstance3D.new()
		pillar.mesh = mesh
		pillar.material_override = _make_material(colors[i], 0.5)
		pillar.position = Vector3(cos(turn), 0, sin(turn)) * 5.2 + Vector3(0, height * 0.5, 0)
		add_child(pillar)


func _add_ground() -> void:
	var mesh: PlaneMesh = PlaneMesh.new()
	mesh.size = Vector2(120, 120)

	var ground: MeshInstance3D = MeshInstance3D.new()
	ground.mesh = mesh
	ground.material_override = _make_material('#242938', 0.95)
	add_child(ground)


func _make_material(hex: String, roughness: float) -> StandardMaterial3D:
	var material: StandardMaterial3D = StandardMaterial3D.new()
	material.albedo_color = Color(hex)
	material.roughness = roughness

	return material


func _add_environment() -> void:
	var sky_material: ProceduralSkyMaterial = ProceduralSkyMaterial.new()
	sky_material.sky_top_color = Color(0.07, 0.09, 0.18)
	sky_material.sky_horizon_color = Color(0.26, 0.29, 0.4)
	sky_material.ground_bottom_color = Color(0.15, 0.17, 0.23)
	sky_material.ground_horizon_color = Color(0.18, 0.2, 0.28)

	var sky: Sky = Sky.new()
	sky.sky_material = sky_material

	var env: Environment = Environment.new()
	env.background_mode = Environment.BG_SKY
	env.sky = sky
	env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
	env.ambient_light_energy = 0.8

	var world: WorldEnvironment = WorldEnvironment.new()
	world.environment = env
	add_child(world)


func _add_light() -> void:
	var light: DirectionalLight3D = DirectionalLight3D.new()
	light.rotation_degrees = Vector3(-55, -40, 0)
	light.light_energy = 1.3
	light.shadow_enabled = true
	add_child(light)

More 3D scene examples