小地图
一台拉远的 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)