Terceira pessoa 3D
Um SpringArm3D que puxa a câmera para perto quando uma parede entra entre ela e o personagem. O painel compara o spring_length com o comprimento que o braço realmente conseguiu.
O editor acima está rodando este projeto. Mude uma linha e ele recarrega.
Classes do Godot usadas
SpringArm3DCamera3DCharacterBody3D
O 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)