Menciptakan gerakan di dalam lingkungan mana pun dalam sebuah permainan membantu agar lingkungan tersebut terasa lebih imersif dan realistis, apakah itu dari gerakan pohon yang bergerak, pintu yang reaktif dari interaksi pemain, atau bahkan kotak yang bergerak saat mereka bertabrakan. Studio memiliki banyak metode unik untuk menciptakan gerakan agar dunia terasa lebih hidup, termasuk sistem fisika, TweenService, dan animasi, serta menganalisis kebutuhan spesifik permainan Anda dapat membantu Anda menentukan mana yang harus digunakan. Dalam 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 mengalami banyak iterasi sebelum kami menetap pada apa yang ada di The Mystery of Duvall Drive. Di awal, kami memikirkan badai sebagai pilar obsidian raksasa, dan dalam iterasi selanjutnya kami menganggapnya sebagai portal raksasa ke ruang yang korup. Setelah bereksperimen dengan banyak badai yang memiliki tampilan dan nuansa unik, kami menetap pada badai dengan "mata" pusat yang lebih kecil karena:
- Badai harus memberikan pemain rasa dampak dari peristiwa ini pada dunia, termasuk pohon yang tertiup angin dan puing-puing yang beterbangan.
- Vortex berputar dari awan itu sendiri harus memberikan pemain sekilas tentang portal pusat tanpa mengungkapkan semuanya. 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 tempat sebagian besar permainan berlangsung.




Untuk membuat badai terasa dinamis, agresif, dan selalu berubah dalam lingkungannya, kami menggunakan sistem dan fitur berikut:
- TweenService - Untuk gerakan awan.
- Perubahan pencahayaan - Untuk menciptakan petir dari awan ke awan.
- Beams - Untuk "pencahayaan volumetrik" dan kilatan petir.
- Particle Emitters - Untuk puing-puing yang terbang ke portal dan terbang di sekitar karena angin yang bertiup.
- Animasi - Untuk pohon yang tertiup angin.
Tambahkan awan dengan tekstur
Sementara awan dinamis sangat bagus untuk awan realistis pada ketinggian normal, kami membutuhkan sesuatu yang terasa dramatis dan yang bisa kami arahkan dan sesuaikan lebih berat. Untuk melakukan ini, kami menerapkan objek penampilan permukaan dengan semi-transparansi ke serangkaian mesh awan yang ditumpuk dan dilapisi dengan berat untuk menipu penutup awan. Mengapa kami menumpuk dan melapisi mereka begitu berat? Karena ketika setiap mesh awan bergerak dengan kecepatan yang berbeda, mereka saling berpotongan dan menciptakan bentuk awan yang masuk dan keluar 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-transparan, karena kami ingin pemain dapat melihat sesuatu yang terang di tengah sebelum tiba di rumah!


Karena setiap mesh awan perlu besar untuk sepenuhnya mengelilingi rumah dan menyampaikan betapa besarnya badai, kami tahu kami perlu menempelkan tekstur yang ingin kami gunakan pada mesh awan individu sehingga akan sering diulang di seluruh permukaan mesh. Kami menguji bahan yang kami buat untuk awan pada bagian sederhana ini, lalu menerapkannya pada vortex!

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

Putar mesh awan
Setelah kami puas dengan penampilan visual keseluruhan dari awan, kami perlu membuatnya bergerak! Kami memiliki bentuk umum dari setiap lapisan awan di tempat, tetapi dibutuhkan beberapa percobaan dan kesalahan untuk memastikan efek berputar terlihat baik dalam praktik. Kami awalnya mencoba menggunakan constraints untuk memperkenalkan kecepatan yang secara fisik mendorong awan untuk bergerak. Ini lebih sulit daripada yang kami inginkan untuk iterasi kemudian, dan pemain tidak akan pernah berinteraksi dengannya, jadi kami tidak perlu gerakannya seakurat itu.
Kami ingin metode yang mudah digunakan untuk memutar instance yang terlalu jauh untuk dapat berinteraksi, seperti awan, atau terlalu kecil atau dekoratif untuk penting bagi gameplay/fisika, seperti furnitur dalam ruangan seperti lampu kecil. Kami memutuskan untuk menggunakan LocalScript untuk mengurangi bandwidth klien-server, memungkinkan gerakan yang lebih halus, dan membuat setiap mesh awan dapat memiliki laju rotasi dan penundaan yang berbeda. Untuk membuatnya lebih umum, kami juga membuatnya mungkin untuk menentukan sumbu rotasi. Dimungkinkan untuk menggunakan 3 atribut, tetapi untuk 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 terpengaruh di Studio menggunakan plugin penandaan instance. Kami hanya memanfaatkan 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 dalam workspace sesuai kebutuhan, jadi kami perlu menangani kasus di mana objek yang ditandai dibuat dan dihancurkan. Dengan LocalScripts, kami juga harus menyadari streaming, di mana mesh dan nilai anak mereka mungkin disiarkan masuk dan keluar. Kami memproses objek yang ditempatkan awalnya dalam fungsi Init(), dan terhubung ke CollectionService.GetInstanceAddedSignal dan CollectionService.GetInstanceRemovedSignal untuk objek yang ditandai untuk menangani objek yang baru 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. Perhatikan bahwa kami tidak memanggil SetupObj dari CollectionService.GetInstanceAddedSignal segera, tetapi kami menambahkan objek ke dalam objInfoQueue. Dengan streaming dan mengkloning objek di server, ketika CollectionService.GetInstanceAddedSignal dipanggil, kami mungkin belum memiliki nilai Axis, Delay, dan Speed kami, jadi kami menambahkan objek ke dalam antrean, dan memanggil SetupObj pada frame berikutnya dari fungsi Update sampai nilai-nilai tersebut ada dan kami dapat membacanya ke dalam struktur "info" per objek.
Kami memutar instance dalam fungsi Update yang terhubung ke heartbeat. Kami mendapatkan transformasi induk (parentTransform), mengakumulasi sudut rotasi baru (curObjInfo.curAngle) berdasarkan kecepatan rotasi objek ini, menghitung transformasi lokal (rotatedLocalCFrame), dan akhirnya mengaturnya ke CFrame. Perhatikan 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
-- bagian utama mungkin belum disiarkan
continue -- tunggu bagian utama untuk 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
endKami memeriksa apakah Model.PrimaryPart yang valid telah diatur untuk menangani streaming. Jika pembaruan dipanggil pada objek kami sementara Model.PrimaryPart (yang dapat menunjuk ke mesh anak) belum disiarkan, kami akan melewatkan pembaruan. Sistem saat ini adalah iterasi kedua dari rotasi objek, dan sistem sebelumnya bekerja dengan cara yang berbeda: nilainya 12 kali berbeda! Untuk menjaga data yang sama, kami mengonversinya dalam skrip kami, seperti "12 * obj.Speed.Value".
Rancang sambaran petir
Karena Studio tidak menawarkan generator petir siap pakai, dan sistem partikel memiliki beberapa keterbatasan yang tidak akan berfungsi untuk sambaran petir hero, kami harus kreatif dengan solusi untuk sambaran petir hero. Kami memutuskan pada dua sistem utama untuk membentuk petir: sinar bertekstur untuk sambaran petir hero yang berasal dari mata badai adalah sinar bertekstur yang diprogram yang mengungkapkan dan menyinkronkan dengan audio dan efek pasca proses, dan efek partikel sederhana untuk petir jarak jauh dari awan ke awan.
Sinar tekstur
Kami biasanya akan menggunakan sequencer atau alat timeline untuk menggerakkan waktu efek sambaran petir seperti ini, tetapi karena Studio belum menawarkan fungsionalitas ini, kami memutuskan untuk menulis skrip yang akan mengontrol waktu sambaran petir. Pemrograman efek ini cukup sederhana, tetapi mencapai tujuan penting berikut:
- Elemen dari sambaran petir, seperti tekstur, kecerahan, dan penundaan, diacak dengan setiap sambaran.
- Perubahan audio dan efek pasca FX disinkronkan dengan efek sambaran.
- Pemain yang berada di dalam ruangan atau di area yang korup tidak akan 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
endDi sisi klien (LightningVFXClient), kami memeriksa apakah klien ini harus menjalankan FX:
local function LightningFunc(info)
…
-- tidak ada FX saat di dalam ruangan
if inVolumesCheckerFunc:Invoke() then
return
end
-- tidak ada FX saat tidak berada di dunia "normal"
if not gameStateInfoFunc:Invoke("IsInNormal") then
return
end
…Selain itu, kami menjalankan urutan untuk mengatur 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 angka 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)
-- Hapus
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 seterusnyaUntuk memeriksa apakah pemain berada di dalam ruangan, kami menggunakan fungsi pembantu inVolumesCheckerFunc, yang memeriksa volume yang telah ditempatkan sebelumnya yang memperkirakan 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 titik dalam beberapa kotak lebih sederhana, dan kami melakukannya hanya ketika pemain bergerak cukup jauh dari posisi yang sebelumnya diuji.
Untuk memeriksa apakah pemain berada di area yang korup, kami memanggil fungsi pembantu gameStateInfoFunc, yang memeriksa status permainan saat ini. Untuk memutar suara acak dari folder, kami juga menggunakan fungsi pembantu PlayOneShot. Untuk sambaran petir itu sendiri, ini sangat mudah dibuat di Photoshop; kami menggambar garis berkelok-kelok, lalu menambahkan efek lapisan "Outer Glow".


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


Buat pohon yang tertiup angin
Setelah kami memiliki awan dan petir bekerja seperti yang kami inginkan, kami kemudian perlu menambahkan dua komponen besar lainnya dari badai: angin dan hujan! Elemen-elemen ini menghadirkan beberapa tantangan, termasuk perlu bekerja dalam batasan saat ini dari fisika Studio dan sistem efek khusus. Misalnya, membuat pohon bergerak dengan angin yang sebenarnya tidak mungkin dalam mesin saat ini, jadi kami memanfaatkan efek pemancar partikel dan animasi karakter kustom untuk pohon-pohon tersebut.
Kami tahu untuk benar-benar menjual efek angin dan hujan, kami perlu pohon itu sendiri bergerak. Ada beberapa cara Anda dapat melakukan ini dalam mesin, termasuk memindahkan bagian menggunakan plugin yang tersedia untuk umum, menggunakan TweenService, atau menganimasi model secara langsung. Untuk tujuan kami, animasi memberi kami kemampuan untuk mengontrol gerakan yang kami inginkan dari pohon kami, dan memungkinkan kami menggunakan satu animasi yang dapat kami bagikan di antara semua pohon dalam permainan.
Kami mulai dengan mengkuliti beberapa pohon dari Endorse Model Pack - Forest Assets. Karena pohon-pohon ini sudah ada, dan permainan kami berlangsung di Pacific Northwest, ini menghemat waktu kami di awal dari harus membuat setiap model pohon.

Setelah kami memilih pohon kami, kami tahu kami perlu mengkulitinya. Mengkulit mesh adalah tindakan menambahkan sendi (atau tulang) ke mesh di aplikasi pemodelan 3D lain, seperti Blender atau Maya, lalu menerapkan pengaruh pada sendi/tulang tersebut untuk menggerakkan mesh. Ini paling umum digunakan dalam karakter humanoid, tetapi dengan karakter kustom, Anda dapat mengkuliti hampir semua hal.
Kami tahu kami ingin menghemat waktu dan menggunakan animasi yang sama, jadi kami membangun rig pohon pertama kami dan memastikan nama sendi bersifat umum karena kami ingin menggunakan nama yang sama ini dalam rig untuk pohon-pohon lainnya. Kami juga tahu kami perlu menyertakan sendi/tulang primer, sekunder, dan tersier agar batang dapat membengkok dengan angin, cabang dapat berayun, dan daun tampak seperti mereka bergetar sebagai respons. Untuk proses ini, kami perlu menciptakan gerakan sekunder, yang merupakan konsep animasi di mana setiap tindakan menyebabkan bagian lain dari objek bereaksi terhadap tindakan tersebut dan tampak mengejar gerakan awal.

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

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

Untuk melakukan ini, kami mengambil pohon Beechwood dari Paket Hutan itu dan membangun rig yang serupa, menggunakan nama yang sama persis untuk sendi. Ini agar animasi yang sebelumnya kami impor dapat diterapkan pada pohon ini juga. Karena animasi semuanya didasarkan pada sendi yang berputar, tidak masalah seberapa besar, kecil, tinggi, atau lebar pohon itu!

Setelah kami rig dan kulit pohon Beechwood, kami kemudian dapat mengimpornya dan menerapkan animasi yang persis sama. Ini berarti iterasi dan pengeditan hanya perlu dilakukan pada satu file, dan juga menghemat kinerja dengan lebih sedikit animasi saat menjalankan permainan.

Setelah kami memiliki semua jenis pohon yang ingin kami animasikan, kami membuat masing-masing menjadi paket sehingga kami dapat terus mengedit dan memperbarui sambil memainkan beberapa animasi di sekitar area utama permainan. Karena kami tahu mereka memiliki biaya kinerja, kami menggunakannya dengan hemat di sekitar rumah di mana efeknya paling berharga! Di masa depan, saat ini menjadi lebih efisien, Anda akan dapat menambahkan lebih banyak dan lebih banyak instance mesh yang dikuliti!

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

Partikel hujan memanfaatkan properti pemancar 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 ada. Cukup ketahui bahwa jika Anda meningkatkan nilai ParticleEmitter.Squash, Anda mungkin perlu meningkatkan properti ParticleEmitter.Size secara keseluruhan juga agar tidak terlalu kurus! Secara keseluruhan, ini hanya masalah bermain-main dengan nilai sampai kami mendapatkan hujan cukup deras, tetapi tidak begitu banyak sehingga menghalangi visibilitas permainan!


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


Setelah itu, kami membuat tekstur daun yang bertiup dan angin, dan mengatur partikel agar semuanya berputar/bergerak dengan kecepatan yang berbeda dan mulai dengan kecepatan yang berbeda. Ini berarti bahwa partikel kabut yang lebih besar akan berinteraksi lebih alami dan tidak terlihat terlalu mirip dengan tekstur yang berulang, terutama mengingat ukuran mereka.



Hasilnya adalah aksi yang hebat antara pohon yang bergerak, jendela yang tertiup, dan petir untuk menciptakan efek badai yang mengelilingi mata badai pusat.
Siapkan mata badai
Mata batu yang retak dengan inti yang bersinar dimaksudkan untuk memberikan pemain petunjuk pertama bahwa ada sesuatu yang jahat dan gaib yang terjadi di rumah yang harus mereka eksplorasi lebih lanjut. Karena adegan kami gelap dan mata berada jauh di langit, penting untuk menciptakan siluet batu yang retak yang dapat dipercaya, tetapi tidak begitu penting untuk menciptakan detail permukaan batu yang dapat dipercaya karena pemain tidak akan dapat 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.


Jarak dari pemain juga berarti bahwa kami dapat sepenuhnya mengandalkan peta normal untuk detail permukaan mata sehingga mesh hanya berupa bola polos! Kami memahat detail ke dalam mesh poligon tinggi dan memanggang peta normalnya ke bola poligon yang jauh lebih rendah sehingga kami dapat mendapatkan semua detail indah itu tanpa biaya kinerja yang besar.



Untuk menambahkan perasaan supernatural pada mata dan untuk menekankan keberadaannya, kami memutuskan untuk menciptakan magma neon yang bersinar yang akan merembes melalui retak-retak tersebut. Meskipun tidak ada saluran emissive untuk penampilan permukaan, kami mengatasi hambatan ini dengan menciptakan mata dari 2 bola: satu untuk permukaan luar berbatu dan satu lagi, sedikit lebih kecil untuk magma yang bersinar. Di Substance Painter, kami membuat tekstur warna dasar untuk bola luar dengan transparansi di area di mana kami ingin inti dalam muncul. Di Blender, kami "melukis vertex" pada bola dalam untuk cara yang murah dan mudah untuk mendapatkan variasi warna di atasnya.

Tantangan lain yang kami hadapi saat menciptakan mata disebabkan oleh penggunaan streaming kami yang dikombinasikan dengan jarak mata dari pemain. Mengingat sentralitas struktur ini, kami ingin agar itu selalu terlihat meskipun jaraknya jauh tetapi, tanpa trik pada mesh-nya, pemain tidak dapat melihat mata kecuali mereka berada di solarium. Kami dapat memaksa keberadaan konstan mata di adegan dengan menambahkan beberapa geometri ke mata dan cincin-cincinnya. Geometri ini terletak tepat di bawah permukaan terrain, dan ini cukup untuk menipu mesin agar berpikir bola tersebut lebih dekat ke pemain daripada yang sebenarnya dan selalu menyiarkannya. Ini harus dilakukan dengan cukup hemat karena memaksa terlalu banyak objek besar untuk disiarkan dapat menghilangkan manfaat dari streaming yang diaktifkan dan berdampak negatif pada kinerja.
Kami dapat menambahkan gerakan pada mata dan cincin-cincinnya 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 adegan dan juga harus menghadapi hambatan yang disebutkan sebelumnya yang ditimbulkan oleh streaming yang diaktifkan. Kami menciptakan adegan yang memiliki banyak kedalaman karena ukuran dan jarak relatif objek, merender gambar dari adegan ini, lalu menggunakan gambar tersebut sebagai decal pada bagian yang ditempatkan tepat di belakang mata badai. Kami menggunakan metode yang sama untuk memutar bagian ini seperti yang kami gunakan untuk mata dan cincin-cincinnya.

Buat pantry yang mengembang
Salah satu hal yang paling menyenangkan untuk diproduksi adalah ruang-ruang yang korup, di mana kami dapat membalikkan ekspektasi pemain tentang realitas 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 seperti terus menjadi lebih panjang. Kami memutuskan untuk membuat pantry yang mengembang yang akan melarikan diri dari pemain saat mereka mencari bahan untuk mengubah ruangan kembali ke keadaan normalnya.
Kami menyiapkan ini dengan gerakan sederhana dari dinding, dan tata letak cerdas dari ruangan kami yang akan muncul di kedua sisi pantry. Dalam keadaan normal ruangan, pantry adalah lorong sederhana, tetapi dalam ruang yang korup, itu sebenarnya jauh lebih panjang dengan beberapa sayap dan dinding palsu!


Dinding palsu adalah grup model yang akan kami gerakkan ke belakang saat pemain memasuki volume pemicu, yang merupakan bagian transparan di dalam pantry yang akan mereka lewati. Pemicu itu juga digunakan dalam skrip yang mirip dengan yang digunakan di semua pintu kami, yang memanggil TweenService untuk bergerak dari satu tujuan ke tujuan lainnya. Kami menggunakan volume bagian untuk memberi tahu operasi tweening di mana posisi awal dan akhir untuk dinding.


Karena TweenService adalah sistem yang sangat umum, semua data model dinding kami harus berisi komponen yang sama. Misalnya, skrip umum "Door_Script" memutar suara yang didefinisikan oleh "nilai" di bawah model "Grow_Wall". Skrip yang sama ini, dengan beberapa modifikasi dalam contoh kode berikut, juga memicu audio untuk pantry yang bergerak. Ini menambah banyak pada 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("sentuh")
playersNear[player] = 1
if doorState == DoorState.Closed then
StartOpening()
end
end
end
endSetelah kami memiliki dinding palsu bergerak ke belakang ruangan, kami perlu membuat sisa konten bergerak bersamanya. Untuk melakukan itu, kami perlu mengelas semua barang longgar di pantry ke dinding saat bergerak. Menggunakan Weld Constraints, kami dengan cepat dapat mengelas semua objek ke dinding pantry untuk bergerak sebagai satu objek. Melakukan ini berarti kami memiliki opsi untuk melepaskan barang-barang ini sehingga pemain dapat menabrak mereka dan menggeser mereka!
Buat rumah pohon yang terkorup
Studio adalah mesin berbasis fisika yang fantastis yang dapat Anda gunakan untuk menciptakan 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 beberapa kursus rintangan yang menyenangkan dan menantang dalam permainan 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 menggantung lampu dari ujung tali. Untuk teka-teki anak, di mana pemain dipindahkan ke keadaan korup dari ruang belajar, kami ingin secara harfiah membalik dunia di sisinya. Melakukannya akan membalikkan ekspektasi pemain tentang realitas dan aturan di sana, sambil tetap menggunakan sistem fisika seperti yang dimaksudkan!

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

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

Untuk melakukan ini, kami perlu terlebih dahulu 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 di dalamnya untuk menceritakan kisah nenek yang membangun rumah pohon. Karena kami tidak ingin membuat banyak platform unik, kami membuat 4 potongan dasar yang berbeda dan potongan pagar secara terpisah. Ini memungkinkan kami untuk mencampur dan mencocokkan potongan dasar dan potongan pagar individu untuk memiliki banyak variasi.

Kami tahu bahwa karena kami menggunakan constraints, kami tidak akan dapat mengunci mesh ini karena mereka tidak akan bergerak bahkan dengan adanya constraint/motor yang menggerakkan mereka. Constraint perlu menjadi anak dari sesuatu yang terikat agar platform tidak jatuh dari dunia. Kami menyelesaikan ini melalui bagian yang kami sebut Motor_Anchor yang memiliki hinge constraint untuk menggerakkan gerakan keseluruhan platform. Setelah itu, kami perlu kedua mesh bergerak sebagai satu, jadi kami membuat bagian yang kami sebut Motor_Turn, lalu kami mengelas kedua mesh ke dalamnya. Dengan cara ini, constraint dapat bekerja pada satu bagian, bukan beberapa engsel yang bekerja dengan beberapa bagian.

Sekarang saatnya untuk mengatur perilaku sebenarnya dari hinge constraint itu sendiri, dan menambahkan lampiran yang akan bertindak sebagai orientasi bagian dan constraint bersama. Kami menempatkan lampiran yang berputar pada Motor_Turn, yang potongan jalan setapak di las, dan lampiran lain untuk perilaku jangkar pada Motor_Anchor itu sendiri, di samping hinge constraint. Karena ini perlu berputar sendiri, bukan dipengaruhi oleh pemain (seperti engsel pintu), kami mengatur HingeConstraint.ActuatorType ke Motor, memperlakukan constraint seperti motor yang bergerak sendiri.
Untuk menjaga platform berputar pada kecepatan konstan, kami kemudian mengatur properti HingeConstraint.AngularVelocity, HingeConstraint.MotorMaxAcceleration, dan HingeConstraint.MotorMaxTorque ke nilai yang akan memungkinkan gerakan dan mencegah gangguan jika seorang pemain melompat di atasnya.

Sekarang kami perlu membuat dinding yang berputar. Dinding perlu berputar di pusat yang tampak, dan kami tahu kami ingin mereka dapat menangani orientasi apa pun relatif terhadap sisa level. Seperti platform, kami membangun ini sehingga semua dinding tidak terikat dan di las ke Motor_Turn.

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. Ini bisa berguna jika Anda ingin menambahkan kotoran pada dinding bata, atau membuat kayu terlihat tua sambil menggunakan bahan kayu dasar yang sama. Objek Texture memiliki perilaku yang sedikit berbeda dari objek Decal dalam hal Anda dapat menempelkan dan menggeser gambar sesuka Anda, yang sangat berguna jika Anda ingin dapat menskalakan tekstur overlay Anda dan tidak keberatan jika itu berulang!

Setelah kami menguji beberapa platform dan dinding yang berputar, kami membuat beberapa variasi dan bermain dengan penempatannya untuk memastikan kursus rintangan menantang, membingungkan, dan juga jelas di mana pemain perlu pergi! Dibutuhkan beberapa penyesuaian pada nilai dan posisi mereka untuk membuatnya berjalan dengan baik. Kami memiliki beberapa titik di mana platform dan dinding saling bertabrakan atau dengan lingkungan, tetapi dengan beberapa pergerakan dan pengujian yang sering, kami dapat mencapai pengaturan yang kami miliki dalam demo!
Jika Anda tidak yakin apa yang sedang ditabrak objek fisik Anda, Anda dapat mengaktifkan Collision fidelity dari widget Visualization Options di sudut kanan atas viewport 3D.



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 memerlukan 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!
