Tworzenie ruchu w dowolnym środowisku w ramach doświadczenia pomaga mu natychmiast poczuć się bardziej zanurzonym i realistycznym w naszym świecie, czy to z powodu ruchu drzew ambient, reaktywnych drzwi z interakcji gracza, czy nawet pudeł, które się poruszają, gdy wpadają do nich.Studio ma wiele unikalnych metod tworzenia ruchu, aby pomóc światom poczuć się bardziej żywymi, w tym system fizyki, TweenService , i animacje, a analiza specyficznych potrzeb doświadczenia może pomóc ci określić, którą z nich użyć.W tej sekcji pokażemy, jak określiliśmy rodzaj ruchu, który chcieliśmy stworzyć w Studio, i jakie narzędzia użyliśmy do osiągnięcia tych odrębnych celów.

Stwórz burzę
Burza przechodziła przez wiele iteracji, zanim osiedliliśmy się na tym, co jest na żywo w Tajemnicy Duvall Drive.Na wczesnym etapie pomyśleliśmy o burzy jako o wielkim obsydianowym filarze, a w późniejszych iteracjach uznaliśmy ją za gigantyczny portal do skorumpowanej przestrzeni.Po eksperymentowaniu z wieloma różnymi burzami o unikalnym wyglądzie i poczuciu dla nich, osiedliliśmy się na burzy z mniejszym centralnym "oczem", ponieważ:
- Burza powinna dać graczom poczucie wpływu tego wydarzenia na świat , w tym unoszenia drzew i lotu odpadów.
- Obracający się wir chmury sama powinna dać graczom spojrzenie na centralny portal bez ujawniania wszystkiego .Zachęciłoby to graczy do zbadania bliżej, aby zobaczyć, co się dzieje.
- Punkt ścisły światła pozwoliłby nam skupić się na składzie domu , który jest zarówno główną postacią, jak i miejscem, w którym znajduje się większość gry.




Aby burza poczuła się dynamiczna, agresywna i zawsze zmieniająca się w swoim środowisko, użyliśmy następujących systemów i funkcji:
- TweenService - Dla ruchu w chmurze.
- Zmiany oświetlenia - Do tworzenia chmury do chmury błyskawicy.
- Promienie - Dla "światła objętościowego" i błysków piorunów.
- Emiterki cząstek > - Dla odłamków latających do portalu i latających wokół ze względu na wiatr.
- Animacje > - Dla drzew, które rozdmuchiwały się w wietrze.
Dodaj chmury z teksturami
Choć dynamiczne chmury są świetne do normalnych, wysokich chmur realistycznych, potrzebowaliśmy czegoś, co wydawało się dramatyczne i co mogliśmy bardziej mocno skierować i dostosować.Aby to zrobić, zastosowaliśmy powierzchnię wyglądu obiektów z półprzezroczystością do serii mocno stosowanych i warstwowych chmur, w celu sfałszowania pokrycia chmury.Dlaczego ustawiliśmy je jeden na drugim i pokryliśmy je tak mocno? Ponieważ kiedy każda siatka chmur porusza się z różną prędkością, przecinają się i tworzą chmurkowe formy, które wchodzą i wychodzą jeden z drugiego.Proces ten sprawił, że chmury poczuły się nieco bardziej dynamiczne i naturalne, mimo że są tylko obracającymi się dyskami.Ważne było również, aby chmury były półprzezroczyste, ponieważ chcieliśmy, aby gracze mogli przez nie rozejrzeć się, aby zobaczyć coś jasnego w środku, zanim dotarli do domu!


Ponieważ każda siatka chmur musiała być ogromna, aby w pełni otoczyć dom i przekazać, jak ogromna była burza, wiedzieliśmy, że musimy pokryć teksturę, którą chcieliśmy użyć na poszczególnych siatkach chmur, aby powtórzyła się mocno na powierzchni siatki.Przetestowaliśmy materiały, które stworzyliśmy dla chmury na tych prostych częściach, a następnie zastosowaliśmy je do wiru!

W przeciwieństwie do emiterów cząstek lub promieni, siatki pozwoliły nam na odbijanie światła z każdej siatki, co było ważne, gdy chcieliśmy wdrożyć pioruny między chmurami.Modelowaliśmy również w wirze, aby oświetlenie odbijające się od niego wyglądało, jakby miało głębię! To było ważne szczególnie w sytuacjach, w których wymagania wydajności doświadczenia obniżyły poziomy jakości naszych obiektów wyglądu powierzchni.

Obróć chmurkowe siatki
Po tym, jak byliśmy zadowoleni z ogólnego wizualnego wyglądu chmur, musieliśmy go wdrożyć! Mieliśmy ogólne kształty każdej warstwy chmury, ale zajęło to trochę prób i błędów, aby upewnić się, że efekt obrotu wygląda dobrze w praktyce.Początkowo próbowaliśmy używać ograniczeń, aby wprowadzić prędkość, która fizycznie napędziłaby chmury do ruchu.Było to trudniejsze niż chcieliśmy, aby później powtarzać, a gracz nigdy nie będzie z nim interakcjonować, więc nie potrzebowaliśmy, aby był tak dokładny w swoim ruchu.
Chcieliśmy łatwego w użyciu sposobu obrotu instancji, które były zbyt odległe, aby były interaktywne, takie jak chmury, lub zbyt małe lub dekoracyjne, aby były ważne dla rozgrywki/fizyki, takie jak małe lampy wewnętrzne.Zdecydowaliśmy się użyć LocalScript do zmniejszenia przepustowości klienta-serwera, umożliwienia bardziej płynnego ruchu i posiadania każdej siatki chmur, która może mieć inną prędkość obrotu i opóźnienie.Aby uczynić go bardziej uniwersalnym, umożliwiliśmy także określenie osi obrotu.Można użyć 3 atrybutów, ale w naszym przypadku użyliśmy 3 wartości: Axis , Delay i Speed .

Podobnie jak w wielu przypadkach w demokracji, użyliśmy tagu LocalSpaceRotation, abyśmy mogli zarządzać dotkniętymi instancjami w Studio za pomocą wtyczka tagowania instancji.Użyliśmy tylko jednego LocalScript, który obsługiwał wszystkie oznaczone instancje za pomocą CollectionService, abyśmy nie mieli tonu skryptów do utrzymania w trakcie procesu rozwoju.
W naszej demonstracji części świata są klonowane z ServerStorage do przestrzeni roboczej tak, jak to jest potrzebne, więc musieliśmy obsługiwać przypadki, w których utworzone i zniszczone zostały oznaczone obiekty.Z LocalScripts musimy również być świadomi przesyłania, w którym siatki i ich dziecięce wartości mogą być przesyłane do i z zewnątrz.Początkowo przetworzyliśmy umieszczone obiekty w funkcji Init() i połączyliśmy je z CollectionService.GetInstanceAddedSignal i CollectionService.GetInstanceRemovedSignal dla oznaczonych obiektów, aby obsłużyć nowo utworzone/usunięte obiekty.Ta sama funkcja SetupObj została użyta do inicjalizacji nowych obiektów w Init() i w CollectionService.GetInstanceAddedSignal .
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 jest mapą, która zawiera informacje dla wszystkich odpowiednich obiektów, takich jak ich prędkość obrotu i osi.Zauważ, że nie dzwonimy do SetupObj z CollectionService.GetInstanceAddedSignal natychmiast, ale dodaliśmy obiekt do objInfoQueue.Podczas przesyłania i klonowania obiektów na serwerze, gdy wezwany zostanie CollectionService.GetInstanceAddedSignal, możemy jeszcze nie mieć naszych Axis, Delay i Speed wartości, więc dodajemy obiekt do kolejki i wzywamy SetupObj na kolejnych ramach z funkcji Update, dopóki wartości nie będą tam i nie będziemy mogli ich odczytać do struktury "info" na poziomie obiektu.
Obróciliśmy instancje w funkcji Update połączonej z biciem serca.Otrzymaliśmy transformację rodziczną (parentTransform), zebraliśmy nowy kąt rotacji ( ) w oparciu o prędkość rotacyjną tego obiektu, obliczyliśmy lokalną transformację ( ) i wreszcie ustawiliśmy ją na >.Zauważ, że zarówno rodzic, jak i obiekt mogą być Model lub MeshPart, więc musieliśmy sprawdzić IsA("Model"), a następnie użyć PrimaryPart.CFrame lub CFrame.
local parentTransformif parentObj:IsA("Model") thenif not parentObj.PrimaryPart then-- pierwsza część może nie być jeszcze przesyłanacontinue -- poczekaj, aż część główna się zreplikujeendparentTransform = 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
Sprawdziliśmy, czy istnieje ważne Model.PrimaryPart do ustawienia do obsługi strumieniowania.Jeśli aktualizacja została wywołana na naszym obiekcie, gdy Model.PrimaryPart (która może wskazać na dziecko siatki) nie została jeszcze przesłana, po prostu pominęlibyśmy aktualizację.Obecny system jest drugą iteracją rotacji obiektu, a poprzedni system działał inaczej: wartości były 12 razy różne! Aby zachować te same dane, przekonwertowaliśmy je w naszym skrypcie, tak jak "12 * obj.Speed.Value".
Projektuj uderzenia piorunów
Ponieważ Studio nie oferuje generatora błyskawic z pudełka i system cząsteczek miał pewne ograniczenia, które nie działałyby dla uderzeń błyskawicy bohatera, musieliśmy być kreatywni w rozwiązaniu dla uderzeń błyskawicy bohatera.Zdecydowaliśmy się na dwie główne systemy, aby stworzyć błyskawicę: teksturowane promienie dla uderzeń błyskawicy bohatera pochodzące z oka burzy są teksturowanymi promieniami, które ujawniają i są zsynchronizowane z efektami audio i post-procesowymi, a prosty efekt cząsteczkowy dla odległej chmury do chmury.
Promienie tekstury
Zwykle używamy sekwencera lub narzędzia harmonogramu, aby sterować czasem uderzenia błyskawicy oświetleniowej, tak jak to, ale ponieważ Studio jeszcze nie oferuje tej funkcjonalności, zdecydowaliśmy się napisać skrypty, które będą kontrolować czas uderzenia błyskawicy oświetleniowej.Skryptowanie tego efektu jest dość proste, ale osiąga następujące ważne cele:
- Elementy błyskawicy uderzają, takie jak ich tekstury, jasność i opóźnienia, są losowo rozdzielane za każdym uderzeniem.
- Zmiany w zakresie audio i post FX są zsynchronizowane z uderzeniem FX.
- Gracze, którzy są w środku lub w zanieczyszczonej strefie, nie będą mogli ich zobaczyć ani usłyszeć.
Mamy stronę serwerową Script, która oblicza różne parametry i terminy, wysyła je do wszystkich klientów i czeka przez losową ilość czasu:
local function LightningUpdate()
while true do
task.wait(rand:NextNumber(3.0, 10.0))
local info = CreateFXData()
lightningEvent:FireAllClients(info)
end
end
Wewnątrz CreateFXData wypełniamy strukturę informacji, aby wszystkie klienty otrzymywały te same parametry.
Na stronie klienta ( LightningVFXClient ), sprawdzamy, czy ten klient powinien uruchomić FX:
local function LightningFunc(info)
…
-- brak FX w środku
if inVolumesCheckerFunc:Invoke() then
return
end
-- nie ma FX, gdy nie jesteś w "normalnym" świecie
if not gameStateInfoFunc:Invoke("IsInNormal") then
return
end
…
Ponadto uruchamiamy sekwencję, aby ustawić tekstury, pozycje i jasność, uruchamiamy nastolatków i używamy task.wait(number).Parametry losowe pochodzą z struktury informacji, którą otrzymaliśmy od serwera, a niektóre liczby są stałe.
beam.Texture = textures[info.textIdx]beamPart.Position = Vector3.new(info.center.X + og_center.X, og_center.Y, info.center.Y + og_center.Z)-- Wyczyśćbeam.Brightness = 10ppCC.Brightness = maxPPBrightnessppBloom.Intensity = 1.1bottom.Position = top.PositiontweenBrightness:Play()tweenPPBrightness:Play()tweenPPBrightness:Play()tweenBottomPos:Play()tweenBrightness.Completed:Wait()-- dźwiękif audioFolder and audioPart thenif audioFolder.Value and audioPart.Value thenaudioUtils.PlayOneShot(audioObj, audioFolder.Value, audioPart.Value)endendtask.wait(info.waitTillFlashes)-- and so on
Aby sprawdzić, czy gracz jest w środku, używamy funkcji pomocniczej inVolumesCheckerFunc, która przeszukuje wcześniej umieszczone objętości zbliżające się do pomieszczeń wewnętrznych i sprawdza, czy pozycja gracza znajduje się w jednym z nich (PointInABox).Moglibyśmy użyć wykrywania opartego na dotykaniu, ale dowiedzieliśmy się, że gdy gracz zajmuje miejsce wewnątrz wolumenu, nie dotyka już wolumenu.Testowanie punktu w kilku pudełkach jest prostsze i robimy to tylko wtedy, gdy gracz przemieści się wystarczająco daleko od wcześniej przetestowanej pozycji.
Aby sprawdzić, czy gracz znajduje się w zanieczyszczonych obszarach, wywołujemy funkcję pomocniczą gameStateInfoFunc, która sprawdza aktualny stan gry.Aby odtworzyć losowy dźwięk z katalogu, użyliśmy także funkcji pomocniczej PlayOneShot.Dla samych piorunów było to bardzo łatwe do stworzenia w Photoshopie; narysowaliśmy krzywą linię, a następnie dodaliśmy efekt warstwy "Zewnętrzne oświetlenie".


Wykorzystaj systemy emiterów cząstek
Błyskawice bohatera są wspierane przez system cząstek, który sugeruje odległą błyskawicę, tworząc wrażenie warstwy chmur w tle, która przyjmuje światło z dalekich uderzeń lub oświetlenia chmury do chmury.Osiągnęliśmy ten efekt za pomocą bardzo prostego systemu cząstek, który migotał billboardem chmury na obrzeżu głównej chmury burzy.System emituje okresycznie cząstkę chmury z przypadkową krzywą przejrzystości:


Spraw, by drzewa rozdmuchały się w wietrze
Po tym, jak chmury i błyskawice zaczęły działać tak, jak chcieliśmy, musieliśmy dodać dwa kolejne ważne składniki burzy: wiatr i deszcz! Elementy te przedstawiały kilka wyzwań, w tym konieczność pracy w ramach obecnych ograniczeń systemów fizyki i efektów specjalnych Studio.Na przykład, nie jest możliwe, aby drzewa poruszały się z rzeczywistym wiatrem w dzisiejszym silniku, więc wykorzystaliśmy efekty emitera cząstek i animacji postaci niestandardowych dla drzew.
Wiedzieliśmy, że naprawdę sprzedamy efekt wiatru i deszczu, potrzebowaliśmy, aby drzewa same się poruszały.Istnieje kilka sposobów, w jaki możesz to zrobić w silniku, w tym przesuwanie części za pomocą plug-inów, które są publicznie dostępne, używając TweenService, lub animowanie modeli bezpośrednio.Dla naszych celów animacje dały nam możliwość kontroli ruchu, który chcieliśmy wykonać z naszych drzew, i pozwoliły nam używać pojedynczej animacji, którą moglibyśmy podzielić wśród wszystkich drzew w ramach doświadczenia.
Zaczęliśmy od skórowania kilku drzew z pakietu modelu zatwierdzającego - zasoby leśne.Ponieważ te drzewa już istniały, a nasze doświadczenie odbyło się w północno-zachodnim Pacyfiku, zaoszczędziło nam to trochę czasu na samym początku, gdy musieliśmy tworzyć każdy model drzewa.

Po wybraniu naszych drzew wiedzieliśmy, że musimy je odsunąć. Skórkowanie siatki to czynność dodawania stawów (lub kości) do siatki w innej aplikacji modelowania 3D, takiej jak Blender lub Maya, a następnie zastosowanie wpływu do tych stawów/kości, aby przesunąć siatkę.Jest to najczęściej używane w postaciach humanoidowych , ale z niestandardowymi postaciami możesz pokryć prawie wszystko.
Wiedzieliśmy, że chcieliśmy zaoszczędzić czas i ponownie wykorzystać tę samą animacja, więc zbudowaliśmy nasz pierwszy ryg drzew i upewniliśmy się, że nazwy stawów były generyczne, ponieważ chcieliśmy używać tych samych nazw w rygach dla innych drzew.Wiedzieliśmy też, że musimy uwzględnić pierwotne, wtórne i wtórne stawy/kości, aby pnie mogły się zginać wraz z wiatrem, gałęzie się kołysały, a liście wydawały się trząść w odpowiedzi.Do tego procesu musieliśmy stworzyć sekundarny ruch , który jest koncepcją animacji, w której każda akcja powoduje, że inne części obiektu reagują na tę akcję i wydają się nadążać za początkowym ruchem.

Po stworzeniu naszych stawów/kości nadszedł czas na stworzenie animacji testowej, aby poruszać się po wszystkich stawach i kościach w Studio, aby sprawdzić, czy porusza się tak, jak chcieliśmy.Aby to zrobić, musieliśmy 导入树到Studio poprzez ustawienie niestandardowego modelu w 3D Importerze , a następnie przesuwać/animować siatkę za pomocą edytora animacji.Ustawiamy materiały i tekstury po tych testach, ale możesz zobaczyć wynik poniżej.

Po tym, jak byliśmy zadowoleni z wyników na tym drzewie, nadszedł czas na przetestowanie tej samej animacji na innym drzewie! Już wiedzieliśmy, że będzie to ta sama animacja między różnymi wpisywaćdrzew, więc po prostu upewniliśmy się, że nasza animacja wygląda jak wystarczająco ogólna, aby działać między dużym Redwood i solidnym Beechwood drzewem!

Aby to zrobić, wzięliśmy drzewo Beechwood z tego pakietu leśnego i zbudowaliśmy podobną konstrukcję, wykorzystując tę samą dokładną nazwę dla stawów.Tak było, aby animacja, którą wcześniej zaimportowaliśmy, mogła zostać zastosowana również do tego drzewa.Ponieważ wszystkie animacje opierały się na obracających się stawach, nie miało znaczenia, jak duże, małe, wysokie czy szerokie było drzewo!

Po tym, jak przygotujemy i pokryjemy skórką drzewo Beechwood, mogliśmy następnie zaimportować je i zastosować dokładnie taką samą animacja.Oznaczało to, że powtarzanie i edytowanie musiały być wykonane tylko na jednym pliku, a także zostało zapisane na wydajności z mniejszą liczbą animacji podczas uruchamiania doświadczenia.

Gdy mieliśmy wszystkie rodzaje drzew, które chcieliśmy animować, uczyniliśmy każde z nich pakietami, więc mogliśmy nadal edytować i aktualizować, grając w kilku animacjach wokół głównego obszaru doświadczenia.Ponieważ wiedzieliśmy, że mają koszt wydajności, używaliśmy ich oszczędnie wokół domu, gdzie efekt był najbardziej wartościowy! W przyszłości, gdy stanie się bardziej wydajny, będziesz mógł dodawać coraz więcej instancji skórek!

Wytwórz odpady burzy
Chcieliśmy, aby deszcz wydawał się ciężki, a mgła i odpady wiały przez drzewa.Aby to zrobić, skonfigurowaliśmy kilka niewidzialnych części, które działają jako objętości cząstek z dziećmi emitery cząstek bezpośrednio pod dużymi chmurami burz.Z powodu ograniczenia liczby cząstek w Studio nie mogliśmy użyć jednego emitera cząstek dla całej przestrzeni.Zamiast tego dodaliśmy kilka, które miały tę samą wielkość co siebie w wzorze siatki nad przestrzenią odtwarzalnego obszaru, ponieważ obecność drzew oznacza, że gracze nie będą w stanie zobaczyć bardzo daleko.

Cząstki deszczu wykorzystały nową właściwość emitera cząstek ParticleEmitter.Squash, która pozwala ci uczynić cząstkę dłuższą lub bardziej rozproszoną.Jest szczególnie przydatny w przypadku deszczu, ponieważ oznaczało to, że nie potrzebowaliśmy dużej tekstury deszczu, po prostu rozciągnij tę, która była tam.Wystarczy wiedzieć, że jeśli zwiększysz wartość ParticleEmitter.Squash, możesz potrzebować zwiększenia ogólnej właściwości ParticleEmitter.Size, aby nie była zbyt szczupła! Ogólnie rzecz biorąc, chodziło tylko o zabawę z wartościami, dopóki nie uzyskaliśmy deszczu wystarczająco ciężkiego, ale nie tak bardzo, że zablokował on widoczność doświadczenia!


Dla mgły, mgły i liści unoszących się poprzez, znacznie łatwiej było dodać pojedynczą większą objętość części pokrywającą mniej obszarów, ponieważ nie potrzebowaliśmy tony cząstek biegających jednocześnie.Zaczęliśmy od ustawienia objętości i uzyskaliśmy częstotliwość cząstek tam, gdzie ich chcieliśmy.


Następnie stworzyliśmy nasze tekstury rozdmuchu liści i tekstury wiatru i ustawiliśmy cząstki na to, aby wszystkie obracały się/poruszały z różną prędkością i zaczynały z różną prędkością.Oznaczało to, że większe cząstki mgły będą interakcjonować bardziej naturalnie i nie będą wyglądały tak bardzo jak powtarzająca się tekstura, szczególnie biorąc pod uwagę ich rozmiar.



Wynikiem było kilka świetnych działań między poruszającymi się drzewami, wysadzanymi oknami i błyskawicami, aby stworzyć efekt burzy otaczającej centralne oko burzy.
Ustaw oko burzy
Pęknięte oczko kamienne z świecącym rdzeniem ma na celu dać graczom pierwszą wskazówkę, że jest coś niepokojącego i tajemniczego dzieje się w domu, który powinni dalej zbadać.Ponieważ nasza scena jest ciemna, a oko jest daleko w niebie, ważne było stworzenie wiarygodnej pękniętej kamiennej sylwetki, ale nie było tak ważne, aby stworzyć wiarygodne szczegóły powierzchni kamienia, ponieważ gracze nie będą w stanie tego zobaczyć.Wiedza, co jest realistyczne dla twoich graczy, aby zobaczyć oświetlenie sceny przed włożeniem dużej ilości czasu w niepotrzebne szczegóły, może zaoszczędzić wiele zasobów w procesie rozwoju.


Odległość od gracza oznaczała również, że mogliśmy polegać wyłącznie na normalnej mapie dla szczegółów powierzchni oka, więc siatka jest po prostu płaską sferą! Rzeźbiliśmy szczegóły w wysokiej siatce poligonów i upieczyliśmy jej normalną mapę na znacznie niższej sferze poligonów, abyśmy mogli uzyskać wszystkie te piękne szczegóły bez dużego kosztu wydajności!



Aby dodać nadprzyrodzone uczucie do oka i podkreślić jego obecność, postanowiliśmy stworzyć świecącą, neonową magmę, która przepływałaby przez jej pęknięcia.Chociaż nie ma kanału emisyjnego dla wyglądu powierzchni, pokonujemy ten przeszkód, tworząc oko z 2 kulek: jedną dla skalistej powierzchni zewnętrznej i drugą, nieco mniejszą, dla świecącej magmy.W Malarzu substancji stworzyliśmy podstawową teksturę koloru dla zewnętrznej kuli z przezroczystością w obszarach, w których chcieliśmy, aby wewnętrzne rdzenie przeszły.W blendera pomalowaliśmy wewnętrzną sferę w tani i łatwy sposób, aby uzyskać pewną odmianę koloru.

Kolejnym wyzwaniem, z którym napotkaliśmy się podczas tworzenia oka, był nałożony przez nasze użycie strumieniowania połączone z odległością oka od gracza.Biorąc pod uwagę centralność tej struktury, chcieliśmy, aby zawsze była widoczna pomimo jej odległości, ale bez żadnych haków do jej siatki gracze nie mogli zobaczyć oka, chyba że byli w solarium.Udało nam się zmusić stałą obecność oka na scenie, dodając trochę geometrii do oka i jego pierścieni.Ta geometria znajduje się tuż pod powierzchnią terenu, a to wystarcza, aby oszukać silnik, aby myślał, że sfera jest bliżej gracza niż jest i zawsze przesyła ją.Należy to jednak zrobić dość oszczędnie, ponieważ zmuszanie zbyt wielu dużych obiektów do przesyłania może odrzucić korzyści z włączonym przesyłaniem i negatywnie wpłynąć na wykonywaniegry.
Udało nam się dodać ruch do oka i jego pierścieni dzięki temu samemu skryptowi, którego używaliśmy do obrotu chmurkowych siatek.Dla ostatniego dotknięcia, zdecydowaliśmy się dodać wskazówkę do obecności innego świata poza chmurami, ale musieliśmy przyjąć kreatywne podejście, aby uniknąć dodawania więcej geometrii do sceny i dodatkowo musieliśmy radzić sobie z wcześniej wspomnianymi przeszkodami spowodowanymi przez włączone strumieniowanie.Stworzyliśmy scenę, która miała dużą głębię ze względu na względny rozmiar i odległość obiektów, wyrenderowaliśmy obraz tej sceny, a następnie użyliśmy powiedzianego obrazu jako naklejki na części położonej tuż za okiem burzy.Użyliśmy tej samej metody do obrotu tej części, jaką użyliśmy dla oka i jego pierścieni.

Zrób rozbudowaną spiżarnię
Jedną z najbardziej zabawnych rzeczy do wytworzenia były skorumpowane przestrzenie, w których mogliśmy podkopać oczekiwania graczy odnośnie rzeczywistości, dosłownie zmieniając ją wokół nich.Na przykład w puzzle ojca chcieliśmy odtworzyć moment podobny do koszmaru, w którym bez względu na to, jak szybko biegniesz, pokój wydaje się coraz dłuższy.Zdecydowaliśmy się stworzyć rozbudowaną spiżarnię, która uciekłaby od graczy, gdy szukali składników, aby odwrócić pokój do jego normalnego stanu.
Ustawiliśmy to za pomocą prostego ruchu ścian i sprytnego rozmieszczenia naszych pokoi, które pojawiłyby się po obu stronach spiżarni.W normalnym stanie pokoju spiżarnia była prostym korytarzem, ale w zanieczyszczonej przestrzeni była w rzeczywistości znacznie dłuższa z kilkoma skrzydłami i fałszywą ścianą!


Fałszywa ściana była grupą modeli, do której powrócimy w momencie, gdy gracze wejdą do głośności spustu, która była przezroczystą częścią przedpokoju, przez którą przejść mogliby wcześniej.Ten trigger został również wykorzystany w skrypcie podobnym do tych używanych na wszystkich naszych drzwiach, który wzywał TweenService do przejścia z jednego celu na drugi.Użyliśmy części objętości, aby powiedzieć operacji tweening, gdzie pozycje początkowa i końcowa były dla ściany.


Ponieważ TweenService jest takim ogólnym systemem, wszystkie nasze modele danych ścian musiały zawierać te same komponenty.Na przykład ogólny skrypt "Door_Script" odtwarza dźwięk zdefiniowany przez "wartość" poniżej modelu "Grow_Wall".Ten sam skrypt, z pewnymi modyfikacjami w następnym przykładzie kodu, również uruchomił audio podczas przemieszczania magazynu.To dodało wiele do ruchu!
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, --Czas/szybkość przejścia drzwi
Enum.EasingStyle.Quart, --Łagodzenie stylu
Enum.EasingDirection.InOut, --Łatwienie kierunku
0, --Powtórz liczbę
false, --Odwróć prawdę
0 --Opóźnienie
)
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
--model["Drzwi"]:Play()
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("dotknij")
playersNear[player] = 1
if doorState == DoorState.Closed then
StartOpening()
end
end
end
end
Gdy mieliśmy fałszywą ścianę poruszającą się do tyłu pokoju, potrzebowaliśmy reszty treści, aby się z nią poruszyła.Aby to zrobić, musieliśmy, aby wszystkie luźne przedmioty w magazynie zostały spawane do ściany, gdy się poruszała.Używając ograniczeń spawalniczych, szybko mogliśmy połączyć wszystkie obiekty z ścianą magazynu, aby poruszać się jako jeden obiekt.Oznaczało to, że mieliśmy opcję odłączenia tych przedmiotów, aby gracze mogli w nie uderzyć i potłuc je!
Zrób zdegradowany domek na drzewie
Studio to fantastyczny silnik oparty na fizycznych podstawach, który możesz używać do tworzenia wszystkiego, od drzwiującej bramy po obracającą się platforma.Dzięki naszej prezentacji chcieliśmy wykorzystać fizykę, aby stworzyć poczucie realizmu w nierealistycznym zestawie środowisk.Używając zaledwie kilku ograniczeń , możesz stworzyć kilka zabawnych i wymagających wyzwań kursów przeszkód w swoich własnych doświadczeniach!

Ograniczenia to grupa silników fizycznych, które dostosowują obiekty i ograniczają zachowania.Na przykład możesz użyć ograniczenia pręta aby połączyć się z obiektami, aby utrzymać je na stałej odległości od siebie lub ograniczenia liny , aby mieć lampę wiszącą na końcu linii.Dla zagadki syna, w której gracze są przenoszeni do skorumpowanego stanu badania, chcieliśmy dosłownie odwrócić świat na bok.Zrobienie tego podważyłoby oczekiwania graczy wobec rzeczywistości i zasad tam panujących, przy jednoczesnym korzystaniu z systemu fizycznego tak, jak było to zamierzone!

Kiedy gracze pracowali w głównej strefie puzzle, powitali ich znajoma sceneria na Roblox: kurs przeszkód.Ta konkretna przeszkoda składała się z kilku obracających się platform i obracających się ścian, wraz z "bezpiecznymi obszarami", które rozwinęły historię.Skupimy się na obracających się/obracających się elementach.

Dlaczego użyliśmy tutaj ograniczeń? Ponieważ TweenService lub inne metody nie przeniosłyby gracza, gdy stał na nich.Bez przemieszczania obiektu gracz może ktoś wskoczyć na platformę i będzie się obracał z nich.Zamiast tego chcieliśmy, aby gracze przeszli przez obracającą się platformę, próbując wykonać skok na następną.Z powodu tego podejścia gracze czuli się zakorzenieni tam, gdzie stali, podejmując decyzję o tym, jak kontynuować dalej przez kurs, i nie musieliśmy robić nic specjalnego, aby zapewnić, że poruszają się z obracającą się powierzchnią!

Aby to zrobić, musieliśmy najpierw wykorzystać zasoby z naszego obecnego zestawu i dodać dowolną nową treść dla efektu wizualnego.Zrobiliśmy kilka niekompletnych ścian i platform z dziurami, aby opowiedzieć historię babci budującej dom na drzewie.Ponieważ nie chcieliśmy tworzyć mnóstwa unikalnych platform, stworzyliśmy 4 różne elementy bazowe i elementy balustrad oddzielnie.Umożliwiło to mieszanie i dopasowywanie poszczególnych części baz i balustrad, aby mieć dużą różnorodność.

Wiedzieliśmy, że ponieważ używaliśmy ograniczeń, nie będziemy w stanie zakotwiczyć tych siatek, ponieważ nie będą się poruszać nawet z obecnością ograniczenia/silnika napędzającego je.Ograniczenie musiało być dzieckiem czegoś, co zostało zakotwiczone, aby platforma nie wypadła tylko z świata.Rozwiązaliśmy to za pomocą części o nazwie Motor_Anchor , która miała ograniczenie sprężynowe do napędzania ogólnego ruchu platformy.Następnie potrzebowaliśmy dwóch siatek, aby poruszały się jako jedna, więc stworzyliśmy część o nazwie Motor_Turn , a następnie spawaliśmy do niej dwie siatki.W ten sposób ograniczenie będzie mogło działać na jednej części, w przeciwieństwie do wielu zawiasów pracujących z wieloma częściami.

Nadszedł czas, aby skonfigurować rzeczywiste zachowanie samego ograniczenia zawiasowego i dodać załączniki, które będą działać jako orientacja części i ograniczenie razem.Umieściliśmy obrotowe załączniki na Motor_Turn, do którego przylutowane były kawałki chodnika, oraz kolejne załączniki dla zachowania przypięcia do samego silnika Motor_Anchor, obok ograniczenia zawiasu.Ponieważ musiało się obracać posiadać, w przeciwieństwie do bycia wpływanym przez gracza (jak zawias drzwi), ustawiliśmy HingeConstraint.ActuatorType na Silnik , traktując ograniczenie jak samobieżący silnik.
Aby utrzymać platformy w stałej prędkości, następnie ustawiamy właściwości HingeConstraint.AngularVelocity , HingeConstraint.MotorMaxAcceleration i HingeConstraint.MotorMaxTorque na wartości, które pozwolą na ruch i zapobiegną przerwaniu, jeśli gracz wskoczy na nie.

Teraz musieliśmy wykonać obrotowe ściany.Ściany musiały się obracać w swoim oczywistym centrum, a wiedzieliśmy, że chcemy, aby mogły radzić sobie z każdą orientacją względem reszty poziomu.Podobnie jak platformy, zbudowaliśmy je tak, aby wszystkie ściany były niezakotwiczone i spawane do Motor_Turn.

Użyliśmy obiektów Texture na górze obiektów SurfaceAppearance do dodania pewnej odmiany do naszych podstawowych materiałów.Tekstury, podobne do naklejek, pozwalają umieścić obraz na planie siatki.Może to być przydatne, jeśli chcesz dodać brud do ściany z cegły lub sprawić, aby drewno wyglądało na starze, używając tego samego materiału drewnianego.Przedmioty Texture mają nieco inne zachowanie niż przedmioty Decal, w których możesz przesuwać i przesuwać obraz tak, jak chcesz, co jest bardzo przydatne, jeśli chcesz móc skalować swoją teksturę nadkładu i nie obawiać się powtórzenia!

Po przetestowaniu kilku platform i obrotowych ścian, dokonaliśmy kilku modyfikacji i bawiliśmy się ich umieszczeniem, aby upewnić się, że kurs przeszkód był wyzywający, zaskakujący umysł i również jasny, gdzie gracz musiał przechodzić, wykonywać! Potrzebowało to pewnych dostosowań zarówno ich wartości, jak i pozycji, aby dobrze działały.Mieliśmy kilka punktów, w których platformy i ściany uderzały w siebie lub otoczenie, ale dzięki poruszaniu się i częstym testom mogliśmy wylądować na ustawieniach, które mamy w demo!
Jeśli nie jesteś pewien, na co uderzają twoje fizyczne obiekty, możesz przełączyć na wierność kolizji z przyciskiem opcje wizualizacji w prawym górnym rogu widoku 3D.



Jak widać poniżej, otwory drzwiowe/okienne są widoczne, ale mniejsze szczegóły, takie jak podpaneling, nie są.Dzieje się tak, ponieważ właściwość CollisionFidelity dla ścian została ustawiona na Pudełko .Nie potrzebowaliśmy precyzji dla tych paneli, więc aby zaoszczędzić koszt wydajności, było to wystarczająco szczegółowe, aby gracze mogli na nim skakać.Z platformami i obracającymi się ścianami skończone, musieliśmy dodać tylko szczegóły, takie jak pudełka i lampy, a następnie był gotowy do gry!
