La creazione di movimento in qualsiasi ambiente all'interno di un'esperienza aiuta a sentirsi più coinvolto e realistico del nostro Mondo, sia che si tratti di un movimento ambientale, di porte reattive dall'interazione del giocatore o anche di scatole che si muovono quando si scontra con
Creazione della tempesta
La tempesta è passata attraverso molte iterazioni prima che noi ci siamo seduti su ciò che è live in The Mystery of Duvall Drive. All'inizio, abbiamo pensato alla tempesta come a un pilastro di obsidiana gigante, e in altre iterazioni abbiamo considerato che fosse un portale gigante allo Spaziocorrotto. Dopo aver sperimentato con molti diversi temporali con un aspetto e una sensazione unici, siamo arrivati a una tempesta con un "eye" centrale più piccolo perché:
- La tempesta dovrebbe dare ai giocatori un senso dell'impatto di questo evento sul mondo , tra cui alberi che fanno esplodere e rifiuti volare in giro.
- Il vortice spinoso delle stesse nuvole dovrebbe dare ai giocatori uno sguardo al portale centrale senza rivelare tutto . Ciò incoraggierebbe i giocatori ad investigare più da vicino per vedere cosa sta succedendo.
- Il punto di luce più stretto ci consentirebbe di concentrarci sulla composizione della casa , che è sia il personaggio principale che dove la maggior parte della giocabilità si trova.
Per rendere la tempesta dinamica, aggressiva e sempre cambiante all'interno del suo Ambiente, abbiamo utilizzato i seguenti sistemi e funzionalità:
- TweenService > - Per il movimento nel cloud.
- Modifiche all'illuminazione - Per la creazione della nuvola per creare un fulmine.
- Raggi) > - Per la "luce volometrica" e i fulmini.
- Emitteri di particelle - Per i rifiuti che volano fino al portale e volano intorno a causa del vento in corso.
- Animazioni > - Per gli alberi che ondeggiavano nel vento.
Aggiungere nuvolose con texture
Mentre le nuvole dinamiche sono grandi per le nuvole di alta quota in real time, abbiamo avuto bisogno di qualcosa che
Poiché ogni mesh di cloud necessario per essere grande per circondare la casa e trasmettere quanto era enorme la tempesta, sapevamo che avevamo bisogno di piastrellare la texture che volevamo utilizzare sui singoli mesh del cloud in modo che fosse ripetuta in modo pesante su tutta la superficie del Mesh, maglia. Abbiamo testato i materiali che abbiamo creato per il cloud su queste parti semplici, quindi li abbiamo applicati al vortice!
A differenza degli emitteri di particelle o dei raggi, le maglie ci hanno permesso di essere in grado di riflettere la luce da ciascuna Mesh, maglia, il che era importante quando volevamo implementare il fulmini tra cloud. Abbiamo anche modellato nel tessuto per consentire che il riflesso della luce avesse la profondità! Questo era importante specialmente in situazioni in cui le richieste di performance dell'esperienza hanno abbassato i livelli di qualità della nostra apparenza di superficie.
Meshes della nuvola rotante
Dopo essere stati soddisfatti dall'aspetto visivo complessivo delle nuvolose, abbiamo dovuto metterlo in movimento! Abbiamo avuto le forme generali di ogni livello di nuvola in Posto, ma ci sono voluti alcuni tentativi e errori per assicurarci che l'effetto di rotazione fosse buono nella pratica. Inizialmente abbiamo utilizzato vincoli
Volevamo un metodo facile da usare per ruotare le istanze che fossero troppo lontane per essere interattabili, come le nuvole, o troppo piccole o decorative per essere important
Come in molti casi nella demo, abbiamo utilizzato un tag LocalSpaceRotation in modo che potessimo gestire le istanze interessate in Studio utilizzando un plugin di tag delle istanze. Utilizziamo solo un singolo LocalScript che gestisce tutte le istanze contrassegnate utilizzando il plugin di tag delle istanze usando il CollectionService per non avere un sacco di script da manten
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 è una mappa che ha informazioni per tutti gli oggetti rilevanti, come la loro velocità di rotazione e asse. Nota che non chiamiamo
Abbiamo rotato le istanze nella funzione Update connessa al battito cardiaco. Abbiam
local parentTransformif parentObj:IsA("Model") thenif not parentObj.PrimaryPart then-- la parte principale potrebbe non essere ancora in streamingcontinue -- aspetta che la parte primaria si 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
Abbiamo controllato per un valido Model.PrimaryPart per essere impostato per gestire lo streaming. Se un aggiornamento è stato chiamato sul nostro oggetto mentre un Model.PrimaryPart (che può puntare a una Mesh, magliafiglia) non è stato ancora streamed, avremmo saltato l'Aggiornarmento. Il sistema attual
Progettazione degli Strizzoni di Luce
Poiché Studio non offre un generatore di fulmini in scatola, e il sistema di parti aveva alcune limitazioni che non funzioneranno per gli attacchi fulmini del eroe, abbiamo dovuto essere creativi con una soluzione per gli attacchi fulmini del eroe. Abbiamo deciso su due sistemi principali per comporre gli effetti degli attacchi fulmini del eroe che vengono dall'occhio della tempesta sono scripted effetti di luce che rivel
Raggi di Texturing
Di solito utilizziamo uno strumento sequencer o cronometristico per guidare il timing di un effetto di luce come questo, ma poiché Studio non offre ancora questa funzionalità, abbiamo deciso di scrivere gli script che controllerebbero il timing di un colpo di luce come questo, ma il scripting di questo effetto è abbastanza semplice, ma raggiunge i seguenti obiettivi importanti:
- Gli elementi degli attacchi del fulmine, come le loro texture, la luce e i ritardi, sono randomizzati con ogni attacco.
- Le modifiche audio e post-FX sono in sincronizzazione con gli strike FX.
- I giocatori che sono in ospedale o nell'area corrotta non potrebbero vederli o ascoltarli.
Abbiamo un lato server Script che calcola vari parametri e tempi, li invia a tutti i client e aspetta un certo numero di tempi casuali:
local function LightningUpdate()
while true do
task.wait(rand:NextNumber(3.0, 10.0))
local info = CreateFXData()
lightningEvent:FireAllClients(info)
end
end
Dentro CreateFXData , inseriamo la struttura informativa, in modo che tutti i client ottengano gli stessi parametri.
Al lato client ( LightningVFXClient ) controlliamo se questo client dovrebbe eseguire il FX:
local function LightningFunc(info)
…
-- nessun FX quando sei dentro
if inVolumesCheckerFunc:Invoke() then
return
end
-- nessun FX quando non in "normale" Mondo
if not gameStateInfoFunc:Invoke("IsInNormal") then
return
end
…
Inoltre, eseguiamo la sequenza per impostare texture, posizioni e brillantezza, run tweens e use task.wait(number) . I parametri randomizzati sono dalla struttura informativa che abbiamo ricevuto dal Servere alcuni numeri sono fissi.
beam.Texture = textures[info.textIdx]beamPart.Position = Vector3.new(info.center.X + og_center.X, og_center.Y, info.center.Y + og_center.Z)-- Cancellabeam.Brightness = 10ppCC.Brightness = maxPPBrightnessppBloom.Intensity = 1.1bottom.Position = top.PositiontweenBrightness:Play()tweenPPBrightness:Play()tweenPPBrightness:Play()tweenBottomPos:Play()tweenBrightness.Completed:Wait()-- audio/suonoif audioFolder and audioPart thenif audioFolder.Value and audioPart.Value thenaudioUtils.PlayOneShot(audioObj, audioFolder.Value, audioPart.Value)endendtask.wait(info.waitTillFlashes)-- and so on
Per controllare se un giocatore è in casa usiamo una funzione inVolumesCheckerFunc , che va oltre i volumi pre-postizionati che si trovano nelle aree interne, e controlla se la posizione del giocatore è all'interno di uno qualsiasi di essi (PointInABox). Potremmo aver utilizzato la rilevazione basata sul tocco, ma abbiamo
Per controllare se un giocatore è in aree corrotte, invochiamo una funzione gameStateInfoFunc che controlla lo stato del gioco corrente. Per giocare un suono casuale da un cartella, abbiamo anche utilizzato una funzione PlayOneShot . Per i fulmini stessi, questi sono stati molto facili da creare in Photoshop; abbiamo disegnato una linea super snodata, quindi
Utilizzo dei sistemi emittenti di particelle
I fulmini dei eroi sono supportati da un sistema di particelle che suggerisce l'eruzione di un lato di nuvole nel background facendo l'impressione di uno strato di nuvole nel background prendendo la luce dai colpi lontani, o illuminazione cloud-to-cloud. Abbiamo raggiunto questo effetto attraverso un sistema di particelle molto semplice che fa clignotare una nuvola billboard sulla periferia della nuvola principale. Il sistema emette un periodo di particelle cloud periodico con una curva di trasparenza casualizzata:
Fare gli alberi a raffica nel vento
Dopo aver avuto le nuvole e il fulmine che funzionassero come volevamo, abbiamo poi dovuto aggiungere due altri principali componenti di una tempesta: il vento e la pioggia! Questi elementi hanno presentato alcuni sfide, tra cui il dover lavorare all'interno delle limitazioni attuali dei nostri sistemi fisici e effetti speciali. Ad esempio, rendere gli alberi a muoversi con il
Sapevamo che davvero vendere l'effetto del vento e della pioggia, abbiamo bisogno degli alberi stessi per Sposta. Ci sono alcuni modi in cui puoi farlo all'interno del motore, inclusa la spostamento delle parti usando ugin che sono pubblicamente disponibili, usando TweenService , o animando i modelli direttamente. Per i
Abbiamo iniziato con lo skinning di diversi alberi dal Endorse Model Pack - Forest Assets . Poiché questi alberi esistono già, e la nostra esperienza si è svolta nel Nord-ovest Pacifico, ci ha risparmiato un po 'di tempo inizialmente dal dover creare ogni modello di albero.
Dopo aver scelto le nostre alberi, sapevamo che avevamo bisogno di pellevarli. Skinning un mesh è l'atto di aggiungere articolazioni (o ossa) a un Mesh, magliain un'altra applicazione di modellazione 3D, come Blender o Maya, quindi applicare l'influen
Sapevamo che volevamo risparmiare tempo e riutilizzare la stessa animazioni, quindi abbiamo costruito la nostra prima riga di alberi e ci siamo assicurati che i nomi congiunti fossero generici poiché volevamo utilizzare questi nomi nelle rig per gli altri alberi
Una volta creati i nostri arti/osso, era ora di creare un'animazione di test per muoversi in tutto gli arti e osso in Studio per vedere se si muoveva come volevamo. Per fare questo, abbiamo dovuto importare l'albero in Studio attraverso il Custom Rig impostazione
Dopo che siamo stati felici dei risultati su quell'albero, era ora di testare la stessa animazione su un altro albero! Sapevamo già che sarebbe stata la stessa animazione tra le diverse righe per ciascun inserisci / scrividi albero, quindi abbiamo assicurato che la nostra animazione assomigliasse a essere generale abbastanza da poter lavorare tra un grande albero di Redwood e un robusto albero di Beechwood!
Per fare questo, abbiamo preso l'albero di Beechwood da quel Forest Pack e costruito un Piattaforma di testsimile, usando lo stesso nome esatto per le giunte. Questo ha reso l'animazione che avevamo precedentemente importato potrebbe essere applicata a questo albero anche. Poiché le animazioni sono basate su tutte le articolazioni rotanti, non importa quanto grande, piccolo, alto o largo sia l'albero!
Dopo aver rigato e skinato l'albero di Beechwood, potremmo quindi importarlo e applicare la stessa animazione esattamente. Ciò ha significato che l'iterazione e l'editing sono necessarie solo in un file, e viene salvato anche sulle prestazioni con meno animazioni quando si esegue l'esperienza.
Una volta che avevamo tutti i tipi di albero che volevamo animati, li abbiamo creati in pacchetti in modo da poter continuare a modificare e aggiornare mentre giocavamo a diversi delle animazioni intorno all'area principale dell'esperienza. Poiché sapevamo che avevamo un costo di prestazione, li abbiamo utilizzati con parcimonia intorno alla casa in cui l'effetto era il più valore! In futuro, man mano che questo
Fare Debris Tempesta
Volevamo che la pioggia apparisse pesante, e per il fumo e le parti di Debris che soffiavano attraverso gli alberi. Per fare questo, abbiamo configurato alcune parti invisibili per agire come volumi di particelle con figli emittenti di particelle immediatamente sotto le grandi nuvole di tempesta. A causa del limite di contanti particelle in Studio, non
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 Struttura, just
Per la nebbia, la nebbia e le foglie che soffiano attraverso, era molto più semplice aggiungere un singolo volume di parti più grande coprendo meno aree poiché non avevamo bisogno di un sacco di particelle in esecuzione contemporaneamente. Iniziamo impostando un volume e ottenere la frequenza delle particelle in cui volevamo loro.
Dopo questo, abbiamo fatto le nostre luci soffiando e le texture del vento, e impostato le parti per tutte le rotazione/spostamento a rate differenti e iniziare a diverse velocità. Ciò ha significato che le più grandi particelle di nebbia interagissero più naturalmente e non sembrassero così tanto come una Strutturaripetitiva, soprattutto dato le loro dimensioni.
Il risultato è stato un'azione tra gli alberi in movimento, la finestra che esplode e l'arcobaleno per creare l'effetto della tempesta che circonda l'occhio centrale della tempesta.
Configurazione dell'Occhio della tempesta
L'occhio di pietra frattato con un nucleo brillante è destinato a dare ai giocatori la prima idea che ci sia qualcosa di sinistro e arcano che accade nella casa che dovrebbero esplorare ulteriormente. Poiché la nostra scena è buia e l'occhio è lontano nel cielo, era importante creare una silhouette di pietra frattata cred
La distanza dal giocatore ha anche significato che potremmo contare interamente su una mappa normale per i dettagli della superficie dell'occhio in modo che il mesh sia solo una sfera pura! Abbiamo scolpito i dettagli in un mesh ad alta performance e abbiamo bruciato la sua mappa normale su una sfera molto inferiore in modo che potessimo ottenere tutto quel bel dettaglio senza il costo di performance massivo.
Per aggiungere un senso sobrenaturale agli occhi e sottolineare la sua presenza, abbiamo deciso di creare un magma magico che avrebbe penetrat
Un'altra sfida che abbiamo incontrato quando abbiamo creato l'occhio è stata impostata dalla nostra utilizzazione di streaming combinata con la distanza dell'
Siamo stati in grado di aggiungere movimento all'occhio e ai suoi anelli grazie allo stesso script che abbiamo usato per ruotare le maglie mesh. Per un tocco finale, abbiamo dec
Creazione della panteria espandente
Una delle cose più divertenti da produrre sono stati gli spazi corrotti, in cui potevamo sovrascrivere le aspettative dei giocatori della realtà letteralmente cambiandola intorno a loro. Ad esempio, nel puzzle del padre abbiamo voluto emulare un momento simile a un incubo in cui non importa quanto velocemente Eseguire, la stanza sembra che continua ad allungarsi. Decidemmo di creare una pantry espandente che potesse allontanarsi dai giocatori mentre
Abbiamo impostato questo con un semplice movimento delle pareti, e una soluzione di layout intelligente delle nostre stanze che apparirebbe su entrambi i lati del pantry. Nell' stato normale della stanza, il pantry era un semplice corridoio, ma nello Spaziocorrotto, era in realtà molto più lungo con più di un paio di ali e una parete falsa!
Il falso muro era un gruppo di modelli che avremmo spostato nel momento in cui i giocatori entrarono in un volume di attivazione, che era una parte trasparente prima nella pantry che sarebbero andati attraverso. Quell'attivazione era anche usata in uno script simile a quelli usati su tutte le nostre porte, che chiamava il TweenService per spostare l'operazione di tweening da un'area all'altra. Usavamo i
Poiché TweenService è un sistema così generico, tutto il nostro modello di dati del muro doveva contenere gli stessi componenti. Ad esempio, la seguente immagine è un esempio di uno script di porta generico che chiama un suono definito da un "valore" sotto il modello "Grow_Wall".
Lo stesso script, con alcune modifiche nel seguente esempio di codice, ha anche attivato l'audio per il movimento della panteria. Questo ha aggiunto molto al 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, --Velocità/Velocità della porta Tween
Enum.EasingStyle.Quart, --Stile di facilezza
Enum.EasingDirection.InOut, --Direzione di facilezza
0, --Ripetere il conteggio
false, --Ritorna vero
0 --Ritardo
)
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
--modello["Door"]:Gioca()
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("触摸")
playersNear[player] = 1
if doorState == DoorState.Closed then
StartOpening()
end
end
end
end
Una volta avessimo il muro falso che si muoveva all'indietro della stanza, avevamo bisogno del resto del contenuto per spostarlo con esso. Per farlo, abbiamo avuto bisogno di tutti gli oggetti liberi sulla panteria per essere saldati alla parete mentre si muoveva. Utilizzando vincoli di saldatura, siamo stati in grado di saldare tutti gli oggetti sulla panteria per
Casa sull'albero corroso
Studio è un fantastico motore fisicamente basato che puoi usare per creare tutto, da un cancello oscillante a una Piattaformaruotante. Con la nostra demo, abbiamo voluto usare la fisica per creare un senso di realismo in un set di ambienti in realtà non realistici. Usando solo alcuni vincoli , puoi creare alcuni percorsi di ostacoli divertenti e impegnativi all'interno delle tue esperienze!
Constraints sono un gruppo di motori fisicamente basati che allineano gli oggetti e limitano i comportamenti. Ad esempio, puoi utilizzare una 限制 della corda per connetterti agli oggetti in modo da mant
Una volta che i giocatori sono scesi nell'area principale del puzzle, sono stati accolti con un viso familiare su Roblox: una gara di ostacoli. Questa particolare gara di ostacoli consisteva in più piattaforme giranti e mura rotanti, oltre a "aree sicure" che progressavano la storia. Ci concentriamo sugli elementi giranti/rotanti.
Perché abbiamo utilizzato le restrizioni qui? Perché Class.TweenService o altri metodi non potrebbero muovere il giocatore mentre stava su di loro. Senza che l'oggetto si muova il Giocatore, qualcuno potrebbe saltare su una piattaforma e girerà fuori da sotto di loro. Invece, abbiamo voluto che i giocatori navigasser
Per fare questo, abbiamo prima utilizzato le risorse dal nostro kit attuale e abbiamo aggiunto qualsiasi nuovo contenuto per un effetto visivo. Abbiamo creato alcune pareti e piattaforme incompiute con fori in loro per raccontare la storia della nonna che ha costruito la casa sull'albero. Poiché non volevamo creare un sacco di parti di base e pezzi di pareggio separati, abbiamo creato 4 pezzi di base e paregg
Sapevamo che poiché stavamo usando vincoli, non saremmo in grado di ancorare queste maglie perché non si muoveranno nemmeno con la presenza di un vincolo/mot
Era ora di impostare il comportamento reale della limitazione del ginocchio stesso, e aggiungere gli accessori che avrebbero funzionato come l'orientamento della parte e la limitazione insieme. Abbiamo posizionato l'accessorio di volto sulla Motor_Turn, che i pezzi del percorso di avvio
Per mantenere le piattaforme in rotazione ad una velocità costante, abbiamo quindi impostato le proprietà HingeConstraint.AngularVelocity, HingeConstraint.MotorMaxAcceleration e HingeConstraint.MotorMaxTorque a valori che consentono il movimento e l'interruzione se un giocatore salta su di essa.
Ora abbiamo dovuto fare le pareti rotanti. Le pareti hanno dovuto ruotare sul loro centro apparente e sapevamo che volevamo che fossero in grado di gestire qualsiasi orientamento rispetto al resto del Livello. Come le piattaforme, abbiamo costruito queste in modo che tutte le pareti fossero non ancorate e saldate al Motor_Turn.
Abbiamo utilizzato Texture oggetti sopra 6> Class.Surface oggetti per aggiungere alcune variazioni alle nostre basi materiali.
Dopo aver testato alcune piattaforme e pareti rotanti, abbiamo fatto diverse variazioni e giocato con il loro posizionamento per assicurarci che il corso degli ostacoli fosse impegnativo, coinvolgente e anche chiaro dove il giocatore doveva Vai! Ci sono stati diversi punti in cui le piattaforme e le pareti si stavano scontrando o gli ambienti, ma con alcune mosse intorno e test frequenti, siamo stat
Se non sei sicuro di ciò che i tuoi oggetti fisici stanno colpendo, puoi attivare Collisioni fidelity dalla visualizzazione Opzioni widget nell'angolo in alto a destra della finestra3D.
Come puoi vedere sotto le porte/le finestre sono visibili, ma i dettagli più piccoli come i sub-panelli non lo sono. Questo perché la proprietà CollisionFidelity per le pareti era impostata su Cassa. Non abbiamo avuto la precisione per questi panelli, quindi per risparmiare sul costo di performance, questo è stato abbastanza dettagli