All lessons
Lesson 19

Camera Follow

The first lesson where the world is bigger than the screen. Limits, smoothing and a deadzone, each one switchable so you can feel what it does.

Loading the game engine. This takes a moment the first time.

THE FIRST LESSON WHERE THE WORLD IS BIGGER THAN THE SCREEN

Every lesson before this one fit on one screen, so the view never had to move. This world is 2400 x 1600. The screen is 1152 x 648. Most of the level is off-screen at any moment, and a camera is what decides which part you are looking at.

WHERE THE CAMERA ACTUALLY IS

Open player.tscn. The Camera2D is a CHILD of the player. A child moves when its parent moves, so the camera follows with no code at all. That is the whole trick. Everything below is just polish.

THE THREE SETTINGS THIS LESSON TURNS ON AND OFF

LIMITS
limit_left / limit_top / limit_right / limit_bottom. Without them the camera walks straight off the edge of the world and you spend half the game looking at grey nothing. With them the view stops at the wall, like a real game.
SMOOTHING
position_smoothing_enabled. Off, the camera is welded to the player and the whole world jitters when you move. On, the camera chases the player and eases into place.
DRAG
drag_horizontal_enabled / drag_vertical_enabled. This makes a box in the middle of the screen where you can move WITHOUT the camera moving at all. Only when you push past the edge of that box does the view start to travel. Almost every 2D game you have played does this.

The scenes

UI node · 2D node · % unique name · .tscn instanced scene

camera_follow.tscn
  • CameraFollow Node2D camera_follow.gd
  • Ground ColorRect
  • Landmarks Node2D
  • A ColorRect
  • B ColorRect
  • C ColorRect
  • D ColorRect
  • E ColorRect
  • F ColorRect
  • G ColorRect
  • H ColorRect
  • I ColorRect
  • Walls Node2D
  • TopWall StaticBody2D
  • CollisionShape2D
  • Visual ColorRect
  • BottomWall StaticBody2D
  • CollisionShape2D
  • Visual ColorRect
  • LeftWall StaticBody2D
  • CollisionShape2D
  • Visual ColorRect
  • RightWall StaticBody2D
  • CollisionShape2D
  • Visual ColorRect
  • CameraPlayer CharacterBody2D % player.tscn
  • HUD CanvasLayer % hud.tscn
hud.tscn
  • HUD CanvasLayer hud.gd
  • MarginContainer
  • Rows VBoxContainer
  • Buttons HBoxContainer
  • LimitsButton Button %
  • SmoothingButton Button %
  • DragButton Button %
  • HintLabel Label
  • HomeButton Button %
player.tscn
  • CameraPlayer CharacterBody2D player.gd
  • Sprite2D
  • CollisionShape2D
  • Camera Camera2D %
extends Node2D

# How big the world is. The camera limits below are built from these, so
# changing the numbers here moves the walls AND the camera edges together.
const WORLD_WIDTH: int = 2400
const WORLD_HEIGHT: int = 1600

@onready var player: CharacterBody2D = %CameraPlayer

func _ready() -> void:
	# The HUD owns the three buttons. It emits a plain signal saying which
	# setting was flipped, and this file does the flipping. The HUD never
	# touches the camera itself.
	%HUD.setting_toggled.connect(_on_setting_toggled)
	_apply_limits(true)

func _on_setting_toggled(setting: String, enabled: bool) -> void:
	var camera: Camera2D = player.camera
	match setting:
		"limits":
			_apply_limits(enabled)
		"smoothing":
			camera.position_smoothing_enabled = enabled
		"drag":
			camera.drag_horizontal_enabled = enabled
			camera.drag_vertical_enabled = enabled

# Turning limits "off" does not delete them. Godot always has limit numbers.
# We push them far outside the world instead, which is the same as having none.
func _apply_limits(enabled: bool) -> void:
	var camera: Camera2D = player.camera
	if enabled:
		camera.limit_left = 0
		camera.limit_top = 0
		camera.limit_right = WORLD_WIDTH
		camera.limit_bottom = WORLD_HEIGHT
	else:
		camera.limit_left = -10000000
		camera.limit_top = -10000000
		camera.limit_right = 10000000
		camera.limit_bottom = 10000000