I'd thought that using set_position to set the Camera2D's position to the position of the node would do this, but the camera doesn't centre on it. Rather it stops just as the node comes into view on the edge of the screen. Is there some way I can have the centre of the camera view line up with the target node?

Yes, that works, but you have to set an offset on the Camera2D (it's at the top of the Inspector) to the position you want the character to be in. You can also enable smoothing and it will automatically interpolate the position.

    What I generally do is have a Node2D that follows the node and set it's position using global_position to the player, and then have the Camera2D node as a child of the Node2D. I find this also can make adding things like Camera shaking relatively easy, as you can just modify the local position of the Camera2D to shake it and return it back to (0, 0) when the effect is over.

    The downside of using a Node2D though is that it requires more manual implementation. While this gives more control over the result, it can be a tad time consuming to implement. Using an offset with the Camera2D node is probably easiest if you want to follow an object and optionally have (linear?) interpolation.

    I think that only works if the Camera2D is a child of the node perhaps? I've got the Camera2D as a child of the main scene and want it to focus on specific nodes (which are their own scenes). I've tried putting separate cameras on each node and switching them to active which works but then my camera scrolling behaviour gets messed up (much faster and the x and y positions seem to become inverted).

    Here is my camera script:

    extends Camera2D
    
    onready var system = get_tree().get_current_scene()
    
    var mouse = get_global_mouse_position()
    
    export (int) var camera_margin = 2
    
    func _ready():
    	system.connect("focus_camera", self, "_on_focus_camera")
    
    func _on_focus_camera():
    	var camera = globalvar.selected_unit.get_node("Camera/Camera2D")
    	camera.current = true
    	
    func _process(delta):
    	var pos = get_viewport().get_mouse_position()
    	var rec = get_viewport().get_visible_rect()
    	if rec.size.x - pos.x <= camera_margin:
    		self.position = get_global_mouse_position()
    	if pos.x <= camera_margin:
    		self.position = get_global_mouse_position()
    	if rec.size.y - pos.y <= camera_margin:
    		self.position = get_global_mouse_position()
    	if pos.y <= camera_margin:
    		self.position = get_global_mouse_position()

    Does using global_position instead of position help? Maybe the Camera2D is being influenced by it's parent node?

    Thanks, that helps with the scrolling! I still can't figure out how to centre the camera on the node though, other than the first time switching to the new camera when the camera is already centred in place.

    Edit: I've managed to make this work by making a new duplicate of the camera each time I focus on the node, and then deleting the old camera.

    10 months later
    a year later