Kembangkan dunia bergerak

*Konten ini diterjemahkan menggunakan AI (Beta) dan mungkin mengandung kesalahan. Untuk melihat halaman ini dalam bahasa Inggris, klik di sini.

Menciptakan gerakan di dalam lingkungan mana pun dalam sebuah pengalaman membantu agar terasa lebih imersif dan realistis, apakah itu dari gerakan pohon yang bergerak, pintu reaktif dari interaksi pemain, atau bahkan kotak yang bergerak ketika mereka bertabrakan. Studio memiliki banyak cara unik untuk menciptakan gerakan agar dunia terasa lebih hidup, termasuk sistem fisika, TweenService, dan animasi, dan menganalisis kebutuhan spesifik pengalaman Anda dapat membantu Anda menentukan mana yang akan digunakan. Di bagian ini, kami akan menunjukkan bagaimana kami menentukan jenis gerakan yang ingin kami ciptakan di Studio, dan alat apa yang kami gunakan untuk mencapai tujuan yang berbeda ini.

Ciptakan badai

Badai melewati banyak iterasi sebelum kami menetapkan apa yang ada di dalam The Mystery of Duvall Drive. Pada awalnya, kami memikirkan badai sebagai pilar obsidian raksasa, dan dalam iterasi berikutnya kami menganggapnya sebagai portal raksasa menuju ruang yang korup. Setelah bereksperimen dengan banyak badai yang memiliki tampilan dan nuansa unik, kami menetapkan badai dengan "mata" pusat yang lebih kecil karena:

  • Badai harus memberikan pemain rasa dampak dari peristiwa ini terhadap dunia, termasuk pohon-pohon yang tertiup dan puing-puing yang beterbangan.
  • Vorteks berputar dari awan itu sendiri harus memberikan pemain sekilas pendek ke portal pusat tanpa mengungkapkan segalanya. Ini akan mendorong pemain untuk menyelidiki lebih dekat untuk melihat apa yang terjadi.
  • Titik cahaya yang lebih ketat akan memungkinkan kami untuk fokus pada komposisi rumah, yang merupakan karakter utama dan di mana sebagian besar gameplay berada.

Untuk membuat badai terasa dinamis, agresif, dan selalu berubah dalam lingkungan, kami menggunakan sistem dan fitur berikut:

  1. TweenService - Untuk gerakan awan.
  2. Perubahan pencahayaan - Untuk menciptakan kilatan petir dari awan ke awan.
  3. Beams - Untuk "pencahayaan volumetrik" dan sambaran petir.
  4. Particle Emitters - Untuk puing-puing yang terbang menuju portal dan terbang di sekitar karena angin yang bertiup.
  5. Animasi - Untuk pohon-pohon yang tertiup oleh angin.

Tambahkan awan dengan tekstur

Sementara awan dinamis sangat bagus untuk awan realistis, tinggi, kami membutuhkan sesuatu yang terasa dramatis dan bisa kami arahkan dan kostumisasi secara lebih mendalam. Untuk melakukan ini, kami menerapkan objek surface appearance dengan semi-transparansi ke serangkaian mesh awan yang terstacking dan berlapis berat untuk menyamarkan tutupan awan. Mengapa kami menumpuknya dan melapisinya begitu berat? Karena ketika setiap mesh awan bergerak pada kecepatan yang berbeda, mereka berinteraksi dan menciptakan bentuk awan yang saling memasuki satu sama lain. Proses ini membuat awan terasa sedikit lebih dinamis dan alami, meskipun hanya berupa cakram yang berputar. Juga penting bahwa awan tersebut semi-transparent, karena kami ingin pemain bisa mengintip melalui mereka untuk melihat sesuatu yang cerah di tengah sebelum tiba di rumah!

Sebuah mesh awan tunggal.
Mesh awan berlapis tanpa tekstur!

Karena setiap mesh awan perlu sangat besar untuk sepenuhnya mengelilingi rumah dan menunjukkan betapa besarnya badai, kami tahu bahwa kami perlu menjadikan tekstur yang ingin kami gunakan pada setiap mesh awan sehingga banyak yang terulang di seluruh permukaan mesh tersebut. Kami menguji bahan-bahan yang kami buat untuk awan ini pada bagian sederhana ini, lalu menerapkannya pada vorteks!

Berbeda dengan partikel emitter atau beams, mesh memungkinkan kami untuk memantulkan cahaya dari setiap mesh, yang penting ketika kami ingin menerapkan kilatan petir dari awan ke awan. Kami juga memodelkan dalam bentuk belitan sehingga cahaya yang memantul akan terlihat memiliki kedalaman! Ini sangat penting terutama dalam situasi di mana permintaan kinerja dari pengalaman menurunkan tingkat kualitas dari objek penampilan permukaan kami.

Setelah kami mulai menambahkan pencahayaan padanya, kami perlu menambahkan detail pada mesh agar lebih responsif terhadap pencahayaan!

Putar mesh awan

Setelah kami puas dengan penampilan visual keseluruhan dari awan, kami perlu membuatnya bergerak! Kami telah memiliki bentuk umum dari setiap lapisan awan di tempatnya, tetapi diperlukan beberapa percobaan dan kesalahan untuk memastikan efek berputar terlihat bagus dalam praktiknya. Awalnya kami mencoba menggunakan constraints untuk memperkenalkan kecepatan yang akan mendorong awan bergerak secara fisik. Ini lebih sulit daripada yang kami inginkan untuk iterasi di kemudian hari, dan pemain tidak akan pernah berinteraksi dengannya, jadi kami tidak perlu agar gerakannya seramah itu.

Kami menginginkan metode yang mudah digunakan untuk memutar instance yang terlalu jauh untuk dapat berinteraksi, seperti awan, atau terlalu kecil atau dekoratif untuk menjadi penting untuk gameplay/fisika, seperti perabotan dalam ruangan seperti lampu kecil. Kami memutuskan untuk menggunakan LocalScript untuk mengurangi bandwidth klien-server, memungkinkan untuk gerakan yang lebih halus, dan agar setiap mesh awan dapat memiliki tingkat putaran dan penundaan yang berbeda. Untuk membuatnya lebih umum, kami juga membuatnya agar memungkinkan untuk menentukan sumbu rotasi. Dimungkinkan untuk menggunakan 3 atribut, tetapi dalam kasus kami kami menggunakan 3 nilai: Axis, Delay, dan Speed.

Seperti dalam banyak kasus dalam demo, kami menggunakan tag LocalSpaceRotation sehingga kami dapat mengelola instance yang terkena dampak di Studio menggunakan plugin penandaan instance. Kami hanya menggunakan satu LocalScript yang menangani semua instance yang ditandai menggunakan CollectionService sehingga kami tidak memiliki banyak skrip untuk dipelihara sepanjang proses pengembangan.

Dalam demo kami, bagian-bagian dunia diklon dari ServerStorage ke workspace sesuai kebutuhan, jadi kami perlu menangani kasus di mana objek-objek yang ditandai dibuat dan dihancurkan. Dengan LocalScripts, kami juga harus menyadari streaming, di mana mesh dan nilai anaknya mungkin disalurkan masuk dan keluar. Kami memproses objek yang ditempatkan awalnya di fungsi Init(), dan terhubung ke CollectionService.GetInstanceAddedSignal dan CollectionService.GetInstanceRemovedSignal untuk objek yang ditandai untuk menangani objek baru yang dibuat/dihancurkan. Fungsi SetupObj yang sama digunakan untuk menginisialisasi objek baru dalam Init() dan dalam 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 adalah peta yang memiliki informasi untuk semua objek yang relevan, seperti kecepatan rotasi dan sumbu mereka. Catatan bahwa kami tidak memanggil SetupObj dari CollectionService.GetInstanceAddedSignal langsung, tetapi kami menambahkan objek ke dalam objInfoQueue. Dengan streaming dan mengkloning objek di server, ketika CollectionService.GetInstanceAddedSignal dipanggil, kami mungkin belum memiliki nilai-nilai Axis, Delay, dan Speed, jadi kami menambahkan objek ke dalam antrean, dan memanggil SetupObj pada frame-frame berikutnya dari fungsi Update sampai nilai-nilai itu ada dan kami bisa membacanya ke dalam struktur "info" per objek.

Kami memutar instance dalam fungsi Update yang terhubung ke heartbeat. Kami mendapatkan transformasi induk (parentTransform), menjumlahkan sudut rotasi baru (curObjInfo.curAngle) berdasarkan kecepatan rotasi objek ini, menghitung transformasi lokal (rotatedLocalCFrame) dan akhirnya menetapkannya ke CFrame. Catatan bahwa baik induk maupun objek dapat berupa Model atau MeshPart, jadi kami harus memeriksa IsA("Model") dan menggunakan baik PrimaryPart.CFrame atau CFrame.

local parentTransform
if parentObj:IsA("Model") then
if not parentObj.PrimaryPart then
-- primary part mungkin mungkin belum disalurkan
continue -- tunggu untuk primary part direplikasi
end
parentTransform = parentObj.PrimaryPart.CFrame
else
parentTransform = parentObj.CFrame
end
curObjInfo.curAngle += dT * curObjInfo.timeToAngle
local rotatedLocalCFrame = curObjInfo.origLocalCFrame * CFrame.Angles( curObjInfo.axisMask.X * curObjInfo.curAngle, curObjInfo.axisMask.Y * curObjInfo.curAngle, curObjInfo.axisMask.Z * curObjInfo.curAngle )
if obj:IsA("Model") then
obj.PrimaryPart.CFrame = parentTransform * rotatedLocalCFrame
else
obj.CFrame = parentTransform * rotatedLocalCFrame
end

Kami memeriksa apakah Model.PrimaryPart yang valid diatur untuk menangani streaming. Jika sebuah Update disebut pada objek kami saat sebuah Model.PrimaryPart (yang dapat mengarah ke mesh anak) belum disalurkan, kami akan melewati update. Sistem saat ini adalah iterasi kedua dari rotasi objek, dan sistem sebelumnya bekerja berbeda: nilai-nilainya 12 kali berbeda! Untuk menjaga data yang sama, kami mengkonversinya dalam skrip kami, seperti "12 * obj.Speed.Value".

Desain sambaran petir

Karena Studio tidak menawarkan generator kilat secara bawaan, dan sistem partikel memiliki beberapa keterbatasan yang tidak bekerja untuk sambaran petir hero, kami harus menjadi kreatif dengan solusi untuk sambaran petir hero. Kami memutuskan untuk membuat dua sistem utama yang membentuk petir: beam bertekstur untuk sambaran petir hero yang berasal dari mata badai adalah beam bertekstur yang diprogram yang memperlihatkan dan sinkron dengan audio dan efek pasca pemrosesan, dan efek partikel sederhana untuk sambaran petir dari satu awan ke awan lain yang jauh.

Beam tekstur

Kami biasanya menggunakan sequencer atau alat garis waktu untuk mengendalikan waktu efek sambaran petir seperti ini, tetapi karena Studio belum menawarkan fungsi ini, kami memutuskan untuk menulis skrip yang akan mengendalikan waktu sambaran petir. Penyusunan efek ini cukup sederhana, tetapi mencapai tujuan penting berikut:

  1. Elemen-elemen dari sambaran petir, seperti tekstur, kecerahan, dan penundaan, diacak dengan setiap sambaran.
  2. Perubahan audio dan efek pasca di-sinkronkan dengan efek sambaran.
  3. Pemain yang berada di dalam ruangan atau di area korup tidak dapat melihat atau mendengar mereka.

Kami memiliki Script sisi server yang menghitung berbagai parameter dan waktu, mengirimkannya ke semua klien, dan menunggu waktu acak:

local function LightningUpdate()
while true do
task.wait(rand:NextNumber(3.0, 10.0))
local info = CreateFXData()
lightningEvent:FireAllClients(info)
end
end

Di sisi klien (LightningVFXClient), kami memeriksa apakah klien ini harus menjalankan efek:

local function LightningFunc(info)
-- tidak ada efek ketika berada di dalam ruangan
if inVolumesCheckerFunc:Invoke() then
return
end
-- tidak ada efek ketika tidak berada di dunia "normal"
if not gameStateInfoFunc:Invoke("IsInNormal") then
return
end

Selain itu, kami menjalankan urutan untuk menetapkan tekstur, posisi, dan kecerahan, menjalankan tween, dan menggunakan task.wait(number). Parameter yang diacak berasal dari struktur info yang kami terima dari server, dan beberapa nomor bersifat tetap.

beam.Texture = textures[info.textIdx]
beamPart.Position = Vector3.new(info.center.X + og_center.X, og_center.Y, info.center.Y + og_center.Z)
-- Wipe
beam.Brightness = 10
ppCC.Brightness = maxPPBrightness
ppBloom.Intensity = 1.1
bottom.Position = top.Position
tweenBrightness:Play()
tweenPPBrightness:Play()
tweenPPBrightness:Play()
tweenBottomPos:Play()
tweenBrightness.Completed:Wait()
-- audio
if audioFolder and audioPart then
if audioFolder.Value and audioPart.Value then
audioUtils.PlayOneShot(audioObj, audioFolder.Value, audioPart.Value)
end
end
task.wait(info.waitTillFlashes)
-- dan seterusnya

Untuk memeriksa apakah seorang pemain berada di dalam ruangan, kami menggunakan fungsi pembantu inVolumesCheckerFunc, yang melalui volume yang telah ditempatkan sebelumnya yang mendekati area dalam ruangan, dan memeriksa apakah posisi pemain berada di dalam salah satu dari mereka (PointInABox). Kami bisa saja menggunakan deteksi berbasis sentuhan, tetapi kami menemukan bahwa ketika seorang pemain duduk di dalam volume, mereka tidak lagi "menyentuh" volume tersebut. Menguji sebuah titik di beberapa kotak lebih sederhana, dan kami hanya melakukannya ketika seorang pemain bergerak jauh dari posisi yang sebelumnya diuji.

Untuk memeriksa apakah pemain berada di area korup, kami memanggil fungsi pembantu gameStateInfoFunc, yang memeriksa status game saat ini. Untuk memainkan suara acak dari folder, kami juga menggunakan fungsi pembantu PlayOneShot. Untuk sambaran petir itu sendiri, ini sangat mudah untuk dibuat di Photoshop; kami menggambar garis berkelok, lalu menambahkan efek layer "Outer Glow".

Manfaatkan sistem emitter partikel

Sambaran petir hero didukung oleh sistem partikel yang menyarankan sambaran petir jauh dengan menciptakan kesan lapisan awan di latar belakang yang menangkap cahaya dari sambaran jauh, atau sambaran petir dari awan ke awan. Kami mencapai efek ini melalui sistem partikel yang sangat sederhana yang berkedip-kedip pada bagian tepi dari awan badai utama. Sistem ini mengeluarkan partikel awan secara berkala dengan kurva transparansi yang diacak:

Buat pohon-pohon tertiup angin

Setelah kami memiliki awan dan sambaran petir berfungsi sebagaimana mestinya, kami kemudian perlu menambahkan dua komponen utama lainnya dari badai: angin dan hujan! Elemen-elemen ini menghadirkan beberapa tantangan, termasuk kebutuhan untuk bekerja dalam keterbatasan sistem fisika dan efek khusus Studio saat ini. Misalnya, membuat pohon bergerak dengan angin yang sebenarnya tidak mungkin dalam mesin saat ini, jadi kami memanfaatkan efek partikel emitter dan animasi karakter kustom untuk pohon-pohon tersebut.

Kami tahu bahwa untuk benar-benar menjual efek angin dan hujan, kami perlu agar pohon-pohon itu sendiri bergerak. Ada beberapa cara Anda dapat melakukan ini di dalam mesin, termasuk memindahkan bagian menggunakan plugin yang tersedia secara publik, menggunakan TweenService, atau menganimasi model secara langsung. Untuk tujuan kami, animasi memberi kami kemampuan untuk mengendalikan gerakan yang kami inginkan dari pohon-pohon kami, dan memungkinkan kami untuk menggunakan satu animasi yang bisa kami bagikan di antara semua pohon dalam pengalaman.

Kami mulai dengan mengeksplorasi beberapa pohon dari Endorse Model Pack - Forest Assets. Karena pohon-pohon ini sudah ada, dan pengalaman kami terjadi di Pacific Northwest, ini menghemat waktu kami saat awal karena kami tidak perlu membuat setiap model pohon.

Paket Hutan berisi beberapa jenis pohon, yang dapat menghemat waktu Anda dalam pengalaman Anda sendiri.

Setelah kami memilih pohon-pohon kami, kami tahu kami perlu untuk menglembungnya. Skinning sebuah mesh adalah tindakan menambahkan sendi (atau tulang) ke mesh dalam aplikasi pemodelan 3D lainnya, seperti Blender atau Maya, kemudian menerapkan pengaruh pada sendi/tulang tersebut untuk menggerakkan mesh. Ini paling umum digunakan dalam karakter humanoid, tetapi dengan karakter kustom, Anda dapat menglembungkan hampir segala sesuatu.

Kami tahu kami ingin menghemat waktu dan menggunakan animasi yang sama, jadi kami membangun rig pohon pertama kami dan memastikan nama-nama sendi tersebut generik karena kami ingin menggunakan nama-nama yang sama ini dalam rig untuk pohon-pohon lainnya. Kami juga tahu kami perlu menyertakan sendi/tulang primer, sekunder, dan tersier agar batangnya dapat menekuk mengikuti angin, cabang bisa bergetar, dan daun terlihat seperti bergetar sebagai respons. Untuk proses ini, kami perlu membuat gerakan sekunder, yang merupakan konsep animasi di mana setiap aksi menyebabkan bagian-bagian lain dari objek merespons aksi tersebut dan tampak seperti mengejar gerakan awal.

Pohon-pohon memiliki sendi primer, sekunder, dan tersier agar gerakannya tampak meyakinkan saat tertiup oleh angin.

Setelah kami membuat sendi/tulang kami, saatnya untuk membuat animasi uji coba untuk menggerakkan semua sendi dan tulang di Studio untuk melihat apakah itu bergerak sesuai yang kami inginkan. Untuk melakukan ini, kami harus mengimpor pohon ke Studio melalui pengaturan Custom Rig dalam Importer, kemudian memindahkan/menganimasi mesh menggunakan Animation Editor. Kami mengatur bahan dan tekstur setelah pengujian ini, tetapi Anda dapat melihat hasilnya di bawah ini.

Hierarki yang sama dalam Studio.

Setelah kami puas dengan hasil pohon itu, saatnya untuk menguji animasi yang sama pada pohon yang berbeda! Kami sudah tahu bahwa itu akan menjadi animasi yang sama antara rig yang berbeda untuk setiap tipe pohon, jadi kami hanya memastikan animasi kami terlihat cukup umum untuk bekerja di antara pohon Redwood yang tinggi dan pohon Beechwood yang kekar!

Animasi yang kami impor pada pohon Redwood.

Untuk ini, kami mengambil pohon Beechwood dari paket Hutan itu dan membangun rig yang serupa, menggunakan nama yang persis sama untuk sendi-sendinya. Ini supaya animasi yang telah kami impor sebelumnya bisa diterapkan pada pohon ini juga. Karena animasi semuanya didasarkan pada sendi yang berputar, tidak masalah seberapa besar, kecil, tinggi, atau lebar pohon tersebut!

Pohon Beechwood memiliki nama yang persis sama untuk sendinya, hanya saja tidak sama jumlahnya. Ini baik-baik saja karena sistem animasi hanya akan menerapkan animasi ke sendi tertentu yang sesuai dengan namanya! Untuk alasan ini, kami bisa menerapkan animasi yang sama ke apa pun yang cocok dengan nama sendi!

Setelah kami menyiapkan dan menglembungkan pohon Beechwood, kami lalu bisa mengimpornya dan menerapkan animasi yang sama persis. Ini berarti bahwa iterasi dan pengeditan hanya perlu dilakukan pada satu file, dan juga menghemat kinerja dengan lebih sedikit animasi saat menjalankan pengalaman.

Dengan menggunakan Animation Editor, kami bisa menerapkan animasi pohon Redwood yang sama ke pohon Beechwood!

Setelah kami memiliki semua tipe pohon yang ingin kami animasikan, kami menjadikannya sebagai paket sehingga kami bisa terus mengedit dan memperbarui sambil memainkan beberapa animasi di sekitar area utama pengalaman. Karena kami tahu bahwa mereka memiliki biaya kinerja, kami menggunakannya secara hemat di sekitar rumah di mana efek tersebut paling berharga! Di masa depan ketika ini menjadi lebih efisien, Anda akan dapat menambahkan lebih banyak lagi instance mesh yang berkulit!

Kami menggunakan pohon animasi langsung di sekitar rumah di mana vortex terkuat dan efek visualnya akan paling berdampak bagi pemain.

Buat puing-puing badai

Kami ingin hujan tampak deras, dan kabut serta puing-puing tertiup di antara pohon-pohon. Untuk melakukan ini, kami mengatur beberapa bagian tak terlihat untuk bertindak sebagai volume partikel dengan anak-anak partikel emitter tepat di bawah awan badai besar. Karena batas jumlah partikel di Studio, kami tidak bisa menggunakan satu partikel emitter untuk seluruh ruang. Sebagai gantinya kami menambahkan beberapa yang memiliki ukuran yang sama dalam pola grid di area yang dapat dimainkan, karena keberadaan pohon-pohon berarti pemain tidak akan bisa melihat terlalu jauh.

Kami menggunakan beberapa volume untuk mendapatkan jumlah hujan dan cakupan hujan spesifik yang kami inginkan.

Partikel hujan memanfaatkan properti emitter partikel baru ParticleEmitter.Squash yang memungkinkan Anda membuat partikel lebih panjang, atau lebih pendek. Ini sangat berguna untuk hujan karena berarti kami tidak perlu tekstur hujan yang besar, cukup meregangkan yang sudah ada. Hanya saja jika Anda meningkatkan nilai ParticleEmitter.Squash, Anda mungkin perlu meningkatkan juga properti ParticleEmitter.Size secara keseluruhan agar tidak terlalu kurus! Secara keseluruhan, hanya soal memainkan nilai-nilai sampai kami mendapatkan hujan yang cukup deras, tetapi tidak begitu banyak sehingga menghalangi visibilitas pengalaman!

Nilai Squash 3 mulai meregangkan tekstur lebih panjang.
Nilai Squash 20 meregangkan partikel jauh lebih panjang, tetapi kami juga perlu meningkatkan nilai Ukuran juga.

Untuk kabut, kabut, dan daun yang tertiup, jauh lebih sederhana untuk menambahkan satu volume part yang lebih besar menutupi area yang lebih sedikit karena kami tidak perlu banyak partikel berjalan sekaligus. Kami mulai dengan menyiapkan satu volume dan mendapatkan frekuensi partikel di mana kami menginginkannya.

Akhirnya ada beberapa volume partikel sehingga partikel tidak masuk ke dalam rumah, dan karena kami merasa mereka tidak perlu bergerak melalui pohon seperti kabut.
Volume partikel kabut jauh lebih besar karena partikel-partikelnya besar, dan kami tidak perlu seakurat mungkin dengan lokasinya.

Setelah itu, kami membuat tekstur angin dan daun yang tertiup, dan mengatur partikel untuk semua berputar/move pada kecepatan yang berbeda dan mulai pada kecepatan yang berbeda. Ini berarti bahwa partikel kabut yang lebih besar akan berinteraksi lebih alami dan tidak terlihat seperti tekstur yang berulang, terutama mengingat ukuran mereka.

Partikel kabut
Partikel daun

Hasilnya adalah aksi yang hebat antara pohon-pohon yang bergerak, jendela yang tertiup, dan sambaran petir untuk menciptakan efek badai yang mengelilingi mata tengah badai.

Siapkan mata badai

Mata batu yang terfragmentasi dengan inti bersinar dimaksudkan untuk memberikan pemain petunjuk pertama bahwa ada sesuatu yang jahat dan gaib terjadi di rumah yang harus mereka selidiki lebih lanjut. Karena adegan kami gelap dan mata tersebut jauh di langit, penting untuk menciptakan siluet batu pecah yang meyakinkan, tetapi tidak terlalu penting untuk menciptakan detail permukaan batu yang meyakinkan karena pemain tidak akan bisa melihatnya. Mengetahui apa yang realistis untuk dilihat pemain dalam pencahayaan adegan Anda sebelum menghabiskan banyak waktu untuk detail yang tidak perlu dapat menghemat banyak sumber daya dalam proses pengembangan.

Menyiapkan pencahayaan akhir dalam adegan Anda sejak awal dapat menghemat banyak pekerjaan yang tidak perlu. Anda tidak akan dapat melihat detail permukaan pada cincin dengan pencahayaan akhir adegan kami, jadi tidak ada perlu menghabiskan waktu untuk menempatkannya di sana!

Jarak dari pemain juga berarti bahwa kami dapat sepenuhnya bergantung pada normal map untuk detail permukaan mata sehingga mesh hanyalah bola yang polos! Kami mengukir detail ke dalam mesh poli tinggi dan memanggang normal map-nya ke dalam bola poli yang jauh lebih rendah sehingga kami dapat mengambil semua detail indah itu tanpa biaya performa yang besar.

Pahat poli tinggi
Mesh poli rendah
Mesh poli rendah dengan informasi normal dari pahatan poli tinggi yang memanggang di dalamnya

Untuk menambahkan nuansa supernatural ke mata dan menekankan kehadirannya, kami memutuskan untuk membuat magma bersinar neon yang akan menyebar melalui retak-retaknya. Meskipun tidak ada saluran emissive untuk penampilan permukaan, kami mengatasi hambatan ini dengan menciptakan mata dari 2 bola: satu untuk permukaan luar berbatu dan yang kedua, sedikit lebih kecil untuk magma yang bersinar. Dalam Substance Painter, kami membuat tekstur warna dasar untuk bola luar dengan transparansi di area di mana kami ingin inti dalam muncul. Dalam Blender, kami "cat vertex" bola dalam untuk metode murah dan mudah untuk mendapatkan variasi warna pada itu.

Pengecatan vertex pada bola dalam. Kami membuat gradien yang paling terang di sekitar mata untuk memberikan rasa kedalaman dan ketertarikan visual yang lebih besar.

Tantangan lain yang kami temui saat menciptakan mata tersebut disebabkan oleh penggunaan streaming yang kami gabungkan dengan jarak mata dari pemain. Mengingat sentralitas struktur ini, kami ingin agar selalu terlihat meskipun jaraknya namun, tanpa adanya hack pada mesh-nya, para pemain tidak dapat melihat mata kecuali mereka berada di solarium. Kami bisa memaksa kehadiran konstan mata dalam adegan dengan menambahkan beberapa geometri ke mata dan cincin-cinginnya. Geometri ini terletak tepat di bawah permukaan terrain, dan ini cukup untuk menipu mesin berpikir bahwa bola lebih dekat ke pemain daripada yang sebenarnya dan selalu mengalirkan ke dalamnya. Ini seharusnya dilakukan dengan cukup hemat, karena memaksa terlalu banyak objek besar untuk disalurkan dapat menghilangkan keuntungan dari streaming yang diaktifkan dan berdampak negatif pada kinerja.

Kami bisa menambahkan gerakan ke mata dan cincin-cinginnya berkat skrip yang sama yang kami gunakan untuk memutar mesh awan. Untuk sentuhan akhir, kami memutuskan untuk menambahkan petunjuk tentang keberadaan dunia lain di balik awan, tetapi kami harus mengambil pendekatan kreatif untuk menghindari menambahkan lebih banyak geometri ke dalam adegan dan juga harus menangani kesulitan yang disebutkan sebelumnya yang dihadapi oleh streaming yang diaktifkan. Kami membuat adegan yang memiliki banyak kedalaman karena ukuran relatif dan jarak objek, merender gambar dari adegan ini, kemudian menggunakan gambar tersebut sebagai decal pada bagian yang diletakkan tepat di belakang mata badai. Kami menggunakan metode yang sama untuk memutar bagian ini seperti yang kami gunakan untuk mata dan cincin-cinginnya.

Gambar yang kami gunakan untuk menciptakan ilusi dunia di balik awan. Ketika pemain jauh dari sesuatu, sebuah gambar sederhana mungkin cukup untuk menciptakan ilusi kedalaman dan kompleksitas yang lebih dalam adegan Anda!

Buat pantry yang mengembang

Salah satu hal yang paling menyenangkan untuk diproduksi adalah ruang-ruang korup, di mana kami bisa menyimpangkan ekspektasi pemain tentang kenyataan dengan secara harfiah mengubahnya di sekitar mereka. Misalnya, dalam teka-teki ayah kami ingin meniru momen yang mirip dengan mimpi buruk di mana tidak peduli seberapa cepat Anda berlari, ruangan terasa seolah-olah terus-menerus memanjang. Kami memutuskan untuk membuat pantry yang mengembang yang akan berlari menjauh dari pemain saat mereka mencari bahan untuk mengembalikan ruangan ke keadaan normalnya.

Kami menyiapkan ini dengan gerakan sederhana dari dinding, dan tata letak cerdas dari ruangan-ruangan kami yang akan muncul di kedua sisi pantry. Dalam keadaan normal ruangan, pantry adalah koridor sederhana, tetapi di ruang korup, sebenarnya jauh lebih panjang dengan beberapa sayap dan dinding palsu!

Keadaan korup dari pantry dapur.
Dinding palsu yang menjauh dari pemain.

Dinding palsu adalah kelompok model yang akan kami gerakkan mundur saat pemain masuk ke volume pemicu, yang merupakan bagian transparan lebih awal di pantry yang akan mereka lewati. Pemicu itu juga digunakan dalam skrip serupa dengan yang digunakan pada semua pintu kami, yang memanggil TweenService untuk bergerak dari satu tujuan ke tujuan lain. Kami menggunakan volume bagian untuk memberi tahu operasi tweening di mana posisi awal dan akhir untuk dinding tersebut.

Volume bagian memicu dinding palsu di belakangnya untuk bergerak ke titik akhirnya. Itu terlihat dalam gambar ini dengan warna kuning.
Target_Closed adalah bagian tujuan umum yang kami gunakan di semua pintu kami untuk menentukan di mana mereka harus berputar. Di sini ia digunakan kembali untuk memberitahu dinding koridor ke mana harus pergi.

Karena TweenService adalah sistem umum, semua model data dinding kami harus berisi komponen yang sama. Misalnya, skrip "Door_Script" umum memainkan suara yang ditentukan oleh "nilai" di bawah model "Grow_Wall". Skrip yang sama ini, dengan beberapa modifikasi dalam sampel kode berikut, juga memicu audio untuk pantry yang bergerak. Ini menambahkan banyak ke gerakan!

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, -- Waktu/Kecepatan Tween Pintu
Enum.EasingStyle.Quart, -- Gaya Easing
Enum.EasingDirection.InOut, -- Arah Easing
0, -- Jumlah Ulang
false, -- Balik true
0 -- Penundaan
)
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["Door"]: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("touch")
playersNear[player] = 1
if doorState == DoorState.Closed then
StartOpening()
end
end
end
end

Setelah kami menggerakkan dinding palsu ke belakang ruangan, kami perlu membuat semua konten lainnya bergerak bersamanya. Untuk melakukan itu, kami perlu mengelas semua barang longgar di pantry dengan dinding saat dinding itu bergerak. Menggunakan Weld Constraints, kami dengan cepat bisa mengelas semua objek ke dinding pantry agar bergerak sebagai satu objek. Melakukan ini berarti kami memiliki opsi untuk membongkar barang-barang tersebut sehingga pemain bisa menabrak mereka dan menggeser mereka!

Buat rumah pohon korup

Studio adalah mesin yang hebat berbasis fisika yang dapat Anda gunakan untuk membuat segalanya mulai dari gerbang yang berayun hingga platform yang berputar. Dengan demo kami, kami ingin menggunakan fisika untuk menciptakan rasa realisme dalam serangkaian lingkungan yang tidak realistis. Menggunakan hanya beberapa constraints, Anda dapat menciptakan kursus rintangan yang menyenangkan dan menantang dalam pengalaman Anda sendiri!

Constraints adalah sekelompok motor berbasis fisika yang menyelaraskan objek dan membatasi perilaku. Misalnya, Anda dapat menggunakan rod constraint untuk menghubungkan objek agar tetap pada jarak tetap satu sama lain, atau rope constraint untuk memiliki lampu gantung dari ujung tali. Untuk teka-teki anak laki-laki yang mana pemain dipindahkan ke keadaan korup dari ruang studi, kami ingin secara harfiah membalik dunia ke samping. Melakukannya akan menyimpangkan ekspektasi pemain tentang kenyataan dan aturan di sana, sambil tetap menggunakan sistem fisika seperti yang dimaksudkan!

Teka-teki anak pertamanya dimulai dengan pemain berada di ruangan yang sama, tetapi semuanya berada di samping.

Setelah pemain bekerja menuju area utama teka-teki tersebut, mereka disambut dengan pemandangan yang akrab di Roblox: sebuah kursus rintangan. Kursus rintangan khusus ini terdiri dari beberapa platform berputar dan dinding berputar, bersama dengan "area aman" yang melanjutkan cerita. Kami akan fokus pada elemen berputar/berputar.

Penampilan yang membingungkan menyembunyikan fakta bahwa gameplay di sini sangat sederhana.

Mengapa kami menggunakan constraints di sini? Karena TweenService atau metode lain tidak akan memindahkan pemain saat mereka berdiri di atasnya. Tanpa objek yang menggerakkan pemain, seseorang bisa melompat ke sebuah platform dan itu akan berputar keluar dari bawah mereka. Sebaliknya, kami ingin pemain menavigasi melalui platform yang berputar sambil mencoba melakukan lompatan mereka ke yang berikutnya. Karena pendekatan ini, pemain merasa terikat di tempat mereka berdiri sambil membuat keputusan tentang bagaimana melanjutkan melalui kursus tersebut, dan kami tidak perlu melakukan sesuatu yang spesial untuk memastikan mereka bergerak dengan permukaan yang berputar!

Anda bisa melihat teman-teman Anda berputar-putar sambil mencoba menavigasi kursus rintangan juga.

Untuk melakukan ini, kami terlebih dahulu perlu menggunakan aset dari kit kami saat ini dan menambahkan konten baru untuk efek visual. Kami membuat beberapa dinding dan platform yang tidak lengkap dengan lubang-lubang di dalamnya untuk menceritakan kisah nenek yang sedang membangun rumah pohon. Karena kami tidak ingin membuat banyak platform yang unik, kami membuat 4 bagian dasar yang berbeda dan bagian pengaman secara terpisah. Ini memungkinkan kami mencampur dan mencocokkan bagian dasar dan pengaman individu untuk memiliki banyak variasi.

Kami tahu bahwa karena kami menggunakan constraints, kami tidak akan dapat menambatkan mesh ini karena mereka tidak akan bergerak bahkan dengan adanya constraint/motor yang mendorongnya. Constraint tersebut perlu menjadi anak dari sesuatu yang terikat agar platform tersebut tidak jatuh dari dunia. Kami menyelesaikan ini melalui bagian yang kami beri nama Motor_Anchor yang memiliki hinge constraint untuk mendorong gerakan keseluruhan platform. Setelah itu, kami membutuhkan dua mesh untuk bergerak sebagai satu, jadi kami membuat bagian yang kami namakan Motor_Turn, lalu kami mengelas kedua mesh tersebut ke dalamnya. Dengan cara ini, constraint dapat berfungsi pada satu bagian, bukannya beberapa engsel yang bekerja dengan banyak bagian.

Sekarang saatnya untuk mengatur perilaku dari hinge constraint itu sendiri, dan menambahkan lampiran yang akan bertindak sebagai orientasi bagian dan constraint bersama-sama. Kami menempatkan lampiran berputar pada Motor_Turn, yang bagian jalan setapak terikat di dalamnya, dan satu lampiran lain untuk perilaku jangkar pada Motor_Anchor itu sendiri, di samping hinge constraint. Karena ini perlu berputar dengan sendirinya, daripada dipengaruhi oleh pemain (seperti engsel pintu), kami mengatur HingeConstraint.ActuatorType ke Motor, memperlakukan constraint ini seperti motor yang bergerak sendiri.

Untuk menjaga platform tetap berputar pada kecepatan konstan, kami kemudian mengatur properti HingeConstraint.AngularVelocity, HingeConstraint.MotorMaxAcceleration, dan HingeConstraint.MotorMaxTorque ke nilai yang akan memungkinkan pergerakan dan mencegah gangguan jika seorang pemain melompat ke atasnya.

Attachment0 secara esensial adalah jangkar untuk engsel dan Attachment1 mewakili engsel itu sendiri. Kami memiliki engsel yang terus berputar, tetapi Anda juga dapat menggunakan hinge constraint untuk pintu.

Sekarang kami perlu membuat dinding yang berputar. Dinding-dinding tersebut perlu berputar di pusat yang tampak, dan kami tahu kami ingin mereka bisa menangani orientasi apapun terkait dengan sisa level. Seperti platform, kami membangun ini sehingga semua dinding tidak terikat dan terikat ke Motor_Turn.

Kami ingin memanfaatkan sebanyak mungkin mesh rumah pohon yang sebenarnya untuk menghemat kinerja, jadi kami mengikuti jalan yang sama dengan platform. Berbagai jenis dinding dibuat yang bisa disatukan dalam kombinasi yang berbeda untuk sedikit variasi.

Kami menggunakan objek Texture di atas objek SurfaceAppearance untuk menambahkan variasi pada bahan dasar kami. Tekstur, mirip dengan Decals, memungkinkan Anda menempatkan gambar pada bidang mesh. Hal ini dapat bermanfaat jika Anda ingin menambahkan kotoran pada dinding batu, atau membuat kayu terlihat tua sambil menggunakan bahan dasar kayu yang sama. Objek Texture memiliki perilaku yang sedikit berbeda dari objek Decal karena Anda dapat mengulangi dan menggeser gambar sesuka Anda, yang sangat berguna jika Anda ingin bisa mengatur tekstur overlay Anda dan tidak keberatan itu berulang!

Anda dapat melihat baik perilaku dan pengaturan yang mirip untuk hinge constraint, dan juga bagaimana kami menggunakan objek Texture.

Setelah kami menguji beberapa platform dan dinding berputar, kami membuat beberapa variasi dan bermain dengan penempatannya untuk memastikan kursus rintangan menantang, membingungkan, dan juga jelas di mana pemain harus pergi! Diperlukan beberapa penyesuaian baik pada nilai maupun posisinya agar mereka dapat berjalan dengan baik. Kami memiliki beberapa titik di mana platform dan dinding saling bertabrakan atau lingkungan di sekitarnya, tetapi dengan beberapa pergerakan dan pengujian yang sering, kami berhasil mencapai pengaturan yang kami miliki dalam demo!

Jika Anda tidak yakin objek fisika apa yang sedang bertabrakan, Anda dapat menyalakan Collision fidelity dari widget Visualization Options di sudut kanan atas viewport 3D.

Tampilan dekat dari viewport 3D dengan tombol Visualization Options yang ditunjukkan di sudut kanan atas.
Ketika visualisasi tabrakan dinonaktifkan, Anda dapat melihat representasi geometri normal yang ditampilkan dalam permainan.
Ketika visualisasi tabrakan diaktifkan, Anda dapat melihat bahwa daun pohon tidak memiliki tabrakan, jadi mereka tidak akan mengganggu platform atau dinding yang berputar.

Seperti yang Anda lihat di bawah, lubang pintu/jendela terlihat, tetapi detail yang lebih kecil seperti sub-paneling tidak terlihat. Ini karena properti CollisionFidelity untuk dinding diatur ke Box. Kami tidak membutuhkan presisi untuk panel-panel ini, jadi untuk menghemat biaya kinerja, ini cukup detail bagi pemain untuk melompat ke atasnya. Dengan platform dan dinding yang berputar selesai, kami hanya perlu menambahkan aset detail seperti kotak dan lampu, lalu itu siap dimainkan!

©2026 Roblox Corporation. Roblox, logo Roblox, dan Powering Imagination termasuk dalam merek dagang kami yang terdaftar dan tidak terdaftar di AS dan negara lainnya.