Crear movimiento en cualquier entorno dentro de una experiencia ayuda a que se sienta más inmersivo y realista para nuestro mundo, ya sea que eso sea del movimiento del árbol ambiental, de las puertas reactivas de la interacción del jugador o incluso de las cajas que se mueven cuando se chocan contra
Creando la Tormenta
La tormenta pasó por muchas iteraciones antes de que nos decidiéramos por lo que hay en The Mystery of Duvall Drive. Al principio, pensamos en la tormenta como un pilar de obsidiana gigante, y en las siguientes iteraciones consideramos que era un portal gigante al espacio corrupto. Después de experimentar con muchos diferentes tormentas con apariencias y sentimientos únicos para ellos, nos acalmamos con una tormenta con un "ojo" central más pequeño porque:
- La tormenta debería dar a los jugadores una sensación de impacto de este evento en el mundo , incluida la caída de árboles y el vuelo de escombro.
- El vórtice giratorio de la nube misma debería dar a los jugadores una vista del portal central sin revelar nada . Esto fomentaría a los jugadores para investigar más cerca para ver qué está sucediendo.
- El punto de luz más apretado nos permitiría enfocarnos en la composición de la casa, que es tanto el personaje principal como donde se encuentra la mayor parte del juego.
Para hacer que la tormenta se sienta dinámica, agresiva y siempre cambiante dentro de su entorno, ambiente, usamos los siguientes sistemas y características:
- TweenService > Para el movimiento de nubes.
- Cambios de iluminación - Para crear la nube para crear un rayo.
- Rayos - Para la "iluminación volumétrica" y los rayos.
- Emisores de partículas > - Para los escombros que vuelan hasta el portal y vuelan alrededor debido al viento.
- Animaciones > - Para los árboles que están soplando en el viento.
Añadir nubes con texturas
Mientras que las nubes dinámicas son geniales para las nubes de alta altitud y realistas, necesitábamos algo que se sintiera dramático y que pudiéramos
Dado que cada malla de nube necesitaba ser masiva para completamente rodear la casa y transmitir lo enorme que era la tormenta, sabíamos que necesitábamos azulejar la textura que queríamos usar en las mallas individuales para que se repitiera repetidamente en toda la superficie de la malla. Probamos los materiales que hicimos para la nube en estas partes simples, y luego los aplicamos a la voluta!
A diferencia de los emisores de partículas o los rayos, las mallas nos permitían poder rebotar la luz de cada malla, lo que era importante cuando queríamos implementar el rayo y el trueno entre nubes. También modelamos en la torsión para que el rebotar de la luz tuviera la apariencia de profundidad. Esto era importante especialmente en situaciones donde las exigencias de rendimiento de la experiencia hicieron que los niveles de rendimiento de la superficie de nuestro objeto de apariencia se vieran afectados.
Mallas de nube giratorias
Luego de que nos satisfeiriéramos con la apariencia visual general de las nubes, necesitábamos que se pusiera en marcha! Teníamos las formas generales de cada capa de nube en su lugar, ¡pero tomó algo de prueba y error para asegurarnos de que el efecto giratorio se vea bien en la práctica. Inicialmente intentamos usar 约束 para introducir velocidad que conduciría
Queríamos un método fácil de usar para rotar instancias que fueran demasiado lejos para ser interactuables, como las nubes, o demasiado pequeñas o decorativas para ser importantes
Como en muchos casos en la demostración, usamos una etiqueta LocalSpaceRotation para que pudiéramos administrar las instancias afectadas en Studio usando un plugin de etiqueta de instancia. Utilizamos solo un solo LocalScript que manejó todas las instancias etiquetadas usando el plugin de etiqueta de instancia.
local function Init()
for _, obj in CollectionService:GetTagged("LocalSpaceRotation") do
if obj:IsDescendantOf(workspace) then
SetupObj(obj)
end
end
end
CollectionService:GetInstanceAddedSignal("LocalSpaceRotation"):Connect(function(obj)
objInfoQueue[obj] = true
end)
CollectionService:GetInstanceRemovedSignal("LocalSpaceRotation"):Connect(function(obj)
if objInfo[obj] then
objInfo[obj] = nil
if objInfoQueue[obj] then
objInfoQueue[obj] = nil
end
end
end)
Init()
obj
Hemos rotado las instancias en la función Update conectada a heartbeat. Hemos obtenido el padre transform (
local parentTransformif parentObj:IsA("Model") thenif not parentObj.PrimaryPart then-- la parte principal aún no puede ser transmitidacontinue -- espera a que la parte principal se replicaendparentTransform = parentObj.PrimaryPart.CFrameelseparentTransform = parentObj.CFrameendcurObjInfo.curAngle += dT * curObjInfo.timeToAnglelocal rotatedLocalCFrame = curObjInfo.origLocalCFrame * CFrame.Angles( curObjInfo.axisMask.X * curObjInfo.curAngle, curObjInfo.axisMask.Y * curObjInfo.curAngle, curObjInfo.axisMask.Z * curObjInfo.curAngle )if obj:IsA("Model") thenobj.PrimaryPart.CFrame = parentTransform * rotatedLocalCFrameelseobj.CFrame = parentTransform * rotatedLocalCFrameend
Comprobamos para un válido Model.PrimaryPart para ser configurado para manejar la transmisión. Si se llamó una actualización en nuestro objeto mientras un Model.PrimaryPart (que puede apuntar a un malla hija) aún no estaba transmitido, simplemente nos saltaríamos la actualización. El sistema actual es una segunda iteración de la
Diseñando Golpes de Relámpago
Debido a que Studio no ofrece un generador de rayos de la caja, y el sistema de partículas tenía algunas limitaciones que no funcionarían para los golpes de rayo del héroe, tuvimos que ser creativos con una solución para los golpes de rayo del héroe. Decidimos en dos sistemas principales para hacer que los rayos del héroe se vuelvan texturizados por el audio y los efectos de sincronización con el audio y el postprocesado,
Características de las luces
Usamos generalmente una herramienta de secuenciador o cronómetro para impulsar el tiempo de un efecto de perforación de iluminación como este, pero ya que Studio no ofrece esta funcionalidad, decidimos escribir scripts que controlarían el tiempo de un golpe de perforación de iluminación. El scripting de este efecto es bastante simple, pero logra los siguientes objetivos importantes:
- Los elementos de los golpes de rayo, como sus texturas, brillo y retrasos, se aleatorizan con cada golpe.
- Los cambios de audio y post FX están sincronizados con los golpes FX.
- Los jugadores que estén en el área corrupta no podrían ver o escucharles.
Tenemos un Script lado del servidor que calcula varios parámetros y tiempo, los envía a todos los clientes y espera por un tiempo aleatorio:
local function LightningUpdate()
while true do
task.wait(rand:NextNumber(3.0, 10.0))
local info = CreateFXData()
lightningEvent:FireAllClients(info)
end
end
Dentro de CreateFXData, rellenamos la estructura de información para que todos los clientes obtengan los mismos parámetros.
En el lado del cliente ( LightningVFXClient ) comprobamos si este cliente debe ejecutar el FX:
local function LightningFunc(info)
…
-- no hay FX en el interior
if inVolumesCheckerFunc:Invoke() then
return
end
-- no FX cuando no estés en el mundo "normal"
if not gameStateInfoFunc:Invoke("IsInNormal") then
return
end
…
Además, ejecutamos la secuencia para establecer texturas, posiciones y brillo, correr a los adolescentes y usar task.wait(number) . Los parámetros aleatorios son de la estructura de información que recibimos del servidor, y algunos números están fijados.
beam.Texture = textures[info.textIdx]beamPart.Position = Vector3.new(info.center.X + og_center.X, og_center.Y, info.center.Y + og_center.Z)-- Borrarbeam.Brightness = 10ppCC.Brightness = maxPPBrightnessppBloom.Intensity = 1.1bottom.Position = top.PositiontweenBrightness:Play()tweenPPBrightness:Play()tweenPPBrightness:Play()tweenBottomPos:Play()tweenBrightness.Completed:Wait()-- sonido, audioif audioFolder and audioPart thenif audioFolder.Value and audioPart.Value thenaudioUtils.PlayOneShot(audioObj, audioFolder.Value, audioPart.Value)endendtask.wait(info.waitTillFlashes)-- and so on
Para verificar si un jugador está en el interior, usamos una función de ayudante inVolumesCheckerFunc, que va sobre los volúmenes predeterminados que se aproximan a las áreas interiores y verifica si la posición del jugador está dentro de cualquiera de ellos (PointInABox). Podríamos haber usado la detección basada en el toque, pero encontramos que cuando un
Para ver si un jugador está en áreas corruptas, invocamos una función de ayudante gameStateInfoFunc , que comprueba el estado actual del juego. Para jugar un sonido aleatorio desde una carpeta, también usamos una función de ayudante PlayOneShot . Para los rayos de trueno en sí mismos, estos fueron super fáciles de crear en Photoshop; creamos una línea con curva, luego
Utilizando sistemas de emisores de partículas
Los héroes rayos son apoyados por un sistema de partículas que sugiere un rayo distante al crear la impresión de una capa de nubes en el fondo que captura la luz del rayo distante, o iluminación de nube a nube. Logramos este efecto a través de un sistema de partículas muy simple que muestra una nube de cartelera en el período de nube del núcleo principal. El sistema emite un período de partículas de nube aleatoriamente con una curva de transparencia aleatorizada:
Haciendo que los árboles salgan en la viento
Luego de que tuvimos las nubes y el rayo funcionando de la manera que queríamos, necesitamos agregar dos otros componentes principales de una tormenta: el viento y la lluvia. Estos elementos presentaron algunos desafíos, incluida la necesidad de trabajar dentro de los límites actuales de nuestros sistemas de física y efectos especiales. Por ejemplo, hacer que los árboles se muevan con el viento no es
Sabíamos que realmente vender el efecto del viento y la lluvia, necesitábamos que los árboles mismos se herramienta de movimiento. Hay algunas formas en que puedes hacer esto dentro del motor, incluyendo mover partes usando plugins que están disponibles públicamente, usar TweenService o animar modelos directamente. Para nuestros propósitos, las animaciones nos dieron la capacidad
Comenzamos por cáscara varios árboles del Paquete de Modelos de Fin - Recursos Forestales . Ya que estos árboles ya existían, y nuestra experiencia tuvo lugar en el noroeste del Pacífico, nos ahorró algo de tiempo al principio de tener que crear cada aplicación de modeladode árbol.
Luego de que elegimos nuestros árboles, sabíamos que necesitábamos hidratarlos. Hidratar una malla es el acto de agregar articulaciones (o huesos) a una malla en otra aplicación de modelado 3D, como Blender o Maya, luego aplicar el efecto a esas articulaciones
Sabíamos que queríamos ahorrar tiempo y reutilizar la misma animaciones, por lo que construimos nuestra primera rígida de árbol y asegurarnos de que los nombres compartidos fueran genéricos porque queríamos usar estos nombres en los rígidos para las otras árboles. También sabíamos que necesitáb
Una vez que hayamos creado nuestras articulaciones/huesos, era hora de crear una animación de prueba para moverse por todas las articulaciones y huesos en Studio para ver si se movía de la manera que queríamos. Para hacer esto, tuvimos que importar el árbol en Studio a través de la configuración personalizada
¡Después de que estuviéramos contentos con los resultados en ese árbol, era hora de probar la misma animación en un árbol diferente! Ya sabíamos que era la misma animación entre los diferentes modelos de árbol para cada introducirde árbol, ¡así que solo nos aseguramos de que nuestra animación se vea como si fuera lo suficientemente genérica para trabajar entre un alto árbol de Redwood y un robusto árbol de Beechwood!
Para hacer esto, tomamos el árbol de Beechwood de ese Pack del Bosque y construimos un rig similar, utilizando el mismo nombre exacto para las juntas. Esto fue para que la animación que habíamos importado anteriormente se pudiera aplicar a este árbol también. ¡Dado que las animaciones se basaban en juntas giratorias, no importaba cuán grande, pequeña, alta o ancha era la árbol!
Luego de que creamos y pielemos el árbol de Beechwood, podríamos importarlo y aplicar la animación exacta. Esto significaba repetir y editar solo se necesitaba hacer en un archivo, y también se guardó en el rendimiento con menos animaciones cuando se ejecuta la experiencia.
Una vez que tuvimos todos los tipos de árboles que queríamos animar, los hicimos en paquetes para que pudiéramos seguir editando y actualizando mientras jugábamos varias de las animaciones alrededor de la zona principal de la experiencia. ¡Dado que sabíamos que tenían un costo de rendimiento, los usamos con parcimonia alrededor de la casa donde la animación era más valiosa! En el futuro, a medida que esto se vuelve más rentable
Haz Debris de Tormenta
Queríamos que el agua pareciera pesada, y para que la niebla y los residuos soplaran a través de los árboles. Para hacer esto, configuramos algunas partes invisibles para actuar como volúmenes de partículas con hijos emisores de partículas inmediatamente debajo de las grandes nubes de tormenta. Debido al límite de contaminantes en Studio
The rain particles leveraged a new particle emitter property ParticleEmitter.Squash that allows you to make a particle longer, or squatter. It is particularly useful for rain because it meant we didn't need a large rain textura, just stretch
Para el mal, la niebla y las hojas que están volando, era mucho más simple añadir un solo volumen de mayor tamaño cubriendo menos áreas porque no necesitábamos una tonelada de partículas que corrieran a la vez. Comenzamos por configurar un volumen y obtuvimos la frecuencia de las partículas donde queríamos que estén.
Después de eso, hicimos nuestras luces de plástico y texturas de viento, y establecimos que las partículas se giraran y se moverían de manera más natural, y comenzaran a diferentes velocidades. Esto significó que las partículas de mayor tamaño interactuarían más naturalmente y no parecerían tan como una textura repetitiva, especialmente dado su tamaño.
El resultado fue una gran acción entre los árboles en movimiento, la ventana que se rompe y el rayo para crear el efecto de la tormenta que rodea el ojo central de la tormenta.
Configurando el Ojo de la Tormenta
El ojo de piedra con un núcleo brillante está destinado a dar a los jugadores la primera pista de que hay algo siniestro y arcano que está ocurriendo en la casa que deben explorar más a fondo. Ya que nuestra escena es oscura y el ojo está muy arriba en el cielo, fue importante crear una silueta de piedra falsa creíble, pero no era tan
La distancia desde el jugador también significó que podíamos confiar en un mapa normal para los detalles de la superficie del ojo, ¡para que el malla es solo una esfera plana! Esculpimos los detalles en un malla de alta resistencia y horneamos su mapa normal en una esfera mucho más baja, para que pudiéramos obtener todo ese detalle sin el costo de rendimiento masivo.
Con el fin de añadirle un sentimiento sobrenatural a los ojos y de resaltar su presencia, decidimos crear un magma aspecto, looky neón que se filtra a través de sus grietas. Mientras no hay canal emisor para la apariencia de la super
Otro desafío que enfrentamos al crear el ojo fue impuesto por nuestro uso de streaming combinado con la distancia del ojo del jugador. Dado la centralidad de esta est
Pudimos añadir movimiento al ojo y sus anillos gracias a la misma script que usamos para rotar las mallas del mundo. Para un toque final, decidimos añadir una pista a la presencia de otro mundo más
Haciendo que la almacenamiento de Expansión
Una de las cosas más divertidas para producir fueron los espacios corruptos, en los que podíamos subvertir las expectativas de los jugadores de la realidad al cambiar literalmente el espacio alrededor de ellos. Por ejemplo, en el rompecabezas del padre queríamos emular un momento similar a una pesadilla donde no importa lo rápido que ejecutar, el espacio se siente como si fuera más largo. Decidimos hacer una pantry que se expandiría para que se alejara de los jugadores mientras busc
Configuramos esto con un simple movimiento de las paredes, y un diseño inteligente de nuestras habitaciones que aparecería en ambos lados de la panadería. En el estado normal de la habitación, la panadería era un simple pasillo, ¡pero en el espacio corrupto, era realmente mucho más larga con varios alas y una pared falsa!
La pared falsa era un grupo de modelos que nosotros moveríamos de vuelta el momento que los jugadores entraran a un volumen de disparo, que era una parte transparente antes en la pantry que ellos caminarían a través. Ese disparador también se usó en un script similar a los usados en todas nuestras puertas, que llamó el TweenService para mover desde un punto de inicio a otro. Usamos volúmenes de partes para contar la operación de tweening donde
Dado que TweenService es un sistema tan general, todo nuestro modelo de datos de pared tenía que contener los mismos componentes. Por ejemplo, la siguiente imagen es un ejemplo de un script de puerta general que llama un sonido definido por un "valor" debajo del aplicación de modelado"Grow_Wall".
Ese mismo script, con algunas modificaciones en el siguiente ejemplo de código, también activó el audio para el movimiento de la almazara. ¡Esto añadió mucho al movimiento!
local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local model = script.Parent
local sound = model.Sound.Value
local trigger = model.Trigger
local left = model.TargetL_Closed
local right = model.TargetR_Closed
local tweenInfo = TweenInfo.new(
model.Speed.Value, --Tiempo/Velocidad de la Puerta
Enum.EasingStyle.Quart, --Estilo de alivio
Enum.EasingDirection.InOut, --EasingDirection
0, --Repetir Cuenta
false, --Reverse true
0 --Retrasar
)
local DoorState = {
["Closed"] = 1,
["Opening"] = 2,
["Open"] = 3,
["Closing"] = 4,
}
local doorState = DoorState.Closed
local playersNear = {}
local tweenL = TweenService:Create(left, tweenInfo, {CFrame = model.TargetL_Open.CFrame})
local tweenR = TweenService:Create(right, tweenInfo, {CFrame = model.TargetR_Open.CFrame})
local tweenLClose = TweenService:Create(left, tweenInfo, {CFrame = model.TargetL_Closed.CFrame})
local tweenRClose = TweenService:Create(right, tweenInfo, {CFrame = model.TargetR_Closed.CFrame})
local function StartOpening()
doorState = DoorState.Opening
sound:Play()
tweenL:Play()
tweenR:Play()
end
local function StartClosing()
doorState = DoorState.Closing
--aplicación de modelado["Puerta"]:Jugar()
tweenLClose:Play()
tweenRClose:Play()
end
local function tweenOpenCompleted(playbackState)
if next(playersNear) == nil then
StartClosing()
else
doorState = DoorState.Open
end
end
local function tweenCloseCompleted(playbackState)
if next(playersNear) ~= nil then
StartOpening()
else
doorState = DoorState.Closed
end
end
tweenL.Completed:Connect(tweenOpenCompleted)
tweenLClose.Completed:Connect(tweenCloseCompleted)
local function touched(otherPart)
if otherPart.Name == "HumanoidRootPart" then
local player = Players:GetPlayerFromCharacter(otherPart.Parent)
if player then
--imprimir ("toc")
playersNear[player] = 1
if doorState == DoorState.Closed then
StartOpening()
end
end
end
end
Una vez que tuvimos la pared falsa en movimiento hacia la parte trasera de la habitación, necesitábamos el resto del contenido para moverlo con ella. Para hacer eso, necesitábamos todos los artículos sueltos en el almacén para ser soldados a la pared a medida que se movía. Al usar Contrapesos Con限aciones, pudimos soldar fácilmente todos los objetos en el almacén para moverlos como un solo objeto
Haciendo la Casa del Árbol Corrupta
Studio es un fantástico motor basado en física que puedes usar para crear todo, desde una puerta pendulante hasta una plataforma giratoria. Con nuestra demostración, queríamos usar la física para crear un sentido de realismo en un conjunto de ambientes de otro tipo. Usando solo algunas limitaciones , puedes crear algunas carreras de obstáculos dentro de tus propias experiencias!
Constricciones son un grupo de motores basados físicamente que alinean objetos y limitan comportamientos. Por ejemplo, puede usar una limitación de caña para conectar a los objetos para mantenerlos a una distancia f
Una vez que los jugadores llegaron al área principal del rompecabezas, se encontraron con un familiar paisaje en Roblox: un curso de obstáculos. Este particular curso de obstáculos consistió en varias plataformas giratorias y paredes giratorias, junto con "áreas seguras" que progresaban la historia. Nos enfocaremos en los elementos giratorios/giratorios.
¿Por qué usamos limitaciones aquí? Porque TweenService o otros métodos no moverían al jugador mientras estuvían de pie. Sin que el objeto se mueva el jugador, alguien podría saltar en una plataforma y girará desde debajo de ellos. En lugar, queríamos que los jugadores navegaran a través de una plataforma giratoria mientras
Para hacer esto, primero tuvimos que usar recursos de nuestro kit actual y agregar cualquier nuevo contenido para un efecto visual. Hicimos algunas paredes y plataformas incompletas con agujeros en ellas para contar la historia de la abuela que construyó la casa del árbol. Debido a que no queríamos crear un montón de piezas de base y piezas de valla únicas, creamos 4 piezas de base y piezas de valla separadas. Esto nos permitió mezclar y comb
Sabíamos que ya que estábamos utilizando restricciones, no podríamos anclar estas mallas porque no se moverían incluso con la presencia de un límite / motor que las impulsaba. La
Ahora era el momento de configurar el comportamiento real de la limitación del eje mismo, y agregar los accesorios que actuarían como la orientación de la parte y la limitación juntos. Colocamos el accesorio giratorio en el Motor_Turn, que las piezas de Bisagraestaban soldadas a, y otro accesorio
Para mantener las plataformas girando a una velocidad constante, entonces configuramos las propiedades HingeConstraint.AngularVelocity, HingeConstraint.MotorMaxAcceleration y HingeConstraint.MotorMaxTorque a valores que permitirían el movimiento y evitarían la interrupción si un jugador se saltaba sobre él.
Ahora necesitábamos hacer las paredes giratorias. Las paredes necesitaban girar en su centro aparente, y sabíamos que queríamos que pudieran manejar cualquier orientación relativa al resto del nivel. Como las plataformas, construimos estas para que todas las paredes estén desancoradas y soldadas a la Motor_Turn.
Usamos Texture objetos por encima de SurfaceAppearance objetos para añadir algo de variación a nuestros materiales de base. Text
Una vez que probamos algunas plataformas y paredes giratorias, hicimos varias variaciones y jugamos con su colocación para asegurarnos de que el recorrido de obstáculos fuera desafiante, sorprendente y también limpio donde el jugador necesitaba ir! Tomó algo de ajuste para ambos sus valores y posiciones para que funcionaran bien. Tuvimos varios puntos donde las plataformas y paredes se golpeaban entre sí o los alreded
Si no estás seguro de qué objetos físicos estás golpeando, puedes alternar Colisión de fidelidad desde el widget Opciones de visualización en la esquina superior derecha de la ventana de vista 3D.
Como puede ver a continuación, las ventajas/ventanas de la puerta están visibles, pero los detalles más pequeños, como los sub-paneles, no lo son. Esto se debe a que la propiedad CollisionFidelity para las paredes estaba configurada como Caja. No necesitábamos la precisión para estos paneles, por lo que para ahorrar costos de rendimiento, est