evilfactorylabs

Cover image for Tumbukan Antar Objek di Love2D
Mabinogion
Mabinogion

Posted on

Tumbukan Antar Objek di Love2D

Heehee. Gokkingenyo, minna-san.

Kemarin kita sudah mencoba untuk menggerakan gambar di window dengan input kibot. Sekarang kita akan menambahkan objek-objek lain didalam game. Objek disini bukan sekedar gambar tapi Nyo-nyo bisa berinteraksi denganya seperti bila ada objek batu maka Nyo-nyo akan berhenti ketika menabrkanya ataupun koin yang akan hilang ketika bersentuhan.

Tulisan ini melanjutkan dari Gambar Bergerak dengan Kibot di Love2D, jadi untuk mengikuti kode ini perlu menyambung kode dari tulisan itu.

Tumbukan

Di dunia nyata dan juga isekai, benda punya massa dan momentum. Ketika benda bertabrakan satu dengan lain mereka akan memiliki reaksi entah itu benda satunya bergerak karena momentum yang lebih besar, tidak berpindah karena benda yang lebih beratbahkan yang paling ekstrim kedua benda tersebut pecah karena gaya inersia. Dalam fisika dikenal dengan tumbukan.

Dalam game, tumbukan (collision) memiliki peran untuk suatu objek berinteraksi dengan yang lain, bisa sebagai aksi fisik seperti terpental, aksi interaksi misalnya bicara dengan NPC atau ngambil item. Cara kerja tumbukan di window game kita perlu mengetahui apakah dua buah objek ruangnya saling overlap (tumpang tindih). Jika iya maka kedua objek saling bersentuhan.

Kata tumbukan berganti bersentuhan mulai dari sini, sebab kita mengabaikan masa dan momentum tumbukan. Fokusnya hanya posisi overlap dalam matriks window untuk tahu apakah Nyo menyentuh (menumbuk) sesuatu ?

Batu dan Plum

Disini kita memiliki objek game : batu dan plum. Batu ini kalau ditabrak Nyo tidak bisa gerak dan plum kalau ditabrak dia ngilang lalu nambah poin.

gambar rock

gambar plum

Silahkan unduh dan simpan gambar dengan nama berikut : batu (rock.png) dan plum (plum.png). Lalu sesuaikan kode di laifsaikel love.load dan love.draw:

function love.load()
  nyo = {}
  nyo.image = love.graphics.newImage("nyo-nyo.png")
  nyo.pos_x = 10
  nyo.pos_y = 10
  nyo.width = nyo.image:getWidth()
  nyo.height = nyo.image:getHeight()
  nyo.speed = 300

  plum = {}
  plum.image = love.graphics.newImage("plum.png")
  plum.width = plum.image:getWidth()
  plum.height = plum.image:getHeight()
  plum.pos_x = 120
  plum.pos_y = 150

  rock = {}
  rock.image = love.graphics.newImage("rock.png")
  rock.width = rock.image:getWidth()
  rock.height = rock.image:getHeight()
  rock.pos_x = 70
  rock.pos_y = 100
end

function love.draw()
  love.graphics.draw(nyo.image, nyo.pos_x,nyo.pos_y)

  -- menampilkan batu dan plum di window
  love.graphics.draw(plum.image, plum.pos_x,plum.pos_y)
  love.graphics.draw(rock.image, rock.pos_x,rock.pos_y)

  love.graphics.print("posisi Nyo-Nyo, x: ".. nyo.pos_x .." y: ".. nyo.pos_y, 0, 0)
end
Enter fullscreen mode Exit fullscreen mode

Penjelasan:

  • Table rock dan plum di love.load akan menggambaran (presentasi) objek batu dan plum untuk berinteraksi dan dirender dalam proses laifsaikel.
  • Table rock dan plum punya attribut yang sama dengan Nyo seperti image untuk gambar lalu pos_x dan pos_y untuk posisi mereka didalam window.

Tampilanya seperti ini:

nyo bersama batu dan plum

Refactor

Pastikan sudah menyesuaikan kode sebelumnya. Kita akan memecah komponen ke beberapa file. Alasanya agar main.lua tidak terlalu panjang untuk dibaca dan lokalisasi attribut supaya cepat kita menangani laifsaikel objek yang di inginkan.

Kita akan membuat tiga file yang menyatakan objek game yakni nyo.lua, plum.lua dan rock.lua di dalam folder yang sama dengan main.lua, isinya seperti ini:

-- nyo.lua

-- laifsaikel load
nyo = {}
nyo.image = love.graphics.newImage("nyo-nyo.png")
nyo.pos_x = 10
nyo.pos_y = 10
nyo.width = nyo.image:getWidth()
nyo.height = nyo.image:getHeight()
nyo.speed = 300

-- laifsaikel update
nyo.window_x_limit = 800
nyo.window_y_limit = 600
nyo.update = function(nyo,dt)

  local accel = math.abs(nyo.speed * dt)
  if love.keyboard.isDown("left") then
    nyo.pos_x = clamp(nyo.pos_x - accel, 0, nyo.window_x_limit)
  elseif love.keyboard.isDown("right") then
    nyo.pos_x = clamp(nyo.pos_x + accel, 0, nyo.window_x_limit - nyo.image:getWidth())
  elseif love.keyboard.isDown("up") then
    nyo.pos_y = clamp(nyo.pos_y - accel, 0, nyo.window_y_limit)
  elseif love.keyboard.isDown("down") then
    nyo.pos_y = clamp(nyo.pos_y + accel, 0, nyo.window_y_limit - nyo.image:getHeight())
  end 

end

 -- laifsaikel update
nyo.draw = function(nyo)
  love.graphics.draw(nyo.image, nyo.pos_x,nyo.pos_y)
end

nyo.drawDebug = function(nyo)
   love.graphics.print("posisi Nyo-Nyo, x: ".. nyo.pos_x .." y: ".. nyo.pos_y, 0, 0)
end

return nyo
Enter fullscreen mode Exit fullscreen mode
-- plum.lua

plum = {}
plum.image = love.graphics.newImage("plum.png")
plum.width = plum.image:getWidth()
plum.height = plum.image:getHeight()
plum.pos_x = 120
plum.pos_y = 150

plum.draw = function(plum)
    love.graphics.draw(plum.image, plum.pos_x,plum.pos_y)
end

return plum
Enter fullscreen mode Exit fullscreen mode
-- rock.lua

rock = {}
rock.image = love.graphics.newImage("rock.png")
rock.width = plum.image:getWidth()
rock.height = plum.image:getHeight()
rock.pos_x = 70
rock.pos_y = 100

rock.draw = function(rock)
  love.graphics.draw(rock.image, rock.pos_x,rock.pos_y)
end

return rock
Enter fullscreen mode Exit fullscreen mode

Setelah membuat file tersebut, sesuaikan kode di main.lua (cukup bagian fungsi laifsaikel : love.load, love.update dan love.draw)

-- main.lua
function love.load()
  nyo = require 'nyo'
  rock = require 'rock'
  plum = require 'plum'
end

function love.update(dt)
  nyo:update(dt)
end

function love.draw()
  nyo:draw()
  rock:draw()
  plum:draw()
end
Enter fullscreen mode Exit fullscreen mode

Penjelasan:

  • Setiap file objek game, menyimpan attribut dan fungsi laifsaikel dari game.
  • require 'nyo' dsb, ini cara kita mengimpor file-file komponen yang kita pecah kedalam main.lua agar bisa dipanggil dan ditampilkan di window.

Saya baru tau kalau fungsi laifsaikel ini dikenal juga dengan callback -- fungsi yang akan dipanggil fungsi lain, jadi sekiranya teman-teman dari bahasa lain bisa menyesuaikan.

Kenapa tiba-tiba refactor ditengah sih? Selingan berkenalan dengan mengrapihin kode.

AABB

Ilustrasi overlap dengan objek nyo dan rock

AABB ini binary fuzzing ato gimna?: axis-aligned bounding-box , adalah teknik untuk mendeteksi overlap/sentuhan objek berdasarkan sumbu posisi x dan y dalam format kotak. Jadi objek seperti plum, rock dan nyo akan diabstrak menjadi bentuk posisi kotak untuk nentuin bersentuhan atau enggak.

Ruang Dimensi

Deskripsi anatomi tumbukan pada objek nyo

Saya jelaskan dengan sebuah kasus, pada sebuah frame titik awal nyo berada di koordinat (50,120) dengan besar objek yakni 48 x 48, lalu tentukanlah Sisi atas, sisi kiri, sisi kanan dan sisi bawah ?

(50,120) mengambarkan sumbu x = 50, sumbu y = 120.

Mari kita kerjakan bersama, kowalski analysis. Sisi atas dan sisi kiri (sudut kiri atas) cukup jelas menjadi titik awal posisi gambar di window, (50,120). Bila kita lihat sisi kanan, tidak berubah posisi ke atas ataupun kebawah jadi sumbu Y tetap 120.

Namun, ia bergerak menjauhi titik awal ke kanan sebanyak lebar Nyo (sumbu X) jadi kita perlu menambah dengan lebar Nyo yakni 48. Sehingga sumbu X di sisi kanan menjadi 50 + 48, 98. Sisi kanan = (98,120).

Sisi bawah, serupa dengan sisi kanan, hanya saja yang berubah tingginya yang menjauh titik awal. Sehingga sumbu Y yang berubah ditambah dengan tinggi Nyo yakni 48, maka didapat 120 + 48 = 168. Sisi bawah bawah = (50,168).

Dari sini kita bisa mendapatkan Sisi ujung dengan menambahkan titik awal dengan ukuran resolusinya Nyo (48,48) menjadi (98,168).

Kira-kra seperti ini rupanya:

Hasil perhitungan AABB

Bila dirumuskan akan seperti ini:

  • Sisi Kanan = Titik awal + Luas Objek
  • Sisi Bawah = Titik awal + Tinggi Objek
  • Sisi Ujung = Titik awal + Tinggi Objek + Luas Objek
function isTouch(a,b)
  -- top dan left itu titik awal hanya beda sumbu
  local a_left = a.pos_x
  local a_right = a.pos_x + a.width
  local a_top = a.pos_y
  local a_bottom = a.pos_y + a.height
  -- titik ujung tidak dipakai untuk kasus ini

  local b_left = b.pos_x
  local b_right = b.pos_x + b.width
  local b_top = b.pos_y
  local b_bottom = b.pos_y + b.height
end
Enter fullscreen mode Exit fullscreen mode

Disini kita bisa menggambarkan area tabarkan kita, baru dari sini kita mencoba berhitung.

Logika sentuhan

Objek dalam game pasti bersentuhan dengan sisi yang berlawanan misalnya sisi kanan Nyo pasti bertabrakan dengan sisi kiri Rock, dan seterusnya, karena mereka kotak dua dimensi seperti waifu anime pada umumnya.

Setelah kita mengetahui batas posisi setiap sisi. Kita akan fokus pada titik awal, sisi kanan dan bawah untuk mendeteksi sentuhan dengan membandingkanya.

Disini kita mencoba mendeteksi tumbukan dari kasus irisan kotak abu-abu (Graybox) dan kotak pink (Pinkbox):

Kumpulan Kasus irisan graybox dan pinkbox

Penjelasan

  1. Sisi Kiri Graybox punya posisi yang lebih kecil dari Sisi kanan pink box.
  2. Sisi Atas Graybox punya posisi yang lebih kecil dari Sisi Bawah Pink Box.
  3. Sisi Bawah Graybox punya posisi yang lebih kecil dari Sisi Atas Pink Box.
  4. Sisi Kanan Graybox punya posisi yang lebih kecil dari Sisi Kiri Pink box.
  5. Aturan kasus tumburan seperti ini valid baik Pink box yang menumbuk Graybox ataupun sebaliknya. Hukumnya asosiatif.
  6. Overlap terjadi bila ke sisi dari ke dua beririsan, misalnya Sisi Kiri Graybox dan Sisi bawah Graybox beririsan dengan Sisi kanan dan Sisi atas Pinkbox.
  7. Kenapa harus beririsan dua sumbu untuk objek bertabrakan? karena objek berada di dua dimensi, jika ada tiga dimensi maka ada tiga irisan, n-dimensi = n-irisan.

Kalau dalam bentuk kodingan seperti ini:

function isTouch(a,b)
  -- top dan left itu titik awal hanya beda sumbu
  local a_left = a.pos_x
  local a_right = a.pos_x + a.width
  local a_top = a.pos_y
  local a_bottom = a.pos_y + a.height
  -- titik ujung tidak dipakai untuk kasus ini

  local b_left = b.pos_x
  local b_right = b.pos_x + b.width
  local b_top = b.pos_y
  local b_bottom = b.pos_y + b.height

  if a_top < b_bottom and 
     a_bottom > b_top and
     a_left < b_right and
     a_right > b_left then
       return true
    else
      return false
  end
end
Enter fullscreen mode Exit fullscreen mode

Attaching debugger

Supaya kita tau apakah pendeteksian tumbukan kita berhasil. Kita buat objek Rock bereaksi ketika bertumbukan dengan Nyo dengan menampilkan teks kalo ia nabrak.

Tambahkan kode ini di file yang sudah dibuat, letakan dibagian bawah sebelum return.

-- rock.lua

rock.drawIsTouchNyo = function(rock,nyo)
  if isTouch(rock,nyo) then
    love.graphics.print("Nyo nyentuh batu", 10,20)
  end
end

-- return
Enter fullscreen mode Exit fullscreen mode

Lalu kita sesuai laifsaikel love.draw di main.lua:

function love.draw()
  nyo:draw()
  rock:draw()
  rock:drawIsTouchNyo(nyo)
  plum:draw()

  love.graphics.print("posisi Nyo-Nyo, x: ".. nyo.pos_x .." y: ".. nyo.pos_y, 0, 0)
end
Enter fullscreen mode Exit fullscreen mode

Hasilnya seperti ini:

Nyo menyentuh batu dan menampilkan tulisan "Nyo nyetuh batu"

Supaya Nyo tidak nyumput di balik batu ?

Tujuan kita biar di bisa deteksi tumbukan supaya dia berhenti ketika mentok batu alias reaksi ketika Nyo numbur Rock. Maka, dari itu kita perlu modifikasi sedikit laifsaikel update di nyo.lua dan main.lua.

-- nyo.lua
nyo.update = function(nyo,dt,rock)

  local accel = math.abs(nyo.speed * dt)
  -- table di lua sifatnya reference,
  -- jadi perlu di copy manual nilainya.
  local temp_nyo = {
    pos_x = nyo.pos_x,
    pos_y = nyo.pos_y,
    width = nyo.width,
    height = nyo.width,
  }

  if love.keyboard.isDown("left") then
    temp_nyo.pos_x = clamp(nyo.pos_x - accel, 0, nyo.window_x_limit)
  elseif love.keyboard.isDown("right") then
    temp_nyo.pos_x = clamp(nyo.pos_x + accel, 0, nyo.window_x_limit - nyo.image:getWidth())
  elseif love.keyboard.isDown("up") then
    temp_nyo.pos_y = clamp(nyo.pos_y - accel, 0, nyo.window_y_limit)
  elseif love.keyboard.isDown("down") then
    temp_nyo.pos_y = clamp(nyo.pos_y + accel, 0, nyo.window_y_limit - nyo.image:getHeight())
  end 

  -- kalo ga nyentuh batu ya update nilainya
  if not isTouch(temp_nyo,rock) then
    nyo.pos_x = temp_nyo.pos_x
    nyo.pos_y = temp_nyo.pos_y
  end

end
Enter fullscreen mode Exit fullscreen mode

Penjelasan:

  • Fungsi laifsaikel ini kita tambahkan parameter rock untuk nerima argument objek Rock, untuk nanti di cek bertumbuk atau tidak.
  • Disini kita bikin variable temp_nyo untuk menampung nilai perubahan sementara yang akan disesuaikan dengan input kibot.
  • Setelah itu, percabangan not isTouch(temp_nyo,rock) mastiin apakah Nyo bertumbukan dengan Rock, jika false nilainya maka ga diupdate supaya posisi lama tetap.
  • Sebaliknya jika true maka nilai temp_nyo bakal di taro ke nyo Ingat ini kondisi benar bila isTouch bernilai false.
-- main.lua
function love.update(dt)
  -- membaharui invokasi laifsaikel update di nyo
  -- dengan argumen objek rock
  nyo:update(dt,rock)
end
Enter fullscreen mode Exit fullscreen mode

Seperti ini hasilnya:

Nyo sudah mentok tidak bisa nyumput di balik batu lagi

Penutup

Achievement Unlocked: Auch! Bump into you

Kali kita membuat cara mendeteksi tumbukan antar objek sederhana. Setiap objek yang bertumbukan memiliki aksi dan reaksi yang mungkin akan dibahas lebih mendetail di tulisan lain, kita sampai sini cukup sampai bagian aksi bertumbuk dan reaksi tidak nembus batu. Ada banyak aksi-reaksi antar objek yang bisa dieksplorasi, contohnya plum yang akan nanti di bahas lain waktu. Anggap saja dia foreshadowing untuk sekarang.

Sepertinya kode kita semakin besar, kita sekarang punya tiga file! Haduh bagaimana ini supaya kodenya lebih enak dibaca dan invokasi fungsi-fungsi lebih mudah. Mungkin ini akan menjadi tema tulisan berikutnya, kita kenalan dengan konsep entitas komponen. Jadi belajar ilmu software engineer buat ngerapihin kode hahaha

Oh iya selamat nataru , padoru, padoru UwU hahahaha

padoru padoru padoru

referensi lanjutan

Top comments (0)