在任何體驗內的環境中創造移動會幫助它讓感覺更加逼真地對我們的世界,無論是從環境樹木運動、反應門從玩家互動或甚至是衝擊到�
創造暴風
暴風通過了許多版本,直到我們決定在神秘的Duvall Drive中什麼是活的。 在早期,我們考慮了暴風作為巨大的黑曜石柱,並在後續的版本中考慮它作為腐敗空間的巨大傳送門。 經過許多不同的暴風,每個都有獨特的外觀和感覺,後來我們決定了一個更小的中心“眼
- 暴風應該讓玩家有一個感覺 這個事件對世界的影響 ,包括樹木被吹走和垃圾飛來飛去。
- 雲自身的旋轉漩渦應該讓玩家們有一個偷看中央傳送門 沒有揭示一切 的機會。這會鼓勵玩家們進一步調查,以便了解發生了什麼事。
- 能更加緊密的光點讓我們能夠 專注於房屋的結構 ,這是遊戲的主角,並且是遊戲的大部分。
為了讓暴風在其環境中感覺動態、攻擊性、並且隨時隨地改變,我們使用了以下系統和功能:
- TweenService > 對於雲端移動。
- 照明變更 - 用於創建雲朵閃電。
- 光束 > 用於「音量照明」和閃電。
- 粒子發射器 > - 對於因風而起飛的垃圾。
- 動畫 > 對於被拋到風中的樹。
添加雲朵以結構
隨著雲的高度、形狀和方向發生變化,動態雲的外觀會改變。我們需要一
由於每個雲朵網格需要擁有巨大的圍欄房屋,並且傳達暴風的大小,因此我們知道我們需要在個別雲朵網格上使用的材料上色,以便在網格表面上重複。我們在這些簡單的零件上測試了我們製作的雲朵,然後將它們應用到漩渦!
與粒子發射器或光束不同,網格讓我們能夠從每個網格上反射光,這是當我們想要實現雲端至雲端閃電時重要的。 我們也在扭轉中模型化,以便讓從每個網格上反射的光看起來像它有深度!這是特別在需要體驗表面外觀對象的性能的情況下重要的。
旋轉雲朵網格
我們在處理雲的整體外觀時很滿意,但我們需要讓它們移動工具行!我們有各個雲層的形狀位置,但需要一些測試和錯誤才能確認旋轉效果在實際中看起來好。我們最初試了使用 限制 來介紹速度,這會使雲在實際中轉動
我們想要一種簡單的方法來旋轉實例,例如雲朵,或太小或裝飾,以便在遊戲/物理學方面對
在很多情況下,我們在示範中使用了 LocalSpaceRotation 標籤,以便我們能夠使用一個實例標籤外掛程式來管理受影響的實例在 Studio 中。 我們使用了只有一個 LocalScript ,這處理了所有標籤實例使用 CollectionService 的方式
在我們的
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 呼叫,但我們會
我們旋轉了Update 功能連接到心跳的 i物件stan
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 (可以指向子網格) 尚未流行,我們就會跳過更新。現有系統是第二
設計閃電襲擊
因為 Studio 不提供預制閃電生成器,而且粒子系統有一些限制,不適合英雄閃電擊中,我們必須對英雄閃電擊的解決方案進行創意。 我們決定了兩個主要系統來構成閃電:由風暴眼來的預制閃電生成器,以及簡單的粒子
結構光束
我們通常會使用程式碼來控制照明箭效果的時間,但 Studio 目前並未提供此功能,因此我們決定以控制照明箭效果的時間的程式碼。這個程式碼的程式碼很簡單,但它完成以下重要目標:
- 閃電衝擊的元素,例如其外觀、亮度和延遲,都是隨機排列的。
- 音頻和 post 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 的函數,它會檢查預先放置的音量,並檢查玩家是否位於任何音量的內部 (點在框中)。我們可以使用觸摸基礎檢測,但我們發
要檢查玩家是否在腐敗區域,我們會使用一個幫助器 gameStateInfoFunc 函數,這會檢查當前遊戲狀態。要從資料夾中播放隨機聲音,我們也使用了一個幫助器 PlayOneShot 功能。對於閃電閃電自己
使用粒子發射器系統
英雄閃電襲擊是基於粒子系統,可以通過創建背景中的雲朵層來建立遠程閃電,或稱雲-到-雲照明。我們通過一個非常簡單的粒子系統來達到此效果,這是通過閃爍的雲朵標誌在主要暴風雲上閃爍的雲朵顆粒來傳播的。系統會定期發出一
使樹木在風中揮動
我們得到了雲和閃電的工作方式,我們想要的,然後我們需要添加兩個其他大氣的主要組成部分:風和雨!這些元素代表了一些挑戰,例如需要在 Studio 的當前限制內運行我們的物理和特效系統。例如,在今天的引擎中,不是可能使樹木移動
我們知道要真正賣出風和雨的效果,我們需要樹自己移動工具動。 有幾種方法可以在引擎內執行此操作,包括使用 plugins 的樹自己移動,或使用 TweenService 來動畫模型。 對於我們的目的,動畫讓我
我們從 Endorse Model Pack - Forest Assets 開始。這棵樹已經存在,因此我們的體驗發生在 Pacific Northwest ,因此我們在創建每個樹模型之前,從一開始就省下了一些時間。
我們選擇樹木後,我們知道我們需要把它們脫離。 脫離網格是在其他 3D 建模應用程式中,例如 Blender 或 Maya ,然後對這些脫離網格添加影響來移動網格。 這最
我們知道我們想要儲存時間,重用相同的動畫,所以我們建立了我們的第一個樹木網格,並確認共用名稱是一般名稱,因為我們想要在樹木上使用這些名稱,因為它們是隨
一旦我們創建了我們的關節/骨頭,就是時候創建一個測試動畫來移動所有關節和骨頭在Studio中看看它移動的方式。為此,我們必須 從樹幹匯入到Studio 通過 自�
我們在那棵樹上的結果很滿意之後,就該在不同的樹上測試相同的動畫了!我們已經知道每個樹輸入的不同結構之間會有相同的動畫,所以我們只是確認我們的動畫看起來像是在高大的紅橡樹和堅硬的桦木樹之間的一個通用動畫一樣了!
為此,我們從那個森林組合包中取得了蜂巢樹,並且建造了一個相似的機骨架,使用相同的正確命名為關節。 因為螢幕上的動畫都是基於旋轉關節的,所以樹的大小並不重要!
我們 審核和添加蜂蠟樹後, 我們可以將它匯入並應用相同的動畫。這意味著只需要在一個檔案上重複和編輯即可完成,並且在執行體驗時也會儲存在性能上。
我們有了所有的樹狀結構類型後,我們想要動畫樹狀結構,我們將它們組成 包裹 以便我們可以繼續編輯和更新遊戲中的動畫。因為我們知道它們有一個價格,因此我們在主區域的體驗玩遊戲時會盡量使用它
製作暴風殘骸
我們想讓雨看起來重,而且讓霧和垃圾通過樹木吹掃。為此,我們設置了幾個隱形零件作為粒子容量與兒童 粒子發射器 立即下方的大風暴雲。 因為 Studio 的粒子數
雨水粒子利用了一個新的粒子發射器屬性 ParticleEmitter.Squash ,可以使粒子更長,或圍欄。
對於濃霧、霧和葉子,通過吹拂的方式,添加單一的大部分體積對掌更少的區域很簡單,因為我們不需要一次滿滿的粒子。我們開始設置一個體積,並獲得粒子所需的頻率,在所需的區域上。
之後,我們製作了我們的葉子吹和風的材質,並將粒子設置為在不同的速度和方向上旋轉/移動。這意味著大型的葉子粒子會更自然地互動,而不會看起來像重複的紋理,尤其是考慮到它們的大小。
結果是移動樹木、窗戶吹拂和閃電創造暴風環繞中央暴風眼的效果。
設定暴風之眼
擁有發光核心的破碎之石眼是為玩家提供第一個暗示房屋內發生了某種邪惡、神秘的事情的線索。因為我們的場景是黑暗的,玩家無法看到那裡的石頭表面,因此,重要的是要創建一個相信的碎石形狀
玩家距離也表示我們能夠完全依賴於普通地圖的表面細節來描述眼睛的表面細節,因此網格只是一個平滑的球體!我們雕刻了細節以一個高度的聚合物網格,並將其普通地圖烤製到更低的聚合物球體上,以便我們能夠得到所有美麗的細節,而不是巨大的性能成本。
為了為眼睛增添超自然感覺,並強調其存在,我們決定創造一個發光的霓虹岩漿,它會穿透它的裂溝。雖然
我們在創建眼睛時遇到了另一個挑戰,因為我們使用 串流 與眼睛的距離結合,而且這
我們能夠添加移動到眼睛和其圓環,感謝我們使用的同一個指令 旋轉雲絲網格 。為了最終�����
建立擴展的儲藏庫
製造的最有趣的事情之一是腐化的空間,這裡我們能以字面意義上的方式改變玩家對現實的期望,讓他們覺得像是在夢境裡一個怪人一樣。 例如,在父親的謎題中,我們想要以一個夢魘般的房間為背景,讓隨著玩家移執行速度變快,房間感覺像是在變長。我
我們使用牆壁的簡單移動和房間的明智布置來設置這一切。 在房間的正常狀態下,牆壁是一個簡單的走廊,但在腐化的空間中,它們実際很長,有幾個翼和一個假的牆!
錯誤的牆是我們將玩家的觸發音量移動回來的模型群組,這是一個透明的部分,以前在配餐中走過。那個觸發音量也是用於所有我們的門的門檻,這是 TweenService 來將開始和終止位置移動到一起的。我們使用部分音量來告訴檯面開始和終止位置
因為 TweenService 是一個通用系統,所有的牆壁資料模型都必須包含相同的組件。例如,下圖是一個通用門指令,它會呼叫一個「值」在「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
--模型【門】:播放()
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 是一種基於物理的引擎,您可以使用它來創建從揮動閘門到旋轉平台的一切。 我們的示範讓我們想使用物理來在其他非實際的設定中創造一個現實感。 使用 限制 幾個,您就可以在您自己的體驗中創建一些有趣的障礙賽!
限制器 是一個物理基礎的動力,可以對對象進行對準和限制行為。 例如,您可以使用棒索限制器來連接到對象以保持它們
玩家通過謎題的主要區域時,他們會看到一個熟悉的景觀:障礙賽。這個特定的障礙賽由多個旋轉平台和旋轉牆組成,並且包含"安全區域"以進行故事。我們將專注於旋轉/旋轉的元素。
為什麼我們在這裡使用限制?因為 TweenService 或其他方法在玩家站在他們上面時不會移動玩家。無論對象移動玩家,或是在他們上面跳躍,都無法從他們下面跳出。因此,我們想要玩家在旋轉平台上嘗試
為此,我們需要先使用我們目前的套件中的資產,並且為視覺效果添加任何新內容。我們做了一些不完整的牆壁和平台,因為我們不想創建一堆獨特的平台,所以我們做了四個不同的基地項目和扶手項目。這允許我們混合不同的基地項目和扶手項目,以獲得豐富的變化。
我們知道,因為我們使用限制,所以我們不能使用這些網格,因為它們不會移動即使有限制/控制器駕駛它
現在是時候設置彈性約束本身的實際行為,並添加將零件和限制器結合起來的附件。我們將彈性約束放在 HingeConstraint.ActuatorType 上,這是零件和限制限制式的焊接位置,並且另
要讓平台在一個固定速度旋轉,我們 then set up the HingeConstraint.AngularVelocity , HingeConstraint.MotorMaxAcceleration 和 HingeConstraint.MotorMaxTorque 屬性值,以便允許移動並防止中斷,如果玩家跳上它。
現在我們需要做旋轉的牆壁。牆壁需要在它們顯然的中心上旋轉,我們知道我們想要讓它們能夠處理任何與剩餘水平相關的方向。像平台一樣,我們構建了這些,以便所有牆壁都能夠解錨和焊接在Motor_Turn。
我們使用 Texture 對象在 SurfaceAppearance 對象上添加一些變化。紋理,與
我們測試了幾個平台和旋轉的牆壁,然後我們做了一些變化,並且與其放置玩家需要前往的地方進行了一些測試,以確認障礙賽是否有挑戰性、心形樣式、或是清除需要的地方!我們需要在其價值和位置上調整平台和牆壁,才能使其正常運行。
如果您不確定物理對象正在擊殺,您可以從視窗選項 上角右邊的 widget 上切換衝突穩定性。
您可以看到門/窗戶的門窗洞在下面,但小細節,例如子板,不是。這是因為牆壁的 CollisionFidelity 屬性設為 箱子 。我們沒有需要精準度這些平板,所以為了節省性能成本,這是足夠詳細的給