Hareketli bir dünya geliştirme

*Bu içerik, yapay zekâ (beta) kullanılarak çevrildi ve hatalar içerebilir. Sayfayı İngilizce görüntülemek için buraya tıkla.

Bir deneyim içindeki herhangi bir ortamda hareket yaratmak, çevreyi anında daha sürükleyici ve gerçekçi hale getirir; bu, çevredeki ağaçların hareketinden, oyuncu etkileşimiyle tepki veren kapılardan veya karşılarına çarptıklarında hareket eden kutulardan olabilir. Studio, dünyaların daha canlı hissetmesine yardımcı olmak için hareket yaratmanın birçok benzersiz yöntemine sahiptir; bunlar arasında bir fizik sistemi, TweenService ve animasyonlar bulunmaktadır. Deneyiminizin özel ihtiyaçlarını analiz etmek, hangi yöntemi kullanmanız gerektiğini belirlemenize yardımcı olabilir. Bu bölümde, Studio'da yaratmak istediğimiz hareket türünü nasıl belirlediğimizi ve bu farklı hedefleri başarmak için hangi araçları kullandığımızı göstereceğiz.

Fırtınayı oluşturun

Fırtına, The Mystery of Duvall Drive'da görmekte olduğumuz canlı hâline gelmeden önce birçok aşamadan geçti. Başlangıçta fırtınayı dev bir obsidyen sütun olarak düşündük ve sonraki aşamalarda onu kirli uzaya açılan dev bir portal olarak değerlendirdik. Farklı görünümler ve hislerle birçok farklı fırtına deneyerek, daha küçük bir merkezi "göz" ile bir fırtına üzerinde karar kıldık çünkü:

  • Fırtına, oyunculara bu olayın dünyaya olan etkisini vermelidir; ağaçların sallanması ve etrafta molozların uçuşması ile.
  • Bulutların dönen vortexi, oyunculara merkezi portaldan her şeyi ifşa etmeden bir bakış sunmalıdır. Bu, oyuncuların daha yakından incelemelerini teşvik edecektir.
  • Daha sıkı bir ışık noktası, evin kompozisyonuna odaklanmamızı sağlayacaktır ki bu hem ana karakterdir hem de çoğu oyun deneyimi buradadır.

Fırtınanın dinamik, agresif ve sürekli değişen bir his vermesi için aşağıdaki sistemleri ve özellikleri kullandık:

  1. TweenService - Bulut hareketleri için.
  2. Aydınlatma değişiklikleri - Bulutlar arası yıldırım yaratmak için.
  3. Işınlar - “Volumetrik aydınlatma” ve yıldırım çarpmaları için.
  4. Parçacık Yayıcıları - Portalın etrafında uçarak onları üfleyen rüzgâr nedeniyle molozların uçuşması için.
  5. Animasyonlar - Rüzgârda sallanan ağaçlar için.

Dokularla bulut ekleyin

Dinamik bulutlar, normal, yüksek irtifa gerçekçi bulutlar için harika olsa da, daha dramatik hissettiren ve daha fazla yönlendirme ve özelleştirme yapabileceğimiz bir şeye ihtiyaç duyduk. Bunu yapmak için, çok sayıda yoğun yığılmış ve katmanlı bulut ağları üzerine yarı saydam surface appearance objeleri uyguladık. Neden bu kadar yığdık ve katmanlandırdık? Çünkü her bir bulut ağı farklı hızlarda hareket ettiğinde, birbirlerinin içine girip çıkan bulut formları oluştururlar. Bu süreç bulutların daha dinamik ve doğal hissettirmesini sağladı, bu da sadece dönen diskler olmalarına rağmen. Bulutların yarı saydam olması da ayrıca önemliydi; çünkü oyuncuların, eve varmadan önce ortada bir şeyin parlak olduğunu görebilmelerini istedik!

Tek bir bulut ağı.
Dokular olmadan katmanlı bulut ağları!

Her bir bulut ağının, evi tamamen çevrelemesi ve fırtınanın ne kadar dev olduğunu iletmesi için devasa olması gerektiğini biliyorduk, bu nedenle kullanmak istediğimiz dokuyu bireysel bulut ağları üzerinde yoğun bir şekilde tekrar etmek için döşemek zorundaydık. Bulut için ürettiğimiz malzemeleri bu basit parçalarda test ettik ve ardından vortex'e uyguladık!

Parçacık yayıcıları veya ışınlardan farklı olarak, ağlar her bir ağdan ışık yansıtabilmemizi sağladı; bu, bulutlar arası yıldırım uygulamak istediğimizde önemliydi. Ayrıca, ışığın binmeden sıçradığında derinliğe sahip gibi görünmesini sağlamak için dönerken modelledik! Bu, özellikle deneyimin performans taleplerinin, yüzey görünüm objelerimizin kalite seviyelerini düşürdüğü durumlarda önemliydi.

Işığı eklemeye başladıktan sonra, ağları ışığa daha iyi yanıt verecek şekilde detaylar eklememiz gerekti!

Bulut ağlarını döndürün

Bulutların genel görsel görünümünden memnun kaldığımızda, hareket ettirmemiz gerekiyordu! Her bir bulut katmanının genel şekillerini yerleştirmiştik, ancak dönen etkinin pratikte iyi görünmesini sağlamak için biraz deneme yanılma yaptık. İlk olarak bulutların fiziksel olarak hareket etmesini sağlayacak hızı tanıtmak için kısıtlamalar kullanmayı denedik. Ancak bu, daha sonra yinelemek istediğimiz kadar kolay değildi ve oyuncunun bu kaynakla etkileşimde bulunması gerekmiyordu, bu yüzden hareketinde bu kadar doğru olmasına gerek yoktu.

Etkileşimde bulunamayan bulutlar gibi çok uzakta olan örnekleri döndürmek için kolay bir yöntem istedik. Ya da küçük veya dekoratif olan eşyalar gibi, oyun / fizik açısından önemsiz olanlar için. LocalScript kullanmaya karar verdik; bu, istemci-sunucu bant genişliğini azaltarak daha pürüzsüz bir hareket sağladı ve her bulut ağına farklı bir dönüş hızı ve gecikme verebilmemizi sağladı. Daha genel hale getirmek için, ayrıca dönerken hangi ekseni belirtme olanağı da sağladık. Üç özellik kullanmak mümkündür, ancak duruma göre Axis, Delay ve Speed olarak üç değer kullandık.

Demo sırasında birçok durumda olduğu gibi, etkilenmiş nesneleri Studio içinde etiketlenmiş olarak yönetebilmek için LocalSpaceRotation etiketini kullandık. Geliştirme süreci boyunca bir sürü script tutmamak için tüm etiketli nesneleri yöneten tek bir LocalScript kullandık.

Demo'muzda dünyadaki bölümler, ihtiyaç duyulduğunda ServerStorage'dan çalışma alanına klonlanır, bu nedenle etiketli nesnelerin oluşturulması ve yok edilmesi gereken durumlarla ilgilenmemiz gerekiyordu. LocalScripts ile ayrıca akışa dikkat etmemiz gerekmektedir, bu durumda, meshler ve çocuk değerleri akışa dahil edilebilir. Başlangıçta yerleştirilen nesneleri Init() fonksiyonunda işledik ve etiketli nesneler için CollectionService.GetInstanceAddedSignal ve CollectionService.GetInstanceRemovedSignal ile bağlantı sağladık. Init()'de ve CollectionService.GetInstanceAddedSignal'da yeni nesneleri başlatmak için aynı SetupObj fonksiyonu kullanıldı.


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, ilgili nesnelerin döndürme hızı ve ekseni gibi bilgilerin olduğu bir harita. CollectionService.GetInstanceAddedSignal'dan hemen SetupObj çağrısı yapmadığımızı unutmayın; yerine objInfoQueue'ya bir nesne ekledik. Sunucudaki akış ve klonlama nesneleri ile CollectionService.GetInstanceAddedSignal çağrıldığında, Axis, Delay, ve Speed değerlerimizi almış olmamamız mümkündü, bu nedenle nesneyi bir kuyruk içine ekledik ve değerler mevcut olduğunda okuyana kadar Update fonksiyonundan sonraki çerçevelerde SetupObj çağırdık.

Nesneleri Update fonksiyonunda kalp atışına bağlı olarak döndürdük. Ebeveyn dönüşümünü (parentTransform) aldık, bu nesnenin dönüş hızına göre yeni bir dönüş açısı (curObjInfo.curAngle) biriktirdik, yerel dönüşümü hesapladık (rotatedLocalCFrame), ve sonrasında CFrame'e ayarladık. Hem ebeveynin hem de nesnenin Model veya MeshPart olabileceğini unutmamak gerekir, bu nedenle IsA("Model") kontrol etmemiz ve ya PrimaryPart.CFrame ya da CFrame kullanmamız gerekiyordu.


local parentTransform
if parentObj:IsA("Model") then
if not parentObj.PrimaryPart then
-- ana parça yine akışta olmayabilir
continue -- ana parçanın çoğaltılmasını bekleyin
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

Geçerli bir Model.PrimaryPart ayarını ele almak için kontrol edildi. Bir Model.PrimaryPart (bir alt mesh gösterebilen) üzerinde Update çağrıldığında akışta çoğaltılmadıysa güncellemeyi atlayacaktık. Mevcut sistem, nesne dönüşümünün ikinci bir versiyonudur; önceki sistem farklı çalışıyordu: değerler tam anlamıyla 12 kat farklıydı! Aynı verileri saklamak için scriptimizde dönüştürmemiz gerekti; "12 * obj.Speed.Value" gibi.

Yıldırım çarpması tasarlayın

Studio, gömme yıldırım jeneratörü sunmadığı için, ve parçacık sisteminin bazı sınırlamaları olduğu için, kahraman yıldırım çarpmaları için yaratıcı bir çözüm bulmak zorundaydık. Yıldırım oluşturmak için iki ana sistemi seçtik: fırtınanın gözünden gelen kahraman yıldırım çarpmaları için betimlenmiş ışınlar, ses ve sonrası efektlerle senkronize edilen ve senkronize olan scripted textured beams. Ayrıca uzak bulutlar arası yıldırımlar için basit bir parçacık efekti ekledik.

Doku ışınları

Genellikle bir zamanlayıcı veya zaman çizelgesi aracını kullanarak bir ışık çarpma efektinin zamanlamasını kontrol ederdik, ancak Studio henüz bu işlevi sunmadığı için ışık çarpmasının zamanlamasını kontrol edecek scriptler yazmaya karar verdik. Bu etkinliğin scriptlenmesi oldukça basittir, ancak aşağıdaki önemli hedefleri gerçekleştirmektedir:

  1. Yıldırım çarpma etkisinin unsurları, dokuları, parlaklıkları ve gecikmeleri her çarpışmada rastgeleleştirilmiştir.
  2. Ses ve sonrası efekt değişiklikleri çarpışma efektleriyle senkronizedir.
  3. İster kapalı alanlarda ister kirli alanda olsun, oyuncular bunları göremez veya duyamazlar.

Sunucu tarafında, birkaç parametre ve zamanlamaları hesaplayan, bunları tüm istemcilere gönderen ve rastgele bir süre bekleyen Scriptimiz var:


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

CreateFXData içinde, tüm istemcilerin aynı parametreleri alabilmesi için bilgi yapısını dolduruyoruz.

İstemci tarafında (LightningVFXClient), bu istemcinin FX'i çalıştırıp çalıştıramayacağını kontrol ediyoruz:


local function LightningFunc(info)
-- kapalı alanda FX yok
if inVolumesCheckerFunc:Invoke() then
return
end
-- "normal" dünyada olmayan FX yok
if not gameStateInfoFunc:Invoke("IsInNormal") then
return
end

Ayrıca, dokular, pozisyonlar ve parlaklık ayarları için bir dizi çalıştırıyoruz, tween'leri çalıştırıyoruz ve task.wait(number) kullanıyoruz. Rastgele parametreler, sunucudan aldığımız bilgi yapısından gelmektedir ve bazı sayılar sabit kalmaktadır.


beam.Texture = textures[info.textIdx]
beamPart.Position = Vector3.new(info.center.X + og_center.X, og_center.Y, info.center.Y + og_center.Z)
-- Sil
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()
-- ses
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)
-- ve devamı

Bir oyuncunun kapalı olup olmadığını kontrol etmek için, önceden yerleştirilmiş hacimleri öngörebilen bir inVolumesCheckerFunc fonksiyonunu kullanırız ve oyuncu pozisyonunun bu hacimlerden herhangi birinin içinde olup olmadığını kontrol ederiz (PointInABox). Temas tabanlı algılama kullanabilirdik, ancak bir oyuncu hacim içinde bir yere oturduğunda, artık hacmin "dokunma" altında olmadıklarını öğrendik. Birkaç kutudaki bir noktanın kontrol edilmesi daha basit ve daha az boyutlu yapıldığında ortaya çıkabilir.

Bir oyuncunun kirli alanlarda olup olmadığını kontrol etmek için, mevcut oyun durumunu kontrol eden bir yardımcı gameStateInfoFunc fonksiyonu çağırılır. Bir klasörden rastgele bir ses çalmak için de, bir yardımcı PlayOneShot fonksiyonu kullandık. Yıldırım çarpmaları, bu efektleri Photoshop'ta oluşturmak oldukça kolaydı; kıvrımlı bir çizgi çizeriz ve ardından "Dış Parıltı" katman efektini ekleriz.

Parçacık yayıcı sistemlerini kullanın

Kahraman yıldırım çarpmaları, arka plandaki bulut katmanlarını, uzak yıldırımlarda ve bulutlar arası yıldırımlarda ışık tutarak bir imaj oluşturmak için desteklenen bir parçacık sistemi tarafından desteklenmektedir. Bu etkiyi, ana fırtına bulutunun çevresinde bir bulut reklamıyla periyodik olarak bir bulut parçacığı yakan çok basit bir parçacık sistemi oluşturarak elde ettik. Sistemin, rastgele bir saydamlık eğrisi ile bir bulut parçacığı çıkmasına olanak tanıdığını görebilirsiniz:

Ağaçların rüzgârda sallanmasını sağlayın

Bulutları ve yıldırımı istediğimiz gibi oluşturduktan sonra, son iki büyük fırtına bileşenini eklememiz gerekiyordu: rüzgâr ve yağmur! Bu unsurlar, Studio'nun fizik ve özel efekt sistemlerindeki mevcut sınırlamaları içinde çalışmak gibi birkaç zorluk sundu. Örneğin, ağaçların gerçek rüzgâr ile hareket etmesini sağlamak, günümüzdeki motorla mümkün değil, bu nedenle ağaçlar için parçacık yayıcı efektleri ve özel karakter animasyonları kullandık.

Rüzgâr ve yağmur etkisini gerçekten satmak için, ağaçların kendilerinin hareket etmesi gerektiğini biliyorduk. Bu, motoru ve yüzeye uygun fizik/oyun deneyimi açısından önemli olmayan diğer dekoratif öğeleri hareket ettirmek de dahil olmak üzere, motoru kullanmanın birkaç yolu bulunmaktadır. Amacımız, animasyonların amacıyla ağaçlarımızın istemci tarafındaki hareketini kontrol etme yeteneğine sahip olmaktı ve deneyim içindeki tüm ağaçlar arasında paylaşılacak tek bir animasyon kullanmamıza olanak sağladı.

Endorse Model Pack - Forest Assets'dan birçok ağacı kıymaya başladık (https://www.roblox.com/library/6432306802/Forest-Pack). Bu ağaçlar zaten mevcut olduğu için ve deneyimimizin Pasifik Kuzeybatısı'nda geçiyordu, bu, her bir ağaç modelini yaratma konusunda daha hızlı bir iş yapmamızı sağladı.

Orman paketi, kendi deneyimlerinizde size zaman kazandıracak birkaç ağaç türü içerir.

Ağaçlarımızı seçtikten sonra, onları kıymamız gerektiğini biliyorduk. Bir ağı (mesh) kıymak için, başka bir 3D modelleme uygulamasında, örneğin Blender veya Maya, bir ağına eklemeler (veya kemikler) eklemek, ardından bu eklemelere etki sağlamaktır. Bu genellikle insana benzer karakterler için kullanılsa da, özelleştirilmiş karakterler ile hemen hemen her şeyi kıyma yapabilirsiniz.

Zaman kazanmak ve aynı animasyonu yeniden kullanmak istediğimiz için, ilk ağaç rigimizi oluşturduk ve eklem isimlerinin genel olmasını sağladık çünkü bu aynı isimleri diğer ağaçların rigleri içinde kullanmak istedik. İlk kez, rüzgârda esneyecek şekilde gövde için birincil, ikincil, ve üçüncül eklemler / kemikler eklememiz gerektiğini de biliyorduk, dalların sallanması ve yaprakların rüzgârda sarsılması gibi. Bu süreçte, nesnenin herhangi bir hareketi diğer kısımlarının bu harekete yanıt vermesi ve başlangıçta gerçekleştirilen harekette hız kazanmasını sağladığı ikincil hareket olarak adlandırılan bir animasyon kavramını oluşturmamız gerekti.

Ağaçlar, rüzgâr tarafından savrulmalarını daha inandırıcı hale getirmek için birincil, ikincil ve üçüncül eklemlere sahiptir.

Eklem / kemikleri oluşturduktan sonra, hepsini Studio'da hızlıca hareket ettirmek için bir test animasyonu oluşturmamız gerekiyordu. Bunu yapmak için, Özelleştirilen Rig ayarı aracılığıyla Importer üzerinden ağacı Studio'ya dahil etmemiz gerekti ve ardından Animasyon Düzenleyici kullanarak ağı hareket ettirdik / animasyonunu yaptık. Bu testlerden sonra malzemeleri ve dokuları ayarladık, ancak aşağıdaki sonucu görebilirsiniz.

Studio içindeki aynı hiyerarşi.

Sonuçlardan memnun kaldığımızda, test ettiğimiz animasyonu farklı bir ağaçta deneme zamanıydı! Farklı ağaç türlerine göre farklı rigler arasında aynı animasyonun olacağını zaten biliyorduk, bu nedenle, animasyonun bir genel görünümü olmasını sağladık!

Redwood ağacına eklediğimiz animasyon.

Bunu yapmak için, Oorman Paketi'nden Beechwood ağacını aldık ve aynı rig'i oluşturarak, eklemler için aynı tam isimleri kullandık. Bu, daha önce içe aktardığımız animasyonun aynı ağaç üzerinde uygulanabilmesi için gerekiyordu. Animasyonlar, tümü eklem döndürmeye dayandığından, ağacın ne kadar büyük, küçük, uzun veya geniş olduğu önemli değil!

Beechwood ağacının eklemleri için tam isimlendirime var; sadece miktarı farklıdır. Bu, animasyon sisteminin yalnızca ismi eşleşen belirli eklemlere animasyon uygulayacağı için memnun edicidir! Bu nedenle, bu isme uyan animasyonlar herhangi bir şeye uygulanabilir!

Beechwood ağacını rig ve kıyma yaptıktan sonra, aynı animasyonu uygulayabiliriz. Bu, yineleme ve düzenlemenin yalnızca bir dosyada yapılması gerektiği anlamına geliyordu ve deneyimi çalıştırırken daha az animasyon ile performans tasarrufu sağlıyordu.

Animasyon Düzenleyici'ni kullanarak, aynı Redwood ağaç animasyonunu Beechwood ağacına uygulayabildik!

Tüm ağaç türlerini animasyonlu hale getirdiğimizde, her birini paketler halinde oluşturduk, böylece oyun alanlarının ana bölgesinin etrafında birçok animasyonu düzenlemeye ve güncellemeye devam edebiliriz. Performansından dolayı az sayıda animasyon içeriği kullanmayı biliyorduk! Gelecekte, bu daha performanslı hale geldiğinde, daha fazla kıyılmış mesh örnepleri ekleyebileceksiniz!

Görüşün en etkili olacağı yerlerde, evin hemen çevresinde animasyonlu ağaçlar kullandık!

Fırtına molozlarını oluşturun

Yağmurun ağır görünmesini istedik ve sis ve molozların ağaçların arasından geçmesini sağladık. Bunu başarmak için, büyük fırtına bulutlarının hemen altına parçacık hacimleri olarak hareket eden birkaç görünmez parça ayarladık. Studio'daki parçacık sayısı sınırı nedeniyle, tüm alan için tek bir parçacık yayıcı kullanamazdık. Bunun yerine, oyuncuların çok uzağa göremeyeceği ağaçların varlığı nedeniyle, oynanabilir alanı kaplayan bir ızgara deseninde birbiriyle aynı boyutta birkaç tane ekledik.

Yağmur miktarını ve istediğimiz yağmurun belirli kaplamasını elde etmek için birkaç hacim kullandık.

Yağmur parçacıkları, bir parçacığı uzatmanıza veya sıkıştırmanıza olanak tanıyan yeni bir parçacık yayıcı özelliği olan ParticleEmitter.Squash'ı kullandı. Bu, yağmur için oldukça uygundur çünkü büyük bir yağmur dokusuna ihtiyaç duymaz; sadece orada olanı germek gerekir. Tekrar belirtmek gerekir ki, ParticleEmitter.Squash değerini artırdığınızda, ParticleEmitter.Size özelliğini de artırmanız gerekebilir, böylece çok derin olmamış olur! Genel olarak, yağmuru yeterince ağır yapmak için değerlerle oynayarak çalışmak gerekiyordu, ancak deneyimin görünürlüğünü engellememesi gerektiği yerlerde değil!

3'lük bir Squash değeri, dokuyu daha uzun hale getirmeye başlar.
20'lik bir Squash değeri parçacıkları oldukça uzun hale getirir, ancak Boyut değerini de artırmamız gerekiyordu.

Sis için, rüzgârda savrulan yapraklar için, bir kişinin çok fazla parçacık çalıştırmasına gerek kalmadan kaplamayı almak için tek bir büyük parça hacim eklemek daha kolaydı. Bir hacim ayarlayarak ve parçacıkların istediğimiz yerdeki frekansında çalışmasını sağladık.

Hacimlerin sayısı, parçacıkların eve girmesini engellemek ve sisin içinden geçmesi gerektiğini düşündüğümüz şekillerde hareket etmesini sağlamak için kullanıldı.
Sis parçacık hacimlerinin büyüklüğü büyük olduğundan, parçalara çok daha fazla ihtiyacımız olmadı.

Daha sonra, yaprak savrulması ve rüzgâr dokuları yaratmayı sağladık ve parçacıklara farklı hızlarda dönecek ve farklı hızlarda başlayacak şekilde ayarladık. Bu, büyük sis parçacıklarının daha doğal bir şekilde etkileşimde bulunmasını sağladı ve değişken dokular gibi görünmedi, özellikle boyutları göz önüne alındığında.

Sis parçacığı
Yaprak parçacığı

Sonuç olarak, ağaçların hareketi, pencerelerin açılması ve yıldırımlar sayesinde fırtınanın gözünün çevresinde dönen etkisini yaratmayı başardık.

Fırtına gözünü ayarlayın

Kırık taş gözle birlikte parlayan bir çekirdek, oyunculara eve daha fazla keşfetmeleri gereken bir şey olduğunu bildirmenin ilk ipucunu vermektedir. Sahnede karanlık olmasından dolayı ve göz uzak bir konumda olduğundan, ikna edici bir kırık taş silueti oluşturmak önemliydi; ancak, göz içindeki ayrıntıların görünmesini sağlamak için ayrıntılı taş yüzey detayları yaratmanın gerekli olmadığı önemliydi. Geliştirirken, sahnenin aydınlatması içinde oyuncuların görebileceği gerçekçi olan şeyleri belirlemek, gereksiz detaylara zaman harcamaktan kurtarabilir.

Sahnenizde son aydınlatmayı erken ayarlamak, çok fazla gereksiz işten tasarruf etmenizi sağlar. Son aydınlatmamızla halka üzerindeki yüzey detaylarını göremeyeceğiniz için, oraya koymak için zaman harcamaya gerek yoktu!

Oyuncunun mesafesi, göz ile yüzey ayrıntıları için tamamen bir normal haritasına güvenmemizi sağladı. Ayrıca, ağın sadece sıradan bir küre olduğunu göstermektedir! Detayları yüksek poli bir ağda (mesh) oymak ve normal haritasını çok daha düşük poli bir küreye alarak bu güzel detayların büyük bir performans maliyeti olmadan elde edilmesini sağladık.

Yüksek poli oymalar
Düşük poli ağ
Normal bilgilerle birlikte düşük poli ağ

Gözün varlığını vurgulamak ve doğaüstü bir his vermek için, çatlaklarının içinden sızdığı parlayan, neon bir magmaya sahip bir göz oluşturmayı kararlaştırdık. Yüzey görünümü için emici kanal olmadığından, bu engeli aşmak için gözü 2 küre halinde yaratmayı seçtik: biri taş dış yüzey ve diğeri, parlayan magma için biraz daha küçük. Substance Painter içinde, dış küre için şeffaflık sağlamak üzere, iç çekirdek tarafından geçmek istemiş olduklarımızda bir temel renk dokusu oluşturduk. Blender içinde iç küreyi "vertex painted" (vertex boyalı) yaparak bazı renk değişmeleri elde etmek için basit bir yol oluşturduk.

İç küredeki vertex boyama. Göz etrafında en açık tonun oluşturulması, derinlik ve görsel ilgi yaratmaya yardımcı olur.

Gözü oluştururken karşılaştığımız bir başka zorluk, akışın (streaming) kullanımı ile gözün oyuncudan uzaklığıydı. Bu yapının merkezi konumda olması nedeniyle, oyuncuların uzaklığını göz önünde bulundurarak daima görünür olmasını istedik, ancak bu mesh'e herhangi bir müdahale olmaksızın oyuncular, göz gözlemleyemediklerinden, yalnızca solaryumun içindeyken göz görebildiler. Göz ve halkalarına bir geometri ekleyerek, bu geometri zemin yüzeyinin hemen altında yer alıyor ve bu da motorun küreyi görünenden daha yakın düşünmesi ve her zaman onu akışta tutmasını sağladı. Ancak, fazla büyük nesneleri akışa saltan etmek, akışın sağladığı faydaları etkisiz hale getirebilir ve performansı olumsuz etkileyebileceği için bunu az miktarda yapmalısınız.

Göz ve halkalarına döndürmek istediğimiz aynı script aracılığıyla hareket ekleyebildik. Son dokunuş olarak, bulutların ötesinde başka bir dünyanın varlığına bir ipucu eklemeye karar verdik; ancak bu, daha fazla geometri eklemekten kaçınmak ve akışla birlikte daha önce bahsedilen zorluklarla başa çıkmak için yaratıcı bir yaklaşım gerektirdi. Bir dizi derinlik kaynağı olmasını sağlamak için gözün arkasına yerleştirilen bir parçayı oluşturduk. Bu, ileri geri geçiş yaparak görsel bir derinlik ve karmaşıklık yaratmaya yardımcı oldu!

Bulutların ötesindeki bir dünya hayalini yaratmak için kullandığımız imaj. Oyuncular uzak bir nesneye baktıklarında, basit bir görüntü, sahnenizde daha fazla derinlik ve karmaşıklık yanılsaması yaratmak için yeterli olabilir!

Genişleyen kiler oluşturun

Üretmesi en keyifli şeylerden biri, oyuncuların gerçeklik algısını dönüştürme konusunda, etrafındakileri değiştirerek altüst ettiğimiz kirli alanlardı. Örneğin, babanın bulmacasında, koşmak ne kadar hızlı olursa olsun, odanın uzadığı bir kabus anını yansıtmayı istemiştik. Oyuncuların, odayı eski haline çevirmek için malzemeleri ararken onlardan kaçan genişleyen bir kiler oluşturmayı kararlaştırdık.

Bunu, duvarların düz hareketiyle ve kilerin her iki tarafında görünecek şekilde akıllı bir oda düzeni ile ayarladık. Odanın normal durumu boyunca kiler, basit bir koridora dönüşmüştü; ancak kirli alanda aslında çok daha uzun olup birçok kolu ve sahte bir duvarı barındırıyordu!

Mutfağın kirli durumu.
Oyunculardan kaçan sahte duvar.

Sahte duvar, oyuncular bir tetik hacmine girdiği anda geri hareket edecek bir model grubu, transparan bir parçanın önceki kiler bölümünde geçileceği bir ölçüyle tasarlandı. Tetik, buradaki TweenService kullanımıyla birlikte tüm kapılarımızla kullandığımız yöntemlere benzer bir scriptte de kullanıldı ve bir hedeften diğerine geçmek üzere çağrılmıştır. Tween işleminin duvarın başlangıç ve bitiş noktalarının nerede olduğunu söylemesi için parça hacimleri kullandık.

Parça hacmi, arkasındaki sahte duvarın uç noktasına hareket etmesini aktifleştirir. Bu görüntüde sarı tonlarıyla görülebilmektedir.
Target_Closed, tüm kapılarımızın döndüğü yerler için kullandığımız genel bir hedef kısmıydı. Burada, koridor duvarına gitmek için yeniden yapılandırılmıştır.

TweenService genel bir sistem olduğu için, tüm duvar veri modelimizin sadece aynı bileşenleri içermesi gerekiyordu. Örneğin, genel bir "Door_Script" sesi "value" altında "Grow_Wall" modelinin altına tanımlamaktadır. Bu script, yukarıda yer alan kod örneğinde bazı değişiklikler ile mutfak hareket ettirilirken sesleri de tetikler. Bu harekete çok fazla değer katan şeydir!


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, --Kapı Tween'in Süresi/Hızı
Enum.EasingStyle.Quart, --Easing Stil
Enum.EasingDirection.InOut, --Easing Direction
0, --Tekrar Sayısı
false, --Ters
0 --Gecikme
)
local DoorState = {
["Kapalı"] = 1,
["Açılma"] = 2,
["Açık"] = 3,
["Kapanma"] = 4,
}
local doorState = DoorState.Kapalı
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.Açılma
sound:Play()
tweenL:Play()
tweenR:Play()
end
local function StartClosing()
doorState = DoorState.Kapanma
--model["Kapı"]:Play()
tweenLClose:Play()
tweenRClose:Play()
end
local function tweenOpenCompleted(playbackState)
if next(playersNear) == nil then
StartClosing()
else
doorState = DoorState.Açık
end
end
local function tweenCloseCompleted(playbackState)
if next(playersNear) ~= nil then
StartOpening()
else
doorState = DoorState.Kapalı
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("dokunma")
playersNear[player] = 1
if doorState == DoorState.Kapalı then
StartOpening()
end
end
end
end

Sahte duvarın odaya doğru hareket etmesini sağladıktan sonra, içeriği de onunla hareket ettirmemiz gerekiyordu. Bunu başarmak için, kilerdeki tüm gevşek eşyaları hareket eden duvara kaynaklamamız gerekiyordu. Weld Constraints kullanarak, tüm nesneleri kiler duvarlarına hızlı bir şekilde kaynaklayabildiğimiz için, bir bütün olarak hareket edebilmelerini sağladık. Bu da, oyuncuların bunları sarsmasına ve dönmesine izin vermek için bu nesneleri çözme seçeneğini sundu.

Kirlenmiş ağaç ev oluşturun

Studio, hareket eden kapılardan dönen platfoma kadar her şey yaratmak için kullanabileceğiniz harika bir fizik tabanlı motordur. Demo'muzda, daha gerçekçi bir deneyim yaratmak için fiziksel hareketi kullanmak istedik. Kısıtlamaları kullanarak, deneyimlerinizi eğlenceli ve zorlayıcı engel yolları oluşturma yöntemi bulabilirsiniz!

Kısıtlamalar, nesneleri hizalayan ve davranışları kısıtlayan fizik tabanlı motorların bir grubudur. Örneğin, nesneleri birbirinden sabit bir mesafede tutmak amacıyla bir çubuk kısıtlaması veya çizgi ucundan bir lambanın askıda kalmasını sağlamak üzere bir ip kısıtlaması kullanabilirsiniz. Oyuncuları kirlenmiş çalışma odası üzerinde taşımaya yarayan bulmacada, dünyayı haritaya tersten yerleştirmek istedik. Böylece, oyuncuların gerçeklik algısının ve orada kurallarının altını çizebiliriz, fizik sistemini de tam anlamıyla kullanmasını sağlarız!

Oğul bulmacası, oyuncular aynı odada bulunurken, her şeyin yanlamasına olduğu bir başlangıç yaptı.

Oyuncular bulmacanın ana alanında ilerlediklerinde, Roblox'ta tanıdık bir görüntü ile karşılandılar: bir engel parkuru. Bu özel engel parkuru, birkaç dönen platform ve dönen duvarın yanı sıra, hikayeyi ilerleten "güvenli alanlar" içeriyordu. Dönen/dönmeyen unsurlar üzerinde duracağız.

Zihin bükücü mağazası, oynanışın oldukça basit olduğunu saklayarak görünüyordu.

Burada neden kısıtlamaları kullandık? Çünkü TweenService veya diğer yöntemler oyuncuyu hareket ettirmeyecektir. Eğer oyuncuya hareket etmemiz gerekmiyorsa, biri bir platforma atlayıp altındaki dönerken sona doğru kayması olabilecektir. Bunun yerine, oyuncuların sonraki platforma atlayabilmesi için dönen bir platformdan geçmesini istemiştik. Bu yaklaşım nedeniyle, oyuncular nerede duruyorlarsa yere köklenmiş hissediyorlar, ne yöne ilerleyeceklerine dair bir karar alırken!

Engel parkurunu geçerken arkadaşlarınızın döne döne ileriye doğru koşmasını izleyebiliyorsunuz.

Bunu gerçekleştirmek için, mevcut kitimizden varlıkları kullanmamız ve herhangi bir yeni içerik ile görsel bir efekt eklememiz gerekiyordu. Büyükanne tarihini anlatmak için yetersiz duvarlar ve delikler olan birkaç tatlı platform oluşturduk. O sebeple birçok benzersiz platform yaratmak istemediğimiz için, sahip olduğumuz 4 temel parça ve korkuluk parçasını ayrı ayrı oluşturduk. Bu, bireysel temel ve korkuluk parçalarını karıştırarak daha fazla çeşitlilik oluşturmaya yardımcı oldu.

Kısıtlamalar kullandığımız için, bu mesh’leri güvence altına almamız mümkün olamaz çünkü kısıtlamalarda bile, onları hareket ettirmek ya da tutmak çalışmazdı. Kısıtlamanın, nesnenin belirlendiği herhangi bir şeyin çocuğu olması gerekiyordu böylece platform yalnızca dünyadan düşmeyecek. Bunu, platformun genel hareketini sağlamak için bir hinge kısıtlaması ile Motor_Anchor olarak adlandırdığımız bir parçayla çözümleme yoluna gittik. Ardından, iki mesh'in bir arada hareket etmesi gerekiyordu, bu nedenle Motor_Turn adını verdiğimiz bir parça oluşturduk ve bu iki mesh'i ona kaynakladık. Böylece kısıtlama tek bir parça üzerinde çalışabilir.

Artık, kısıtlama koşulunu kendisi oluşturacak şekilde, döner kısıtlamanın davranışını ayarlama zamanıydı ve kısıtlama ile parçanın iletişim kuracak olan bağlantıları ekleyin. Motor_Turn üzerinde döner bağlantıyı yerleştirdik ve yürüyüş parçaları bu bağlantıya kaynaklandılar, bir başka bağlantı da Motor_Anchor üzerinde kısıtlamalar üzerine kurulu olmalıdır. Bu, oyuncudan etkilenmek istiyormuş gibi döner kısıtlamayla ilişkili bir kısıtlamadır.

Platformların dönerken sabit bir hızda dönmesini sağlamak için, ardından HingeConstraint.AngularVelocity, HingeConstraint.MotorMaxAcceleration, ve HingeConstraint.MotorMaxTorque değerlerini koymamız gerekiyordu.

Attachment0 aslında kısıtlamanın dayanacağı noktadır ve Attachment1 de kısıtlama olarak kullanılır. Sürekli dönen hıza erişiyor; ancak kapılar için de bir menteşe kısıtlaması kullanabilirsiniz.

Artık dönen duvarları yapma aşamasına geçmiş bulunmaktayız. Duvarlar, görünür merkezlerine göre dönme işlemini gerçekleştirmesi gerekiyordu ve ayrıca kalan seviyeye göre herhangi bir konumda hareket edebilmeliydi. Duvarlar, platformlarda olduğu gibi, birleşik ve kaynaklanmamış olarak yapılmış olmalıydı.

Performanstan tasarruf etmek için mümkün olan en fazla make-up boyutunu geri döndürmeye çalışmamız gerekmektedir, bu nedenle duvar türlerini çeşitli kombinasyonlarla bir araya getirerek farklılık yaratacak şekilde oluşturduk.

Texture nesneleri ile SurfaceAppearance nesneleri üzerine yerleştirerek temel materyallerimizde farklılık yaratabildik. Dokular, benzer Dekaller gibi, bir imaj yerleştirmenize izin verir. Bu, bir tuğla duvara kir eklemek veya ahşabı yaşlandırmak istiyorsanız size kullanışlı olabilir; tıpkı aynı temel ahşap materyali kullanarak. Texture objeleri, Decal objelerinden biraz farklı davranışlara sahip; bu dokuyu hangi şekilde yerleştirmek istiyorsanız, istediğiniz gibi yerleştirme ve kaydırma yapmanıza izin verebilir, eğer üstü örtü dokunursa veya tekrarlamak konusunda bir sıkıntınız yoksa!

Bizim için de HingeConstraint için benzer davranışların kullanılıp test edilmesi ve bunun yanında Texture nesnelerini nasıl kullandığımızı görebilirsiniz.

Birkaç platform ve dönen duvar test ettikten sonra, birçok çeşit oluşturduk ve yerleştirmelerini oynayarak, engel parkurunun zorlayıcı, zihin bükücü ve oyuncunun nereye gitmesi gerektiğini net bir şekilde belirlemeyi sağladık! Hem değerlerine hem de konumlarına göre harika performans göstermeleri için biraz ayarlama yapmam gerekiyordu. Platformlar ve duvarlar belirli noktalarda birbirleriyle çarpışıyor veya çevrelerindeki kenarlarla çarpışıyor; bu nedenle bir bolluk yaparak sürekli testlereîçerisine girdik ve demo’da değerleri sağladık!

Eğer fiziksel nesnelerinizin neyi çarptığını tam olarak bilmiyorsanız, sağ üst köşedeki 3D görüntü alanında Collision fidelity seçeneğini açmak için Görselleştirme Seçenekleri widget’ını kullanabilirsiniz.

A close up view of the 3D viewport with the Visualization Options button indicated in the upper-right corner.
Çarpışma görselleştirmesi devre dışı bırakıldığında, oyunda gösterilen normal geometri temsilini görebilirsiniz.
Çarpışma görselleştirme etkinleştirildiğinde, ağaç yapraklarının çarpışma olmadığını görebiliriz, bu nedenle dönen platformlar veya duvarlarla etkilenmezler.

Aşağıdaki kapı/ pencere deliklerinin görünür halde olduğunu görebilirsiniz; ancak daha küçük detaylar, alt panolar gibi görünmez. Bunun sebebi, duvarların CollisionFidelity özelliğinin Kutular (Box) olarak ayarlanmış olmasıdır. Bu panellerin hassasiyetine ihtiyaç duymadığımız için, daha düşük performans maliyetine tasarruf etmek istedik; oyuncuların üzerine atlayabileceği kadar detaylı bir görünüm yeterli olur. Platformlar ve dönen duvarlar ile tamamladığımızda, sadece kutular ve lambalar gibi detaylandırma nesneleri ekleyerek, oynatmaya hazır hale getirdik!

©2026 Roblox Corporation. Roblox, the Roblox logo and Powering Imagination are among our registered and unregistered trademarks in the U.S. and other countries.