小地图

一台拉远的 Camera2D 放在共享游戏 World2D 的 SubViewport 里。HUD 不属于那个世界,所以不会出现在地图上。

上方的编辑器正在运行这个项目。改一行代码,它就会重新加载。

用到的 Godot 类

SubViewportCamera2DWorld2D

代码

scripts/main.gd
extends Node

const MAP: Vector2 = Vector2(2600, 1600)
const SPEED: float = 400.0
const MINIMAP_SIZE: Vector2i = Vector2i(320, 240)

var stage: SubViewport
var player: Node2D
var minimap_camera: Camera2D
var route: PackedVector2Array = PackedVector2Array()
var target: int = 1
var manual: bool = false


func _ready() -> void:
	_add_background()

	var view: Vector2 = get_viewport().get_visible_rect().size

	var container: SubViewportContainer = SubViewportContainer.new()
	container.stretch = true
	add_child(container)
	container.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

	stage = SubViewport.new()
	stage.size = Vector2i(view)
	stage.render_target_update_mode = SubViewport.UPDATE_ALWAYS
	container.add_child(stage)

	_add_world()

	route = PackedVector2Array([Vector2(420, 400), Vector2(2180, 420), Vector2(2160, 1200), Vector2(1300, 800), Vector2(440, 1180)])

	player = _add_player()
	player.position = route[0]

	var camera: Camera2D = Camera2D.new()
	camera.position_smoothing_enabled = true
	camera.position_smoothing_speed = 5.0
	camera.limit_left = 0
	camera.limit_top = 0
	camera.limit_right = int(MAP.x)
	camera.limit_bottom = int(MAP.y)
	player.add_child(camera)
	camera.make_current()

	var minimap: SubViewport = SubViewport.new()
	minimap.size = MINIMAP_SIZE
	minimap.render_target_update_mode = SubViewport.UPDATE_ALWAYS
	add_child(minimap)
	# a Viewport is born with its own World2D, so sharing one has to be explicit
	minimap.world_2d = stage.world_2d

	minimap_camera = Camera2D.new()
	minimap_camera.zoom = Vector2(0.26, 0.26)
	minimap_camera.position = player.position
	minimap.add_child(minimap_camera)
	minimap_camera.make_current()

	_add_hud(minimap)

	print('the corner panel is a second Camera2D on the same World2D, wasd drives the square')


func _process(delta: float) -> void:
	var drive: Vector2 = Vector2(
		float(Input.is_key_pressed(KEY_D)) - float(Input.is_key_pressed(KEY_A)),
		float(Input.is_key_pressed(KEY_S)) - float(Input.is_key_pressed(KEY_W))
	)

	if drive != Vector2.ZERO:
		manual = true

	if manual:
		player.position += drive.normalized() * SPEED * delta
		player.position = player.position.clamp(Vector2(80, 80), MAP - Vector2(80, 80))
	else:
		var goal: Vector2 = route[target]
		player.position = player.position.move_toward(goal, SPEED * delta)

		if player.position.distance_to(goal) < 4.0:
			target = (target + 1) % route.size()

	minimap_camera.position = player.position


func _add_world() -> void:
	var outside: Polygon2D = _make_rect(MAP + Vector2(2400, 2400), Color('#12141a'))
	outside.position = MAP * 0.5
	stage.add_child(outside)

	var ground: Polygon2D = _make_rect(MAP, Color('#12141a').lerp(Color('#2a2f3d'), 0.5))
	ground.position = MAP * 0.5
	stage.add_child(ground)

	for x in range(200, int(MAP.x), 200):
		_add_grid_line(Vector2(x, 0), Vector2(x, MAP.y))

	for y in range(200, int(MAP.y), 200):
		_add_grid_line(Vector2(0, y), Vector2(MAP.x, y))

	for wall in [
		{ at = Vector2(MAP.x * 0.5, 45), size = Vector2(MAP.x, 90) },
		{ at = Vector2(MAP.x * 0.5, MAP.y - 45), size = Vector2(MAP.x, 90) },
		{ at = Vector2(45, MAP.y * 0.5), size = Vector2(90, MAP.y) },
		{ at = Vector2(MAP.x - 45, MAP.y * 0.5), size = Vector2(90, MAP.y) },
		{ at = Vector2(900, 760), size = Vector2(560, 90) },
		{ at = Vector2(1700, 480), size = Vector2(90, 620) },
		{ at = Vector2(1820, 1160), size = Vector2(620, 90) },
		{ at = Vector2(700, 1300), size = Vector2(90, 520) },
		{ at = Vector2(1180, 1420), size = Vector2(700, 90) },
		{ at = Vector2(2280, 800), size = Vector2(90, 480) },
		{ at = Vector2(420, 700), size = Vector2(90, 340) },
	]:
		var block: Polygon2D = _make_rect(wall.size, Color('#8da5f3').lerp(Color('#12141a'), 0.45))
		block.position = wall.at
		stage.add_child(block)

	var rng: RandomNumberGenerator = RandomNumberGenerator.new()
	rng.seed = 7

	for i in 26:
		var crate: Polygon2D = _make_rect(Vector2(90, 90), Color('#f3d48d').lerp(Color('#12141a'), 0.35))
		crate.position = Vector2(rng.randf_range(200, MAP.x - 200), rng.randf_range(200, MAP.y - 200))
		crate.rotation = rng.randf_range(-0.4, 0.4)
		stage.add_child(crate)


func _add_player() -> Node2D:
	var body: Node2D = Node2D.new()
	body.add_child(_make_rect(Vector2(80, 80), Color('#8eef97')))
	body.add_child(_make_rect(Vector2(30, 30), Color('#fc7f7f')))
	stage.add_child(body)

	return body


func _add_grid_line(from: Vector2, to: Vector2) -> void:
	var line: Line2D = Line2D.new()
	line.points = PackedVector2Array([from, to])
	line.width = 3.0
	line.default_color = Color('#2a2f3d')
	stage.add_child(line)


func _make_rect(size: Vector2, color: Color) -> Polygon2D:
	var rect: Polygon2D = Polygon2D.new()
	rect.polygon = PackedVector2Array([-size * 0.5, Vector2(size.x, -size.y) * 0.5, size * 0.5, Vector2(-size.x, size.y) * 0.5])
	rect.color = color

	return rect


func _add_hud(minimap: SubViewport) -> void:
	var hud: CanvasLayer = CanvasLayer.new()
	add_child(hud)

	var view: Vector2 = get_viewport().get_visible_rect().size
	var origin: Vector2 = Vector2(view.x - MINIMAP_SIZE.x - 34, 34)

	var frame: ColorRect = ColorRect.new()
	frame.color = Color('#8da5f3')
	frame.position = origin - Vector2(4, 4)
	frame.size = Vector2(MINIMAP_SIZE) + Vector2(8, 8)
	hud.add_child(frame)

	var picture: TextureRect = TextureRect.new()
	picture.texture = minimap.get_texture()
	picture.position = origin
	picture.size = Vector2(MINIMAP_SIZE)
	hud.add_child(picture)

	var label: Label = Label.new()
	label.text = 'minimap  zoom 0.26'
	label.add_theme_font_size_override('font_size', 20)
	label.add_theme_color_override('font_color', Color('#8da5f3'))
	label.position = origin + Vector2(0, MINIMAP_SIZE.y + 10)
	hud.add_child(label)

	var title: Label = Label.new()
	title.text = 'the HUD is outside the shared world, so it stays out of the minimap'
	title.add_theme_font_size_override('font_size', 20)
	title.add_theme_color_override('font_color', Color('#8eef97'))
	title.position = Vector2(40, 40)
	hud.add_child(title)


func _add_background() -> void:
	var layer: CanvasLayer = CanvasLayer.new()
	layer.layer = -1
	add_child(layer)

	var background: ColorRect = ColorRect.new()
	background.color = Color('#12141a')
	layer.add_child(background)
	background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

更多相机示例