경험 내의 모든 환경에서 이동을 생성하면 경험이 즉시 우리 세계에 더 몰입감 있고 현실감 있게 느껴지도록 도와줍니다. 이는 환경 트리 이동, 플레이어 상호 작용에서 반응하는 문, 혹은 그들에 부딪히면 움직이는 상자일 수 있습니다.Studio에는 물리 시스템, TweenService 및 애니메이션을 포함하여 세계가 더 생생하게 느껴지도록 도울 수 있는 많은 독특한 방법이 있습니다. 경험의 특정 요구 사항을 분석하면 사용할 방법을 결정하는 데 도움이 될 수 있습니다.이 섹션에서는 Studio에서 만들고 싶은 이동 유형과 이러한 구별된 목표를 달성하기 위해 사용한 도구를 결정하는 방법을 보여 줍니다.

폭풍 생성
폭풍은 The Mystery of Duvall Drive에서 라이브로 사용되는 것에 대해 결정하기 전에 많은 반복을 거쳤습니다.처음에는 폭풍을 거대한 오브시디안 기둥으로 생각했고, 나중에 반복에서는 그것을 부패한 공간으로 가는 거대한 포털로 간주했습니다.그들에게 독특한 모양과 느낌을 가진 많은 다른 폭풍을 실험한 후, 더 작은 중앙 "눈"을 가진 폭풍에 합의했습니다. 왜냐하면:
- 폭풍은 플레이어에게 이 이벤트가 세계에 미치는 영향 에 대한 감각 을 제공해야 하며, 나무가 날아가고 쓰레기가 날아가는 것을 포함합니다.
- 클라우드 자체의 회전하는 소용돌이는 플레이어에게 중앙 포털을 미리 보여줄 수 있어야 합니다 모든 것을 공개하지 않고 .이렇게 하면 플레이어가 더 가까이서 상황을 조사하도록 유도할 수 있습니다.
- 조명의 더 좁은 지점은 우리가 주요 캐릭터와 게임플레이의 대부분이 위치한 집의 구성 에 집중할 수 있게 해줄 것입니다.




폭풍이 환경 내에서 동적이고, 공격적이고, 계속 변화하는 느낌을 주기 위해, 다음 시스템과 기능을 사용했습니다:
- TweenService - 클라우드 이동용.
- 조명 변경 - 구름에서 구름으로 조명을 생성하기 위해.
- 광선 - "볼륨 조명"과 번개 볼트용
- 입자 방출기 - 포털로 날아가는 쓰레기와 바람이 부는 주변을 날아다니는 쓰레기를 위해.
- 애니메이션 - 바람에 날리는 나무를 위해
텍스처가 있는 클라우드 추가
동적 클라우드는 일반적인, 고고도의 현실적인 클라우드에 좋지만, 극적인 느낌을 원하고 더 많이 지시하고 사용자 지정할 수 있는 무언가가 필요했습니다.이를 위해, 우리는 구름 덮개를 위조하기 위해 반투명성을 갖는 표면 모양 개체를 여러 겹으로 쌓여 있고 중첩된 구름 메쉬 시리즈에 적용했습니다.왜 우리는 그들을 쌓아서 그들을 그렇게 무겁게 층으로 만들었나요? 각 클라우드 메시가 서로 다른 속도로 이동할 때, 그들은 교차하여 서로 안과 밖으로 가는 구름 형태를 만듭니다.이 프로세스는 단지 회전 디스크일 뿐인데도 불구하고 클라우드가 조금 더 동적이고 자연스럽게 느껴지게 했습니다.또한 구름이 반투명이어야 했는데, 플레이어가 집에 도착하기 전에 중앙에서 밝은 것을 볼 수 있도록 하기 위해서였습니다!


각 클라우드 메시는 집을 완전히 둘러싸고 폭풍이 얼마나 크었는지 전달하기 위해 거대해야 했기 때문에, 개별 클라우드 메시에서 사용하고 싶은 텍스처를 타일링해야 메시 표면 전체에 크게 반복될 수 있다는 것을 알았습니다.클라우드에 만든 재료를 이러한 간단한 부품에서 테스트한 다음 볼텍스에 적용했습니다!

입자 방출기나 광선과는 달리, 메쉬를 사용하면 각 메쉬에서 빛을 반사할 수 있어 구름에서 구름으로 번개를 구현하려는 경우 중요했습니다.또한 회전하는 동안 조명이 그것에서 바운스되어 깊이가 있는 것처럼 보이도록 모델링했습니다! 이는 경험의 성능 요구가 우리 표면 모양 개체의 품질 수준을 떨어뜨린 상황에서 특히 중요했습니다.

클라우드 메쉬 회전
우리가 구름의 전체적인 시각적 모양에 만족했을 때, 그것을 움직여야 했습니다! 각 클라우드 레이어의 일반적인 모양이 있었지만, 회전 효과가 실제로 좋아 보이도록 하기 위해 시도와 실수가 필요했습니다.처음에는 제약 조건을 사용하여 클라우드를 물리적으로 이동시키는 속도를 소개하려고 했습니다.이것은 나중에 반복하기를 원하는 것보다 더 어려웠고, 플레이어는 결코 그것과 상호작용하지 않을 것이므로 그 이동에서 정확하게 필요하지 않았습니다.
구름이나 소형 램프와 같이 상호 작용할 수 없을 정도로 멀리 있거나 게임플레이/물리학에 중요하지 않을 정도로 너무 작거나 장식적인 인스턴스를 회전하기 위해 사용하기 쉬운 방법을 원했습니다.클라이언트-서버 대역폭을 줄이고, 더 부드러운 이동을 허용하며, 각 클라우드 메시가 회전 속도와 지연 시간이 다를 수 있도록 하기 위해 LocalScript를 사용하기로 결정했습니다.더 일반적으로 만들기 위해 회전 축을 지정할 수도 있습니다.3개의 특성을 사용할 수는 있지만, 우리의 경우 3개의 값을 사용했습니다: Axis, Delay, 그리고 Speed .

데모의 많은 경우와 마찬가지로 인스턴스 태깅 플러그인을 사용하여 Studio에서 영향을 받은 인스턴스를 관리할 수 있도록 LocalSpaceRotation 태그를 사용했습니다.개발 과정 내내 유지해야 할 스크립트가 많지 않도록 모든 태그된 인스턴스를 처리하는 단일 만 사용했습니다.
우리의 데모에서는 세계의 일부가 필요에 따라 ServerStorage 에서 작업 영역으로 복제되므로 태그가 지정된 개체가 생성되고 삭제된 경우를 처리해야 했습니다.With LocalScripts , 메쉬와 그 자식 값이 들어가고 나갈 수 있는 스트리밍에 대해서도 알아야 합니다.처음에는 Init() 함수에 배치된 개체를 처리하고 CollectionService.GetInstanceAddedSignal 및 CollectionService.GetInstanceRemovedSignal에 태그가 지정된 개체를 처리하기 위해 연결했습니다.동일한 SetupObj 함수가 Init() 및 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는 회전 속도 및 축과 같은 모든 관련 개체의 정보가 있는 맵입니다.즉시 SetupObj 에서 CollectionService.GetInstanceAddedSignal 를 호출하지는 않지만 objInfoQueue 에 개체를 추가했습니다.서버에서 스트리밍 및 복제 개체를 사용하면, CollectionService.GetInstanceAddedSignal 가 호출될 때, 우리는 아직 Axis , Delay 및 Speed 값을 가지고 있지 않을 수 있으므로 개체를 큐에 추가하고 다음 프레임에서 SetupObj 함수에서 값이 있을 때까지 읽어 Update 개체 정보 구조에 대해 호출했습니다.
심장 박동에 연결된 Update 함수에서 인스턴스를 회전했습니다.부모 변환(parentTransform)을 가져와 이 개체의 회전 속도에 따라 새로운 회전 각도(curObjInfo.curAngle)를 누적했고, 로컬 변환(rotatedLocalCFrame))을 계산하고 마지막으로 CFrame에 설정했습니다.부모와 개체 모두가 Model 또는 MeshPart 일 수 있으므로 IsA("모델")를 확인하고 PrimaryPart.CFrame 또는 CFrame 을 사용해야 했습니다.
local parentTransformif parentObj:IsA("Model") thenif not parentObj.PrimaryPart then-- 기본 부품은 아직 스트리밍되지 않을 수 있습니다continue -- 주 부품의 복제 대기endparentTransform = 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
스트리밍을 처리하기 위해 Model.PrimaryPart 유효한 값을 설정하도록 확인했습니다.업데이트가 우리 개체에 호출되었지만, Model.PrimaryPart (자식 메쉬를 가리킬 수 있는)가 아직 스트리밍되지 않았다면, 업데이트를 건너뛰겠습니다.현재 시스템은 개체 회전의 두 번째 반복이며, 이전 시스템은 다르게 작동했습니다: 값이 12 배 달랐습니다! 동일한 데이터를 유지하기 위해 우리 스크립트에서 "12 * obj.Speed.Value"와 같이 변환했습니다.
디자인 번개 공격
Studio는 상자에서 나오는 번개 생성기를 제공하지 않으며, 입자 시스템은 영웅 번개 공격에 작동하지 않는 몇 가지 제한 사항이 있었기 때문에 영웅 번개 공격에 대한 해결책을 찾아야 했습니다.우리는 번개를 구성하기 위해 두 가지 주요 시스템을 결정했습니다: 폭풍의 눈에서 오는 영웅 번개 공격에 대한 텍스처화된 광선은 오디오와 포스트 프로세스 효과를 드러내고 동기화하는 텍스처화된 광선이며, 먼 구름에서 구름으로 번개에 대한 간단한 입자 효과입니다.
텍스처 빔
일반적으로 순차기나 타임라인 도구를 사용하여 조명 볼트 효과의 타이밍을 구동하지만, Studio가 이 기능을 아직 제공하지 않기 때문에 조명 볼트 타이밍을 제어할 스크립트를 작성하기로 결정했습니다.이 효과의 스크립팅은 간단하지만 다음과 같은 중요한 목표를 달성합니다:
- 텍스처, 밝기 및 지연과 같은 번개 폭격의 요소는 모든 폭격과 함께 랜덤화됩니다.
- 오디오 및 포스트 FX 변경은 스트라이크 FX와 동기화됩니다.
- 실내에 있거나 부패한 영역에 있는 플레이어는 그들을 볼 수 없거나 들을 수 없을 것입니다.
다양한 매개변수와 시간을 계산하고, 모든 클라이언트에 전송하고, 랜덤 시간을 기다리는 서버 측 Script가 있습니다:
local function LightningUpdate()
while true do
task.wait(rand:NextNumber(3.0, 10.0))
local info = CreateFXData()
lightningEvent:FireAllClients(info)
end
end
내부에서 CreateFXData , 모든 클라이언트가 동일한 매개변수를 받도록 정보 구조를 채웁니다.
클라이언트 측면( LightningVFXClient )에서 이 클라이언트가 FX를 실행해야 하는지 확인합니다.
local function LightningFunc(info)
…
-- 실내에서는 FX 없음
if inVolumesCheckerFunc:Invoke() then
return
end
-- 일반 세계에 없을 때의 FX 없음
if not gameStateInfoFunc:Invoke("IsInNormal") then
return
end
…
또한, 시퀀스를 실행하여 텍스처, 위치 및 밝기를 설정하고, 십대를 실행하고, task.wait(number)를 사용합니다.랜덤화된 매개변수는 서버로부터 받은 정보 구조에서 나오며, 일부 숫자는 고정됩니다.
beam.Texture = textures[info.textIdx]beamPart.Position = Vector3.new(info.center.X + og_center.X, og_center.Y, info.center.Y + og_center.Z)-- 지우기beam.Brightness = 10ppCC.Brightness = maxPPBrightnessppBloom.Intensity = 1.1bottom.Position = top.PositiontweenBrightness:Play()tweenPPBrightness:Play()tweenPPBrightness:Play()tweenBottomPos:Play()tweenBrightness.Completed:Wait()-- 오디오if audioFolder and audioPart thenif audioFolder.Value and audioPart.Value thenaudioUtils.PlayOneShot(audioObj, audioFolder.Value, audioPart.Value)endendtask.wait(info.waitTillFlashes)-- and so on
플레이어가 실내에 있는지 확인하기 위해 미리 배치된 실내 영역을 접근하는 도우미 함수 inVolumesCheckerFunc 를 사용하여 플레이어 위치가 그 중 하나에 포함되어 있는지 확인합니다(PointInABox).터치 기반 검색을 사용할 수도 있었지만, 플레이어가 볼륨 내부에 자리를 잡으면 더 이상 "터치"하지 않는다는 것을 알아냈습니다.몇 개의 상자에서 지점을 테스트하는 것이 더 간단하며, 플레이어가 이전에 테스트된 위치에서 충분히 멀리 이동할 때만 수행합니다.
플레이어가 부패한 영역에 있는지 확인하려면 현재 게임 상태를 확인하는 도우미 함수 gameStateInfoFunc를 호출합니다.폴더에서 랜덤 사운드를 재생하려면 도우미 함수 PlayOneShot 도 사용했습니다.번개 자체에 대해서는 Photoshop에서 쉽게 만들 수 있었습니다; 우리는 흐릿한 선을 그렸고 외광 레이어 효과를 추가했습니다.


입자 방출 시스템 활용
영웅 번개 공격은 먼 번개를 제안하는 입자 시스템으로 지원되며, 배경에서 먼 공격이나 구름에서 구름으로 조명을 캡처하는 층의 인상을 만들어 먼 번개를 제안합니다.주 폭풍 구름의 주변에서 구름 광고판을 섬광시키는 매우 간단한 입자 시스템을 통해 이 효과를 달성했습니다.시스템은 무작위 투명도 곡선으로 정기적으로 구름 입자를 방출합니다:


나무가 바람에 날아가게 하기
우리가 원하는 방식으로 구름과 번개가 작동하게 되면, 우리는 폭풍의 또 다른 주요 구성 요소인 바람과 비를 추가해야 했습니다! 이러한 요소는 물리학과 특수 효과 시스템의 현재 제한 내에서 작동해야 하는 몇 가지 도전을 제시했습니다.예를 들어, 실제 바람으로 나무를 이동시키는 것은 현재 엔진에서 불가능하므로 나무에 대해 입자 방출기 효과와 사용자 지정 캐릭터 애니메이션을 사용했습니다.
바람과 비의 효과를 정말로 판매해야 한다는 것을 알았고, 나무 자체가 이동해야 했습니다.공개적으로 사용할 수 있는 플러그인을 사용하여 부품을 이동하거나 직접 모델을 애니메이션하는 것을 포함하여 엔진 내에서 이를 수행할 수 있는 몇 가지 방법이 있습니다.목적에 따라 애니메이션은 우리가 우리 나무에서 원하는 움직임을 제어할 수 있게 해주었고, 경험 내의 모든 나무 사이에서 공유할 수 있는 단일 애니메이션을 사용할 수 있게 해주었습니다.
우리는 승인 모델 팩 - 숲 자산에서 여러 나무를 스킨하여 시작했습니다.이 트리들이 이미 존재했고, 우리의 경험이 태평양 북서쪽에서 발생했기 때문에, 각 트리 모델을 만들어야 하는 시간을 일찍 절약할 수 있었습니다.

우리가 나무를 골랐을 때, 우리는 그들을 스킨해야 한다는 것을 알았습니다.메쉬 스킨은 블렌더나 마야와 같은 다른 3D 모델링 응용 프로그램에서 메쉬에 관절(또는 뼈)을 추가하고 이러한 관절/뼈에 영향을 적용하여 메쉬를 이동하는 작업입니다.이것은 인간형 캐릭터에서 가장 일반적으로 사용되지만, 사용자 지정 캐릭터로 거의 모든 것을 스킨할 수 있습니다.
시간을 절약하고 동일한 애니메이션을 재사용하고 싶다는 것을 알고 있었기 때문에 첫 번째 트리 리그를 만들고 조인트 이름이 일반적이라는 것을 확인했습니다, 다른 나무에 대한 리그에서 이러한 이름을 사용하기 위해서입니다.우리는 또한 트렁크가 바람에 굴복하고, 가지가 흔들리고, 나뭇잎이 흔들리는 것처럼 보이기 위해 주, 보조, 삼차 관절/뼈를 포함해야 한다는 것을 알고 있었습니다.이 프로세스에서는 모든 작업으로 인해 개체의 다른 부분이 그 작업에 반응하고 초기 이동에 따라 캐치업하는 것처럼 보이는 애니메이션 개념인 보조 이동 을 만들어야 했습니다.

우리의 관절/뼈를 만든 후에는 스튜디오의 모든 관절과 뼈를 이동하여 원하는 대로 이동하는지 테스트 애니메이션을 만들 시간이었습니다.이를 위해서는 3D 가져오기에서 사용자 지정 리그 설정을 통해 트리를 Studio로 가져와야 하고, 애니메이션 편집기를 사용하여 메시를 이동/애니메이션하여야 합니다.이러한 테스트 후에 재료와 텍스처를 설정했지만 아래 결과를 확인할 수 있습니다.

그 나무에서 결과가 만족스러웠으면 다른 나무에서 동일한 애니메이션을 테스트할 시간이었습니다! 각 나무 입력대해 다른 리그 사이에서 동일한 애니메이션이 될 것이라는 것을 이미 알고 있었기 때문에 우리는 단지 우리의 애니메이션이 높은 레드우드와 단단한 베치우드 나무 사이에서 일하는 것처럼 보이는지 확인했습니다!

이를 위해 우리는 그 숲 팩에서 Beechwood 나무를 가져와 동일한 이름으로 조인트를 사용하여 유사한 장비를 구축했습니다.이전에 가져온 애니메이션이 이 트리에도 적용될 수 있도록 하기 위해서였습니다.애니메이션은 모두 회전 관절을 기반으로 했기 때문에 나무가 얼마나 크고, 작고, 높고, 넓은지는 상관이 없었습니다!

우리가 리그와 스킨을 완료한 후에, Beechwood 나무를 가져와 정확히 동일한 애니메이션을 적용할 수 있었습니다.이는 반복 및 편집이 한 파일에서만 수행되고 경험을 실행할 때 애니메이션이 줄어들어 성능에 저장되어야 한다는 것을 의미했습니다.

원하는 모든 트리 유형을 애니메이션화했으면 각각을 패키지로 만들어 경험의 주요 영역 주변의 여러 애니메이션을 계속 편집하고 업데이트할 수 있었습니다.성능 비용이 있다는 것을 알고 있었기 때문에, 효과가 가장 값진 곳에서 약간만 사용했습니다! 미래에 이것이 더 효율적으로 되면, 점점 더 많은 스킨된 메시 인스턴스를 추가할 수 있을 것입니다!

폭풍 잔해 만들기
비가 무거워 보이고, 안개와 쓰레기가 나무를 통해 날아가기를 원했습니다.이를 위해 몇 가지 투명한 부품을 설정하여 큰 폭풍 구름 아래에서 즉시 입자 볼륨으로 작동하는 자식 입자 방출기를 생성합니다.Studio의 입자 수 제한으로 인해 전체 공간에 하나의 입자 방출기를 사용할 수 없었습니다.대신 재생 가능한 영역 공간에서 서로 같은 크기의 그리드 패턴에 여러 개를 추가했는데, 나무가 존재하면 플레이어가 매우 멀리 볼 수 없을 것이기 때문입니다.

비 입자는 새로운 입자 방출기 속성 ParticleEmitter.Squash을 활용하여 입자를 더 길게 또는 착륙시킬 수 있습니다.비에 특히 유용하며, 큰 비 텍스처가 필요하지 않았기 때문에 거기에 있던 것을 단지 늘리면 되었습니다.단지 ParticleEmitter.Squash 값을 늘리면 너무 얇아지지 않도록 전체 ParticleEmitter.Size 속성을 늘려야 할 수도 있다는 것을 알고 있으면 됩니다! 전체적으로, 우리가 충분히 비가 올 때까지 값을 조작하는 것만으로 충분했지만, 경험의 가시성을 차단할 정도로는 아니었습니다!


안개와 나뭇잎이 날아가면서, 더 작은 영역을 덮는 단일 더 큰 부품 볼륨을 추가하는 것이 훨씬 간단했습니다. 한 번에 많은 입자가 실행되지 않아도 되었기 때문입니다.볼륨을 설정하여 시작하고 원하는 위치의 입자 빈도를 가져왔습니다.


그 후, 우리는 나뭇잎의 흩어짐과 바람 텍스처를 만들고 입자가 모두 다른 속도로 회전하고 다른 속도로 시작하도록 설정했습니다.이는 더 큰 안개 입자가 더 자연스럽게 상호 작용하고 크기에 비해 반복되는 텍스처처럼 보이지 않을 것임을 의미했습니다.



결과는 나무가 움직이고, 창이 불어오고, 번개가 폭풍의 중심 눈을 둘러싸는 효과를 만드는 중요한 행동이었습니다.
폭풍의 눈 설정
빛나는 핵을 가진 골절된 돌 눈은 플레이어에게 그들이 더 자세히 조사해야 하는 집에서 불길한 아르켜가 발생하고 있음을 첫 번째 힌트를 제공하기 위한 것입니다.우리 장면이 어둡고 눈이 하늘에 있기 때문에 신뢰할 수 있는 갈라진 돌 실루엣을 만드는 것이 중요했지만, 플레이어가 그것을 볼 수 없을 것이기 때문에 신뢰할 수 있는 돌 표면 세부 사항을 만드는 것은 그다지 중요하지 않았습니다.불필요한 세부 사항에 많은 시간을 들이기 전에 장면의 조명 내에서 플레이어가 볼 수 있는 것이 현실적인지 알면 개발 과정에서 많은 리소스를 절약할 수 있습니다.


플레이어로부터의 거리는 또한 우리가 눈의 표면 세부 사항에 대해 일반 맵에만 전적으로 의존할 수 있다는 것을 의미했으므로 메시는 단순한 구형입니다! 우리는 세부 사항을 높은 폴리 메쉬로 조각하고 일반 맵을 훨씬 낮은 폴리 구형에 굽어서 막대한 성능 비용 없이 모든 아름다운 세부 사항을 얻을 수 있었습니다.



눈에 초자연적인 느낌을 더하고 존재를 강조하기 위해, 균열을 통해 흘러나오는 빛나는 네온 마그마를 만드는 것이 좋다고 결정했습니다.표면 모습위한 방사 채널이 없지만, 바위 외부 표면과 빛나는 마그마에 대한 두 번째, 약간 더 작은 눈을 만들어 이 장애물을 극복합니다.In 질감 화가 , 우리는 내부 코어가 통과해야 하는 영역에서 투명도가 있는 외부 구체의 기본 색상 텍스처를 만들었습니다.In 블렌더 , we "vertex painted" 내부 구체에 쉽고 저렴한 방법으로 색상 변화를 얻을 수 있도록 했습니다.

눈을 만들 때 발생한 또 다른 도전은 플레이어와의 거리에서 스트리밍을 사용하는 우리의 사용으로 부과되었습니다.이 구조의 중앙성을 고려하여, 거리에도 불구하고 항상 표시되기를 원했지만, 메시에 대한 어떤 해킹도 없이 플레이어는 솔라리움에 없으면 눈을 볼 수 없었습니다.눈과 눈의 링에 일부 기하 구조를 추가하여 눈의 상시 존재를 장면에 강제할 수 있었습니다.이 기하학은 지형의 표면 바로 아래에 있으며, 이것으로 엔진을 속이고 구체가 플레이어에게 더 가깝다고 생각하고 항상 스트리밍하도록 만들 수 있습니다.그러나 너무 많은 대형 개체를 스트리밍하도록 강제하면 스트리밍 활성화의 이점을 무효화하고 게임 이행부정적인 영향을 미칠 수 있으므로 이러한 작업은 상당히 제한적으로 수행해야 합니다.
구름 메쉬를 회전하기 위해 사용하던 동일한 스크립트 덕분에 눈과 그 링에 움직임을 추가할 수 있었습니다 rotate the cloud meshes.최종 터치를 위해 구름 너머의 다른 세계의 존재에 힌트를 추가하기로 결정했지만, 더 많은 지오메트리를 장면에 추가하고 스트리밍 활성화로 인해 언급된 장애물을 처리해야 하는 문제를 피하기 위해 창의적인 접근법을 취해야 했습니다.우리는 물체의 상대적인 크기와 거리 때문에 깊이가 많은 장면을 만들었고, 이 장면의 이미지를 렌더링하고 폭풍의 눈 바로 뒤에 배치된 부품에 데칼로 사용했습니다.이 부품을 눈과 그 링에 사용한 것과 동일한 방법으로 회전시켰습니다.

확장된 창고 만들기
생산하는 데 가장 재미있는 일 중 하나는 부패한 공간이었는데, 이곳에서 우리는 플레이어가 현실에 대한 기대를 뒤집을 수 있도록 말 그대로 주변을 바꿀 수 있었습니다.예를 들어, 아버지의 퍼즐에서 우리는 당신이 얼마나 빨리 달리든 상관없이 악몽과 비슷한 순간을 모방하고 싶었습니다. 방은 점점 길어지는 것처럼 느껴집니다.플레이어가 방을 정상 상태로 되돌리기 위한 재료를 찾는 동안 팬트리를 확장시키기로 결정했습니다.
우리는 벽의 간단한 움직임과 판트리 양쪽에 나타날 우리 방의 영리한 레이아웃으로 이것을 설정했습니다.방의 정상 상태에서 창고는 간단한 복도였지만, 오류가 발생한 공간에서는 실제로 여러 날개와 가짜 벽이 있어 훨씬 길었습니다!


가짜 벽은 플레이어가 트리거 볼륨에 들어갈 순간 이전의 창고에 투명한 부분이었던 모델 그룹이었습니다.그 트리거는 또한 모든 문에 사용되는 스크립트와 유사한 스크립트에서도 사용되었으며, 이는 TweenService를 호출하여 한 목표에서 다른 목표로 이동했습니다.부품 볼륨을 사용하여 벽의 시작 및 끝 위치가 어디에 있는지 전환 작업을 알렸습니다.


왜냐하면 TweenService 는 그러한 일반적인 시스템이기 때문에, 모든 벽 데이터 모델에 포함되어야 하는 것은 동일한 구성 요소였습니다.예를 들어, 일반적인 "Door_Script" 스크립트는 "Grow_Wall" 모델 아래에서 "값"으로 정의된 소리를 재생합니다.다음 코드 샘플에서 약간의 수정을 한 동일한 스크립트는 또한 창고 이동에 대한 오디오를 트리거했습니다.이것은 운동에 많은 것을 추가했습니다!
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, --문 사이의 시간/속도
Enum.EasingStyle.Quart, --스타일 완화
Enum.EasingDirection.InOut, --완화 방향
0, --반복 계산
false, --진실 반전
0 --지연
)
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
--모델["문"]: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("터치")
playersNear[player] = 1
if doorState == DoorState.Closed then
StartOpening()
end
end
end
end
우리가 가짜 벽이 방의 뒤로 이동하기 시작했을 때, 나머지 콘텐츠도 함께 이동해야 했습니다.이를 위해 저장소의 모든 분리된 아이템이 이동하는 동안 벽에 접합되도록 해야 했습니다.용접 제약 조건 을 사용하여 우리는 빠르게 모든 개체를 창고 벽에 용접하여 단일 개체로 이동할 수 있었습니다.이렇게 하면 플레이어가 이러한 항목에 부딪혀 이동할 수 있도록 용접을 해제할 수 있는 옵션이 있었습니다!
오염된 트리하우스 만들기
Studio는 흔들리는 게이트에서 회전하는 플랫폼까지 모든 것을 만들 수 있는 훌륭한 물리적 기반 엔진입니다.데모로 우리는 물리학을 사용하여 현실적이지 않은 환경 집합에서 현실감을 만들고 싶었습니다.몇 가지 제약 조건 만 사용하여 자신의 경험 내에서 재미있고 도전적인 장애물 코스를 만들 수 있습니다!

제약 조건 은 물리적으로 기반을 둔 모터 그룹으로, 개체를 정렬하고 동작을 제한합니다.예를 들어, 고정된 거리를 유지하기 위해 막대 제약조건을 사용하여 개체에 연결하거나 줄 제약조건을 사용하여 줄 끝에 램프를 매달 수 있습니다.플레이어가 연구의 부패한 상태로 이동되는 아들의 퍼즐에서, 우리는 말 그대로 세계를 그 측면으로 뒤집고 싶었습니다.그러면 플레이어의 현실 및 규칙에 대한 기대를 무너뜨리고 물리 시스템을 의도한 대로 계속 사용하면서도 계속 사용할 수 있습니다!

플레이어가 퍼즐의 주요 영역까지 내려갔을 때, Roblox에서 익숙한 모습으로 맞이했습니다: 장애물 코스.이 특정 장애물 코스는 여러 개의 회전 플랫폼과 회전하는 벽, 그리고 스토리를 진행시키는 "안전 구역"으로 구성되었습니다.회전/회전 요소에 집중하겠습니다.

왜 여기에 제약 조건을 사용했나요? 왜냐하면 TweenService 또는 다른 메서드는 플레이어가 그들 위에 서 있는 동안 이동하지 않을 것이기 때문입니다.플레이어를 이동시키는 개체가 없으면 누군가 플랫폼에 점프할 수 있고 그들 아래에서 회전할 수 있습니다.대신, 플레이어가 회전하는 플랫폼을 통해 이동하면서 다음으로 점프하려고 시도하기를 원했습니다.이 접근 방식으로 인해 플레이어는 코스를 진행하는 방법에 대한 결정을 내리는 동안 서 있던 곳에 뿌리를 내리고, 회전 표면으로 이동하도록 특별한 조치를 취할 필요가 없었습니다!

이를 위해서는 먼저 현재 키트의 자산을 사용하고 시각적 효과를 위한 새로운 콘텐츠를 추가해야 했습니다.할머니가 나무집을 짓는 이야기를 전하기 위해 몇 개의 불완전한 벽과 플랫폼을 만들었습니다.우리는 독특한 플랫폼 묶음을 만들고 싶지 않았기 때문에 4개의 다른 기본 조각과 난간 조각을 별도로 만들었습니다.이를 통해 개별 베이스와 레일링 조각을 혼합하여 다양성을 많이 갖출 수 있었습니다.

제약 조건을 사용하고 있기 때문에 이러한 메쉬를 고정할 수 없을 것이라는 점을 알고 있었습니다. 제약 조건/드라이빙 모터가 존재하더라도 이동하지 않을 것입니다.플랫폼이 단순히 세상에서 떨어지지 않도록 고정된 무언가의 자식이 되어야 하는 제약 조건.이를 해결하기 위해 플랫폼의 전체 이동을 구동하는 모터_앵커 라는 부품에 hinge 제약 조건을 추가했습니다.그 후에는 두 메쉬가 하나로 이동해야 했으므로 Motor_Turn 이라는 부품을 만들고 두 메쉬를 접합했습니다.이렇게 하면 제약 조건이 여러 부품을 사용하는 여러 힌지와 달리 단일 부품에서 작동할 수 있습니다.

이제 힌지 제약 조건 자체의 실제 동작을 설정하고 부품과 제약 조건의 방향으로 작용할 부착물을 추가할 시간이었습니다.도보 조각이 접합된 모터_회전에 회전 부착물을 배치하고 모터_앵커 자체의 앵커 동작을 위한 또 다른 부착물을 힌지 제약 조건 옆에 배치했습니다.이것은 플레이어에 의해 영향을 받지 않고 보유회전해야 했기 때문에, 문틀처럼 플레이어에 영향을 받지 않고 독립적으로 움직이는 제약조건을 처리하기 위해 HingeConstraint.ActuatorType를 모터 로 설정했습니다.
플랫폼이 일정한 속도로 회전하도록 유지하려면 HingeConstraint.AngularVelocity , HingeConstraint.MotorMaxAcceleration 및 HingeConstraint.MotorMaxTorque 속성을 플레이어가 점프하면 이동하고 중단을 방지할 수 있는 값으로 설정합니다.

이제 회전하는 벽을 만들어야 했습니다.벽들은 그들의 명백한 중심에서 회전해야 했고, 우리는 그들이 레벨의 나머지와 관련된 모든 방향을 처리할 수 있기를 원했습니다.플랫폼과 마찬가지로, 우리는 모든 벽이 고정되지 않고 Motor_Turn에 접합되도록 이것들을 만들었습니다.

우리는 기본 재료에 변형을 추가하기 위해 Texture 개체를 사용하여 SurfaceAppearance 개체 위에 배치했습니다.텍스처 , 데칼과 유사하게, 메시의 평면에 이미지를 배치할 수 있습니다.이것은 벽돌 벽에 먼지를 추가하거나 동일한 기본 나무 재료를 사용하여 나무를 오래 보이게 하려는 경우에 유용할 수 있습니다. 개체는 타일링하고 이미지를 오프셋할 수 있는 방식이 약간 다르며, 이는 오버레이 텍스처를 확장하고 반복하는 것을 신경쓰지 않으려는 경우 매우 유용합니다!

몇 개의 플랫폼과 회전 벽을 테스트한 후, 여러 가지 변형을 만들고 플레이어가 가야 할 장애물 코스가 도전적이고 뇌 회전적이며 또한 명확하다는 것을 확인했습니다! 플레이어가 잘 실행하도록 두 가지 값과 위치를 조정하는 데 약간의 조정이 필요했습니다!플랫폼과 벽이 서로 또는 주변 환경에 충돌하는 여러 지점이 있었지만, 이동하고 자주 테스트하여 데모에 있는 설정에 착륙할 수 있었습니다!
물리적 개체가 타격하고 있는 것이 무엇인지 확신할 수 없는 경우, 3D 뷰포트의 오른쪽 상단 모서리에 있는 시각화 옵션 위젯에서 충돌 정확도를 토글할 수 있습니다



아래 출입구/창문 구멍에서 볼 수 있듯이 하위 패널링과 같은 작은 세부 사항은 표시되지 않습니다.이는 벽에 대한 CollisionFidelity 속성이 상자 로 설정되었기 때문입니다.이러한 패널에 대한 정밀도가 필요하지 않았으므로 성능 비용을 절감하기 위해 플레이어가 점프할 수 있을 정도로 상세했습니다.플랫폼과 회전 벽이 완료되면 상자와 램프와 같은 세부 자산만 추가하면 플레이할 준비가 완료되었습니다!
