分屏
并排的两个 SubViewport 各带一台相机,同时看向同一个 World3D。每个画面跟随自己的立方体。
上方的编辑器正在运行这个项目。改一行代码,它就会重新加载。
用到的 Godot 类
SubViewportSubViewportContainerCamera3D
代码
scripts/main.gd
extends Node
const SPEED: float = 6.0
var blue: Node3D
var green: Node3D
var blue_camera: Camera3D
var green_camera: Camera3D
var elapsed: float = 0.0
var manual: bool = false
func _ready() -> void:
_add_environment()
_add_light()
_add_arena()
blue = _add_runner(Color('#8da5f3'))
green = _add_runner(Color('#8eef97'))
var row: HBoxContainer = HBoxContainer.new()
row.add_theme_constant_override('separation', 6)
add_child(row)
row.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
blue_camera = _add_pane(row)
green_camera = _add_pane(row)
_add_hud()
_place_cameras()
print('two SubViewports share one World3D, wasd drives the blue cube and the arrows the green one')
func _process(delta: float) -> void:
elapsed += delta
var left: Vector3 = Vector3(
float(Input.is_key_pressed(KEY_D)) - float(Input.is_key_pressed(KEY_A)),
0.0,
float(Input.is_key_pressed(KEY_S)) - float(Input.is_key_pressed(KEY_W))
)
var right: Vector3 = Vector3(
float(Input.is_key_pressed(KEY_RIGHT)) - float(Input.is_key_pressed(KEY_LEFT)),
0.0,
float(Input.is_key_pressed(KEY_DOWN)) - float(Input.is_key_pressed(KEY_UP))
)
if left != Vector3.ZERO or right != Vector3.ZERO:
manual = true
if manual:
blue.position += left.normalized() * SPEED * delta
green.position += right.normalized() * SPEED * delta
else:
blue.position = Vector3(cos(elapsed * 0.7) * 7.0, 0.7, sin(elapsed * 0.7) * 7.0)
green.position = Vector3(cos(-elapsed * 0.5 + 2.2) * 4.5, 0.7, sin(-elapsed * 0.5 + 2.2) * 4.5)
blue.rotation.y = elapsed * 1.4
green.rotation.y = -elapsed * 1.4
_place_cameras()
func _place_cameras() -> void:
_aim(blue_camera, blue, Vector3(0, 4.5, 8))
_aim(green_camera, green, Vector3(6, 5.5, -6))
func _aim(camera: Camera3D, target: Node3D, offset: Vector3) -> void:
camera.position = target.position + offset
camera.look_at(target.position, Vector3.UP)
func _add_pane(row: HBoxContainer) -> Camera3D:
var container: SubViewportContainer = SubViewportContainer.new()
container.stretch = true
container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
container.size_flags_vertical = Control.SIZE_EXPAND_FILL
row.add_child(container)
var pane: SubViewport = SubViewport.new()
pane.render_target_update_mode = SubViewport.UPDATE_ALWAYS
pane.msaa_3d = Viewport.MSAA_4X
container.add_child(pane)
var camera: Camera3D = Camera3D.new()
pane.add_child(camera)
camera.current = true
return camera
func _add_runner(color: Color) -> Node3D:
var mesh: BoxMesh = BoxMesh.new()
mesh.size = Vector3(1.4, 1.4, 1.4)
var material: StandardMaterial3D = StandardMaterial3D.new()
material.albedo_color = color
material.roughness = 0.4
var runner: MeshInstance3D = MeshInstance3D.new()
runner.mesh = mesh
runner.material_override = material
add_child(runner)
return runner
func _add_arena() -> void:
var ground_mesh: PlaneMesh = PlaneMesh.new()
ground_mesh.size = Vector2(50, 50)
var ground_material: StandardMaterial3D = StandardMaterial3D.new()
ground_material.albedo_color = Color('#2a2f3d')
var ground: MeshInstance3D = MeshInstance3D.new()
ground.mesh = ground_mesh
ground.material_override = ground_material
add_child(ground)
var pillar_mesh: BoxMesh = BoxMesh.new()
pillar_mesh.size = Vector3(1.2, 4, 1.2)
var pillar_material: StandardMaterial3D = StandardMaterial3D.new()
pillar_material.albedo_color = Color('#fc7f7f')
pillar_material.roughness = 0.6
for i in 14:
var angle: float = TAU * i / 14.0
var pillar: MeshInstance3D = MeshInstance3D.new()
pillar.mesh = pillar_mesh
pillar.material_override = pillar_material
pillar.position = Vector3(cos(angle), 0, sin(angle)) * 17.0 + Vector3(0, 2, 0)
add_child(pillar)
func _add_hud() -> void:
var hud: CanvasLayer = CanvasLayer.new()
hud.layer = 2
add_child(hud)
var view: Vector2 = get_viewport().get_visible_rect().size
for item in [{ text = 'P1 wasd', color = Color('#8da5f3'), x = 40.0 }, { text = 'P2 arrows', color = Color('#8eef97'), x = view.x * 0.5 + 40.0 }]:
var label: Label = Label.new()
label.text = item.text
label.add_theme_font_size_override('font_size', 24)
label.add_theme_color_override('font_color', item.color)
label.position = Vector2(item.x, 30)
hud.add_child(label)
func _add_environment() -> void:
var sky_material: ProceduralSkyMaterial = ProceduralSkyMaterial.new()
sky_material.sky_top_color = Color(0.05, 0.06, 0.12)
sky_material.sky_horizon_color = Color(0.14, 0.16, 0.24)
sky_material.ground_bottom_color = Color(0.02, 0.02, 0.04)
sky_material.ground_horizon_color = Color(0.1, 0.11, 0.16)
var sky: Sky = Sky.new()
sky.sky_material = sky_material
var env: Environment = Environment.new()
env.background_mode = Environment.BG_SKY
env.sky = sky
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
env.ambient_light_color = Color(0.44, 0.48, 0.6)
env.ambient_light_energy = 0.7
var world: WorldEnvironment = WorldEnvironment.new()
world.environment = env
add_child(world)
func _add_light() -> void:
var light: DirectionalLight3D = DirectionalLight3D.new()
light.rotation_degrees = Vector3(-70, -35, 0)
light.light_energy = 1.2
light.shadow_enabled = true
add_child(light)