🎮
【PICO-8】PICO-8でオブジェクト指向プログラミングっぽいブロック崩しを作る
はじめに
PICO-8という、ミニマムなゲームを制作できるツールを使って、オブジェクト指向を利用したブロック崩しを作ってみました。
その際のナレッジをこちらの記事にまとめます。
PICO-8概要
- 128x128の解像度
- 色数12種類のみ
- コードにもトークンという単位で、文字数制限がある
などの制約で、ミニマムなゲームを制作できる
PIC-8におけるオブジェクト指向
結論ですが、トークンという文字数制限があるため、オブジェクト指向が必ずしもベストプラクティスとは言えないのがPICO-8の面白いところです。
利便性、可読性が向上するメリットがありますが、クラス(メタテーブル)の定義などにトークンを消費してしまいます。
celesteなどの中~大規模ゲームは、トークン制約によって使いたくても使えない場合があります。
今回のようなブロック崩しのような小規模ゲームで利用できるかもしれません。
メタテーブル
メタテーブルという機能を利用することで、本格的なオブジェクト指向なコーディングをすることができます。
基底クラス
-- Base Object
objects = {}
Object = {
x, y, clr
}
function Object:new(o, x, y, clr)
o = o or {}
setmetatable(o, { __index = self })
o.x = x or 0
o.y = y or 0
o.clr = clr or colors.white
return o
end
function Object:update()
end
function Object:draw()
end
-- Stage Object
StageObject = Object:new()
function StageObject:new(o, x, y, w, h, clr)
o = Object:new(o, x, y, clr)
setmetatable(o, { __index = self })
o.w = w or 1
o.h = h or 1
return o
end
function StageObject:on_hit(ball)
sfx(0)
...
end
- クラスを定義
- コンストラクタを定義
-
setmetatable
で自身を継承させる
1. クラスを定義
-
クラス名 = {}
で定義 - 使用するプロパティを定義
(VSCodeで編集したので、先頭大文字になっていますが、PICO-8の場合すべて小文字にするべきだそうです。c_object
のような命名が望ましいかもしれません。)
Object = {
x, y, clr
}
2. コンストラクタを定義
-
クラス名:new()
でコンストラクタの定義 -
o
などの変数で、オブジェクトを参照する-
o = o or {}
:引数から受け取ったオブジェクトがnilだった場合に、テーブルを新規作成
-
-
o
に値を設定、o
を返す
function Object:new(o, x, y, clr)
o = o or {}
setmetatable(o, { __index = self })
o.x = x or 0
o.y = y or 0
o.clr = clr or colors.white
return o
end
setmetatable
)
3. 継承させる(-
setmetatable(o, {__index = self})
で、__index
に自身を参照させることで自身の情報を継承させる
setmetatable(o, { __index = self })
仕組み(AI回答)
-
obj.some_method()
の呼び出し -
obj
にsome_method()
が存在しない場合、obj
のメタテーブル__index
を探す -
__index
がObject
を指しているので、Object
に定義されたsome_method()
を探し、実行
派生クラス
-- block
Block = StageObject:new()
function Block:new(o, x, y, w, h, clr)
o = StageObject:new(o, x, y, w, h, clr)
setmetatable(o, { __index = self })
return o
end
function Block:on_hit(ball)
-- override
StageObject.on_hit(self, ball)
sfx(1)
-- speed up ball
ball:speedup()
-- remove block
del(objects, self)
del(blocks, self)
-- check clear
check_clear()
end
1. クラスの継承
Block = StageObject:new()
2. コンストラクタの定義
- インスタンス時、oに対してオブジェクトを指定し、親クラスのnew()に指定する
- setmetatable()で継承
- 派生クラス側にもこれがないと、正しくオーバーライドされない
function Block:new(o, x, y, w, h, clr)
o = StageObject:new(o, x, y, w, h, clr)
setmetatable(o, { __index = self })
return o
end
3. メソッドのオーバーライド
- 親クラスに定義されたメソッドと同名のメソッドを定義する
- 親クラスのメソッドを呼び出すことで、オーバーライド可能
function Block:on_hit(ball)
-- override
StageObject.on_hit(self, ball)
sfx(1)
インスタンス
function stage_init()
blocks = {}
local block_count_x = 6
local block_count_y = 4
-- generate blocks
for by = 0, block_count_y - 1 do
for bx = 0, block_count_x - 1 do
local block_w = 16
local block_h = 6
local spacing = 2
local offset_x = 10
local offset_y = 8
local block = Block:new(
self,
offset_x + bx * (block_w + spacing),
offset_y + by * (block_h + spacing),
block_w,
block_h,
colors.peach
)
add(blocks, block)
add(objects, block)
end
end
end
1. インスタンス化
-
クラス名:new()
でインスタンス化
local block = Block:new(
self,
offset_x + bx * (block_w + spacing),
offset_y + by * (block_h + spacing),
block_w,
block_h,
colors.peach
)
また、基底クラスの配列を作成して、_update()
などで対象オブジェクトをまとめて呼び出して更新したりできます。
function _update()
if current_game_state == game_states.playing then
-- update objects
for obj in all(objects) do
obj:update()
end
end
メタメソッド (メソッドのオーバーライド)
setmetatableで継承を実装することができますが、特定のテーブルのto_string()、演算子のメソッドなどをオーバーライドすることが可能です。
- オーバーライドする関数を格納するテーブルを定義
-
__xxx = function()
でオーバーライド - setmetatable()に対象オブジェクトと、定義したテーブルを指定
- オーバーライド対象メソッドに、対象オブジェクトを指定して呼び出すことで、オーバーライドした内容が反映されて呼び出される
local obj = {}
local mt = {
__tostring = function(t)
return "This is a custom object."
end
}
setmetatable(obj, mt)
print(tostr(obj)) -- -> This is a custom object.
テーブル
先述の通り、メタテーブルはトークンを消費したり、ややクセがありますが、
テーブルを使用することで簡易的にオブジェクト指向を利用することができます。
ball = {
x = 64,
y = 24,
size = 2,
xdir = 1,
ydir = 1,
init = function(x, y, size, xdir, ydir)
self.x = x
self.y = y
self.size = size
self.xdir = xdir
self.ydir = ydir
end,
}
さいごに
制約上、ゴリゴリにオブジェクト指向を取り入れることは推奨されないかもしれません。
テーブルによる簡易的なオブジェクトの定義が手っ取り早いかも。
Discussion