Criar movimento em qualquer ambiente dentro de uma experiência ajuda-a a se sentir mais imersiva e realista para o nosso mundo, seja isso de movimento de árvore ambiente, reações de portas do jogador ou até mesmo caixas que se movem quando elas colidem com elas. O Studio tem muit
Criando a Tempestade
A tempestade passou por muitas iterações antes de nós termos nos deitado no que é vivo no Mistério do Duvall Drive. No início, pensamos na tempestade como um grande pilão de obsidiana, e em iterações posteriores, consideramos-a um portal gigante para o espaço corrupto. Depois de experimentar com muitas diferentes tempestades com olhos e sensações únicos, chegamos a uma tempestade com um olho central menor porque:
- A tempestade deve dar aos jogadores uma sensação de impacto deste evento no mundo , incluindo árvores soprando e dejetos voando ao redor.
- O vortex giratório da própria nuvem deve dar aos jogadores uma olhada no portal central sem revelar tudo . Isso encorajaria os jogadores a investigar mais de perto para ver o que está acontecendo.
- O ponto de luz mais apertado nos permitiria concentrar-nos na composição da casa , que é tanto o personagem principal quanto onde a maioria do gameplay está localizada.
Para fazer com que a tempestade se sinta dinâmica, agressiva e sempre mudando dentro de seu ambiente, usamos os seguintes sistemas e recursos:
- TweenService > para movimento de nuvem.
- Mudanças de iluminação - Para criar a nuvem para relâmpago nuvem.
- Raios - Para a "iluminação volumétrica" e os relâmpagos.
- Emissores de Partículas - Para o lixo voar até o portal e voar ao redor devido ao vento soprando.
- Animações > - Para as árvores que estavam soprando no vento.
Adicionando Nuvens com Texturas
Enquanto nuves dinâmicas são ótimas para nuvens de alta altitude, realistas, nós precisávamos de algo que se sentisse dramát
Como cada malha de nuvem necessária para ser massiva para cercar a casa e transmitir o quão enorme a tempestade estava, sabíamos que precisávamos azulejar a textura que queríamos usar nas malhas individuais para que fosse repetida repetidamente em toda a superfície da malha. Nós testamos os materiais que fazemos para a nuvem nessas peças simples, e então aplicamos-os ao vortex!
Ao contrário de emissores de partículas ou feixes, malhas nos permitem bater luz de cada malha, o que foi importante quando queríamos implementar raios de choque entre nuvens. Nós também modelamos no torcimento para que o bounce de luz de cada malha fosse como se tivesse profundidade! Isso foi importante especialmente em situações onde os requisitos de desempenho da experiência derrubaram os níveis de qualidade de nossos objetos de aparência da superfície.
Meshes de Nuvem Rotativos
Depois de termos ficado satisfeitos com a aparência geral das nuvens, precisávamos fazer com que elas se movimento! Tínhamos as formas gerais de cada camada de nuvem em local, mas foi preciso algumas tentativas e erros para garantir que o efeito de rotação fosse bom na prática. Nós inicialmente tentamos usar 约束 para introduzir velocidade que
Queríamos um método fácil de usar para girar instâncias que estavam muito longe para serem interativas, como nuvem, ou muito pequenas ou decorativas para serem important
Como em muitos casos na demonstração, usamos uma etiqueta LocalSpaceRotation para que pudéssemos gerenciar instâncias afetadas no Studio usando um plugin de etiqueta de instância. Nós utilizamos apenas um único LocalScript que gerenciou todas as instâncias afetadas usando o plugin de etiqueta de instância CollectionService para que não tivéssemos um monte de
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()
objInfo é um mapa que tem informações para todos os objetos relevantes, como sua velocidade de rotação e eixo. Observe que não chamamos
Nós giramos as instâncias na função Update conectada ao bate-p
local parentTransformif parentObj:IsA("Model") thenif not parentObj.PrimaryPart then-- a parte primária pode não estar disponível aindacontinue -- espere pela parte primária para se replicarendparentTransform = 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
Verificamos um válido Model.PrimaryPart para ser definido para lidar com streaming. Se uma atualização for chamada em nosso objeto enquanto um Model.PrimaryPart (que pode apontar para uma malha filha) ainda não estiver em streaming, nós simplesmente pularíamos a atualização. O sistema at
Projetando Golspes de Relâmpago
Como o Studio não oferece um gerador de raios do caixote, e o sistema de partículas tinha algumas limitações que não funcionariam para os ataques de raios do herói, nós tivemos que ser criativos com uma solução para os ataques de raios do herói. Nós decidimos em dois sistemas principais para compor os raios de textura para os heróis que estão vindo do olho da tempestade
Texturando Raios
Normalmente, usamos uma ferramenta de sequenciador ou de linha do tempo para impulsionar o tempo de um efeito de choque de iluminação como este, mas, como o Studio ainda não oferece essa funcionalidade, decidimos escrever scripts que controlariam o tempo de um choque de iluminação. O scripting desse efeito é muito simples, mas alcança os seguintes objetivos importantes:
- Os elementos dos golpes de relâmpago, como suas texturas, brilho e atrasos, são aleatórios com cada golpe.
- As mudanças de áudio e pós-FX estão sincronizadas com as mudanças de strike FX.
- Jogadores que estão dentro ou na área corrompida não poderiam vê-los ou ouvi-los.
Temos um Script do lado do servidor que calcula vários parâmetros e tempos, envia-os para todos os clientes e aguarda uma quantidade aleatória de tempo:
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, nós preenchemos a estrutura de informação, para que todos os clientes obtenham os mesmos parâmetros.
No lado do cliente ( LightningVFXClient ) verificamos se este cliente deve executar o FX:
local function LightningFunc(info)
…
-- Sem FX ao ar livre
if inVolumesCheckerFunc:Invoke() then
return
end
-- Sem FX quando não estiver no mundo "normal"
if not gameStateInfoFunc:Invoke("IsInNormal") then
return
end
…
Além disso, executamos a sequência para definir texturas, posições e brilho, executamos adolescentes e usamos task.wait(number) . Os parâmetros aleatórios são da estrutura de informação que recebemos do servidor e alguns números são fixos.
beam.Texture = textures[info.textIdx]beamPart.Position = Vector3.new(info.center.X + og_center.X, og_center.Y, info.center.Y + og_center.Z)-- Limparbeam.Brightness = 10ppCC.Brightness = maxPPBrightnessppBloom.Intensity = 1.1bottom.Position = top.PositiontweenBrightness:Play()tweenPPBrightness:Play()tweenPPBrightness:Play()tweenBottomPos:Play()tweenBrightness.Completed:Wait()-- áudioif 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 se um jogador está dentro de casa, usamos uma função de ajuda inVolumesCheckerFunc, que vai por cima de volume pré-posicionado aproximando áreas dentro de áreas, e verifica se a posição do jogador está dentro de qualquer deles (PointInABox). Nós poderíamos ter detectado detecção baseada em toque, mas encontramos que quando um jogador se
Para verificar se um jogador está em áreas corrompidas, invocamos uma função de ajuda gameStateInfoFunc, que verifica o estado de jogo atual. Para jogar um som aleatório de um pasta, usamos também uma função de ajuda PlayOneShot. Para os relâmpagos em si, esses foram super fáceis de criar no Photoshop; nós desenhamos uma linha curva, então adicionamos
Utilizando sistemas de emissão de partículas
Os heróis relâmpagos são apoiados por um sistema de partículas que suga uma nuvem distante criando a impressão de uma camada de nuvem no fundo capturando luz de distantes ataques, ou iluminação de nuvem para nuvem. Nós alcançamos esse efeito através de um sistema de partículas muito simples que acende uma nuvem em um bilhete de nuvem no lado periférico da nuvem principal. O sistema emite uma nuvem de partículas periodically com uma curva de transparência
Fazendo Árvores Soprar no Vento
Depois de termos as nuvens e o relâmpago funcionando da maneira que queríamos, nós então precisávamos adicionar dois outros componentes principais de uma tempestade: o vento e a chuva! Esses elementos apresentaram alguns desafios, incluindo precisar trabalhar dentro dos limites atuais do nosso sistema de física e efeitos especiais. Por exemplo, fazer as árvores se mover
Sabíamos que realmente vendíamos o efeito do vento e chuva, precisávamos de árvores em si mesmas para se movimento. Existem algumas maneiras de fazer isso dentro do motor, incluindo mover peças usando plugins que estão disponíveis publicamente, usando TweenService ou animando modelos diretamente. Para nossos propósitos,
Começamos por derrotar várias árvores do Pacote de Modelos de Endorse - Floresta Assets. Como essas árvores já existiam, e nossa experiência teve lugar no noroeste do Pacífico, isso nos salvou um tempo cedo de ter que criar cada modelo de árvore.
Depois de escolhermos nossas árvores, sabíamos que precisávamos delas. Skinning um malha é o ato de adicionar articulações (ou ossos) à uma malha em outro aplicativo de modelagem 3D, como Blender ou Maya, então aplicar a influência às articulações
Sabíamos que queríamos economizar tempo e reutilizar a mesma animações, então construímos nossa primeira rede de árvores e certificamos-nos de que os nomes das juntas fossem genéricos, pois queríamos usar esses nomes nas juntas para os outros árvores. Nós também sabí
Uma vez que tivéssemos criado nossas articulações/ossos, era hora de criar uma animação de teste para mover ao redor de todas as articulações e ossos no Studio para ver se ela se movia da maneira que queríamos. Para fazer isso, tínhamos que importar a árvore no Studio atrav
Depois de estarmos satisfeitos com os resultados naquele árvore, era hora de testar a mesma animação em uma árvore diferente! Nós já sabíamos que seria a mesma animação entre as diferentes ricas para cada digitarde árvore, então nós apenas certificamos que nossa animação parecia que era geral o suficiente para trabalhar entre uma alta Redwood e um robusto Beechwood árvore!
Para fazer isso, nós pegamos a árvore de Beechwood daquele Forest Pack e construímos um rig / plataforma / equipamentosimilar, usando o mesmo nome exato para as juntas. Isso foi para que a animação que importamos anteriormente pudesse ser aplicada a essa árvore também. Como as animações eram todas baseadas em articulações giratórias, não importava o quão grande, pequeno, alto ou largo a árvore fosse!
Depois de termos rigado e pintado a árvore de Beechwood, podemos então importá-la e aplicar a mesma animação exata. Isso significou iteração e edição apenas necessárias para serem feitas em um arquivo, e também salva na performance com menos animações quando a experiência é executada.
Uma vez que tínhamos todos os tipos de árvores que queríamos animações, nós fazemos cada um em pacotes para que pudéssemos continuar editando e atualizando enquanto estamos jogando várias das animações ao redor da área principal da experiência. Como sabíamos que eles tinham um custo de desempenho, nós os usamos de forma ponderada ao redor da casa onde o efeito foi mais valioso! No futuro, à medida que isso se
Fazendo Números da Tempestade
Queríamos que a chuva parecesse pesada, e para que a névoa e os débris pudessem soprar através das árvores. Para fazer isso, configuramos algumas peças invisíveis para atuar como volume de partículas com filho emissor de partículas imediatamente abaixo das grandes nuvens de tempestade. Devido ao limite de
As partículas de chuva usaram uma nova propriedade de emissor de partículas ParticleEmitter.Squash que permite que você faça uma partícula mais longa ou coloque. É particularmente
Para o pó, névoa e folhas voando, era muito mais simples adicionar um único volume maior cobrindo menos áreas porque não precisávamos de uma tonelada de partículas em uma vez. Começamos por configurar um volume e obtive a frequência das partículas onde queríamos elas.
Depois disso, nós fizemos nossas texturas de folha soprando e vento, e configuramos as partículas para que todas girassem/se movêssem em diferentes taxas e começássem em diferentes velocidades. Isso significou que as maiores partículas de névoa interagiriam mais naturalmente e não parecessem tão repetitivas, especialmente dado o tamanho delas.
O resultado foi alguma ação grande entre as árvores se movendo, a janela soprando e o relâmpago para criar o efeito da tempestade ao redor do olho central da tempestade.
Configurando o Olho da Tempestade
O olho de pedra quebrado com um núcleo brilhante está destinado a dar às jogadores a primeira dica de que algo sinistro e arcano está ocorrendo na casa que eles devem explorar mais a frente. Como nossa cena é escura e o olho está muito longe do céu, foi importante criar uma silhueta de pedra quebrada de
A distância do jogador também significou que podíamos confiar inteiramente em um mapa normal para os detalhes da superfície do olho, para que o malha fosse apenas uma esfera plana! Nós esculpimos os detalhes em uma malha de polímero de alta performance e assamos seu mapa normal em uma esfera muito menor para que pudéssemos obter todo o detalhe sem o custo de desempenho massivo.
Para adicionar um senso sobrenatural aos olhos e para enfatizar sua presença, decidimos criar uma magma brilhante e neon que se espalharia por seus rachaduras. Embora não haja um canal emissivo para a aparência
Outro desafio que enfrentamos ao criar o olho foi imposto pelo nosso uso de streaming combinado com a distância do malhado jogador. Dado a centralidade desta est
Pudemos adicionar movimento ao olho e seus anéis graças ao mesmo script que usamos para girar as malhas de mundo. Para um toque final, decidimos adicionar uma dica à presença de outro mundo além
Fazendo a Expansão da Pantry
Uma das coisas mais divertidas para produzir foram os espaços corrompidos, onde podíamos subverter as expectativas dos jogadores de realidade alterando-as literalmente ao seu redor. Por exemplo, no quebra-cabeça do pai, queríamos emular um momento semelhante a um pesadelo, onde não importa o quão rápido você executar, o salão parece ficar cada vez mais longo. Decidimos fazer uma pantry expandida que ficaria longe dos jogadores enqu
Configuramos isso com um simples movimento das paredes, e um layout inteligente de nossas salas que apareceria em ambos os lados da pantry. No estado normal da sala, a pantry era um corredor simples, mas no espaço corrupto, era realmente muito maior com várias alas e uma parede falsa!
A parede falsa era um grupo de modelos que nós movíamos de volta quando os jogadores entravam em um volume de gatilho, que era uma parte transparente antes na pantry que eles caminhariam através. Aquele gatilho também foi usado em um script semelhante aos usados em todas as nossas portas, que chamou o TweenService para mover de uma posição de início e término para a parede. N
Como TweenService é um sistema tão geral, todo o nosso modelo de dados de parede tinha que conter os mesmos componentes. Por exemplo, a seguinte imagem é um exemplo de um script de porta geral que chama um som definido por um "valor" abaixo do modelo "Grow_Wall".
Aquele mesmo script, com algumas modificações no seguinte código de exemplo, também gerou áudio para o movimento da panela. Isso adicionou muito ao movimento!
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, --Velocidade/Tempo de Ajuste da Porta
Enum.EasingStyle.Quart, --Estilo de Aplainar
Enum.EasingDirection.InOut, --Direção de fácil
0, --Repetir Contagem
false, --Reverter verdade
0 --Atraso
)
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
--modelo["Porta"]:Jogar()
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
--print("toque")
playersNear[player] = 1
if doorState == DoorState.Closed then
StartOpening()
end
end
end
end
Uma vez que tínhamos a parede falsa se movendo para trás da sala, precisávamos do resto do conteúdo para se mover com ele. Para fazer isso, precisávamos de todos os itens soltos na pantry para serem soldados na parede enquanto se movia. Para fazer isso, precisávamos usar Con約束 Con限 , para que pêssemos em todos os itens na pantry para se mover como um ú
Fazendo a Corrupta Treehouse
O Studio é um motor baseado em física fantástico que você pode usar para criar tudo, desde um portão balançando até uma plataforma girando. Com nossa demonstração, queríamos usar a física para criar um senso de realismo em um conjunto de ambientes de outra forma não realista. Usando apenas alguns constrangimentos , você pode criar alguns cursos de obstáculos divertidos e desafiadores dentro de suas próprias experiências!
Constraints > são um grupo de motores baseados em pisos que alinham objetos e limita comportamentos. Por exemplo, você pode usar uma restrrição de pano para conectar-se a objetos para manter-os em
Quando os jogadores chegaram à área principal do quebra-cabeça, eles foram recebidos com uma visão familiar no Roblox: uma pista de obstáculos. Essa pista de obstáculos particular consistia em várias plataformas giratórias e paredes giratórias, juntamente com "áreas seguras" que progrediam a história. Nós nos concentraremos nos elementos giratórios/giratórios.
Por que usamos restrições aqui? Porque TweenService ou outros métodos não moveriam o jogador enquanto eles estavam em cima deles. Sem o objeto se movendo o jogador, alguém poderia pular em uma plataforma e ela giraria a partir deles. Em vez disso, queríamos que os jogadores navegassem atrav
Para fazer isso, precisávamos usar primeiro recursos de nosso kit atual e adicionar qualquer novo conteúdo para um efeito visual. Nós fizemos algumas paredes e plataformas incompletas com buracos nelas para contar a história da avó que construiu a casa na árvore. Como não queríamos criar um monte de plataformas e peças de base diferentes, nós fizemos 4 peças de base diferentes e peças de carvalho separ
Sabíamos que, uma vez que estávamos usando restrições, não poderíamos ancorar essas malhas, pois elas não se moveriam mesmo com a presença de uma restrição/mot
Agora era hora de configurar o comportamento real da restrição de dobradiça, e adicionar os acessórios que agiriam como a orientação da peça e a restrição juntos. Colocamos o acessório de giro na Motor_Turn, que as peças de caminhada estavam soldadas, e out
Para manter as plataformas girando em uma velocidade constante, então configuramos as propriedades HingeConstraint.AngularVelocity, HingeConstraint.MotorMaxAcceleration e HingeConstraint.MotorMaxTorque para valores que permitiriam movimento e impediriam interrupção se um jogador pular sobre ela.
Agora, precisávamos fazer as paredes giratórias. As paredes precisavam girar em seu centro aparente, e sabíamos que queríamos que elas pudessem lidar com qualquer orientação em relação ao resto do nível. Como as plataformas, nós construímos essas para que todas as paredes fossem ancoradas e soldadas ao Motor_Turn.
Usamos objetos Texture em cima de objetos SurfaceAppearance para adicionar alguma variação aos nossos materiais de base. Texturas</
Depois de testarmos algumas plataformas e paredes giratórias, nós fizemos várias variações e jogamos com seu posicionamento para garantir que o obstacle course fosse desafiador, mind-bending e também limpo onde o jogador precisava ir! Levou algumas ajustes para ambos os seus valores e posições para garantir que eles fossem bem. Tivemos vários pontos onde as plataformas e paredes estavam bater umas
Se você não tiver certeza de que seus objetos físicos estão aterrissando, você pode ativar Colisão fiel a partir do widget Opções de Visão no canto superior direito da janela de visualização 3D.
Como você pode ver abaixo das portas/janela, os buracos são visíveis, mas os detalhes menores, como sub-panela, não são. Isso é porque a propriedade CollisionFidelity para as paredes foi definida como Caixa. Não precisávamos da precisão para esses painéis, então, para economizar o custo de desem