Tercera persona 3D

Un SpringArm3D que acerca la cámara cuando una pared se mete entre ella y el personaje. El panel compara spring_length con el largo que el brazo consiguió de verdad.

El editor de arriba está ejecutando este proyecto. Cambia una línea y se recarga.

Clases de Godot usadas

SpringArm3DCamera3DCharacterBody3D

El código

scripts/main.gd
extends Node

const SPEED: float = 5.5
const LAP_RADIUS: float = 5.6
const ARM_LENGTH: float = 6.5

var player: CharacterBody3D
var pivot: Node3D
var arm: SpringArm3D
var readout: Label
var angle: float = 0.0
var yaw: float = 0.0
var manual: bool = false


func _ready() -> void:
	_add_environment()
	_add_light()
	_add_arena()
	_add_player()

	pivot = Node3D.new()
	add_child(pivot)

	arm = SpringArm3D.new()
	arm.spring_length = ARM_LENGTH
	arm.margin = 0.2
	arm.rotation_degrees = Vector3(-22, 0, 0)
	arm.collision_mask = 1
	arm.shape = SphereShape3D.new()
	pivot.add_child(arm)

	var camera: Camera3D = Camera3D.new()
	arm.add_child(camera)
	camera.current = true

	_add_hud()

	print('wasd drives the capsule, the SpringArm3D pulls the camera in when a wall is behind it')


func _physics_process(delta: float) -> void:
	var drive: 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))
	)

	if drive != Vector3.ZERO:
		manual = true

	if manual:
		player.velocity.x = drive.normalized().x * SPEED
		player.velocity.z = drive.normalized().z * SPEED
	else:
		angle += delta * 0.7
		var goal: Vector3 = Vector3(cos(angle), 0, sin(angle)) * LAP_RADIUS
		player.velocity.x = (goal.x - player.position.x) * 3.0
		player.velocity.z = (goal.z - player.position.z) * 3.0

	player.velocity.y -= 22.0 * delta
	player.move_and_slide()

	if Vector2(player.velocity.x, player.velocity.z).length() > 0.6:
		yaw = lerp_angle(yaw, atan2(-player.velocity.x, -player.velocity.z), delta * 4.0)

	pivot.position = player.position + Vector3(0, 1.3, 0)
	pivot.rotation.y = yaw
	readout.text = 'spring_length %.1f      hit length %.2f' % [arm.spring_length, arm.get_hit_length()]


func _add_player() -> void:
	var mesh: CapsuleMesh = CapsuleMesh.new()
	mesh.radius = 0.5
	mesh.height = 1.8

	var material: StandardMaterial3D = StandardMaterial3D.new()
	material.albedo_color = Color('#8eef97')
	material.roughness = 0.4

	var visual: MeshInstance3D = MeshInstance3D.new()
	visual.mesh = mesh
	visual.material_override = material

	var shape: CapsuleShape3D = CapsuleShape3D.new()
	shape.radius = mesh.radius
	shape.height = mesh.height

	var collision: CollisionShape3D = CollisionShape3D.new()
	collision.shape = shape

	player = CharacterBody3D.new()
	# the arm casts on layer 1 only, so the player never blocks its own camera
	player.collision_layer = 2
	player.collision_mask = 1
	player.position = Vector3(LAP_RADIUS, 1.0, 0)
	player.add_child(visual)
	player.add_child(collision)
	add_child(player)


func _add_arena() -> void:
	var ground_mesh: BoxMesh = BoxMesh.new()
	ground_mesh.size = Vector3(34, 1, 34)

	var ground_material: StandardMaterial3D = StandardMaterial3D.new()
	ground_material.albedo_color = Color('#2a2f3d')

	var ground_visual: MeshInstance3D = MeshInstance3D.new()
	ground_visual.mesh = ground_mesh
	ground_visual.material_override = ground_material

	var ground_shape: BoxShape3D = BoxShape3D.new()
	ground_shape.size = ground_mesh.size

	var ground_collision: CollisionShape3D = CollisionShape3D.new()
	ground_collision.shape = ground_shape

	var ground: StaticBody3D = StaticBody3D.new()
	ground.position = Vector3(0, -0.5, 0)
	ground.add_child(ground_visual)
	ground.add_child(ground_collision)
	add_child(ground)

	for i in 16:
		var boundary: float = TAU * i / 16.0
		_add_wall(Vector3(cos(boundary), 0, sin(boundary)) * 13.0 + Vector3(0, 2.5, 0), Vector3(1.4, 5, 5.6), -boundary)

	for i in 8:
		var post: float = TAU * i / 8.0 + 0.2
		_add_wall(Vector3(cos(post), 0, sin(post)) * 7.8 + Vector3(0, 2.5, 0), Vector3(1.4, 5, 3.0), -post)


func _add_wall(at: Vector3, size: Vector3, rotation_y: float) -> void:
	var mesh: BoxMesh = BoxMesh.new()
	mesh.size = size

	var material: StandardMaterial3D = StandardMaterial3D.new()
	material.albedo_color = Color('#fc7f7f')
	material.roughness = 0.7

	var visual: MeshInstance3D = MeshInstance3D.new()
	visual.mesh = mesh
	visual.material_override = material

	var shape: BoxShape3D = BoxShape3D.new()
	shape.size = size

	var collision: CollisionShape3D = CollisionShape3D.new()
	collision.shape = shape

	var wall: StaticBody3D = StaticBody3D.new()
	wall.position = at
	wall.rotation.y = rotation_y
	wall.add_child(visual)
	wall.add_child(collision)
	add_child(wall)


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

	var title: Label = Label.new()
	title.text = 'SpringArm3D shortens itself when a wall is in the way'
	title.add_theme_font_size_override('font_size', 22)
	title.add_theme_color_override('font_color', Color('#8eef97'))
	title.position = Vector2(40, 34)
	hud.add_child(title)

	readout = Label.new()
	readout.add_theme_font_size_override('font_size', 20)
	readout.add_theme_color_override('font_color', Color('#f3d48d'))
	readout.position = Vector2(40, 66)
	hud.add_child(readout)


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.46, 0.5, 0.62)
	env.ambient_light_energy = 1.1

	var world: WorldEnvironment = WorldEnvironment.new()
	world.environment = env
	add_child(world)


func _add_light() -> void:
	var light: DirectionalLight3D = DirectionalLight3D.new()
	light.rotation_degrees = Vector3(-52, -35, 0)
	light.light_energy = 1.2
	light.shadow_enabled = true
	add_child(light)

Más ejemplos de Cámara