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.
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
Penjelasan:
- Table
rock
danplum
dilove.load
akan menggambaran (presentasi) objek batu dan plum untuk berinteraksi dan dirender dalam proses laifsaikel. - Table
rock
danplum
punya attribut yang sama dengan Nyo sepertiimage
untuk gambar lalupos_x
danpos_y
untuk posisi mereka didalam window.
Tampilanya seperti ini:
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
-- 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
-- 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
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
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 kedalammain.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
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
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:
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
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):
Penjelasan
- Sisi Kiri Graybox punya posisi yang lebih kecil dari Sisi kanan pink box.
- Sisi Atas Graybox punya posisi yang lebih kecil dari Sisi Bawah Pink Box.
- Sisi Bawah Graybox punya posisi yang lebih kecil dari Sisi Atas Pink Box.
- Sisi Kanan Graybox punya posisi yang lebih kecil dari Sisi Kiri Pink box.
- Aturan kasus tumburan seperti ini valid baik Pink box yang menumbuk Graybox ataupun sebaliknya. Hukumnya asosiatif.
- 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.
- 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
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
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
Hasilnya seperti ini:
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
Penjelasan:
- Fungsi laifsaikel ini kita tambahkan parameter
rock
untuk nerima argument objekRock
, 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, jikafalse
nilainya maka ga diupdate supaya posisi lama tetap. - Sebaliknya jika
true
maka nilaitemp_nyo
bakal di taro kenyo
Ingat ini kondisi benar bila isTouch bernilaifalse
.
-- main.lua
function love.update(dt)
-- membaharui invokasi laifsaikel update di nyo
-- dengan argumen objek rock
nyo:update(dt,rock)
end
Seperti ini hasilnya:
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
Top comments (0)