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)