Un monociclo que anda sobre el Humanoid.
Resumen. Este es un monociclo para Roblox que se entrega como un único Model. Se copia, se pega en cualquier juego las veces que se quiera y funciona sin configurar nada. No usa VehicleSeat ni ruedas simuladas con restricciones físicas: el jugador queda unido al monociclo con un Motor6D, su propio Humanoid es el que se desplaza, y un script del cliente decide en cada cuadro hacia dónde y a qué velocidad con un modelo cinemático de rueda. En ese modelo no hay movimiento lateral, el arranque cuesta, pedir ir hacia atrás frena, las pendientes empujan cuesta abajo y un deslizamiento de lado se convierte en rodadura. El proyecto casi no tiene lógica de juego. Lo interesante está en la geometría del montaje (dónde queda el asiento, qué HipHeight pone la rueda en el suelo con cualquier avatar) y en unas pocas ecuaciones diferenciales elegidas para que se sienta bien, más que para ser exactas.
1. El problema
Un vehículo en Roblox se suele armar con un VehicleSeat y ruedas de verdad, es decir, partes unidas con HingeConstraint, fricción y un motor. Para un carro de cuatro ruedas eso funciona porque el carro es estable por sí solo. Un monociclo no lo es. Físicamente es un péndulo invertido sobre una rueda, y mantenerlo de pie con el motor de física exigiría un controlador de equilibrio peleando contra la gravedad en cada paso de la simulación, con todo lo que eso trae en red: quién es dueño de las partes y cómo se ve el vehículo de otro jugador con latencia.
Yo quería tres cosas. La primera, que se condujera como un monociclo y no como un personaje caminando, o sea, sin desplazamiento lateral, con giros que cuestan y un arranque pesado. La segunda, que fuera robusto, que no saliera volando por un error de la física ni temblara en la pantalla de otros jugadores. La tercera, que fuera un modelo autosuficiente: todo el sistema (partes, scripts y configuración) vive dentro de Workspace.Monociclo, y llevarlo a otro juego es copiar y pegar, sin tocar ServerScriptService, ReplicatedStorage ni StarterPlayer.
2. El Humanoid como vehículo
La decisión que define todo lo demás es no simular el monociclo. El Humanoid ya resuelve casi todo lo difícil de moverse por un mapa: se mantiene de pie, sigue el suelo, sube escalones y choca con las paredes. Además, como el personaje lo simula el cliente de su dueño, se replica a los demás sin mandar nada por la red. Lo único que le falta es moverse como una rueda en vez de como una persona, y eso se le puede imponer desde afuera con dos cosas, Humanoid:Move(dirección) y WalkSpeed.
Con eso el monociclo queda partido en dos. Una mitad es geométrica: las partes del modelo se pegan al personaje con un Motor6D, sin masa y sin colisión, para que se vean donde deben y las muevan las animaciones del jugador. La otra es cinemática: un estado de tres variables (rumbo, velocidad de avance y velocidad lateral) que el cliente integra en cada cuadro y le entrega al Humanoid. El monociclo nunca se cae, porque nada lo está equilibrando. Es una renuncia consciente y la discuto en las limitaciones (sección 10).
3. Un modelo que se copia y se pega
Que todo arranque desde dentro del modelo tiene consecuencias concretas. Los scripts de cliente no pueden ser LocalScript, porque un LocalScript no corre en Workspace. Son Script con RunContext = Client, que en el proyecto de Rojo sale de la opción emitLegacyScripts: false. Cada script encuentra su modelo subiendo desde sí mismo (script.Parent.Parent), así que no hay ningún nombre global que dos copias puedan pisarse.
Lo que sí es global por naturaleza son los nombres que se registran en servicios del cliente, como el paso de RunService:BindToRenderStep y la acción de ContextActionService. Con dos monociclos en el mismo juego, los dos registrarían “UnicycleControl” y el segundo reemplazaría al primero, por eso cada copia le pega un GUID a esos nombres al cargar.
Cada jugador corre un script de cliente por monociclo, pero solo hace algo el del monociclo que ese jugador va montando. El servidor anuncia quién va montado escribiendo el atributo OccupantUserId en el modelo, el atributo se replica solo, y cada cliente lo compara con su UserId. Eso evita un RemoteEvent de “empezaste a montar” y garantiza que el cliente nunca crea que va montado cuando el servidor no lo sabe.
La configuración son ValueBase dentro de Source/Configuration (MaxSpeed, Acceleration, StartFriction, etc.). Se leen en vivo en cada consulta, de modo que se pueden ajustar en Studio con el juego corriendo, y cada lectura compara el tipo con el del valor por defecto. Si alguien borra la carpeta o mete un StringValue donde iba un número, se usa el default del código y el servidor avisa una vez cuáles faltan, en vez de que un nil reviente en medio de la física.
4. El montaje
4.1 Por qué los Motor6D se mudan al personaje
El asiento es una parte llamada Plane. Cuando un personaje R15 la toca, el servidor crea un Motor6D con Part0 = LowerTorso y Part1 = Plane, suelta las partes del monociclo (sin anclar, con CanCollide = false y Massless = true) y desconecta el evento de toque para que nadie más se monte encima.
Las animaciones son las que mueven el asiento, la rueda y los pedales, y ahí aparece una restricción del motor, porque el Animator de un personaje solo anima articulaciones que son descendientes del personaje. Si el Motor6D de la rueda se queda dentro del monociclo, la animación de pedaleo no lo mueve. La salida obvia sería meter el monociclo entero dentro del personaje, pero entonces los scripts saldrían del modelo y se destruirían con el personaje al morir. Lo que hace el sistema es mudar solo las articulaciones: el Motor6D del asiento y los Motor6D propios del modelo (rueda y pedales) pasan a una carpeta UnicycleJoints dentro del personaje, y las partes y los scripts se quedan donde estaban.
Al soltar las partes, Massless hace que el monociclo no le cambie la masa al ensamblaje del personaje, así que el control de fuerzas del Humanoid no se entera de que lleva algo encima. CanCollide = false deja que la cápsula del Humanoid sea lo único que toca el suelo.
4.2 La cadena de transformaciones
Un Motor6D coloca su Part1 con
donde y son los CFrame de las dos partes, y son los desplazamientos fijos de la articulación y es el Transform que escribe el Animator en cada cuadro. El Motor6D del asiento deja en la identidad y usa como el MountOffset, que sale de la articulación LowerTorso.Plane del rig R15 con el que se hicieron las animaciones. Es una traslación seguida de media vuelta sobre el eje vertical:
Copiar ese en vez de calcular uno a mano es lo que hace que las animaciones encajen, porque se hicieron contra ese desplazamiento exacto. Tiene un efecto secundario que confunde bastante. En reposo, con , el asiento queda unos 1.04 studs más arriba que durante las animaciones, casi en el centro del UpperTorso (lo medí a 0.135 studs de su centro). Si las animaciones no corren, el monociclo aparece incrustado en el pecho del jugador y parece un error de soldadura, cuando en realidad hay dos causas posibles. La más común es que el juego no sea dueño del asset de animación, porque una animación solo se reproduce en experiencias del mismo dueño, así que al pegar el modelo en el juego de otra cuenta hay que volver a subirlas. La otra es que alguna animación del juego con prioridad Action4 esté tapando la de pedaleo. El cliente revisa las dos cosas 1.5 s después de montar: si la pista tiene Length cero, no cargó, y si cargó pero el del asiento sigue siendo la identidad, algo la está pisando. Cada caso tiene su propio aviso en el Output.
4.3 Desmontar y reconstruir
Al cargar, el servidor guarda una instantánea de cada Motor6D del modelo con su nombre, su padre, Part0, Part1, y . Al desmontar, las articulaciones vuelven a su padre original. Si el personaje murió o el jugador se fue, la carpeta UnicycleJoints se destruyó con el personaje y las articulaciones con ella. En ese caso asignarles Parent falla, porque una instancia destruida tiene el padre bloqueado, y se reconstruyen desde la instantánea. La prueba de que una articulación sigue sana es que sus partes coincidan con las de la instantánea y que reasignarle el padre dentro de un pcall funcione.
Un viaje puede terminar de varias formas: el jugador pide bajarse, el Humanoid muere, se sienta en otro asiento, el personaje sale de Workspace, alguien destruye la articulación del asiento o el jugador abandona el juego. Todas llaman a la misma función, y como destruir la articulación del asiento dispara a su vez el evento de “alguien destruyó la articulación”, la función tiene que soportar que la llamen desde dentro de sí misma. Lo resuelve el orden de las operaciones. Lo primero que hace es poner el viaje actual en nil y desconectar todas sus conexiones, y solo después destruye cosas, así que una segunda llamada encuentra nil y vuelve sin hacer nada.
Solo cuando el jugador pide bajarse se le mueve a un lado, porque si no quedaría parado dentro del monociclo cuando le vuelven las colisiones. Se prueban tres direcciones en orden (derecha, izquierda y atrás, relativas a hacia dónde mira) con un rayo de 4 studs, y el personaje se mueve 3 studs hacia la primera libre. Después hay 1.5 s antes de que ese jugador pueda volver a montarse, para que el mismo toque que lo bajó no lo monte otra vez.
5. HipHeight para cualquier avatar
El Humanoid mantiene la base de su HumanoidRootPart a HipHeight del suelo. Con el monociclo puesto, lo que tiene que tocar el suelo es la rueda y no los pies, así que hay que calcular un HipHeight nuevo al montar. El problema es que depende del avatar, porque la distancia entre el HumanoidRootPart y el LowerTorso cambia con la escala del cuerpo, y el tamaño del HumanoidRootPart también.
El primer paso es encontrar el punto más bajo del monociclo en el espacio del asiento, una sola vez al cargar. Cada parte es una caja orientada de tamaño con ejes locales y centro , todo expresado relativo al asiento. Su media altura sobre el eje vertical del asiento es el radio de proyección de la caja sobre ese eje [3]:
y el punto más bajo del modelo es sobre todas las partes . Es exacto para cajas y una cota conservadora para mallas, porque el Size de una malla es su caja envolvente.
El segundo paso, ya al montar, lleva ese punto al espacio del HumanoidRootPart. La posición de reposo del LowerTorso respecto a la raíz depende del tipo de rig. Los personajes clásicos los unen con un Motor6D, y entonces el desplazamiento de reposo es de esa articulación. Los avatares nuevos usan un AnimationConstraint entre dos Attachment, y el desplazamiento es con los CFrame de los adjuntos. Este segundo caso lo agregué después de probar con mi propio avatar, que usa el rig nuevo y con el que el cálculo fallaba sin dar ningún error. Con eso, el punto más bajo de la rueda en el espacio de la raíz es
Si la raíz tiene altura , su centro está a del suelo, y la rueda toca el suelo cuando esa altura más da cero:
El resultado solo se aplica si cae entre 0.1 y 50 studs. Fuera de ese rango algo está raro en el personaje y es más seguro dejar el HipHeight que traía, que de todas formas se guarda y se restaura al bajarse.
6. El modelo cinemático
6.1 Estado y restricción
En robótica, el “modelo de monociclo” es justamente el nombre de la cinemática de un cuerpo con una rueda que no patina de lado [1][2]:
La velocidad siempre va en la dirección del rumbo , que es la restricción no holonómica . El vehículo puede llegar a cualquier posición, pero no puede moverse de lado para llegar a ella.
El estado del cliente sigue esa idea con un agregado. Guarda un rumbo (vector horizontal unitario), una velocidad de avance y una velocidad lateral que solo aparece en pendientes y se apaga sola. La velocidad que recibe el Humanoid es
y con es exactamente el modelo de monociclo. Todo se integra en el cliente una vez por cuadro, con recortado a 0.1 s para que un tirón de varios cientos de milisegundos no se convierta en un salto de velocidad.
6.2 De la entrada a una dirección
En Roblox el movimiento es relativo a la cámara, y el sistema lo respeta. La entrada no dice “acelera” ni “gira a la izquierda”, dice “quiero ir hacia allá” en coordenadas de pantalla. El vector de movimiento sale del ControlModule por defecto, gracias a lo cual cualquier dispositivo que soporte Roblox funciona sin código propio, y si el juego reemplazó el PlayerModule hay un lector de respaldo para teclado y gamepad.
Con la dirección de la cámara aplanada al plano horizontal y su derecha, la dirección pedida es
y la intensidad del pedaleo es , con una zona muerta de 0.1. Si la cámara mira justo hacia abajo, su dirección aplanada es casi cero y se usa su vector hacia arriba, que en ese caso es lo que el jugador ve como “arriba” en la pantalla.
Con teclado esto cambia la sensación respecto a caminar. A no desplaza al jugador a la izquierda, sino que pide ir hacia la izquierda de la cámara, y el monociclo gira hacia allá mientras avanza, así que dibuja una curva.
6.3 Giro con velocidad limitada
El rumbo gira hacia con una velocidad angular máxima (TurnSpeed, 200°/s por defecto). El ángulo con signo entre los dos vectores es
y en cada cuadro el rumbo rota alrededor del eje vertical por . Guardar el rumbo como vector y rotarlo, en vez de guardar un ángulo, evita tener que normalizar ángulos y el salto entre y .
Girar fuerte también le quita fuerza al pedaleo. Después de rotar, la intensidad se multiplica por
que vale 1 mientras la dirección pedida esté a menos de del rumbo, baja linealmente con el coseno y llega a cero a . Sin este factor, un giro cerrado a toda velocidad trazaría un arco enorme. Con él, el monociclo deja de empujar mientras gira, el arco se cierra mucho y vuelve a acelerar cuando ya apunta hacia donde se quería ir.
6.4 Frenar en vez de reversa
Una rueda de monociclo puede rodar hacia atrás, pero conducir de reversa no es algo que un jugador espere ni algo que las animaciones contemplen. La regla es que si la dirección pedida está a más de 120° del rumbo () y el monociclo todavía rueda a más de 1.5 studs/s, esa entrada frena: se anula la intensidad y no se gira. Por debajo de 1.5 studs/s la misma entrada pasa al caso de la sección anterior y el monociclo da la vuelta en el sitio. Para el jugador, “atrás” primero frena y luego da la vuelta, que es lo que haría alguien montando uno de verdad.
6.5 El arranque pesado
Con un pedaleo de aceleración constante, el monociclo arranca igual de rápido que corre, y se siente como un personaje con una rueda pegada. StartFriction () hace que el primer empujón cueste más:
con la aceleración (Acceleration) y la velocidad máxima (MaxSpeed). En reposo la aceleración es y crece linealmente con la velocidad hasta llegar a en . Con el pedal a fondo y en línea recta () la ecuación es lineal y tiene solución cerrada:
Es una exponencial creciente, por lo tanto una curva convexa: el arranque es lento y la velocidad sube cada vez más rápido cerca del final. Con los valores por defecto (, , ) queda , y la velocidad máxima llega en s. Sin fricción de arranque llegaría en 1.5 s, y en el límite la expresión se reduce a la recta . El código recorta a 0.95, porque con la aceleración en reposo es cero y el monociclo nunca arrancaría.
El código integra esto con Euler explícito, , que para esta ecuación da en vez de . Como queda por debajo de , el tiempo hasta crece un poco con cuadros más largos: 2.30 s a 60 fps y 2.34 s a 10 fps, que es el peor caso posible por el recorte de . Un 2% de diferencia en el peor caso no se nota al jugar, y por eso no usé la solución cerrada, que obligaría a guardar un instante de inicio y a recalcularla cada vez que cambia la intensidad del pedal o el ángulo de giro.
6.6 Frenado y rodadura libre
Las dos desaceleraciones son constantes y no proporcionales a la velocidad:
con (BrakeDeceleration) al frenar y (CoastDeceleration) al soltar el pedal. Elegí una fricción constante en vez de un arrastre viscoso porque el arrastre viscoso nunca llega a cero. La velocidad decae para siempre y el vehículo se queda arrastrándose, que se siente flotante. Con desaceleración constante , en cambio, el monociclo se detiene en un tiempo y una distancia finitos, y además fáciles de razonar al ajustar los valores:
Desde 24 studs/s, frenar toma 0.67 s y 8 studs, y dejar de pedalear toma 3 s y 36 studs.
Hay un tercer caso, que es pedalear yendo más rápido que , por ejemplo después de bajar una pendiente con LimitSlopeSpeed apagado. Pedalear no puede sostener esa velocidad, así que se aplica pero con piso en , y la velocidad baja hasta la máxima de pedaleo y ahí se queda.
6.7 Pendientes
Un rayo hacia abajo desde la raíz, de largo , da la normal del suelo. En una pendiente de ángulo , la componente horizontal de la normal mide y apunta cuesta abajo, de modo que
La gravedad a lo largo de un plano inclinado es , y como el Humanoid recibe una velocidad horizontal, lo que interesa es su proyección horizontal, . Con la escala (SlopeGravityScale) queda
que en el código es el producto de workspace.Gravity, la escala, slopeSine y groundNormal.Y, porque . Es máxima a 45°. Con la gravedad por defecto de Roblox (196.2 studs/s²) y , una pendiente de 20° da studs/s², casi lo mismo que pedalear a fondo.
Esa aceleración se reparte según hacia dónde mira el monociclo. Con :
Mirando cuesta abajo empuja la gravedad completa. La parte paralela al rumbo acelera la rueda, y la perpendicular, , de módulo con el ángulo entre rumbo y pendiente, empuja de lado. Mirando cuesta arriba la gravedad se ignora, y eso no es física sino una decisión de juego: subir con un freno constante era tedioso, y que subir se sienta como andar en plano es más divertido que ser exacto. La transición está en , que vale 1 mientras el monociclo no mire cuesta arriba () y se apaga linealmente hasta cero cuando el rumbo queda a 60° o menos de la dirección cuesta arriba (). Sin la fuerza lateral cambiaría de golpe al cruzar la horizontal.
Con LimitSlopeSpeed activo, la velocidad nunca pasa de . Con el límite apagado, el único freno al bajar mirando cuesta abajo es , y la gravedad gana cuando :
O sea que con el límite apagado, cualquier pendiente de más de unos 9.5° acelera al monociclo sin fin. Para eso existe AbsoluteMaxSpeed (400 studs/s), un tope de seguridad que no forma parte del ajuste normal.
6.8 Agarre lateral
La velocidad lateral es lo único que rompe la restricción de la rueda, y el sistema la elimina de dos formas a la vez. Primero, el rumbo gira hacia la velocidad real , con una velocidad angular que crece con el deslizamiento:
con °/s (SlopeTurnSpeed). Es lo que hace la rueda de un carrito de supermercado, que si la empujan de lado gira hasta que el empuje le queda de frente. Después, la velocidad se vuelve a proyectar sobre el rumbo nuevo , y lo que queda de lado se atenúa:
La proyección es la que convierte el deslizamiento en rodadura, porque a medida que el rumbo se alinea con , cada vez más de la velocidad cuenta como avance y menos como lateral. El factor , con (LateralGrip), es el agarre de la goma, y reduce lo que queda de deslizamiento a la mitad cada s. A diferencia del pedaleo, este paso usa el factor exacto de la exponencial y no Euler, así que el decaimiento es el mismo a cualquier framerate.
En una pendiente cruzada el resultado es que el monociclo se va un poco de lado, gira solo hacia cuesta abajo y termina rodando hacia abajo, como haría una rueda real.
6.9 Contra una pared
Si el jugador pedalea contra una pared, el modelo cinemático no lo sabe y subiría hasta . Al quitarse de la pared, el monociclo saldría disparado a toda velocidad. Para evitarlo, mientras está en el suelo, la velocidad ordenada se ata a la velocidad real del ensamblaje:
El margen de 4 studs/s es lo que permite arrancar. Desde el reposo la velocidad real es cero, y sin margen nunca podría subir de cero. Con él, sube hasta 4, el Humanoid empieza a moverse, la velocidad real sube y el tope sube con ella. De frente contra una pared la velocidad real se queda en cero y se queda en 4.
Las animaciones usan otra velocidad, , para que los pedales no giren en el aire mientras el jugador está atascado.
6.10 Del estado al Humanoid
Al final del cuadro el estado se entrega así:
y el CFrame de la raíz se fija mirando hacia , con AutoRotate apagado. Separar las dos cosas permite que el jugador mire hacia un lado mientras resbala hacia otro, que es justo lo que pasa durante un deslizamiento lateral.
El orden dentro del cuadro importa. Los controles por defecto de Roblox también llaman a Move() en cada cuadro, desde RenderStep con prioridad Input. El monociclo se registra con prioridad Camera + 1, de modo que corre después de los controles y de la cámara, y su Move() es el último del cuadro y el que gana. Eso también hace que lea la cámara ya actualizada en ese cuadro, sin un cuadro de retraso.
Mientras dura el viaje se desactivan los estados Climbing, FallingDown y Ragdoll (y Jumping si AllowJump está apagado), para que el monociclo no se ponga a escalar escaleras ni a dar volteretas. En el aire el rumbo todavía puede girar, pero el pedaleo, el frenado y las pendientes solo actúan con la rueda en el suelo, así que un salto conserva la velocidad que traía.
7. Animaciones
Hay dos pistas en bucle. La de reposo corre con prioridad Action3 durante todo el viaje y es la que coloca el monociclo bajo el jugador (sección 4.2). La de pedaleo va encima, con Action4, solo mientras la rueda gira, y su velocidad de reproducción sigue a la rueda:
Para que la rueda no se vea patinando contra el suelo, la rotación de la animación tiene que coincidir con la rodadura, . Si un ciclo de la animación dura y la rueda da vueltas en él, eso pasa a velocidad normal cuando . AnimationReferenceSpeed es ese número, y como depende de la animación y del radio de la rueda, lo dejé configurable en vez de calcularlo.
La pista de pedaleo empieza cuando la rueda pasa de 0.75 studs/s y se detiene cuando baja de 0.35. Con un solo umbral, una velocidad que oscila alrededor de él (frenando contra una pared, por ejemplo) encendería y apagaría la animación varias veces por segundo. Con dos umbrales hay histéresis y eso no puede pasar. Las dos transiciones se hacen con un fundido de 0.25 s.
8. Red y autoridad
El reparto es simple. El servidor decide quién va montado: valida el toque (personaje R15, vivo, sin otro asiento, sin ir montado ya en otro monociclo y fuera del tiempo de espera), crea las articulaciones y escribe el atributo. El cliente del jinete simula todo el movimiento. Como el personaje es propiedad de red de su jugador y el monociclo queda soldado a él y sin masa, todo el conjunto se replica como parte del personaje. No hay un solo mensaje por cuadro entre cliente y servidor, y el servidor no gasta nada mientras alguien conduce.
El único RemoteEvent es Dismount. El cliente lo dispara con E, ButtonB o un botón táctil, con un tiempo de espera de 0.3 s, y el servidor solo lo acepta si quien lo manda es el jinete actual. En móvil, el botón se mete dentro del TouchControlFrame por defecto, justo encima del botón de saltar y al 70% de su tamaño, así que aparece y desaparece junto con los demás controles táctiles. Si el juego reemplazó esos controles, el botón cae a un ScreenGui propio en la esquina.
Que el cliente sea dueño del movimiento significa que un cliente modificado podría mover su monociclo como quisiera. Pero eso no es nuevo, porque ese mismo cliente ya podía mover su personaje así antes de montarse, y la protección contra eso le corresponde al juego, no al vehículo.
9. Lo que no se ve
Algunos detalles no salen de ningún modelo, pero sin ellos el sistema fallaba en juegos reales:
- Para saber quién tocó el asiento, el servidor sube desde la parte tocada hasta encontrar un
Modelque sea el personaje de un jugador, porque los accesorios y las herramientas también tocan. - El atributo
RidingUnicycleen el personaje impide montarse en dos monociclos a la vez cuando hay varios juntos. - La colisión y la masa originales de cada parte se guardan antes de soltarlas y se restauran al bajar, en vez de ponerlas en un valor fijo, porque quien arma el modelo puede haberlas configurado a su manera.
- Al terminar el viaje, el cliente devuelve
WalkSpeed,AutoRotatey cada estado delHumanoidal valor que tenían antes de montar y no al valor por defecto de Roblox, porque el juego anfitrión puede tener su propia velocidad de caminar.
10. Limitaciones
El monociclo no se cae. No hay equilibrio simulado y por lo tanto tampoco hay forma de perderlo, que es la diferencia más grande con uno real. Agregar un péndulo invertido encima del Humanoid (una inclinación visual que crezca con la aceleración y una caída si pasa de cierto ángulo) es la extensión más obvia, y se podría hacer sin tocar el resto, porque sería solo un término más en el estado.
Solo funcionan personajes R15, ya que el montaje depende del LowerTorso. El HipHeight se calcula con la pose de reposo y no ve el Transform de las animaciones, así que si una animación nueva cambia la altura de la rueda respecto a la raíz, hay que ajustar MountOffset. La integración de Euler hace que la aceleración dependa del framerate en un 2% como mucho, que me pareció aceptable. La gravedad cuesta arriba se ignora a propósito, lo que deja al monociclo subir pendientes que en la vida real no podría. Y las animaciones hay que volver a subirlas con la cuenta o el grupo dueño de cada juego donde se pegue el modelo. Es una restricción de Roblox y no del sistema, pero es lo primero con lo que se va a encontrar quien lo use.
11. Lo que me dejó el proyecto
Lo que más me sirvió fue dejar de pensar en el monociclo como un objeto físico. Cuando lo traté como una restricción sobre algo que ya se movía bien, el Humanoid, el problema pasó de “cómo mantengo de pie una rueda en red” a “qué velocidad le pido a un personaje en cada cuadro”. Eso son unas pocas ecuaciones de primer orden que se resuelven a mano, se ajustan con números que significan algo (segundos hasta la velocidad máxima, studs hasta frenar) y se prueban sin pelear con el motor de física. Las partes difíciles terminaron siendo las del motor, como el Animator que solo ve las articulaciones del personaje o los avatares con AnimationConstraint, y no las matemáticas.
Referencias
- S. M. LaValle. Planning Algorithms. Cambridge University Press, 2006.
- R. Siegwart e I. R. Nourbakhsh. Introduction to Autonomous Mobile Robots. MIT Press, 2004.
- C. Ericson. Real-Time Collision Detection. Morgan Kaufmann, 2005.
Este texto de Mastedore. Todos los derechos reservados.