This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this tutorial in English
Bu öğreticide boş bir projeyle başlayıp animasyonlu bir karakter, fiziksel çarpışmalar, toplanabilir nesneler ve puanlama içeren eksiksiz bir koşu oyunu oluşturacağız.
Yeni bir oyun motorunu öğrenirken kavranması gereken çok şey vardır; bu öğreticiyi başlamanıza yardımcı olmak için hazırladık. Motorun ve düzenleyicinin nasıl çalıştığını adım adım anlatan, oldukça kapsamlı bir öğreticidir. Programlamaya biraz aşina olduğunuzu varsayıyoruz.
Lua programlamaya giriş yapmak istiyorsanız Defold’da Lua kılavuzumuza göz atın.
Bu öğreticinin başlangıç için biraz fazla kapsamlı olduğunu düşünüyorsanız farklı zorluk düzeylerinde öğreticiler sunduğumuz öğreticiler sayfamıza göz atın.
Video öğreticileri izlemeyi tercih ediyorsanız YouTube’daki video sürümüne göz atın.
Diğer iki öğreticideki oyun varlıklarını (asset) küçük değişikliklerle kullanıyoruz. Öğretici, her biri bizi tamamlanmış oyuna önemli ölçüde yaklaştıran birkaç adıma ayrılmıştır.
Sonuçta, bir ortamda koşarak paraları toplayan ve engellerden kaçınan bir kahraman karakteri kontrol ettiğiniz bir oyun elde edeceksiniz. Kahraman sabit süratle koşar; oyuncu tek bir düğmeye basarak (veya mobil cihazda ekrana dokunarak) yalnızca kahramanın zıplamasını kontrol eder. Bölüm, üzerine zıplanacak platformların ve toplanacak paraların sonsuz akışından oluşur.
Bu öğreticinin herhangi bir yerinde veya oyununuzu oluştururken takılırsanız Defold Forum üzerinden bizden yardım istemekten çekinmeyin. Forumda Defold hakkında konuşabilir, Defold ekibinden yardım isteyebilir, diğer oyun geliştiricilerinin sorunlarını nasıl çözdüğünü görebilir ve yeni fikirler bulabilirsiniz. Hemen başlayın.
Öğretici boyunca kavramların ayrıntılı açıklamaları ve belirli işlemlerin nasıl yapılacağı bu paragraftaki gibi işaretlenmiştir. Bu bölümlerin fazla ayrıntıya girdiğini düşünüyorsanız onları atlayabilirsiniz.
Öyleyse başlayalım. Bu öğreticiyi uygularken çok eğlenmenizi ve Defold’a başlamanıza yardımcı olmasını umuyoruz.
Bu öğreticinin varlıklarını buradan indirin.
İlk adım, şu dosyaları indirmektir.
Defold düzenleyicisini henüz indirip kurmadıysanız şimdi bunu yapma zamanı:
macOS, Windows ve Linux (Ubuntu) için Download düğmelerinin bulunduğu Defold indirme sayfasına gidin:

Düzenleyiciyi başlatmak için “Applications” klasörünü açın ve “Defold” dosyasına çift tıklayın.

D:\Defold) taşıyın. Düzenleyicinin güncellenmesini engelleyeceği için Defold’u C:\Program Files (x86)\ veya C:\Program Files\ konumuna taşımamanız önerilir.Düzenleyiciyi başlatmak için “Defold” klasörünü açın ve “Defold.exe” dosyasına çift tıklayın.

Bir terminalden “Defold-x86_64-linux.zip” arşiv dosyasını bulun ve “Defold” adlı hedef dizine çıkarın.
$ unzip Defold-x86_64-linux.zip -d Defold
Düzenleyiciyi başlatmak için uygulamayı çıkardığınız dizine geçin, ardından Defold yürütülebilir dosyasını çalıştırın veya masaüstünde dosyaya çift tıklayın.
$ cd Defold
$ ./Defold
Help > Create Desktop Entry menüsünde bir masaüstü girdisi kurmak için yardımcı araç bulunur.
Düzenleyiciyi başlatırken, bir projeyi açarken veya bir Defold oyununu çalıştırırken herhangi bir sorunla karşılaşırsanız sık sorulan soruların Linux bölümüne bakın.
Defold’un her beta ve kararlı sürümüne GitHub’dan da ulaşabilirsiniz.
Düzenleyici kurulup başlatıldıktan sonra yeni bir proje oluşturup hazırlama zamanı gelmiştir. “Empty Project” şablonundan yeni bir proje oluşturun.
Bu öğretici Spine Extension içindeki Spine özelliklerini kullanır. Eklentiyi game.project dosyasının bağımlılıklar bölümüne ekleyin.
Düzenleyiciyi ilk kez başlattığınızda hiçbir proje açık olmadan boş olarak açılır; bu yüzden menüden Open Project seçeneğini seçip yeni oluşturduğunuz projeyi açın. Ayrıca proje için bir “dal” (branch) oluşturmanız istenecektir.
Artık Assets bölmesinde projenin parçası olan bütün dosyaları göreceksiniz. “main/main.collection” dosyasına çift tıklarsanız dosya ortadaki düzenleyici görünümünde açılır:

Düzenleyici şu ana alanlardan oluşur:
print() ve pprint() hata ayıklama iletilerini yakalar. Uygulamanız veya oyununuz başlamıyorsa ilk kontrol etmeniz gereken yer konsoldur. Konsolun arkasında hata bilgilerini gösteren sekmeler ve parçacık efektleri oluştururken kullanılan bir eğri düzenleyicisi bulunur.“Empty” proje şablonu gerçekten tamamen boştur. Yine de projeyi derleyip oyunu başlatmak için Project ▸ Build seçeneğini seçin.

Siyah bir ekran pek heyecan verici olmayabilir, ancak bu çalışan bir Defold oyun uygulamasıdır ve onu kolayca daha ilgi çekici bir şeye dönüştürebiliriz. Öyleyse bunu yapalım.
Defold düzenleyicisi dosyalar üzerinde çalışır. Assets bölmesindeki bir dosyaya çift tıklayarak onu uygun bir düzenleyicide açarsınız. Ardından dosyanın içeriği üzerinde çalışabilirsiniz.
Bir dosyayı düzenlemeyi bitirdiğinizde onu kaydetmeniz gerekir. Ana menüden File ▸ Save seçeneğini seçin. Düzenleyici, kaydedilmemiş değişiklikler içeren her dosyanın sekmesindeki dosya adına bir yıldız ‘*’ ekleyerek bunu belirtir.

Başlamadan önce projemizin birkaç ayarını yapalım. Assets Pane içinden game.project varlığını açın ve Display bölümüne kadar aşağı kaydırın. Projenin width ve height değerlerini sırasıyla 1280 ve 720 olarak ayarlayın.
Kahraman karakteri canlandırabilmemiz için projeye Spine eklentisini de eklemeniz gerekir. Spine eklentisinin, kurduğunuz Defold düzenleyicisi sürümüyle uyumlu bir sürümünü ekleyin. Kullanılabilir Spine sürümlerini burada görebilirsiniz:
https://github.com/defold/extension-spine/releases
Kullanmak istediğiniz sürümün zip dosyası bağlantısına sağ tıklayın:

Sürüm bağlantısını game.project bağımlılıkları listenize ekleyin. Spine eklentisi eklendikten sonra, eklentiyle birlikte gelen düzenleyici entegrasyonunu etkinleştirmek için düzenleyiciyi yeniden başlatmanız da gerekir.
İlk küçük adımları atıp karakterimiz için bir alan, daha doğrusu kayan bir zemin parçası oluşturalım. Bunu birkaç adımda yapacağız.
Bir atlas (Atlas), ayrı görüntüleri tek ve daha büyük bir görüntü dosyasında birleştiren bir dosyadır. Bunun amacı yerden tasarruf etmek ve performansı artırmaktır. Atlaslar ve diğer 2B grafik özellikleri hakkında daha fazla bilgiyi 2B grafik belgelerinde bulabilirsiniz.


Neden çalışmıyor!? Defold’a yeni başlayanların sık yaşadığı bir sorun kaydetmeyi unutmaktır! Bir atlasa görüntü ekledikten sonra bu görüntüye erişebilmek için dosyayı kaydetmeniz gerekir.
Zemin için ground.collection adlı bir koleksiyon (collection) dosyası oluşturup içine 7 oyun nesnesi ekleyin (Outline görünümünde koleksiyonun köküne sağ tıklayıp Add Game Object seçeneğini seçin). Properties görünümündeki Id özelliğini değiştirerek nesnelere “ground0”, “ground1”, “ground2” vb. adlar verin. Defold’un yeni oyun nesnelerine otomatik olarak benzersiz bir tanımlayıcı atadığını unutmayın.
Her nesneye bir sprite bileşeni ekleyin (Outline görünümünde oyun nesnesine sağ tıklayıp Add Component seçeneğini seçin, ardından Sprite seçeneğini seçip OK düğmesine tıklayın), sprite bileşeninin Image özelliğini az önce oluşturduğunuz atlasa ayarlayın ve sprite bileşeninin varsayılan animasyonunu iki zemin görüntüsünden birine ayarlayın. Sprite bileşeninin (oyun nesnesinin değil) X konumunu 190, Y konumunu 40 yapın. Görüntünün genişliği 380 piksel olduğundan ve onu bunun yarısı kadar piksel yana kaydırdığımızdan, oyun nesnesinin dayanak noktası sprite görüntüsünün en sol kenarında olacaktır.


Muhtemelen en kolayı, sprite bileşeni eklenmiş ve ölçeği ayarlanmış tek bir oyun nesnesi oluşturup onu kopyalamaktır. Nesneyi Outline görünümünde seçin, ardından Edit ▸ Copy ve sonra Edit ▸ Paste seçeneğini seçin.
Daha büyük veya daha küçük karolar istiyorsanız yalnızca ölçeği değiştirebilirsiniz. Ancak bunu yaptığınızda bütün zemin oyun nesnelerinin X konumlarını da yeni genişliğin katlarına ayarlamanız gerekir.
Dosyayı kaydedin, ardından ground.collection dosyasını main.collection dosyasına ekleyin: önce main.collection dosyasına çift tıklayın, ardından Outline görünümünde kök nesneye sağ tıklayıp Add Collection From File seçeneğini seçin. İletişim kutusunda ground.collection dosyasını seçip OK düğmesine tıklayın. ground.collection koleksiyonunu 0, 0, 0 konumuna yerleştirdiğinizden emin olun; aksi halde görüntüde kaymış olacaktır. Kaydedin.
Her şeyin yerinde olduğunu görmek için oyunu başlatın (Project ▸ Build).

Şimdiye kadar oluşturduğumuz bütün bu şeylerin aslında ne olduğu konusunda kafanız karışmış olabilir. Bu yüzden biraz durup herhangi bir Defold projesindeki en temel yapı taşlarına bakalım:
Şimdilik bu açıklamalar muhtemelen yeterlidir. Yine de bu konuların çok daha kapsamlı bir incelemesini Yapı taşları kılavuzunda bulabilirsiniz. Defold’un nasıl çalıştığını daha derinden anlamak için daha sonra bu kılavuzu okumanız iyi olacaktır.
Bütün zemin parçaları yerinde olduğuna göre onları hareket ettirmek oldukça basittir. Fikir şu: parçaları sağdan sola taşıyın; bir parça ekranın dışındaki en sol kenara ulaştığında onu en sağdaki konuma taşıyın. Bütün bu oyun nesnelerini hareket ettirmek için bir Lua betiği gerekir; öyleyse bir tane oluşturalım:
-- ground.script
local pieces = { "ground0", "ground1", "ground2", "ground3",
"ground4", "ground5", "ground6" } -- <1>
function init(self) -- <2>
self.speed = 360 -- Speed in pixels/s
end
function update(self, dt) -- <3>
for i, p in ipairs(pieces) do -- <4>
local pos = go.get_position(p)
if pos.x <= -228 then -- <5>
pos.x = 1368 + (pos.x + 228)
end
pos.x = pos.x - self.speed * dt -- <6>
go.set_position(pos, p) -- <7>
end
end
init() işlevi, oyun nesnesi oyunda hayata geçtiğinde çağrılır. Zeminin süratini tutan, bu nesneye özgü bir üye değişkenine başlangıç değeri atıyoruz.update() her karede bir kez, genellikle saniyede 60 kez çağrılır. dt, son çağrıdan bu yana geçen saniye sayısını içerir.dt ile çarpın.Defold, verilerinizi ve oyun nesnelerinizi yöneten hızlı bir motor çekirdeğidir. Oyununuz için gereken her türlü mantık veya davranış Lua dilinde oluşturulur. Lua, oyun mantığı yazmaya çok uygun, hızlı ve hafif bir programlama dilidir. Dili öğrenmek için Programming in Lua kitabı ve resmî Lua başvuru kılavuzu gibi harika kaynaklar bulunur.
Defold, Lua’nın üzerine bir dizi API ve oyun nesneleri arasındaki iletişimi programlamanızı sağlayan bir ileti aktarımı (message passing) sistemi ekler. Bunun nasıl çalıştığına ilişkin ayrıntılar için İleti aktarımı kılavuzuna bakın.
Düzenleyicinin Assets Pane, Console ve Outline bölümlerini sırasıyla F6, F7 ve F8 tuşlarıyla açıp kapatabilirsiniz
Artık bir betik dosyamız olduğuna göre bir oyun nesnesindeki bileşene bu dosyanın başvurusunu eklememiz gerekir. Böylece betik, oyun nesnesinin yaşam döngüsünün bir parçası olarak yürütülür. Bunun için ground.collection içinde yeni bir oyun nesnesi oluşturup bu nesneye, az önce oluşturduğumuz Lua betik dosyasına başvuran bir Script bileşeni ekleyeceğiz:

Artık oyunu çalıştırdığınızda “controller” oyun nesnesi, Script bileşenindeki betiği çalıştırarak zeminin ekran boyunca akıcı biçimde kaymasını sağlayacaktır.
Kahraman karakter, şu bileşenlerden oluşan bir oyun nesnesi olacak:
Vücut parçalarının görüntülerini içe aktararak başlayın; ardından bunları hero.atlas adını vereceğimiz yeni bir atlasa ekleyin:

Spine animasyon verilerini de içe aktarıp bunlar için bir Spine Scene hazırlamamız gerekir:

hero.spinejson dosyası Spine JSON biçiminde dışa aktarılmıştır. Bu tür dosyalar oluşturabilmek için Spine animasyon yazılımına ihtiyacınız vardır. Başka bir animasyon yazılımı kullanmak istiyorsanız animasyonlarınızı sprite sayfaları olarak dışa aktarabilir ve bunları Tile Source veya Atlas kaynaklarından kare dizisi animasyonları (flip-book animation) olarak kullanabilirsiniz. Daha fazla bilgi için Animasyon kılavuzuna bakın.
Artık kahramanın oyun nesnesini oluşturmaya başlayabiliriz:

Şimdi çarpışmanın çalışması için fizik ekleme zamanı:
Kinematik çarpışma (“Kinematic”), çarpışmaların algılanmasını istediğimiz, ancak fizik motorunun çarpışmaları otomatik olarak çözmeyeceği ve nesnelerin simülasyonunu yapmayacağı anlamına gelir. Fizik motoru birkaç farklı çarpışma nesnesi türünü destekler. Bunlar hakkında daha fazla bilgiyi Fizik belgelerinde bulabilirsiniz.
Çarpışma nesnesinin nelerle etkileşime girmesi gerektiğini belirtmemiz önemlidir:
Son olarak yeni bir hero.script dosyası oluşturup oyun nesnesine ekleyin.
handle_geometry_contact() işlevi yapar.)
Çarpışmayı kendimiz ele almamızın nedeni şudur: karakterin çarpışma nesnesinin türünü dinamik olarak ayarlasaydık motor, ilgili cisimler için Newton mekaniğine dayalı bir simülasyon yapardı. Bunun gibi bir oyunda böyle bir simülasyon en uygun çözüm olmaktan uzaktır; bu nedenle çeşitli kuvvetlerle fizik motoruyla uğraşmak yerine kontrolü tamamen ele alıyoruz.
Bunu yapmak ve çarpışmaları doğru biçimde ele almak için biraz vektör matematiği gerekir. Kinematik çarpışmaların nasıl çözüleceğine ilişkin ayrıntılı bir açıklama Fizik belgelerinde bulunur.
-- gravity pulling the player down in pixel units/sˆ2
local gravity = -20
-- take-off speed when jumping in pixel units/s
local jump_takeoff_speed = 900
function init(self)
-- this tells the engine to send input to on_input() in this script
msg.post(".", "acquire_input_focus")
-- save the starting position
self.position = go.get_position()
-- keep track of movement vector and if there is ground contact
self.velocity = vmath.vector3(0, 0, 0)
self.ground_contact = false
end
function final(self)
-- Return input focus when the object is deleted
msg.post(".", "release_input_focus")
end
function update(self, dt)
local gravity = vmath.vector3(0, gravity, 0)
if not self.ground_contact then
-- Apply gravity if there's no ground contact
self.velocity = self.velocity + gravity
end
-- apply velocity to the player character
go.set_position(go.get_position() + self.velocity * dt)
-- reset volatile state
self.correction = vmath.vector3()
self.ground_contact = false
end
local function handle_geometry_contact(self, normal, distance)
-- project the correction vector onto the contact normal
-- (the correction vector is the 0-vector for the first contact point)
local proj = vmath.dot(self.correction, normal)
-- calculate the compensation we need to make for this contact point
local comp = (distance - proj) * normal
-- add it to the correction vector
self.correction = self.correction + comp
-- apply the compensation to the player character
go.set_position(go.get_position() + comp)
-- check if the normal points enough up to consider the player standing on the ground
-- (0.7 is roughly equal to 45 degrees deviation from pure vertical direction)
if normal.y > 0.7 then
self.ground_contact = true
end
-- project the velocity onto the normal
proj = vmath.dot(self.velocity, normal)
-- if the projection is negative, it means that some of the velocity points towards the contact point
if proj < 0 then
-- remove that component in that case
self.velocity = self.velocity - proj * normal
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("contact_point_response") then
-- check if we received a contact point message. One message for each contact point
if message.group == hash("geometry") then
handle_geometry_contact(self, message.normal, message.distance)
end
end
end
local function jump(self)
-- only allow jump from ground
if self.ground_contact then
-- set take-off speed
self.velocity.y = jump_takeoff_speed
end
end
local function abort_jump(self)
-- cut the jump short if we are still going up
if self.velocity.y > 0 then
-- scale down the upwards speed
self.velocity.y = self.velocity.y * 0.5
end
end
function on_input(self, action_id, action)
if action_id == hash("jump") or action_id == hash("touch") then
if action.pressed then
jump(self)
elseif action.released then
abort_jump(self)
end
end
end
İsterseniz şimdi kahraman karakteri geçici olarak ana koleksiyona ekleyip oyunu çalıştırmayı deneyerek karakterin dünyanın içinden geçip düştüğünü görebilirsiniz.
Kahramanın işlevsel olması için gereken son şey girdidir. Yukarıdaki betik, “jump” ve “touch” (dokunmatik ekranlar için) eylemlerine yanıt veren bir on_input() işlevi zaten içerir. Bu eylemler için girdi eşlemeleri (input binding) ekleyelim.

Kahraman karakteri çarpışma dahil her şeyiyle hazırladığımıza göre, çarpışabileceği (veya üzerinde koşabileceği) bir şey olması için zemine de çarpışma eklememiz gerekir. Birazdan bunu yapacağız; ancak önce küçük bir yeniden yapılandırma yapıp bölümle ilgili her şeyi ayrı bir koleksiyona yerleştirelim ve dosya yapısını biraz düzenleyelim:
Şimdiye kadar fark etmiş olabileceğiniz gibi, Assets bölmesinde görünen dosya hiyerarşisi, koleksiyonlarınızda oluşturduğunuz içerik yapısından bağımsızdır. Koleksiyon ve oyun nesnesi dosyalarından tek tek dosyalara başvurulur, ancak bu dosyaların konumları tamamen size bağlıdır.
Bir dosyayı yeni bir konuma taşımak istediğinizde Defold, dosyaya yapılan başvuruları otomatik olarak güncelleyerek (yeniden yapılandırma) yardımcı olur. Oyun gibi karmaşık bir yazılım geliştirirken, proje büyüyüp değiştikçe yapısını değiştirebilmek son derece yararlıdır. Defold bunu destekler ve süreci kolaylaştırır; bu yüzden dosyalarınızı taşımaktan çekinmeyin!
Bölüm koleksiyonuna, betik bileşeni olan bir denetleyici oyun nesnesi de eklememiz gerekir:
Betik dosyasını açın, aşağıdaki kodu içine kopyalayın ve dosyayı kaydedin:
-- controller.script
go.property("speed", 360) -- <1>
function init(self)
msg.post("ground/controller#ground", "set_speed", { speed = self.speed })
end

“controller” oyun nesnesi ayrı bir dosyada bulunmaz; bölüm koleksiyonunda yerinde (in-place) oluşturulur. Bu, oyun nesnesi örneğinin yerinde tanımlanmış verilerden oluşturulduğu anlamına gelir. Bu nesne gibi tek bir amaca hizmet eden oyun nesneleri için bu uygundur. Bir oyun nesnesinin birden çok örneğine ihtiyacınız varsa ve her örneği oluşturmak için kullanılan prototipi/şablonu değiştirebilmek istiyorsanız, bir oyun nesnesi dosyası oluşturup oyun nesnesini dosyadan koleksiyona eklemeniz yeterlidir. Bu işlem, dosyaya prototip/şablon olarak başvuran bir oyun nesnesi oluşturur.
Bu “controller” oyun nesnesinin amacı, çalışan bölümle ilgili her şeyi kontrol etmektir. Yakında bu betik, kahramanın etkileşime gireceği platformları ve paraları oluşturmakla sorumlu olacak; ancak şimdilik yalnızca bölümün süratini ayarlayacaktır.
Bölüm denetleyicisi betiğinin init() işlevi, zemin denetleyicisi nesnesinin betik bileşenine, tanımlayıcısıyla adresleyerek bir ileti gönderir:
msg.post("ground/controller#controller", "set_speed", { speed = self.speed })
Denetleyici oyun nesnesi “ground” koleksiyonunda bulunduğundan tanımlayıcısı "ground/controller" olarak ayarlanır. Ardından nesne tanımlayıcısını bileşen tanımlayıcısından ayıran "#" karakterinden sonra "controller" bileşen tanımlayıcısını ekleriz. Zemin betiğinde henüz set_speed iletisine tepki verecek kod bulunmadığını unutmayın; bu yüzden ground.script dosyasına bir on_message() işlevi ve bunun için gereken mantığı eklememiz gerekir.
-- ground.script
function on_message(self, message_id, message, sender)
if message_id == hash("set_speed") then -- <1>
self.speed = message.speed -- <2>
end
end

Bu noktada zemine fiziksel çarpışma eklememiz gerekir:

Artık oyunu çalıştırmayı deneyebilirsiniz (Project ▸ Build). Kahraman karakterin zeminde koşması ve Space tuşuyla zıplayabilmesi gerekir. Oyunu mobil cihazda çalıştırırsanız ekrana dokunarak zıplayabilirsiniz.
Oyun dünyamızdaki hayatı biraz daha az sıkıcı hale getirmek için üzerine zıplanacak platformlar eklemeliyiz.
platform.script adlı bir Script dosyası oluşturun (Assets bölmesine sağ tıklayıp New ▸ Script File seçeneğini seçin) ve aşağıdaki kodu dosyaya yerleştirip kaydedin:
-- platform.script
function init(self)
self.speed = 540 -- Default speed in pixels/s
end
function update(self, dt)
local pos = go.get_position()
if pos.x < -500 then
go.delete() -- <1>
end
pos.x = pos.x - self.speed * dt
go.set_position(pos)
end
function on_message(self, message_id, message, sender)
if message_id == hash("set_speed") then
self.speed = message.speed
end
end

Hem platform.go hem de platform_long.go dosyasının aynı betik dosyasına başvuran Script bileşenleri bulunduğunu unutmayın. Bu iyi bir durumdur; çünkü betik dosyasında yaptığımız her değişiklik hem normal hem de uzun platformların davranışını etkileyecektir.
Oyunun fikri, basit bir sonsuz koşu oyunu olmasıdır. Bu, platform oyun nesnelerinin düzenleyicide bir koleksiyona yerleştirilemeyeceği anlamına gelir. Onları çalışma sırasında dinamik olarak oluşturmamız gerekir:
-- controller.script
go.property("speed", 360)
local grid = 460
local platform_heights = { 100, 200, 350 } -- <1>
function init(self)
msg.post("ground/controller#controller", "set_speed", { speed = self.speed })
self.gridw = 0
end
function update(self, dt) -- <2>
self.gridw = self.gridw + self.speed * dt
if self.gridw >= grid then
self.gridw = 0
-- Maybe spawn a platform at random height
if math.random() > 0.2 then
local h = platform_heights[math.random(#platform_heights)]
local f = "#platform_factory"
if math.random() > 0.5 then
f = "#platform_long_factory"
end
local p = factory.create(f, vmath.vector3(1600, h, 0), nil, {}, vmath.vector3(0.6, 0.6, 1))
msg.post(p, "set_speed", { speed = self.speed })
end
end
end
update() işlevi her karede bir kez çağrılır; bunu, belirli aralıklarla (üst üste binmeyi önlemek için) ve yüksekliklerde normal veya uzun bir platform oluşturup oluşturmayacağımıza karar vermek için kullanırız. Farklı oynanışlar oluşturmak için çeşitli oluşturma algoritmalarını denemek kolaydır.Şimdi oyunu çalıştırın (Project ▸ Build).
Vay canına, bu (neredeyse) oynanabilir bir şeye dönüşmeye başlıyor…

İlk yapacağımız şey, kahraman karaktere hayat vermek. Şu anda zavallı karakter bir koşu döngüsüne takılıp kalmış durumda; zıplamalara veya başka şeylere iyi tepki vermiyor. Varlık paketinden eklediğimiz Spine dosyası aslında tam da bunun için bir dizi animasyon içeriyor.
update() işlevinden önce ekleyin: -- hero.script
local function play_animation(self, anim)
-- only play animations which are not already playing
if self.anim ~= anim then
-- tell the spine model to play the animation
local anim_props = { blend_duration = 0.15 }
spine.play_anim("#spinemodel", anim, go.PLAYBACK_LOOP_FORWARD, anim_props)
-- remember which animation is playing
self.anim = anim
end
end
local function update_animation(self)
-- make sure the right animation is playing
if self.ground_contact then
play_animation(self, hash("run"))
else
play_animation(self, hash("jump"))
end
end
update() işlevini bulup bir update_animation çağrısı ekleyin: ...
-- apply it to the player character
go.set_position(go.get_position() + self.velocity * dt)
update_animation(self)
...

Lua’da yerel değişkenler için “sözcüksel kapsam” (lexical scope) vardır ve local işlevleri yerleştirdiğiniz sıra önemlidir. update() işlevi, yerel update_animation() ve play_animation() işlevlerini çağırır; yani çalışma zamanı ortamının bu yerel işlevleri çağırabilmesi için onları önceden görmüş olması gerekir. Bu yüzden işlevleri update() işlevinden önce koymalıyız. İşlevlerin sırasını değiştirirseniz hata alırsınız. Bunun yalnızca local değişkenler için geçerli olduğunu unutmayın. Lua’nın kapsam kuralları ve yerel işlevler hakkında daha fazla bilgiyi http://www.lua.org/pil/6.2.html adresinde bulabilirsiniz
Kahramana zıplama ve düşme animasyonları eklemek için gerekenlerin hepsi bu kadar. Oyunu çalıştırırsanız oynamanın çok daha iyi hissettirdiğini fark edeceksiniz. Ne yazık ki platformların kahramanı ekranın dışına itebildiğini de fark edebilirsiniz. Bu, çarpışmaların ele alınışının bir yan etkisidir; ancak çözümü kolaydır: biraz şiddet ekleyip platformların kenarlarını tehlikeli hale getirin!
Dosyayı kaydedin.

hero.go dosyasını açın, Collision Object bileşenini seçip Mask özelliğine “danger” adını ekleyin. Ardından dosyayı kaydedin.

hero.script dosyasını açın ve kahraman karakter bir “danger” kenarına çarptığında tepki alabilmek için on_message() işlevini değiştirin:
-- hero.script
function on_message(self, message_id, message, sender)
if message_id == hash("reset") then
self.velocity = vmath.vector3(0, 0, 0)
self.correction = vmath.vector3()
self.ground_contact = false
self.anim = nil
go.set(".", "euler.z", 0)
go.set_position(self.position)
msg.post("#collisionobject", "enable")
elseif message_id == hash("contact_point_response") then
-- check if we received a contact point message
if message.group == hash("danger") then
-- Die and restart
play_animation(self, hash("death"))
msg.post("#collisionobject", "disable")
-- <1>
go.animate(".", "euler.z", go.PLAYBACK_ONCE_FORWARD, 160, go.EASING_LINEAR, 0.7)
go.animate(".", "position.y", go.PLAYBACK_ONCE_FORWARD, go.get_position().y - 200, go.EASING_INSINE, 0.5, 0.2,
function()
msg.post("#", "reset")
end)
elseif message.group == hash("geometry") then
handle_geometry_contact(self, message.normal, message.distance)
end
end
end
Nesnenin başlangıç işlemlerini yapmak için init() işlevini bir “reset” iletisi gönderecek şekilde değiştirin, ardından dosyayı kaydedin:
-- hero.script
function init(self)
-- this lets us handle input in this script
msg.post(".", "acquire_input_focus")
-- save position
self.position = go.get_position()
msg.post("#", "reset")
end
Oyunu şimdi denerseniz sıfırlama mekanizmasının çalışmadığı hemen anlaşılır. Kahraman doğru biçimde sıfırlanır; ancak sıfırlama sizi anında bir platform kenarına düşüp yeniden öldüğünüz bir duruma kolayca sokabilir. İstediğimiz, ölüm gerçekleştiğinde bütün bölümü düzgün biçimde sıfırlamaktır. Bölüm yalnızca çalışma sırasında oluşturulan bir dizi platformdan oluştuğu için, oluşturulan bütün platformları takip edip sıfırlama sırasında silmemiz yeterlidir:
controller.script dosyasını açıp oluşturulan bütün platformların tanımlayıcılarını saklayacak şekilde kodu düzenleyin:
-- controller.script
go.property("speed", 360)
local grid = 460
local platform_heights = { 100, 200, 350 }
function init(self)
msg.post("ground/controller#controller", "set_speed", { speed = self.speed })
self.gridw = 0
self.spawns = {} -- <1>
end
function update(self, dt)
self.gridw = self.gridw + self.speed * dt
if self.gridw >= grid then
self.gridw = 0
-- Maybe spawn a platform at random height
if math.random() > 0.2 then
local h = platform_heights[math.random(#platform_heights)]
local f = "#platform_factory"
if math.random() > 0.5 then
f = "#platform_long_factory"
end
local p = factory.create(f, vmath.vector3(1600, h, 0), nil, {}, vmath.vector3(0.6, 0.6, 1))
msg.post(p, "set_speed", { speed = self.speed })
table.insert(self.spawns, p) -- <1>
end
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("reset") then -- <2>
-- Tell the hero to reset.
msg.post("hero#hero", "reset")
-- Delete all platforms
for i,p in ipairs(self.spawns) do
go.delete(p)
end
self.spawns = {}
elseif message_id == hash("delete_spawn") then -- <3>
for i,p in ipairs(self.spawns) do
if p == message.id then
table.remove(self.spawns, i)
go.delete(p)
end
end
end
end
platform.script dosyasını açıp en sol kenara ulaşan bir platformu yalnızca silmek yerine, bölüm denetleyicisine platformun kaldırılmasını isteyen bir ileti gönderecek şekilde değiştirin:
-- platform.script
...
if pos.x < -500 then
msg.post("/level/controller#controller", "delete_spawn", { id = go.get_id() })
end
...

-- hero.script
...
go.animate(".", "position.y", go.PLAYBACK_ONCE_FORWARD, go.get_position().y - 200, go.EASING_INSINE, 0.5, 0.2,
function()
msg.post("controller#controller", "reset")
end)
...

Artık temel yeniden başlama ve ölme döngüsü hazır!
Sırada uğruna yaşayacak bir şey var: paralar!
Fikir, oyuncunun toplaması için bölüme paralar yerleştirmektir. İlk sorulması gereken, bunları bölüme nasıl yerleştireceğimizdir. Örneğin platform oluşturma algoritmasıyla bir şekilde uyumlu bir oluşturma düzeni geliştirebiliriz. Ancak sonunda çok daha kolay bir yaklaşım seçtik ve paraları platformların kendisinin oluşturmasını sağladık:
coin.script adlı yeni bir betik dosyası oluşturun (Assets bölmesindeki level klasörüne sağ tıklayıp New ▸ Script File seçeneğini seçin). Şablon kodunu aşağıdakiyle değiştirin:
-- coin.script
function init(self)
self.collected = false
end
function on_message(self, message_id, message, sender)
if self.collected == false and message_id == hash("collision_response") then
self.collected = true
msg.post("#sprite", "disable")
elseif message_id == hash("start_animation") then
pos = go.get_position()
go.animate(go.get_id(), "position.y", go.PLAYBACK_LOOP_PINGPONG, pos.y + 24, go.EASING_INOUTSINE, 0.75, message.delay)
end
end
Betik dosyasını para nesnesine bir Script bileşeni olarak ekleyin (Outline görünümündeki köke sağ tıklayıp Add Component from File seçeneğini seçin).

Plan, paraları platform nesnelerinden oluşturmaktır; bu yüzden platform.go ve platform_long.go dosyalarına paralar için fabrikalar yerleştirin.

Şimdi platform.script dosyasını, paraları oluşturup silecek şekilde değiştirmemiz gerekir:
-- platform.script
function init(self)
self.speed = 540 -- Default speed in pixels/s
self.coins = {}
end
function final(self)
for i,p in ipairs(self.coins) do
go.delete(p)
end
end
function update(self, dt)
local pos = go.get_position()
if pos.x < -500 then
msg.post("/level/controller#controller", "delete_spawn", { id = go.get_id() })
end
pos.x = pos.x - self.speed * dt
go.set_position(pos)
end
function create_coins(self, params)
local spacing = 56
local pos = go.get_position()
local x = pos.x - params.coins * (spacing*0.5) - 24
for i = 1, params.coins do
local coin = factory.create("#coin_factory", vmath.vector3(x + i * spacing , pos.y + 64, 1))
msg.post(coin, "set_parent", { parent_id = go.get_id() }) -- <1>
msg.post(coin, "start_animation", { delay = i/10 }) -- <2>
table.insert(self.coins, coin)
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("set_speed") then
self.speed = message.speed
elseif message_id == hash("create_coins") then
create_coins(self, message)
end
end
Üst-alt nesne ilişkileri yalnızca sahne grafını (scene graph) değiştirir. Alt nesne (child), üst nesnesiyle birlikte dönüşüme uğrar (taşınır, ölçeklenir veya döndürülür). Oyun nesneleri arasında ek “sahiplik” ilişkilerine ihtiyacınız varsa bunları kodda ayrıca takip etmeniz gerekir.
Bu öğreticinin son adımı, controller.script dosyasına birkaç satır eklemektir:
-- controller.script
...
local platform_heights = { 100, 200, 350 }
local coins = 3 -- <1>
...
-- controller.script
...
local coins = coins
if math.random() > 0.5 then
f = "#platform_long_factory"
coins = coins * 2 -- Twice the number of coins on long platforms
end
...
-- controller.script
...
msg.post(p, "set_speed", { speed = self.speed })
msg.post(p, "create_coins", { coins = coins })
table.insert(self.spawns, p)
...

Artık basit ama işlevsel bir oyunumuz var! Buraya kadar geldiyseniz kendi başınıza devam edip şunları eklemek isteyebilirsiniz:
Projenin tamamlanmış sürümünü buradan indirin
Bu giriş öğreticisi burada sona eriyor. Şimdi Defold’u keşfetmeye devam edin. Size yol göstermek için hazırladığımız pek çok kılavuz ve öğretici var; takılırsanız foruma bekleriz.
Defold ile iyi çalışmalar!