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.

New code in this lesson

None of the earlier lessons use these. Each one shows the line where it appears here.

position_smoothing_enabled

Makes the camera glide after the player instead of snapping to it.

game.gd
camera.position_smoothing_enabled = enabled

drag_horizontal_enabled

With drag_vertical_enabled: lets the player move a little way before the camera starts to follow.

game.gd
camera.drag_horizontal_enabled = enabled

limit_left

With limit_right, limit_top and limit_bottom: the edges a Camera2D will not scroll past.

game.gd
camera.limit_left = 0

:=

Makes a variable and lets Godot work out its type from the value on the right.

hud.gd
var _state := {"limits": true, "smoothing": true, "drag": true}

{ }

A dictionary: values stored under names, like stats["speed"], instead of by position.

hud.gd
var _state := {"limits": true, "smoothing": true, "drag": true}

.bind()

Adds extra values to a function before connecting it, so one function can tell several buttons apart.

hud.gd
limits_button.pressed.connect(_on_toggled.bind("limits"))

The scenes

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

game.tscn
  • Game Node2D game.gd
  • Ground Polygon2D
  • Landmarks Node2D
  • A Polygon2D
  • B Polygon2D
  • C Polygon2D
  • D Polygon2D
  • E Polygon2D
  • F Polygon2D
  • G Polygon2D
  • H Polygon2D
  • I Polygon2D
  • Walls Node2D
  • TopWall StaticBody2D block.tscn
  • BottomWall StaticBody2D block.tscn
  • LeftWall StaticBody2D block.tscn
  • RightWall StaticBody2D block.tscn
  • Player 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
player.tscn
  • Player CharacterBody2D player.gd
  • Sprite2D
  • CollisionShape2D
  • Camera Camera2D %
block.tscn
  • Block StaticBody2D
  • Polygon2D
  • CollisionPolygon2D

FileSystem

  • game.tscn
  • game.gd
  • hud.tscn
  • hud.gd
  • player.tscn
  • player.gd
  • block.tscn

The code

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 = %Player
@onready var hud: CameraHUD = %HUD

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