2011年5月24日 星期二

Lua OO Library - bmclass 簡介

前言

一開始是為了練習 Lua 裡面的 metatable 而開始實作 OO 系統的,而實作過程當然也是一邊參考其它 Lua OO Library 的 source code 以及 demo,一邊進行開發。但參考這些 Library 時,有些功能較多的範例不是很好懂(source code 更難懂);而較簡單的我又認為不夠用,因此便朝著自己想像的功能進行開發,最後便是 bmclass 這個 Library 的誕生。

本篇的兩個範例,一個是最基本的使用方式,一個是針對主要功能多重繼承的範例。剩下的等未來有空時再慢慢針對 bmclass 做些解說與介紹。

 

Library 下載

bmclass.lua

 

使用範例

範例一(最基本的使用方式)

 
class = require("bmclass")
 
a = class("a")
 
function a:init()
    print("for init")
end
 
function a:hello()
    print(tostring(self) .. " : Hello!!!")
end
 
b = a("b") -- output "for init"
b:hello() -- output "Object b : Hello!!!"

 

範例二(多重繼承)

 
class = require("bmclass")
 
a = class("a")
b = class("b")
c = class("c", a, b) -- superclass a and b
 
function a:init() print("a init") end
function b:init() print("b init") end
function c:init()
    c:next(self, "init") -- next to the superclass
    print("c init")
end
 
function a:hello1() print("Hello1") end
function b:hello2() print("Hello2") end
function c:hello3() print("Hello3") end
 
d = c("d")
-- output
-- a init
-- b init
-- c init
 
d:hello1() -- output "Hello1"
d:hello2() -- output "Hello2"
d:hello3() -- output "Hello3"
 

2011年5月18日 星期三

Lua functions in table & syntactic sugar

前言

這篇主要的目的在於講解 Lua 的 functions 在 table 中,可以特殊宣告/呼叫的語法。順帶一提的是,這個特殊語法可以實現 OO 裡 method 的概念。

 

語法糖(syntactic sugar)

程式的世界裡,如果用另一種句法可以更簡潔的編寫、更清晰的表達程式,但完全符合原本的行為,就叫做 syntactic sugar。

用以下的範例來示意 Lua 的 syntactic sugar

呼叫 table 內的 function

-- object.method(self, args)
goodman.speak(goodman, "hi")

syntactic sugar

-- object:method(args)
goodman:speak("hi")

 

這兩種寫法是等價的,只要 function 儲存在 table 內,就可以利用「冒號」,將自身當成第一個參數傳入。

 

實際範例

以下四個範例皆會得到一樣的結果

例子一(宣告/呼叫皆用 syntactic sugar)

goodman = {name = "Good Man"}
 
function goodman:speak(str)
    print("i am " .. self.name)
    print(str)
end
 
goodman:speak("hi")
 
-- output is :
-- i am Good Man
-- hi

例子二(宣告/呼叫皆不用 syntactic sugar)

goodman = {name = "Good Man"}
 
function goodman.speak(self, str)
    print("i am " .. self.name)
    print(str)
end
 
goodman.speak(goodman, "hi")
 
-- output is :
-- i am Good Man
-- hi

例子三(宣告用普通的方式,呼叫用 syntactic sugar)

goodman = {name = "Good Man"}
 
-- 亂定義一個 aaa,再利用 syntactic sugar 傳入值也可以
function goodman.speak(aaa, str)
    print("i am " .. aaa.name)
    print(str)
end
 
goodman:speak("hi")
 
-- output is :
-- i am Good Man
-- hi

例子四(宣告用 syntactic sugar,呼叫用普通的方式)

goodman = {name = "Good Man"}
 
function goodman:speak(str)
    print("i am " .. self.name)
    print(str)
end
 
goodman.speak(goodman, "hi")
 
-- output is :
-- i am Good Man
-- hi

 

註:若用 syntactic sugar 進行宣告,則第一個參數名稱必定為 self。

 

底下這個例子,用普通的方式呼叫 goodman 裡面的 function,但傳入另一個 table - badman(實際應用上不太會出現的例子,只是為了更清楚的解釋)

badman = {name = "Bad Man"}
goodman = {name = "Good Man"}
 
-- 這邊用 goodman 的 syntactic sugar 宣告 function
function goodman:speak(str)
    print("i am " .. self.name)
    print(str)
end
 
-- 這邊傳入 badman ...
goodman.speak(badman, "hi")
 
-- output is :
-- i am Bad Man
-- hi

以上的例子可以看到,雖然在goodman.speak function 裡面用的是 self,但其實指到的是 badman 這個 table。

 

參考連結

2011年5月16日 星期一

Lua Metatables 概念與範例

在 Lua 裡,metatables 的存在是非常重要的,它提供了高度靈活性供程式設計師實現較良好的程式架構來進行後續的開發,例如:OO 就是由 metatables 裡面的 __index 來實現。

但對我來說,這概念不是很好懂,而且應用上有點 magic。

在講 metatables 之前,還是要先聊聊 Lua 裡面的 table。table 可以存放各種資料型態(除了 nil),因此一份 table 裡面有五花八門的資料是可能的。但不管是什麼資料,都一定會有一個 key 來做對應,例如:

t = {name = 'goodman', age = 29, 123, 456, 789}
for key, value in pairs(t) do
    print(key, value)
end
-- output is :
-- 1       123
-- 2       456
-- 3       789
-- name    goodman
-- age     29

 

了解 table 裡面的 key 是什麼之後,再來聊聊 metatables。

每一個 table 都可以當成別人的 metatable,因此 metatable 也只是一個普通的 table 而已。在每一個 table 裡,都有一些特殊的 key,不過平時將這些 key 賦值也是沒有任何特殊意義的。但當 table 本身成為別人的 metatable 時,這些特殊的 key 開始影響另一個 table,舉例來說:table A 為 table B 的 metatable,則 table A 裡面的特殊 key 影響到的是 table B 的行為。

每一個特殊 key 對應到某種操作或行為,在 Lua 5.0 Reference Manual 裡稱做 event (其實非常貼切),在這邊拿最常見的 __index 作為例子。

__index 會影響到的行為是:從 table 中取值的動作。(嚴格說起來,應該是取「不存在的值」的動作)

而 metatable 中的 __index 的內容,又會因為型態的不同,而有兩種不同的處理

欲看所有 key 的細節,請參考Lua 5.0 Reference Manual # 2.8

 

這是 metatable.__index 型態為 function 的例子

首先先建立一個 table (等等要拿來當作 metatable 的 table),並在 __index 這個特殊的 key 上填入一個 function。

-- 許多網路範例中,都會取名叫 mt(MetaTable),在這邊也不失流行叫做 mt
mt = {}
 
-- 當取值時,table 會來詢問 metatable 是否有宣告過如何處理,這個範例是一個 function print 因此會到兩個參數 table, key
mt.__index = print

 

接著建立 table t,並將其 metatable 設置為 mt

t = {}
setmetatable(t, mt)

 

此時對 table t 做取值的動作(如下圖),這邊可以看到 function print 收到了兩筆參數,分別是 table 本身以及 key,因此印出了「table: 0055D250 a」以及「table: 0055D250 b」的內容。

image

 

這是 metatable.__index 型態為 table 的例子

首先先建立一個 table (等等要拿來當作 metatable 的 table),並在 __index 這個特殊的 key 上填入一個 table。

-- 某個存在的 table
sometable = {a = 1, b = 2}
 
mt = {}
mt.__index = sometable

 

接著建立 table t,並將其 metatable 設置為 mt

t = {}
setmetatable(t, mt)

 

此時對 table t 做取值的動作(如下圖),這邊可以看到 table t 只是把 key 原封不動轉去問 sometable 而已

image

 

重點整理

  1. metatable 也是個普通的 table
  2. 每個 table 內都有一些特殊的 key (如 __index, __add…etc)
  3. 這些特殊 key 不影響 table 本身,而是影響將其當為 metatable 的 table
  4. __index 會影響到的行為是:從 table 中取值的動作。(嚴格說起來,應該是取「不存在的值」的動作)
  5. __index 會依照內容的型態,對取值的動作有不同的處理。

 

參考連結

2011年5月11日 星期三

在 LÖVE 實作追蹤系統

前言

之前用 actionscript3.0 做守塔遊戲的時候,有做了一個簡單的追蹤系統,現在把它移植到 LÖVE 上。

追蹤系統可以用在導彈,或是其它需要尾隨的應用。

 

範例程式下載

Tracker.love

 

情境說明

建立四個半徑不同大小的圓,追蹤的速度與半徑大小成反比;最小的圓追滑鼠,剩下的分別追比自己小一階的圓。

 

Screenshot

SNAGHTMLf1fcca

SNAGHTMLf2b4b0

 

程式碼(都在範例檔案內)

main.lua

 
require("lua/Circle")
require("lua/Tracker")
 
function love.load()
    mouse = {}
    
    circle1 = Circle.create(5)
    circle2 = Circle.create(10)
    circle3 = Circle.create(15)
    circle4 = Circle.create(20)
end
 
function love.update(dt)
    -- we need tracker/target provide x,y index for Tracker system
    
    -- let mouse provide x,y index
    mouse.x, mouse.y = love.mouse.getPosition()
    
    -- args = speed, tracker, target
    Tracker.trace(8, circle1, mouse)
    Tracker.trace(6, circle2, circle1)
    Tracker.trace(4, circle3, circle2)
    Tracker.trace(2, circle4, circle3)
end
 
function love.draw()
    circle1:draw()
    circle2:draw()
    circle3:draw()
    circle4:draw()
end

conf.lua

function love.conf(t)
    t.title = "Tracker"         -- The title of the window the game is in (string)
    t.author = "bmzz"           -- The author of the game (string)
    t.identity = nil            -- The name of the save directory (string)
    t.version = 072             -- The LOVE version this game was made for (number)
    t.console = false           -- Attach a console (boolean, Windows only)
    t.screen.width = 800        -- The window width (number)
    t.screen.height = 600       -- The window height (number)
    t.screen.fullscreen = false -- Enable fullscreen (boolean)
    t.screen.vsync = true       -- Enable vertical sync (boolean)
    t.screen.fsaa = 0           -- The number of FSAA-buffers (number)
    t.modules.joystick = false  -- Enable the joystick module (boolean)
    t.modules.audio = false     -- Enable the audio module (boolean)
    t.modules.keyboard = false  -- Enable the keyboard module (boolean)
    t.modules.event = true      -- Enable the event module (boolean)
    t.modules.image = false     -- Enable the image module (boolean)
    t.modules.graphics = true   -- Enable the graphics module (boolean)
    t.modules.timer = true      -- Enable the timer module (boolean)
    t.modules.mouse = true      -- Enable the mouse module (boolean)
    t.modules.sound = false     -- Enable the sound module (boolean)
    t.modules.physics = false   -- Enable the physics module (boolean)
end

lua/Circle.lua

 
Circle = {}
Circle.__index = Circle
 
function Circle.create(radius, attributes)
    attrs = attributes or {}
    
    local self = {}
    setmetatable(self, Circle)
    
    self.radius = radius
    self.segments = radius / 2
    if self.segments < 50 then self.segments = 50 end
    
    self.mode = attrs.mode or "line"
    
    self.x = attrs.x or 0
    self.y = attrs.y or 0
    
    self.color = attrs.color or {255, 255, 255}
    
    return self
end
 
function Circle:draw()
    love.graphics.setColor(self.color)
    love.graphics.circle(self.mode, self.x, self.y, self.radius, self.segments)
end

lua/Tracker.lua

 
Tracker = {}
 
function Tracker.trace(speed, tracker, target)
    local diffx = target.x - tracker.x
    local diffy = target.y - tracker.y
    
    local dist = math.sqrt(diffx ^ 2 + diffy ^ 2)
    local perc = speed / dist
    
    if dist > speed then
        tracker.x = tracker.x + diffx * perc
        tracker.y = tracker.y + diffy * perc
    else
        tracker.x = target.x
        tracker.y = target.y
    end
end