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
Beginner
Bu öğretici, yeniden yapmayı deneyebileceğiniz en yaygın klasik oyunlardan birini oluşturma sürecinde size yol gösterir. Bu oyunun birçok çeşidi vardır; burada “yiyecek” yiyen ve yalnızca yediğinde büyüyen bir yılan bulunur. Bu yılan ayrıca engellerin bulunduğu bir oyun alanında sürünür.
![]()
Bu öğreticide şunları öğreneceksiniz:
Bu öğretici yeni başlayanlar için tasarlanmıştır; ancak Defold ve oyun geliştirme konusunda tamamen yeniyseniz önce giriş niteliğindeki bazı kılavuzları, özellikle Defold’un yapı taşları ve Terim sözlüğü kılavuzlarını okumanızı öneririz. Henüz Defold’u indirmediyseniz Kurulum kılavuzuna bakın. Düzenleyiciyi hızla tanımak için Düzenleyiciye genel bakış kılavuzuna da göz atmanız önerilir; ayrıca burada her adım için ekran görüntüleri sunuyoruz.
Defold’u başlatın ve:

Tamam!
Oyunun çözünürlüğünü belirleyerek başlayacağız.
game.project dosyasını bulun. Açmak için dosyaya çift tıklayın.game.project dosyasının Display bölümüne gidin.Width ve Height) 768⨉768 veya 16’nın başka bir katı olacak şekilde ayarlayın.
Bunu yapmanızın nedeni, oyunun her parçası 16x16 piksel olan bir ızgara üzerine çizilecek olmasıdır; böylece oyun ekranı hiçbir parçayı kısmen kesmez. game.project dosyası projenin tüm önemli ayarlarını içerir; bunların tamamını Proje ayarları kılavuzunda okuyabilirsiniz.
Tamam!
Sade bir Snake benzeri oyun için çok az grafik gerekir. Yılan için 16⨉16 boyutunda bir yeşil parça, engeller için bir beyaz blok ve yiyeceği temsil eden daha küçük bir kırmızı blok.
Önce Defold düzenleyicisinde varlıklar (assets) için bir dizin oluşturun:
main klasörüne sağ tıklayınNew Folder seçeneğini seçin.assets yazın ve Create Folder düğmesine tıklayın.
Tamam!
Aşağıdaki görüntü, ihtiyacınız olan tek varlıktır:


Varlıkları içe aktarma hakkında daha fazla ayrıntıyı burada da okuyabilirsiniz.
Tamam!
Defold, ızgara üzerinde hizalanmış karolardan (tiles) oluşan oyun alanını oluşturmak için kullanacağınız yerleşik bir karo haritası (Tile Map) bileşeni (component) sunar. Karo haritası, karoları tek tek ayarlamanıza ve okumanıza olanak tanır; bu da bu oyun için çok uygundur. Karo haritaları grafiklerini bir karo kaynağından (Tile Source) aldığı için bir karo kaynağı oluşturmanız gerekir:
assets klasörüne sağ tıklayın.New ▸ Tile Source seçeneğini seçin.snake.tilesource olarak kaydedecektir).
Karo kaynağı, bu dosya türüne özel Tile Source Editor içinde açılır ve çalışabilmesi için bir görüntü belirtmeniz istenir. Sağ tarafta Properties bölmesini bulabilirsiniz:
Image özelliğini az önce içe aktardığınız grafik dosyasına ayarlayın.

Width ve Height özellikleri 16 (varsayılan değer) olarak bırakılmalıdır. Bu, 32⨉32 piksellik görüntüyü 1–4 olarak numaralandırılmış 4 karoya böler.

Extrude Borders özelliğinin 2 piksel olarak ayarlandığına dikkat edin. Bu, grafikleri kenara kadar uzanan karoların çevresinde görüntü kusurları oluşmasını önler.
Bir dosyada değişiklik yaparsanız sekmesinde adının yanında bir yıldız işareti * görünür. Tüm dosyaları kaydetmek için File ▸ Save All seçeneğini seçin veya Ctrl+S (Mac’te ⌘Cmd + S) kısayolunu kullanın.
Tamam!
Artık kullanıma hazır bir karo kaynağınız var; şimdi oyun alanının karo haritası bileşenini oluşturma zamanı:
main klasörüne sağ tıklayın ve “Components” bölümünde New ▸ Tile Map seçeneğini seçin. Yeni dosyaya “grid” adını verin (düzenleyici dosyayı “grid.tilemap” olarak kaydedecektir).

Dosya Tile Map Editor içinde açılır ve bir Tile Source gerektiğini belirtir; bu nedenle Tile Source özelliğini daha önce oluşturduğunuz “snake.tilesource” dosyasına ayarlayın.

Tamam!
Defold, karo haritasının yalnızca kullanılan alanını saklar; bu nedenle ekran sınırlarını dolduracak kadar karo eklemeniz gerekir.
Outline bölmesinde layer1 katmanını seçin.Karo paletini göstermek için Edit ▸ Select Tile... menü seçeneğini veya Space kısayolunu kullanın, ardından boyarken kullanmak istediğiniz karoya tıklayın.


Oyun ekranını doldurmak için 48x48 karo boyutunda bir karo haritasına ihtiyacınız olacak (çünkü ekran boyutumuz 768 ve karolarımız 16 piksel, dolayısıyla 768/16 = 48).
Bitirdiğinizde karo haritasını kaydedin.
Tamam!
Şimdi karo haritamızı oyuna eklememiz gerekiyor. Defold’un yapı taşlarını biliyorsanız bileşenlerin oyun nesnelerinin (game objects) bir parçası olduğunu ve oyun nesnelerinin koleksiyonlarda (collections) tanımlanabildiğini de biliyorsunuzdur.
Assets bölmesinde main.collection dosyasına çift tıklayarak açın. Bu dosya, Empty Project şablonunda varsayılan olarak motor başlatıldığında yüklenen başlangıç koleksiyonudur (bootstrap collection).
Outline görünümünde köke sağ tıklayın ve Add Game Object seçeneğini seçin. Bu işlem, oyun başladığında yüklenen koleksiyonda yeni bir oyun nesnesi oluşturur.

Yeni oyun nesnesine sağ tıklayın ve Add Component File seçeneğini seçin. Az önce oluşturduğunuz “grid.tilemap” dosyasını seçin.

Artık oyun koleksiyonumuzda bir karo haritası var. Oyunu düzenleyiciden çalıştırdığınızda görünür olmalıdır.
Project ▸ Build seçeneğini seçin veya Ctrl + B (Mac’te ⌘Cmd + B) kısayolunu kullanın.
Tamam!
Assets tarayıcısında main klasörüne sağ tıklayın ve Scripts bölümünde New ▸ Script seçeneğini seçin. Yeni betik (script) dosyasına “snake” adını verin (“snake.script” olarak kaydedilecektir). Bu dosya oyunun tüm mantığını içerecektir.

main.collection dosyasına dönün ve karo haritasını barındıran oyun nesnesine sağ tıklayın. Add Component File seçeneğini seçin ve “snake.script” dosyasını seçin.

Artık karo haritası bileşeni ve betik hazır.
Tamam!
Yazacağınız betik oyunun tamamını yönetecek. Özellikleri tek tek ekleyeceğiz.
Nasıl çalışacağına ilişkin fikir şu:
snake.script dosyasını açın ve init() işlevini bulun. Oyun başladığında betiğin başlangıç işlemleri yapılırken motor bu işlevi çağırır. Kodu aşağıdaki gibi değiştirin:
function init(self)
self.segments = { -- <1>
{x = 7, y = 24},
{x = 8, y = 24},
{x = 9, y = 24},
{x = 10, y = 24}
}
self.dir = {x = 1, y = 0} -- <2>
self.speed = 7.0 -- <3>
self.time = 0 -- <4>
end
Bu kodda şunları yapıyoruz:
self.segments adlı bir Lua tablosunda saklıyoruz. Bu tablo, her biri bir parçanın X ve Y konumunu tutan tabloların listesini içerir.self.dir adlı bir tabloda saklıyoruz.self.speed içinde saklıyoruz.self.time içinde saklıyoruz.Yukarıdaki betik kodu Lua dilinde yazılmıştır. Kod hakkında dikkat etmeniz gereken birkaç nokta var; ancak aşağıdakileri henüz anlamıyorsanız endişelenmeyin. Adımları izleyin, denemeler yapın ve kendinize zaman tanıyın; sonunda anlayacaksınız. Şimdilik init() içinde kullanacağımız değişkenlere yalnızca başlangıç değerlerini verdiğimizi aklınızda tutmanız yeterli.
self parametresi aracılığıyla geçerli betik bileşeni örneğine (instance) bir başvuru geçirir. self başvurusu örneğe ait verileri saklamak için kullanılır.self başvurusu, içinde veri saklayabileceğiniz bir Lua tablosu olarak kullanılabilir. Diğer tablolarda olduğu gibi noktalı gösterimi kullanmanız yeterlidir: self.data = "value". Başvuru, betiğin yaşam süresi boyunca geçerlidir; bu örnekte oyunun başlangıcından siz oyundan çıkana kadar.{} içine yazılır.{x = 10, y = 20}), iç içe Lua tabloları ({ {a = 1}, {b = 2} }) veya başka veri türleri olabilir.Tamam!
init() işlevi, betik bileşeninin çalışan oyunda bir örneği oluşturulduğunda tam olarak bir kez çağrılır. Buna karşılık update() işlevi her karede bir kez çağrılır. Bu da işlevi gerçek zamanlı oyun mantığı için ideal kılar.
Güncellemenin temel fikri şu: belirli aralıklarla aşağıdakileri yapın:

:::sidenote Yılanın başının tablonun sonunda, kuyruğunun ise başında olduğunu unutmayın. :::
update() işlevini bulun ve kodu aşağıdaki gibi değiştirin:function update(self, dt)
self.time = self.time + dt -- <1>
if self.time >= 1.0 / self.speed then -- <2>
local head = self.segments[#self.segments] -- <3>
local newhead = {
x = head.x + self.dir.x,
y = head.y + self.dir.y
} -- <4>
table.insert(self.segments, newhead) -- <5>
local tail = table.remove(self.segments, 1) -- <6>
tilemap.set_tile("#grid", "layer1", tail.x, tail.y, 0) -- <7>
for i, s in ipairs(self.segments) do -- <8>
tilemap.set_tile("#grid", "layer1", s.x, s.y, 2) -- <9>
end
self.time = 0 -- <10>
end
end
Bu kodda şunları yapıyoruz:
update() işlevinin son çağrılışından bu yana geçen süre (saniye cinsinden), yani “delta time” veya dt kadar ilerletiyoruz.#, dizi olarak kullanılan bir tablonun uzunluğunu almak için kullanılan işleçtir. Bizim tablomuz da dizi olarak kullanılır; tüm parçalar, anahtar belirtilmeden tanımlanmış tablo değerleridir.self.dir) göre yeni bir baş parçası oluşturuyoruz.#grid karo haritamızda layer1 adlı yalnızca 1 katman bulunur.i, tablodaki konuma (1’den başlayarak), s ise geçerli parçaya ayarlanır.Oyunu şimdi çalıştırırsanız 4 parça uzunluğundaki yılanın oyun alanında soldan sağa süründüğünü görmelisiniz.

Tamam!
Oyuncu girdisine tepki verecek kodu eklemeden önce girdi bağlantılarını ayarlamanız gerekir.
input klasöründeki game.input_binding dosyasını bulun ve açmak için dosyaya çift tıklayın.
Girdi eşlemesi (input binding) dosyası, gerçek kullanıcı girdilerini (tuşlar, fare hareketleri vb.) girdi almayı talep etmiş betiklere iletilen eylem adlarıyla eşler.
Tamam!
Eşlemeler hazır olduğunda girdi odağını (input focus) almak için snake.script dosyasını açın ve init() işlevinin başına aşağıdaki satırı ekleyin:
function init(self)
msg.post(".", "acquire_input_focus") -- <1>
self.segments = {
{x = 7, y = 24},
{x = 8, y = 24},
{x = 9, y = 24},
{x = 10, y = 24}
}
self.dir = {x = 1, y = 0}
self.speed = 7.0
self.time = 0
end
Eklenen satır:
Ardından on_input işlevini bulun ve aşağıdaki kodu yazın:
function on_input(self, action_id, action)
if action_id == hash("up") and action.pressed then -- <1>
self.dir.x = 0 -- <2>
self.dir.y = 1
elseif action_id == hash("down") and action.pressed then
self.dir.x = 0
self.dir.y = -1
elseif action_id == hash("left") and action.pressed then
self.dir.x = -1
self.dir.y = 0
elseif action_id == hash("right") and action.pressed then
self.dir.x = 1
self.dir.y = 0
end
end
Bu if...elseif... dalları şunları yapar:
action tablosunun pressed alanı true değerindeyse (oyuncu tuşa basmışsa):Oyunu yeniden çalıştırın ve yılanı yönlendirebildiğinizi kontrol edin.
Tamam!
İki tuşa aynı anda basarsanız her basış için bir tane olmak üzere on_input() işlevinin iki kez çağrılacağına dikkat edin. Yukarıdaki kodda, sonraki on_input() çağrıları self.dir içindeki değerlerin üzerine yazacağından yalnızca en son çağrı yılanın yönünü etkiler.
Ayrıca yılan sola giderken right tuşuna basarsanız yılanın kendi üzerine döneceğine dikkat edin. Bu sorunun görünüşte bariz çözümü, on_input() içindeki if koşullarına ek bir koşul koymaktır:
if action_id == hash("up") and self.dir.y ~= -1 and action.pressed then
...
elseif action_id == hash("down") and self.dir.y ~= 1 and action.pressed then
...
Ancak yılan sola giderken oyuncu bir sonraki hareket adımı gerçekleşmeden önce hızla sırasıyla up ve right tuşlarına basarsa yalnızca right tuşuna basılması etkili olur ve yılan kendi üzerine hareket eder. Yukarıda gösterilen if ifadelerine koşullar eklendiğinde girdi yok sayılır. Bu iyi değil!
Bu sorunu doğru biçimde çözmek için girdiyi bir kuyrukta saklayıp yılan hareket ettikçe bu kuyruktan girdileri almak gerekir:
function init(self)
msg.post(".", "acquire_input_focus")
self.segments = {
{x = 7, y = 24},
{x = 8, y = 24},
{x = 9, y = 24},
{x = 10, y = 24}
}
self.dir = {x = 1, y = 0}
self.speed = 7.0
self.time = 0
self.dirqueue = {} -- <1>
end
Bu kez:
self.dirqueue değişkenini ekledik.update() işlevine şunları ekleyin:
function update(self, dt)
self.time = self.time + dt
if self.time >= 1.0 / self.speed then
local newdir = table.remove(self.dirqueue, 1) -- <1>
if newdir then
local opposite = newdir.x == -self.dir.x or newdir.y == -self.dir.y -- <2>
if not opposite then
self.dir = newdir -- <3>
end
end
local head = self.segments[#self.segments]
local newhead = {x = head.x + self.dir.x, y = head.y + self.dir.y}
table.insert(self.segments, newhead)
local tail = table.remove(self.segments, 1)
tilemap.set_tile("#grid", "layer1", tail.x, tail.y, 0)
for i, s in ipairs(self.segments) do
tilemap.set_tile("#grid", "layer1", s.x, s.y, 2)
end
self.time = 0
end
end
newdir null değilse) newdir yönünün self.dir yönünün tersi olup olmadığını kontrol edin.Ayrıca on_input işlevini, bunun yerine geçerli girdiyi kuyrukta saklayacak şekilde değiştirin:
function on_input(self, action_id, action)
if action_id == hash("up") and action.pressed then
table.insert(self.dirqueue, {x = 0, y = 1}) -- <1>
elseif action_id == hash("down") and action.pressed then
table.insert(self.dirqueue, {x = 0, y = -1})
elseif action_id == hash("left") and action.pressed then
table.insert(self.dirqueue, {x = -1, y = 0})
elseif action_id == hash("right") and action.pressed then
table.insert(self.dirqueue, {x = 1, y = 0})
end
end
self.dir değerini doğrudan ayarlamak yerine girdi yönünü yön kuyruğuna ekleyin.Oyunu başlatın ve beklendiği gibi oynandığını kontrol edin.
Tamam!
Yılanın uzaması ve hızlanması için haritada yiyeceğe ihtiyacı var. Bunu ekleyelim!
init() işlevinin üstüne yeni bir işlev ekleyin:
local function put_food(self) -- <1>
self.food = {x = math.random(2, 47), y = math.random(2, 47)} -- <2>
tilemap.set_tile("#grid", "layer1", self.food.x, self.food.y, 3) -- <3>
end
Bu işlevde şunları yapıyoruz:
put_food() adlı yeni bir işlev tanımlıyoruz.self.food adlı değişkende saklıyoruz.Ardından bu işlevi init() işlevinin sonunda çağırın:
function init(self)
msg.post(".", "acquire_input_focus")
self.segments = {
{x = 7, y = 24},
{x = 8, y = 24},
{x = 9, y = 24},
{x = 10, y = 24}
}
self.dir = {x = 1, y = 0}
self.dirqueue = {}
self.speed = 7.0
self.time = 0
math.randomseed(socket.gettime()) -- <1>
put_food(self) -- <2>
end
math.random() ile rastgele değerler almaya başlamadan önce rastgelelik tohumunu ayarlayın; aksi takdirde aynı rastgele değer dizisi üretilir. Bu tohum yalnızca bir kez ayarlanmalıdır.put_food() işlevini çağırın.Tamam!
Artık yılanın bir şeye çarpıp çarpmadığını algılamak için karo haritasında yılanın gideceği yerde ne olduğuna bakıp buna tepki vermek yeterli.
Yılanın hayatta olup olmadığını takip eden bir değişken ekleyin:
function init(self)
msg.post(".", "acquire_input_focus")
self.segments = {
{x = 7, y = 24},
{x = 8, y = 24},
{x = 9, y = 24},
{x = 10, y = 24}
}
self.dir = {x = 1, y = 0}
self.dirqueue = {}
self.speed = 7.0
self.time = 0
self.alive = true -- <1>
math.randomseed(socket.gettime())
put_food(self)
end
Ardından duvar/engel ve yiyecekle çarpışmayı denetleyen mantığı ekleyin:
function update(self, dt)
self.time = self.time + dt
if self.time >= 1.0 / self.speed and self.alive then -- <1>
local newdir = table.remove(self.dirqueue, 1)
if newdir then
local opposite = newdir.x == -self.dir.x or newdir.y == -self.dir.y
if not opposite then
self.dir = newdir
end
end
local head = self.segments[#self.segments]
local newhead = {x = head.x + self.dir.x, y = head.y + self.dir.y}
table.insert(self.segments, newhead)
local tile = tilemap.get_tile("#grid", "layer1", newhead.x, newhead.y) -- <2>
if tile == 2 or tile == 4 then
self.alive = false -- <3>
elseif tile == 3 then
self.speed = self.speed + 1 -- <4>
put_food(self)
else
local tail = table.remove(self.segments, 1) -- <5>
tilemap.set_tile("#grid", "layer1", tail.x, tail.y, 1)
end
for i, s in ipairs(self.segments) do
tilemap.set_tile("#grid", "layer1", s.x, s.y, 2)
end
self.time = 0
end
end
Şimdi oyunu deneyin ve oynanışın düzgün olduğundan emin olun!
Öğretici burada sona eriyor; ancak oyun üzerinde denemeler yapmaya devam edin ve aşağıdaki alıştırmalardan bazılarını tamamlayın!
Tamam!
Başvurmanız için betiğin tüm kodu aşağıdadır:
local function put_food(self)
self.food = {x = math.random(2, 47), y = math.random(2, 47)}
tilemap.set_tile("#grid", "layer1", self.food.x, self.food.y, 3)
end
function init(self)
msg.post(".", "acquire_input_focus")
self.segments = {
{x = 7, y = 24},
{x = 8, y = 24},
{x = 9, y = 24},
{x = 10, y = 24}
}
self.dir = {x = 1, y = 0}
self.dirqueue = {}
self.speed = 7.0
self.time = 0
self.alive = true
math.randomseed(socket.gettime())
put_food(self)
end
function update(self, dt)
self.time = self.time + dt
if self.time >= 1.0 / self.speed and self.alive then
local newdir = table.remove(self.dirqueue, 1)
if newdir then
local opposite = newdir.x == -self.dir.x or newdir.y == -self.dir.y
if not opposite then
self.dir = newdir
end
end
local head = self.segments[#self.segments]
local newhead = {x = head.x + self.dir.x, y = head.y + self.dir.y}
table.insert(self.segments, newhead)
local tile = tilemap.get_tile("#grid", "layer1", newhead.x, newhead.y)
if tile == 2 or tile == 4 then
self.alive = false
elseif tile == 3 then
self.speed = self.speed + 1
put_food(self)
else
local tail = table.remove(self.segments, 1)
tilemap.set_tile("#grid", "layer1", tail.x, tail.y, 1)
end
for i, s in ipairs(self.segments) do
tilemap.set_tile("#grid", "layer1", s.x, s.y, 2)
end
self.time = 0
end
end
function on_input(self, action_id, action)
if action_id == hash("up") and action.pressed then
table.insert(self.dirqueue, {x = 0, y = 1})
elseif action_id == hash("down") and action.pressed then
table.insert(self.dirqueue, {x = 0, y = -1})
elseif action_id == hash("left") and action.pressed then
table.insert(self.dirqueue, {x = -1, y = 0})
elseif action_id == hash("right") and action.pressed then
table.insert(self.dirqueue, {x = 1, y = 0})
end
end
Şu iyileştirmeleri uygulamayı denemek iyi bir alıştırmadır: