El Humanoid por defecto de Roblox es adecuado para experiencias sociales, pero resulta tosco e impreciso en juegos de plataformas exigentes y shooters tácticos.
Diseñar un controlador físico personalizado permite un control milimétrico. Utilizando cápsulas flotantes impulsadas por VectorForce y adaptando la velocidad a las pendientes con raycast, se logra un desplazamiento suave y reactivo propio de producciones de primer nivel.
1. Límites del Humanoid nativo en juegos de acción
Por qué los proyectos competitivos reemplazan el Humanoid estándar:
- Máquina de estados cerrada: Las transiciones rígidas dificultan cambios de dirección inmediatos.
- Expulsiones en plataformas: Desincronizaciones físicas en vehículos que disparan al personaje por los aires.
- Cortes en escaleras: Saltos bruscos de altura que generan temblores molestos en la cámara.
- Control determinista: Cálculo matemático de la velocidad cuadro a cuadro en Heartbeat.
2. Cápsula flotante y suspensión con VectorForce
Flotación sin contacto directo para evitar enganches con el terreno:
- Suspensión virtual: Un raycast descendente aplica una fuerza de muelle que mantiene al personaje en el aire.
- Cascarón sin fricción: Cápsula suave con fricción cero que nunca se queda trabada en grietas.
- Estabilización AlignOrientation: Mantiene al personaje erguido permitiendo giros libres.
- Respuesta LinearVelocity: Cambios de dirección instantáneos sin aceleraciones lentas.
--!strict
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local Workspace = game:GetService("Workspace")
local Players = game:GetService("Players")
local CustomController = {}
local player = Players.LocalPlayer
local char = player.Character or player.CharacterAdded:Wait()
local hrp = char:WaitForChild("HumanoidRootPart") :: BasePart
local TARGET_HEIGHT = 4.2
local SPRING_STIFFNESS = 1800
local SPRING_DAMPING = 160
local WALK_SPEED = 24
local RAY_PARAMS = RaycastParams.new()
RAY_PARAMS.FilterType = RaycastFilterType.Exclude
local linearVelocity = Instance.new("LinearVelocity")
linearVelocity.MaxForce = 40000
linearVelocity.VelocityConstraintMode = Enum.VelocityConstraintMode.Vector
linearVelocity.RelativeTo = Enum.ActuatorRelativeTo.World
local attachment = Instance.new("Attachment")
attachment.Parent = hrp
linearVelocity.Attachment0 = attachment
linearVelocity.Parent = hrp
function CustomController.Update(dt: number)
local moveDir = Vector3.zero
local cam = Workspace.CurrentCamera
if UserInputService:IsKeyDown(Enum.KeyCode.W) then moveDir += cam.CFrame.LookVector end
if UserInputService:IsKeyDown(Enum.KeyCode.S) then moveDir -= cam.CFrame.LookVector end
if UserInputService:IsKeyDown(Enum.KeyCode.D) then moveDir += cam.CFrame.RightVector end
if UserInputService:IsKeyDown(Enum.KeyCode.A) then moveDir -= cam.CFrame.RightVector end
moveDir = Vector3.new(moveDir.X, 0, moveDir.Z)
if moveDir.Magnitude > 0.01 then moveDir = moveDir.Unit end
RAY_PARAMS.FilterDescendantsInstances = { char }
local ray = Workspace:Raycast(hrp.Position, Vector3.new(0, -TARGET_HEIGHT * 1.5, 0), RAY_PARAMS)
local vForce = 0
local targetVel = moveDir * WALK_SPEED
if ray and ray.Instance and ray.Instance.CanCollide then
local dist = (hrp.Position - ray.Position).Magnitude
targetVel = moveDir - (ray.Normal * moveDir:Dot(ray.Normal))
targetVel = targetVel.Unit * WALK_SPEED
local err = TARGET_HEIGHT - dist
vForce = (err * SPRING_STIFFNESS) - (hrp.AssemblyLinearVelocity.Y * SPRING_DAMPING)
end
linearVelocity.VectorVelocity = Vector3.new(targetVel.X, targetVel.Y + (vForce * dt), targetVel.Z)
end
RunService.Heartbeat:Connect(CustomController.Update)
return CustomController
3. Detección de suelo por raycast y proyección en rampas
Mantenimiento de la velocidad uniforme al subir o bajar colinas:
- Vector normal de superficie: Detección exacta del ángulo del suelo mediante raycast.
- Proyección matemática: Redirección del vector horizontal de movimiento a lo largo de la rampa.
- Límite de pendiente: Pérdida controlada de tracción en pendientes mayores a 45 grados.
- Detección de cornisas: Rayos auxiliares en los bordes para prever caídas al vacío.
4. Inercia, impulso y deslizamiento dinámico
Implementación de mecánicas fluidas de parkour y velocidad:
- Deslizamiento al agacharse: Reducción temporal de fricción tras esprintar.
- Aceleración cuesta abajo: Aumento de velocidad influenciado por la gravedad en cuestas.
- Conservación de inercia: Transición gradual tras aterrizajes y deslizamientos.
- Control aéreo (Air Strafing): Capacidad de maniobra en el aire sin ganar velocidad infinita.
5. Sincronización de animaciones y raíz procedural
Coordinación entre velocidad física y esqueleto de animación:
- Fin del deslizamiento de pies: Adaptación dinámica de la velocidad de animación a la rapidez real.
- Árbol de mezcla en 8 direcciones: Fusión fluida según el vector de movimiento.
- Posicionamiento con IKControl: Pies que se apoyan de forma natural en desniveles.
- Computación local: Todo el cálculo físico ocurre en el cliente para máxima respuesta.
Frequently Asked Questions
¿Por qué hacer flotar la cápsula en lugar de tocar el suelo?
El contacto físico directo provoca tropiezos con bordes microscópicos; la suspensión por muelle garantiza un deslizamiento sin trabas.
¿Cómo evita la proyección de vectores que el personaje frene en cuestas?
Orienta la fuerza de empuje paralela a la rampa en lugar de chocar de frente contra ella.
¿Evita los fallos de eyección en plataformas móviles?
Sí, porque permite heredar directamente la velocidad de la plataforma detectada por raycast.