Membuat game sokoban dengan defold part 5

Halo..

Balik lagi lanjutin tutorial buat game pake defold. Kali ini videonya berbeda dengan tutorial sebelumnya.

Hari ini saya tidak melakukan live coding, tetapi hanya menampilkan code dan apa saja yang berubah dari sebelumnya.

Catatan

Oh iya, artikel ini ada versi video juga. Jika tertarik buat nonton videonya bisa di cek link youtube ini. Dan mungkin artikel dengan video ada perbedaan sedikit, tetapi punya tujuan dan makna yang sama

Perubahan level collection

  1. Membuat 2 Gameobject baru
    Ada 2 gameobject tambahan yang akan di panggil di factory (gameobject-trigger, dan gameobject-box)
  2. Menambahkan factory
    Ada 2 factory tambahan (sekarang menjadi 3 factory), yaitu factory-box, factory-trigger, dan factory-player
  3. Menganti script player_movement menjadi object_movement
    pergantian ini dimaksudkan untuk membuat 1 script saja, sehingga box dan player akan menunggu perintah dari controller untuk melakukan aksi
  4. Menggunakan game-controller.script sebagai controller di dalam game
    disini game-controller dibuat seperti menara controller, yang menugaskan object untuk pindah / atau berubah.

Gameobject trigger

Didalam gameobject trigger sendiri tidak memiliki script. Tetapi didalamnya terdapat component Collision Object yang di atur menjadi trigger dan dan hanya bereaksi saat bersentuhan dengan mask box.

Selain itu juga ada component sprite tapi saya isi dengan frame kosong / tidak bergambar

GameObject Trigger dengan collision component

Gameobject box

Kalian bisa copy paste / duplicate dari gameobject player, lalu ubah spritenya menjadi box, dan ubah juga collision component pada collisionobject, di properties group dan mask

GameObject Box dengan collision component

Game object game-controller

Contoh tower controller

Jadi saya buat game-controller layaknya menara controller yang melakukan perintah untuk melakukan perubahan kepada object-object yang di kontrolnya (dibawah kendali game-controller).

Di dalam game-controller script sendiri juga terdapat logic yang mengatur apakah didepan ada object atau tidak.

code dari game controller adalah seperti berikut :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
local actors = {}
local group_trigger = {}
local gate_locks = {}

local function map_position(x, y)
return vmath.vector3(x*64-32, y*64-32, 0.3)
end

local function setup_map(self)
local our_url = msg.url()
local x, y, w, h = tilemap.get_bounds("map#level1")
for ix = x, x+w-1 do
for iy = y, y+h-1 do
local map_id = tilemap.get_tile("map#level1", "setup", ix, iy)
local map_id_foreground = tilemap.get_tile("map#level1", "foreground", ix, iy)
local p_pos = map_position(ix, iy)
-- setup map
if map_id == 74 then
self.p = factory.create("/factory#factory_player", p_pos, nil, {is_player=true, control_url = our_url})
tilemap.set_tile("map#level1", "setup", ix, iy, 0)
table.insert(actors, self.p)
elseif map_id == 40 then
local varlue_m = {grid_x = ix, grid_y = iy}
factory.create("/factory#factory_trigger", p_pos)
table.insert(group_trigger, varlue_m)
elseif map_id == 41 then
local o = factory.create("/factory#factory_box", p_pos, nil, {control_url = our_url})
tilemap.set_tile("map#level1", "setup", ix, iy, 0)
table.insert(actors, o)
end

-- collect foreground gate
if map_id_foreground == 11 then
local value_foreground = {grid_x = ix, grid_y = iy}
table.insert(gate_locks, value_foreground)
end
end
end
end

local function check_tile_foreground(self, x, y)
return tilemap.get_tile("map#level1", "foreground", x, y)
end

local function gameobject_current_grid(self, gameobject)
local pos = go.get_position(gameobject)
local grid = {x = math.floor((pos.x - 32) / 64)+1, y = math.floor((pos.y - 32) / 64)+1}
return grid
end

local function is_not_wall(self, grid_x, grid_y)
local tilemap_id = check_tile_foreground(self, grid_x, grid_y)
local is_wall = not (tilemap_id == 8 or tilemap_id == 11)
return is_wall
end

local function object_on_tiles(self, x, y)
for key, value in ipairs(actors) do
local o = actors[key]
local grid_o = gameobject_current_grid(self, o)
if grid_o.x == x and grid_o.y == y then
return o
end
end
return nil
end

local function input_vector(self, action_id)
local vec3_move = vmath.vector3()
if action_id == hash("left") then
vec3_move.x = -1
elseif action_id == hash("right") then
vec3_move.x = 1
elseif action_id == hash("up") then
vec3_move.y = 1
elseif action_id == hash("down") then
vec3_move.y = -1
end
return vec3_move
end

local function grid_to_string(self, grid)
local string = ""
if grid.x == -1 and grid.y == 0 then
string = "left"
elseif grid.x == 1 and grid.y == 0 then
string = "right"
elseif grid.x == 0 and grid.y == 1 then
string = "up"
elseif grid.x == 0 and grid.y == -1 then
string = "down"
end
return string
end

local function check_all_trigger_locked(self)
for key, value in ipairs(group_trigger) do
local o = object_on_tiles(self, value.grid_x, value.grid_y)
if o == nil then
return true
end
end
return false
end

local function open_all_gate(self, lock)
local gate = lock and 19 or 11
for key, value in ipairs(gate_locks) do
tilemap.set_tile("map#level1", "foreground", value.grid_x, value.grid_y, gate)
end
end


local function simulate_movement(self, add_x, add_y)
local p = self.p
local grid_p = gameobject_current_grid(self, p)
local o = object_on_tiles(self, grid_p.x+add_x, grid_p.y+add_y)
local string_move = "move_" .. grid_to_string(self, vmath.vector3(add_x, add_y, 0))
if o then
local grid_o = gameobject_current_grid(self, o)
local o_loop2 = object_on_tiles(self, grid_o.x+add_x, grid_o.y+add_y)
if(is_not_wall(self, grid_o.x+add_x, grid_o.y+add_y) and o_loop2 == nil) then
msg.post(o, string_move)
msg.post(p, string_move)
end
elseif is_not_wall(self, grid_p.x+add_x, grid_p.y+add_y) then
msg.post(p, string_move)
end
end

local function check_winning(self)
local grid_p = gameobject_current_grid(self, self.p)
local tile_id = check_tile_foreground(self, grid_p.x, grid_p.y)
if tile_id == 65 then
msg.post("ui#ingame", "winning")
msg.post(".", "release_input_focus")
end
end

function init(self)
msg.post(".", "acquire_input_focus")
setup_map(self)
end

function on_input(self, action_id, action)
if(action.pressed)then
if(action_id == hash("left") or action_id == hash("right") or action_id == hash("up") or action_id == hash("down") )then
local vec3 = input_vector(self, action_id)
simulate_movement(self, vec3.x, vec3.y)
end
timer.delay(0.05, false, check_winning)
end
end

function on_message(self, message_id, message, sender)
if message_id == hash("open_gate") then
local will_open = not check_all_trigger_locked(self)
if will_open then
open_all_gate(self, true)
end
elseif message_id == hash("close_gate") then
open_all_gate(self, false)
end
end

Code dari object_movement

Seperti yang sudah saya jelaskan sebelumnya. Object movement disini hanya menerima pesan dari controller saat berpindah, dan mengirim pesan ke game-controller saat bersingungan dengan object trigger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
local function current_grid(self)
local pos = go.get_position(".")
local grid = {x = math.floor((pos.x - 32) / 64)+1, y = math.floor((pos.y - 32) / 64)+1}
return grid
end

local function move(self, string_message)
local pos = go.get_position(".")
if string_message == hash("move_left") then
pos.x = pos.x-64
elseif string_message == hash("move_right") then
pos.x = pos.x+64
elseif string_message == hash("move_up") then
pos.y = pos.y+64
elseif string_message == hash("move_down")then
pos.y = pos.y-64
end
go.set_position(pos)
end


go.property("is_player", false)
go.property("control_url", msg.url())

function on_message(self, message_id, message, sender)
-- move
if message_id == hash("move_left") or message_id == hash("move_right") or message_id == hash("move_up") or message_id == hash("move_down") then
move(self, message_id)
end

if message_id == hash("trigger_response")then
local grid = current_grid(self)
if message.enter and message.other_group == hash("box_place") and message.own_group == hash("box") then
msg.post(self.control_url, "open_gate", {grid_x = grid.x, grid_y = grid.y})
elseif message.enter == false and message.other_group == hash("box_place") and message.own_group == hash("box") then
msg.post(self.control_url, "close_gate", {grid_x = grid.x, grid_y = grid.y})
end
end
end

Penutup

Disini saya membuat gameobject dengan component collision object yaitu physics 2d (defold yang menggunakan box-2d sebagai physics engine), dan sebetulnya cakupan disini cukup luas. Mungkin lain kali saya akan membahas tentang physics engine defold di artikel sendiri.

Selain itu saya juga membuat gamenya menjadi 1 controller, supaya memudahkan saya dalam mengatur setiap permainannya.

Dan melakuakn pangilan perintah dengan mengirim pesan, ke object satu dengan object yang lain untuk berkomunikasi. Ini juga merupakan core concept dari defold itu sendiri.

Jika ada kesulitan, ataupun masalah. Kalian bisa bertanya disini ataupun di dalam komentar video youtube