delacroix01
Eroge Addict
Code:
1. Giới thiệu : Lua là 1 ngôn ngữ lập trình dựa trên C nên có rất nhiều điểm giống C, nhưng gọn nhẹ hơn nhiều vì nhắm vào 1 số mục tiêu chứ ko ứng dụng trong nhiều lĩnh vực như C.
Automation của Aegisub sử dụng ngôn ngữ này để viết automation effect (bao gồm karaoke). Aegisub 1.1 sử dụng lua 5.0, trong khi Aegisub 2.0 sử dụng lua 5.1 với một số tính năng mạnh hơn. Tuy nhiên hiện tại 5.1 chưa thể thay thế 5.0 do nhiều lý do khách quan.
Điểm đầu tiên cần chú ý là automation ko sử dụng hết các thành phần của lua mà chỉ 1 phần. Thứ nữa là trình biên dịch automation ko giống với các trình biên dịch lua khác, nên các script chạy được trong các trình biên dịch khác sẽ ko thể chạy được với automation và ngược lại. Toàn bộ script sẽ được test ngay trong Automation Manager của Aegisub.
Chú ý : Lua là ngôn ngữ có phân biệt chữ hoa hay chữ thường (giống C/C++). Tuy nhiên nhiều từ khóa được định nghĩa với nhiều cách viết khác nhau nên bạn ko cần quá lo lắng về việc này, mà quan trọng là viết tên các biến cho đúng là đủ.
2. Các từ khóa :
and
break
do
else
elseif
end
false
for
function
if
in
local
nil
not
or
repeat
return
then
true
until
while
3. Các ký hiệu :
+
-
*
/
^
=
~=
<=
>=
<
>
==
(
)
{
}
[
]
;
:
,
.
..
...
\a --- Chuông
\b --- Backspace
\f --- form feed (Hình như là nhảy thẳng xuống dòng tiếp theo, để từ từ check lại)
\n --- Xuống dòng
\r --- Nhảy về đầu dòng
\t --- Horizontal tab
\v --- Vertical tab
\\ --- Backslash
\" --- Ngoặc kép
\' --- Dấu nháy đơn
\[ --- Ngoặc vuông mở
\] --- Ngoặc vuông đóng
Comment :
-- Cho 1 line
[[..............]] Cho 1 paragraph
4. Giá trị và kiểu dữ liệu :
Khác với các ngôn ngữ khác, các biến ko có kiểu dữ liệu mà chỉ có các giá trị mới có. Vì vậy, khi khai báo biến, bạn ko cần định nghĩa kiểu dữ liệu.
Có 8 kiểu cơ sở : nil, boolean (Logic), number (số), string (chuỗi), function (hàm), userdata (dữ liệu người dùng), thread, table (bảng).
Nil là kiểu đặc biệt, ko giống với bất kỳ 1 kiểu nào khác. Nó thường được dùng để thay thế giá trị vắng mặt.
Bool là kiểu logic, với 2 giá trị có thể có là true hay false. Cả nil và false làm cho điều kiện trở thành sai. Các giá trị khác làm điều kiện trở thành đúng.
Number ở đây là số thực
String là chuỗi ký tự, giống trong các ngôn ngữ khác, bao gồm cả embed zeros ('\0')
Function (hàm) là giá trị loại first-class nên có thể được gán cho biến, làm tham số cho các hàm khác, và trả về kết quả. Lua có thể gọi các hàm viết bằng Lua cũng như C.
Userdata cho phép dữ liệu tùy ý trong C được chứa trong các biến của Lua. Các dữ liệu này chưa được định nghĩa từ trước trong Lua, ngoại trừ việc gán và kiểm tra định danh. Thông qua metatables, người lập trình có thể định nghĩa các phép toán đối với các giá trị userdata. Các giá trị này ko thay đổi được trong Lua mà thông qua C API. (Phần này ko quan trọng)
Thread (ko quan trọng)
Table thực thi các mảng phức hợp, có thể bao gồm ko chỉ các giá trị số mà còn các giá trị loại khác (trừ nil). Tables có thể đại diện cho mảng bình thường, bảng ký hiệu, tập hợp, bản ghi, đồ thị... Để đại diện cho bản ghi, Lua sử dụng tên trường (field name) làm chỉ số. Cách đại diện này có cú pháp dạng a.name. Có nhiều cách tạo tables trong Lua.
Tương tự các chỉ số, giá trị các trường của tables có thể là bất kỳ giá trị nào (trừ nil). Vì các hàm là giá trị loại first-class, các trường của tables có thể chứa các hàm. Do đó tables cũng có thể chứa methods (coi giáo trình C# để hiểu đ/n method)
Giá trị của tables, function và userdata là các đối tượng. Các biến ko thực sự chứa các giá trị mà chỉ tham chiếu đến. Các phép gán, truyền tham số, trả về giá trị hàm luôn tham chiếu đến các giá trị đó.
Việc định nghĩa kiểu dữ liệu được thực hiện thông qua từ khóa type
5. Auto conversion :
Lua cho phép chuyển đổi tự động giữa kiểu string và kiểu number lúc run time. Bất kỳ phép toán nào được gán cho 1 chuỗi ký tự sẽ convert chuỗi đó thành số theo các quy luật thông thường. Ngược lại, mỗi khi 1 số được sử dụng thay cho 1 chuỗi, số đó sẽ được convert thành chuỗi. Để hiểu rõ hơn, mời tham khảo hàm format trong string library.
6. Biến :
Biến dùng để lưu trữ các giá trị. Có 3 loại biến : toàn cục (global), địa phương (local), table fields. 1 cái name có thể đại diện cho 1 biến toàn cục (hay tham số hình thức của 1 hàm, 1 dạng biến cục bộ)
Name
Mặc định, biến được xem là toàn cục, trừ khi nó được khai báo là địa phương. Các biến địa phương có tầm tác dụng riêng, có thể được tự do truy cập bởi hàm được định nghĩa trong phạm vi đó.
Trước khi được gán giá trị, biến mang giá trị nil
Ngoặc vuông dùng với các bảng :
prefixexp `[´ exp `]´
Biểu thức thứ nhất (prefixexp) có kết quả là 1 bảng. Biểu thức thứ 2 (exp) xác định giá trị nào trong bảng đó. Biểu thức chỉ định bảng được truy cập với cú pháp hạn chế.
Cú pháp var.NAME cũng tương tự var["NAME"] :
prefixexp `.´ Name
Ý nghĩa việc truy cập vào các biến toàn cục và các field (trường) trong các bảng có thể được thay đổi thông qua metatables. Việc truy cập tới t[i] cũng tương đương việc gọi hàm gettable_event(t,i). Chú ý là hàm này chưa được định nghĩa trong lua. Script của nó sẽ được nói tới sau.
Tất cả các biến toàn cục tồn tại như các field trong 1 bảng Lua thông thường, được gọi là environment tables hay đơn giản là environment (môi trường). Mỗi hàm viết trong Lua (Lua function) đều có tham chiếu đến môi trường. Các biến trong các hàm đó sẽ tham chiếu đến environment table. Khi 1 hàm được tạo ra, nó thừa kế môi trường từ hàm tạo ra nó. Để thay đối hoặc nhận environment table trong 1 hàm Lua, gọi hàm setfenv hay getfenv.
Truy cập đến biến toàn cục x cũng tương đương với _env.x và tương đương với gettable_event(_env, "x") với _env là môi trường của hàm đang thực thi (biến _env ko được định nghĩa trong Lua. Ở đây chỉ ghi để mang tính chất tham khảo).
7. Lệnh :
Lua hỗ trợ hầu hết các tập lệnh, tương tự Pascal hay C. Các tập lệnh này bao gồm lệnh gán, cấu trúc điều khiển, gọi hàm, xây dựng bảng, khai báo biến.
a/ Lệnh gán :
Lua cho phép gán nhiều lần 1 lúc. Do đó, cú pháp lệnh gán chỉ định 1 danh sách các biến nằm bên trái và 1 danh sách các biểu thức nằm bên phải. Các yếu tố trong 2 danh sách ngăn cách bởi dấu phẩy.
varlist1 `=´ explist1
var{`,´ var}
exp{`,´ exp}
Trước khi lệnh gán được thực hiện, danh sách các giá trị được điều chỉnh theo độ dài danh sách các biến. Nếu có nhiều giá trị hơn mức cần thiết, các giá trị thừa ra sẽ bị bỏ đi. Ngược lại, nếu thiếu thì nó sẽ thay bằng các giá trị nil đến khi nào đủ thì thôi. Nếu danh sách các biểu thức kết thúc bằng lệnh gọi hàm, tất cả các giá trị do hàm đó trả về được đưa vào danh sách các giá trị, trước khi thay đổi (trừ khi lệnh gọi nằm trong ngoặc đơn).
Lệnh gán đầu tiên sẽ tính toán tất cả các biểu thức rồi mới gán. Đoạn code
i = 3
i, a[i] = i+1, 20
Sẽ gán a[3] = 20 mà ko ảnh hưởng tới a[4], vì i trong a[i] bằng 3 trước khi nó được gán lại thành 4, tương tự, lệnh :
x, y = y, x sẽ hoán vị x và y, thay vì phải dùng biến trung gian như các ngôn ngữ khác
Ý nghĩa của lệnh gán đối với biến toàn cục và table fields có thể được thay đổi thông qua metatables. 1 lệnh gán 1 phần tử t[i] = val tương đương với lệnh settable_event(t,i,val). Định nghĩa hàm này tham khảo phía dưới. Nó chưa được định nghĩa sẵn trong Lua. Ở đây dùng mang tính chất tham khảo.
Một lệnh gán 1 biến toàn cục x = val tương đương lệnh assignment _env.x = val, tương đương với settable_event(_env, "x", val) với _env là môi trường của hàm đang thực thi. Hàm này cũng chưa được định nghĩa sẵn. Ở đây dùng mang tính chất tham khảo.
b/ Cấu trúc điều khiển :
Ý nghĩa các lệnh if, while, repeat cũng giống như trong các ngôn ngữ khác với cú pháp tương tự :
while exp do block end
repeat block until exp
if exp then block {elseif exp then block} [else block] end
Vòng for trong lua có 2 dạng sẽ được trình bày ở mục tiếp theo.
Biểu thức điều kiện (exp) trong mỗi cấu trúc điều khiển có thể trả về bất cứ giá trị gì. Cả false và nil đều được coi là false. Tất cả các giá trị khác nil hay false được coi là true, kể cả số 0 và chuỗi rỗng.
Lệnh return được dùng để trả về giá trị của 1 hàm hay 1 chunk (wtf?). Hàm và chunk có thể trả về nhiều hơn 1 giá trị. Cú pháp lệnh return :
return [explist]
Lệnh break có thể được dùng để ngắt việc thực thi các vòng lặp while, repeat hay for, và nhảy tới lệnh nằm ngay sau vòng lặp.
Cú pháp : break
Lệnh break kết thúc vòng lặp nằm trong cùng. Vì lý do cú pháp, return và break chỉ được phép viết ở cuối mỗi khối lệnh (nằm trong 1 vòng lặp). Nếu cần thiết phải return hay break ở giữa 1 block thì có thể sử dụng thêm 1 block nằm trong, chẳng hạn như do return end hay do break end, vì lúc này return và break là lệnh cuối cùng thuộc block nằm trong. Chú ý là cách viết này chỉ dùng khi debug.
c/ Vòng for :
Vòng for có 2 dạng : Numeric và Generic
Vòng for dạng số lặp 1 khối mã lệnh trong khi 1 biến điều khiển chạy theo thuật toán. Cú pháp :
for Name `=´ exp `,´ exp [`,´ exp] do block end
Block được lặp lại theo tên bắt đầu với giá trị của của biểu thức thứ 1, cho tới khi nó chuyển tới biểu thức thứ 2 bằng các bước trong biểu thức thứ 3. Chính xác hơn, 1 vòng for có dạng:
for var = e1, e2, e3 do block end
tương đương với đoạn code sau :
Code:
do
local var, _limit, _step = tonumber(e1), tonumber(e2), tonumber(e3)
if not (var and _limit and _step) then error() end
while (_step>0 and var<=_limit) or (_step<=0 and var>=_limit) do
block
var = var + _step
end
end
Code:
Chú ý những điểm sau đây :
Cả 3 biểu thức điều khiển được tính toán 1 lần duy nhất trước khi vòng lặp bắt đầu. Tất cả đều phải trả về giá trị số.
_limit và _step là các biến ko nhìn thấy. Ở đây tên của chúng chỉ dùng để giải thích.
Hành vi chưa được định nghĩa nếu gán giá trị cho biến var nằm trong block.
Nếu biểu thức thứ 3 (step) vắng mặt, bước nhảy bằng 1 được sử dụng.
Có thể sử dụng lệnh break để thoát khỏi 1 vòng lặp.
Biến lặp var là biến cục bộ của câu lệnh. Bạn ko thể sử dụng giá trị của biến này sau khi vòng for kết thúc hoặc bị ngắt. Nếu cần giá trị của nó, có 1 cách là gán nó cho 1 biến khác trước khi thoát khỏi vòng lặp.
Vòng for dạng generic làm việc theo hàm, gọi là iterators. Với mỗi iteration, nó gọi hàm iterator tạo ra 1 giá trị mới và dừng lại khi giá trị mới là nil. Vòng lặp for dạng generic có cú pháp sau :
for Name {`,´ Name} in explist1 do block end
1 vòng for dạng :
for var_1, ..., var_n in explist do block end
tương đương đoạn code sau :
Code:
do
local _f, _s, var_1 = explist
local var_2, ... , var_n
while true do
var_1, ..., var_n = _f(_s, var_1)
if var_1 == nil then break end
block
end
end
Chú ý những điểm sau :
explist được tính toán 1 lần duy nhất. Kết quả của nó là 1 hàm iterator, 1 state, và 1 giá trị khởi tạo cho biến iterator đầu tiên.
_f and _s là biến ko nhìn thấy. Ở đây tên chúng chỉ dùng để giải thích.
Hành vi chưa được định nghĩa nếu gán giá trị cho var_1 bên trong block.
Có thể sử dụng lệnh break để thoát khỏi vòng lặp.
Biến lặp var_i là biến cục bộ trong câu lệnh. Ko thể dùng nó sau khi vòng for kết thúc. Nếu cần giá trị của chúng, bạn phải gán chúng cho các biến khác trước khi thoát khỏi vòng lặp.
8. Lệnh gọi hàm :
Để cho phép các hiệu ứng phụ có thể có, lệnh gọi hàm có thể được thực thi với câu lệnh :
functioncall (Tên hàm)
Trong trường hợp này, tất cả các giá trị trả về đều bị bỏ đi. Lệnh gọi hàm sẽ được giải thích sau.
9. Khai báo cục bộ (địa phương) :
Các biến cục bộ có thể được khai báo ở bất kỳ đâu bên trong 1 block. Khai báo có thể kèm theo 1 giá trị khởi tạo :
local namelist [`=´ explist1]
Name {`,´ Name}
Nếu được khai báo, 1 lệnh gán khởi tạo cũng giống như 1 lệnh gán nhiều biến. Ngược lại, tất cả các biến được khởi tạo với giá trị nil.
1 chunk cũng là 1 block, vì vậy các biến cục bộ có thể được khai báo trong 1 chunk bên ngoài 1 block bất kỳ nào. Các biến cục bộ đó mất đi khi chunk kết thúc.
Chi tiết về biến cục bộ sẽ được giải thích sau.
10. Biểu thức :
Biểu thức cơ bản trong lua có những dạng sau :
prefixexp
nil | false | true (Logic)
Number (Số)
Literal (chữ)
function (hàm)
tableconstructor
var (biến) | functioncall (lời gọi hàm) | `(´ exp `)´
Số, chuỗi ký tự, biến đã được giải thích ở trên; hàm, lời gọi hàm và tableconstructor sẽ được giải thích dần ở các mục dưới.
1 biểu thức nằm trong ngoặc đơn luôn cho kết quả là 1 giá trị. Do đó (f(x, y, z)) luôn là 1 giá trị đơn, cho dù nó có trả về nhiều giá trị đi chăng nữa. Giá trị của (f(x, y, z)) là giá trị đầu tiên được trả về bởi f hay nil nếu f ko trả về giá trị nào.
1 biểu thức cũng có thể được xây dựng với các toán tử số học, so sánh và logic. Tất cả sẽ được giải thích trong mục tiếp theo.
a/ Toán tử số học :
Lua hỗ trợ các toán tử số học thông thường : + - * / (nhị phân) và ^ (lũy thừa), đổi dấu (-).
Nếu toán hạng là số hay chuỗi có thể chuyển thành số thì tất cả các toán tử trừ lũy thừa có ý nghĩa thông thường. Lũy thừa gọi 1 hàm toàn cục _pow; nếu ko, 1 metamethod tương đương sẽ được gọi. Thư viện toán học chuẩn định nghĩa hàm _pow, cho phép đạt được ý nghĩa như mong muốn đối với lũy thừa.
b/ Toán tử quan hệ :
Các toán tử quan hệ trong Lua bao gồm :
== ~= < > <= >=
Các toán tử này luôn trả về giá trị false hoặc true
Toán tử bằng nhau (==) đầu tiên sẽ so sánh kiểu của các toán hạng. Nếu khác kiểu, giá trị sẽ là false. Ngược lại, giá trị của các toán hạng sẽ được so sánh. Số và chuỗi được so sánh theo cách thông thường. Đối tượng (bảng, dữ liệu người dùng, thread và hàm) được so sánh bằng tham chiếu : 2 đối tượng được coi như bằng nhau khi và chỉ khi chúng cùng 1 đối tượng. Mỗi khi tạo ra 1 đối tượng mới (bảng, dữ liệu người dùng, hay hàm), đối tượng mới khác với tất cả các đối tượng đã tồn tại.
Có thể thay đổi cách Lua so sánh bảng và dữ liệu người dùng bằng metamethod "eq" (sẽ giải thích sau)
Luật chuyển đổi nói ở phần đầu của hướng dẫn này ko áp dụng với so sánh bằng nhau. Do đó "0" == 0 trả về false, và t[0] và t["0"] chỉ định những giá trị khác nhau trong 1 bảng.
Toán tử ~= (khác) là phủ định của bằng (==).
Thứ tự của các toán tử như sau. Nếu các đối số đều là số, chúng được so sánh với nhau. Nếu chúng đều là chuỗi, giá trị của chúng được so sánh theo bảng mã đang dùng. Nếu ko phải, Lua sẽ gọi "lt" hay "le" metamethod.
Code:
c/ Toán tử logic :
Các toán tử logic trong Lua bao gồm :
and or not
Giống như cấu trúc điều khiển, tất cả các toán tử logic coi cả false và nil là false và các giá trị còn lại là true. Toán tử not luôn trả về false hay true.
Toán tử and trả về đối số đầu tiên nếu giá trị này là là false hay nil; ngược lại thì trả về đối số thứ 2.
Toán tử or trả về đối số thứ nhất nếu giá trị này khác với nil và false, ngược lại thì trả về đối số thứ 2.
Cả and và or sử dụng tính toán nhanh. Toán hạng thứ 2 được tính chỉ khi cần thiết. VD :
10 or error() -> 10
nil or "a" -> "a"
nil and 10 -> nil
false and error() -> false
false and nil -> false
false or nil -> nil
10 and 20 -> 20
d/ Concatenation (Nối chuỗi) :
Toán tử nối chuỗi ký tự trong lua định bởi dấu ".."
Chú ý : 2 chấm, ko phải 3. Nếu cả 2 toán hạng là chuỗi hay số, chúng được convert thành chuỗi theo luật được nói ở đầu hướng dẫn. Ngược lại, metamethod "concat" được gọi. (Sẽ giải thích sau)
e/ Thứ tự ưu tiên :
Thứ tự ưu tiên từ thấp tới cao trong Lua như sau :
Code:
or (hoặc)
and (và)
< > <= >= ~= == (so sánh)
.. (nối chuỗi)
+ - (cộng trừ)
* / (nhân chia)
not - (đổi dấu)
^ (lũy thừa)
Bạn có thể sử dụng ngoặc đơn để thay đổi độ ưu tiên trong 1 biểu thức. Toán tử nối chuỗi (`..´) và lũy thừa (`^´) được liên hợp phải. Các toán tử nhị phân còn lại liên hợp trái.
f/ Table constructor :
Table constructor là các biểu thức tạo ra tables. Mỗi khi 1 constructor được tính toán, một table mới sẽ được tạo ra. Constructor có thể được sử dụng để tạo ra table rỗng, hoặc tạo ra 1 table và khởi tạo 1 vài fields của nó. Cú pháp thông thường của constructor là như sau :
tableconstructor ::= `{´ [fieldlist] `}´
fieldlist ::= field {fieldsep field} [fieldseparator]
field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp
fieldseparator ::= `,´ | `;´
Mỗi field trong biểu thức [exp1] = exp2 thêm vào table mới 1 phần tử với khóa exp1 và giá trị exp2. Một field trong biểu thức name = exp tương đương với [“name”] = exp. Cuối cùng, các field trong biểu thức exp tương đương với [i] = exp, với i là các số nguyên tuần tự bắt đầu từ 1. Các field dạng khác ko ảnh hưởng việc đếm này. VD :
a = {[f(1)] = g; "x", "y"; x = 1, f(x), [30] = 23; 45}
tương đương với :
Code:
do
local temp = {}
temp[f(1)] = g
temp[1] = "x" -- 1st exp
temp[2] = "y" -- 2nd exp
temp.x = 1 -- temp["x"] = 1
temp[3] = f(x) -- 3rd exp
temp[30] = 23
temp[4] = 45 -- 4th exp
a = temp
end
Nếu field cuối cùng trong danh sách có dạng exp và biểu thức là lời gọi hàm, khi đó tất cả các giá trị do lời gọi hàm trả về sẽ vào danh sách 1 cách tuần tự. Để tránh điều này, đóng lời gọi hàm trong ngoặc đơn.
Danh sách các fields có thể có thêm thành phần ngăn cách (separator) để tiện lợi cho machine-generated code.
g/ Function calls :
Lời gọi hàm trong Lua có cú pháp như sau :
prefixexp args
Khi gọi hàm, đầu tiên prefixexp và args sẽ được tính toán. Nếu giá trị của prefixexp có kiểu là hàm (function) thì hàm đó sẽ được gọi với tham số đã cho trong câu lệnh. Ngược lại, “call” metamethod sẽ được gọi với tham số đầu tiên là giá trị của prefixexp, theo sau là các đối số được gọi.
Câu lệnh
prefixexp `:´ Name args
Có thể được dùng để gọi “methods”. 1 lời gọi v:name(...) tương tự v.name(v, ...), chỉ khác ở chỗ v được tính 1 lần duy nhất.
Các đối số (arguments) có cú pháp như sau :
`(´ [explist1] `)´
tableconstructor
Literal
Tất cả các biểu thức của đối số được tính toán trước lời gọi. 1 lời gọi có dạng f{...} cũng giống như f({...}). Danh sách các đối số là 1 table mới. 1 lời gọi có dạng f'...' (hay f"..." hoặc f[[...]]) cũng giống với f('...'), với danh sách các tham số là chuỗi ký tự.
Vì 1 lời gọi hàm có thể trả về bao nhiêu kết quả tùy thích nên số lượng kết quả phải được điều chỉnh trước khi sử dụng. Nếu lời gọi hàm là statement thì danh sách trả về của nó được điều chỉnh thành zero element, tức là bỏ qua tất cả các giá trị mà nó trả về. Nếu 1 hàm được gọi bên trong 1 biểu thức khác hoặc giữa 1 danh sách các biểu thức thì danh sách các biến mà nó trả về được điều chỉnh thành 1 element, bỏ qua tất cả các giá trị trừ giá trị đầu tiên. Nếu hàm được gọi là yếu tố cuối cùng trong danh sách các biểu thức thì ko có điều chỉnh nào (trừ khi lời gọi nằm trong ngoặc đơn).
1 vài VD :
Code:
f() -- Điều chỉnh còn 0 element
g(f(), x) -- f() được điều chỉnh còn 1 kết quả
g(x, f()) -- g lấy giá trị của x và tất cả các giá trị do f() trả về
a,b,c = f(), x -- f() được điều chỉnh thành 1 kết quả (c mang giá trị nil)
a,b,c = x, f() -- f() được điều chỉnh còn 2 kết quả
a,b,c = f() -- f() được điều chỉnh thành 3 kết quả
return f() -- Trả về tất cả các giá trị do f trả về
return x,y,f() -- Trả về x, y, và tất cả các giá trị do f() trả về
{f()} -- Tạo ra 1 danh sách với tất cả các giá trị do f() trả về
{f(), nil} -- f() được điều chỉnh lại thành 1 kết quả
Nếu đóng lời gọi hàm trong ngoặc đơn thì nó được điều chỉnh lại thành 1 kết quả duy nhất :
Code:
return x,y,(f()) -- Trả về x, y và giá trị đầu tiên của f()
{(f())} -- Tạo ra 1 bảng với 1 giá trị duy nhất
1 ngoại lệ đối với cú pháp free-format của Lua là bạn ko thể đặt ngắt dòng trước ‘(‘ trong lời gọi hàm. Việc hạn chế này nhằm tránh xung đột trong ngôn ngữ. Nếu bạn viết :
a = f
(g).x(a)
Lua sẽ đọc a = f(x).x(a). Nếu bạn muốn 2 statements, bạn phải thêm dấu chấm phẩy vào giữa. Nếu bạn thực sự muốn gọi f, bạn phải bỏ ngắt dòng trước (g).
1 lời gọi dạng return gọi là tail call. Lua thực thi tail call thích hợp : Trong 1 tail call, hàm được gọi sử dụng lại stack entry của hàm gọi (stack : ngăn xếp). Do đó, ko có giới hạn về số lượng nested tail calls (coi hướng dẫn AE để hiểu nested là gì) mà chương trình có thể thực hiện. Tuy nhiên, 1 tail call xóa tất cả thông tin debug của hàm gọi. Chú ý là 1 tail call chỉ có thể dùng với 1 cú pháp đặc trưng, với return có 1 lời gọi hàm là đối số. Cú pháp này làm cho lời gọi hàm trả về chính xác các giá trị do hàm được gọi trả về. Vì vậy, các vd tiếp theo ko phải là tail calls :
Code:
h/ Định nghĩa hàm :
Cú pháp định nghĩa hàm như sau :
funtion funcbody
với funcbody ::= `(´ [parlist1] `)´ block end
Các cú pháp sau đây đơn giản hóa định nghĩa hàm :
function funcname funcbody
local function Name funcbody
với funcname ::= Name {`.´ Name} [`:´ Name]
Câu lệnh :
function f () ... end
có nghĩa là :
f = function () ... end
Câu lệnh :
function t.a.b.c.f () ... end
tức là :
t.a.b.c.f = function () ... end
Câu lệnh :
local function f () ... end
tức là :
local f; f = function () ... end
1 định nghĩa hàm là 1 biểu thức thực thi được. Giá trị có kiểu là hàm. Khi lua pre-compile 1 chunk, toàn bộ thân hàm cũng được pre-compile. Mỗi lần Lua thực thi định nghĩa hàm, hàm đó được đóng lại. Khóa hàm là giá trị cuối cùng của biểu thức. Các khóa khác nhau của cùng 1 hàm có thể tham chiếu đến các biến cục bộ ngoài khác nhau và có thể có environment table khác nhau.
Các tham số được xem như biến cục bộ được khởi tạo với giá trị của đối số :
parlist1 ::= namelist [`,´ `...´]
parlist1 ::= `...´
Khi 1 hàm được gọi, 1 danh sách các đối số được điều chỉnh theo độ dài danh sách tham số, trừ khi hàm là 1 variadic hay hàm vararg, được biểu thị bởi dấu 3 chấm ('...') ở cuối danh sách tham số. 1 hàm vararg ko điều chỉnh danh sách tham số. Thay vì đó, nó gom tất cả các đối số thừa vào 1 tham số ẩn gọi là arg. Giá trị của arg là 1 table, với 1 trường 'n' lưu trữ số đối số thừa tại vị trí 1, 2,..., n.
VD :
function f(a, b) end
function g(a, b, ...) end
function r() return 1,2,3 end
Như vậy, chúng ta có các ánh xạ từ các đối số sang tham số như sau :
Code:
CALL PARAMETERS
f(3) a=3, b=nil
f(3, 4) a=3, b=4
f(3, 4, 5) a=3, b=4
f(r(), 10) a=1, b=10
f(r()) a=1, b=2
g(3) a=3, b=nil, arg={n=0}
g(3, 4) a=3, b=4, arg={n=0}
g(3, 4, 5, 8) a=3, b=4, arg={5, 8; n=2}
g(5, r()) a=5, b=1, arg={2, 3; n=2}
Các kết quả được trả về bằng lệnh return. Nếu con trỏ lệnh chạy đến cuối hàm mà ko gặp lệnh return thì hàm ko được trả lại giá trị nào.
Dấu 2 chấm được sử dụng để định nghĩa methods, là hàm có 1 tham số thừa được ẩn đi. Theo đó, câu lệnh :
function t.a.b.c:f (...) ... end
tương đương với :
t.a.b.c.f = function (self, ...) ... end
11. Tầm tác dụng :
Lua là ngôn ngữ có tầm nhìn về mặt từ ngữ. Tầm hoạt động của các biến bắt đầu với câu lệnh đầu tiên sau khi khai báo và kết thúc cho tới hết block trong cùng có chứa khai báo. VD :
Code:
x = 10 -- Biến toàn cục
do -- Block mới
local x = x -- Biến `x' mới được gán giá trị = 10 của biến cũ
print(x) --> 10
x = x+1
do -- Block khác
local x = x+1 -- `x' khác
print(x) --> 12
end
print(x) --> 11
end
print(x) --> 10 (Biến toàn cục)
Chú ý rằng, trong 1 khai báo dạng local x = x, biến x mới được khai báo chưa có tầm tác dụng ngay, và x thứ 2 tham chiếu tới biến x nằm bên ngoài.
Do luật tầm tác dụng, biến cục bộ có thể được tự do truy cập bởi các hàm định nghĩa bên trong tầm của chúng, vd :
Code:
local counter = 0
function inc (x)
counter = counter + x
return counter
end
Một biến cục bộ được sử dụng bởi 1 hàm bên trong được gọi là upvalue (tăng giá trị), hay biến cục bộ ngoài, trong hàm bên trong này.
Chú ý là mỗi thực thi 1 câu lệnh cục bộ định nghĩa biến cục bộ mới. Theo dõi vd sau :
Code:
a = {}
local x = 20
for i=1,10 do
local y = 0
a[i] = function () y=y+1; return x+y end
end
Vòng lặp tạo ra 10 khóa hàm (10 giá trị của hàm nặc danh (chưa khai báo tên). Mỗi khóa sử dụng 1 y khác nhau, trong khi cùng sử dụng 1 biến x.
12. Xử lý lỗi :
Vì lua là 1 ngôn ngữ mở rộng, tất cả hành động của Lua bắt đầu từ mã C nằm trong chương trình host gọi là Lua library. Mỗi khi 1 lỗi xuất hiện trong quá trình biên dịch hay thực thi, con trỏ trả về C để thực hiện việc tương ứng (như in thông báo lỗi).
Lua code có thể thực sự tạo ra 1 lỗi bằng cách gọi hàm error. Nếu bạn cần tìm lỗi trong Lua, bạn có thể sử dụng hàm pcall. (sẽ nói sau)
13. Metatables :
Mọi table và đối tượng dữ liệu người dùng trong Lua đều có thể có metatable. Metatable này là 1 giá trị Lua bình thường định nghĩa hành vi của table gốc và dữ liệu người dùng dưới những phép toán đặc biệt. Bạn có thể thay đổi vài phương diện của hành vi của 1 đối tượng bằng cách thiết lập các trường đặc trưng trong metatable của nó. VD, khi 1 đối tượng là toán hạng của 1 phép cộng, Lua kiểm tra 1 hàm trong trường "__add" trong metatable của nó. Nếu tìm thấy, Lua gọi hàm đó để thực thi phép cộng.
Ta gọi các khóa trong 1 metatable là events, và giá trị của chúng là metamethods. Trong vd trước, event là "add" và metamethod là hàm thực thi phép cộng.
Bạn có thể truy vấn và thay đổi metatable trong 1 đối tượng thông qua các hàm set/getmetatable.
1 metatable có thể điều khiển hành vi của 1 đối tượng trong các phép toán số học, so sánh, nối, indexing. 1 metatable cũng có thể định nghĩa 1 hàm để gọi khi 1 dữ liệu người dùng là rác được gom lại. Đối với mỗi phép toán đó, Lua gán 1 khóa đặc trưng gọi là event. Mỗi khi lua thực hiện 1 trong số các phép toán trên 1 table hay dữ liệu người dùng, nó kiểm tra đối tượng đó có metatable với event tương ứng hay ko. Nếu có, giá trị được gắn với khóa (metamethod) sẽ điều khiển việc Lua thực hiện phép toán.
Metatable điều khiển các phép toán được liệt kê tiếp theo. Mỗi phép toán được định danh bởi tên tương ứng. Khóa cho mỗi phép toán là 1 chuỗi với tên có tiếp đầu ngữ là 2 gạch dưới, vd : khóa cho phép toán cộng (add) là chuỗi "__add". Các từ ngữ trong phép toán tốt hơn nên được giải thích bởi 1 hàm lua miêu tả interpreter thực thi phép toán.
Đám code dưới đây chỉ để minh họa. Hành vi thực được hard coded trong interpreter và hiệu quả hơn nhiều so với code giả lập này. Tất cả hàm được sử dụng trong miêu tả (rawget, tonumber,...) sẽ được miêu tả trong phần giải thích về hàm. Để nhận metamethod của 1 đối tượng đã cho, ta sử dụng biểu thức
metatable(obj)[event]
Có thể đọc là :
rawget(metatable(obj) or {}, event)
Cái này tức là truy cập vào 1 metamethod ko tác động tới metamethod khác, và truy cập vào đối tượng ko có metatable ko thất bại mà chỉ đơn giản trả về nil.
"add": Phép toán cộng (+)
Hàm getbinhandler dưới đây mô tả việc Lua chọn 1 handler cho 1 phép toán nhị phân. Đầu tiên, Lua sẽ thử với toán hạng đầu tiên. Nếu kiểu của chúng ko định nghĩa 1 handler cho hàm, Lua sẽ thử với toán hạng thứ 2.
Code:
function getbinhandler (op1, op2, event)
return metatable(op1)[event] or metatable(op2)[event]
end
Sử dụng hàm này, hành vi của op1 và op2 là :
Code:
function add_event (op1, op2)
local o1, o2 = tonumber(op1), tonumber(op2)
if o1 and o2 then -- cả 2 toán hạng là số
return o1 + o2 -- `+' ở đây là `cộng' nguyên thủy
else -- Ít nhất 1 trong số các toán hạng ko phải số
local h = getbinhandler(op1, op2, "__add")
if h then
-- gọi handler với cả 2 toán hạng
return h(op1, op2)
else -- ko có handler : thực hiện hành vi mặc định
error("...")
end
end
end
"sub": Phép toán trừ (-). Hành vi tương tự phép toán "cộng".
"mul": Phép toán nhân (*). Hành vi tương tự phép toán "cộng".
"div": Phép toán chia (/). Hành vi tương tự phép toán "cộng".
"pow": Phép toán lũy thừa (^).
Code:
function pow_event (op1, op2)
local o1, o2 = tonumber(op1), tonumber(op2)
if o1 and o2 then -- cả 2 toán hạng là số
return __pow(o1, o2) -- gọi `__pow' toàn cục
else -- ít nhất 1 trong số 2 toán hạng ko phải số
local h = getbinhandler(op1, op2, "__pow")
if h then
-- gọi handler với cả 2 toán hạng
return h(op1, op2)
else -- ko có handler : thực hiện hành vi mặc định
error("...")
end
end
end
"unm": Phép toán đổi dấu (-)
Code:
function unm_event (op)
local o = tonumber(op)
if o then -- Toán hạng là số ?
return -o -- `-' ở đây là phép đổi dấu nguyên thủy
else -- Toán hạng ko phải số
-- Thử get 1 handler từ toán hạng operand
local h = metatable(op).__unm
if h then
-- gọi handler với toán hạng và nil
return h(op, nil)
else -- Ko có handler : thực hiện hành vi mặc định
error("...")
end
end
end
"concat": Phép nối (..)
Code:
function concat_event (op1, op2)
if (type(op1) == "string" or type(op1) == "number") and
(type(op2) == "string" or type(op2) == "number") then
return op1 .. op2 -- Nối chuỗi nguyên thủy
else
local h = getbinhandler(op1, op2, "__concat")
if h then
return h(op1, op2)
else
error("...")
end
end
end
"eq": Phép toán bằng (==). Hàm getcomphandler định nghĩa việc Lua chọn 1 metamethod cho các toán tử so sánh. 1 metamethod chỉ được chọn khi cả 2 đối tượng được so sánh có cùng kiểu và cùng metamethod đối với phép toán đã chọn.
Code:
function getcomphandler (op1, op2, event)
if type(op1) ~= type(op2) then return nil end
local mm1 = metatable(op1)[event]
local mm2 = metatable(op2)[event]
if mm1 == mm2 then return mm1 else return nil end
end
"eq" event được định nghĩa như sau:
Code:
function eq_event (op1, op2)
if type(op1) ~= type(op2) then -- Khác kiểu?
return false -- Khác đối tượng
end
if op1 == op2 then -- Bằng nguyên thủy?
return true -- 2 đối tượng bằng nhau
end
-- thử metamethod
local h = getcomphandler(op1, op2, "__eq")
if h then
return h(op1, op2)
else
return false
end
end
a ~= b có giá trị bằng not (a == b).
Code:
"lt": Phép toán bé hơn (<).
Code:
function lt_event (op1, op2)
if type(op1) == "number" and type(op2) == "number" then
return op1 < op2 -- So sánh số
elseif type(op1) == "string" and type(op2) == "string" then
return op1 < op2 -- So sánh chữ
else
local h = getcomphandler(op1, op2, "__lt")
if h then
return h(op1, op2)
else
error("...");
end
end
end
a > b cũng y như b < a.
"le": Phép toán bé hơn hay bằng (<=).
Code:
function le_event (op1, op2)
if type(op1) == "number" and type(op2) == "number" then
return op1 <= op2 -- So sánh số
elseif type(op1) == "string" and type(op2) == "string" then
return op1 <= op2 -- So sánh chuỗi
else
local h = getcomphandler(op1, op2, "__le")
if h then
return h(op1, op2)
else
h = getcomphandler(op1, op2, "__lt")
if h then
return not h(op2, op1)
else
error("...");
end
end
end
end
a >= b bằng với b <= a. Chú ý rằng khi vắng mặt "le" metamethod, Lua sẽ thử "lt", cho rằng a <= b bằng not (b < a).
"index": Bảng liệt kê truy cập [key].
Code:
function gettable_event (table, key)
local h
if type(table) == "table" then
local v = rawget(table, key)
if v ~= nil then return v end
h = metatable(table).__index
if h == nil then return nil end
else
h = metatable(table).__index
if h == nil then
error("...");
end
end
if type(h) == "function" then
return h(table, key) -- Gọi handler
else return h[key] -- hoặc lặp lại phép toán trên chính nó
end
"newindex": Bảng liệt kê gán [key] = value.
Code:
function settable_event (table, key, value)
local h
if type(table) == "table" then
local v = rawget(table, key)
if v ~= nil then rawset(table, key, value); return end
h = metatable(table).__newindex
if h == nil then rawset(table, key, value); return end
else
h = metatable(table).__newindex
if h == nil then
error("...");
end
end
if type(h) == "function" then
return h(table, key,value) -- Gọi handler
else h[key] = value -- hoặc lặp lại phép toán trên chính nó
end
"call": được gọi khi Lua gọi giá trị.
Code:
14. Thu gom rác :
Lua tự động sắp xếp bộ nhớ. Bạn ko cần phải lo lắng về việc cấp phát bộ nhớ cho đối tượng mới và giải phóng nó khi đối tượng ko còn cần đến nữa. Lua quản lý bộ nhớ tự động bằng 1 trình thu gom rác theo thời gian để thu gom toàn bộ đối tượng đã chết (đối tượng ko thể truy cập bởi Lua nữa). Tất cả các đối tượng trong Lua là các thành phần được quản lý tự động : tables, dữ liệu người dùng, hàm, thread, chuỗi.
Lua sử dụng 2 số để điều khiển vòng thu gom rác. 1 số đếm số byte bộ nhớ động Lua đang sử dụng. Số còn lại là threshold. Khi số lượng bytes đạt đến mức threshold, Lua chạy trình thu gom rác để lấy lại bộ nhớ từ các đối tượng đã chết. Biến đếm byte được thay đổi, và threshold được reset thành 2 lần giá trị mới của biến đếm byte.
Thông qua C API, bạn có thể truy vấn những số đó và thay đổi threshold. Set threshold thành zero thực ra là bắt buộc vòng thu thập rác chạy ngay lập tức, trong khi set thành số lớn tạo ra hiệu quả dừng vòng thu gom rác. Thông qua sử dụng Lua code, bạn có thể điều khiển giới hạn vòng thu gom rác thông qua gcinfo và hàm collectgarbage
a/ Metamethod thu gom rác :
Sử dụng C API cho phép bạn thiết lập metamethod thu gom rác đối với dữ liệu người dùng. Các metamethods này được gọi là finalizers. Các finalizers này cho phép bạn phối hợp trình thu gom rác của Lua với việc quản lý tài nguyên bên ngoài (vd như đóng file, network hay liên kết cơ sở dữ liệu, giải phóng bộ nhớ).
Giải phóng dữ liệu người dùng với 1 field __gc trong metatable chưa thu gom chúng ngay lập tức bằng trình gom rác. Thay vì đó, Lua đặt chúng vào 1 danh sách. Sau khi thu gom, Lua thực hiện hàm tương đương sau cho mỗi dữ liệu người dùng trong danh sách :
Code:
function gc_event (udata)
local h = metatable(udata).__gc
if h then
h(udata)
end
end
Cuối mỗi vòng thu gom rác, finalizer cho dữ liệu người dùng được gọi theo thứ tự đảo so với khi chúng được tạo ra, cùng với những phần đã được thu gom trong vòng. Finalizer đầu tiên được gọi là cái đầu tiên sẽ được áp dụng với dữ liệu người dùng được tạo ra sau cùng trong chương trình.
b/ Tables yếu :
1 table yếu là 1 table mà các yếu tố của nó tham chiếu yếu. 1 tham chiếu yếu được bỏ qua bởi trình thu gom rác. Có nghĩa là, nếu tham chiếu duy nhất tới đối tượng là tham chiếu yếu, thì trình gom rác sẽ gom đối tượng đó.
1 table yếu có thể có khóa yếu, giá trị yếu hay cả 2. 1 table có khóa yếu cho phép thu gom các khóa của nó, nhưng ngăn chặn thu gom giá trị của nó. 1 table có cả key yếu và giá trị yếu cho phép trình gom rác thu thập cả khóa và giá trị của nó. Dù sao thì hoặc khóa hoặc table cũng sẽ được thu gom. Cả cặp được loại bỏ khỏi table. Điểm yếu của 1 table được điều khiển bởi 1 giá trị của __mode field trong metatables của nó. Nếu trường "__mode" là 1 chuỗi có chứa ký tự 'k', khóa trong table là khóa yếu. Nếu __mode có chứ ký tự 'v', giá trị của table là yếu.
Sau khi bạn sử dụng 1 table như 1 metatable, bạn ko nên thay đổi giá trị của trường __mode. Nếu ko, hành vi yếu của table được điều khiển bởi metatable này sẽ ko được định nghĩa.
15. Coroutines :
Lua hỗ trợ coroutines, còn gọi là semi-coroutines hay cộng tác đa luồng. 1 coroutine trong lua đại diện 1 luồng thực thi độc lập. Ko giống những luồng trong các hệ thống đa luồng, nhưng 1 coroutine chỉ ngừng thực thi bởi việc thực sự gọi hàm nhường.
Bạn tạo ra 1 coroutine với 1 lời gọi tới coroutine.create. Đối số của nó là 1 hàm main của coroutine. Hàm tạo chỉ tạo ra 1 coroutine mới và trả về 1 handle (1 đối tượng có kiểu thread). Nó ko bắt đầu thực thi coroutine.
Khi bạn gọi coroutine.resume lần đầu, cho nó là đối số đầu tiên mà thread trả về bởi coroutine.create, coroutine bắt đầu thực thi, tại line đầu tiên của hàm main của nó. Các đối số thừa chuyển tới coroutine.resume được cho là tham số của hàm main của coroutine. Sau khi coroutine bắt đầu chạy, nó chạy tới khi ngắt hoặc nhường.
1 coroutine có thể ngắt thực thi của chính nó theo 2 cách : Bình thường, khi hàm main return (rõ ràng hay hoàn toàn, sau chỉ thị cuối cùng); và bất thường, khi có 1 lỗi ko bảo vệ. Trường hợp thứ nhất, coroutine.resume trả về true, với bất cứ giá trị nào trả về bởi hàm main của coroutine. Trong trường hợp lỗi, coroutine.resume trả về false với 1 thông báo lỗi.
1 coroutine nhường bằng cách gọi coroutine.yeild. Khi 1 coroutine nhường, coroutine.resume tương ứng return ngay lập tức, cho dù việc nhường xảy ra trong nested function call (ko phải hàm main, mà trong 1 hàm trực tiếp hay ko trực tiếp được gọi bởi hàm main). Trong trường hợp nhường, coroutine.resume cũng trả về true, cộng với bất cứ giá trị nào được chuyển cho coroutine.yeild. Lần tiếp theo bạn resume cùng coroutine, nó tiếp tục thực thi từ vị trí nó nhường, với lời gọi tới coroutine.yeild trả về bất cứ đối số thừa nào được chuyển cho coroutine.resume.
Hàm coroutine.wrap tạo ra 1 coroutine giống như coroutine.create, nhưng thay vì trả về chính coroutine, nó trả về 1 hàm mà khi được gọi, nó resume coroutine. Bất kỳ đối số nào được chuyển cho hàm đó coi như đối số thừa để resume. Hàm đó return tất cả các giá trị được trả về bởi lệnh resume, trừ cái đầu tiên (mã lỗi boolean). Ko giống coroutine.resume, hàm này ko bắt lỗi. Bất cứ lỗi nào đều được truyền cho hàm gọi.
VD :
Code:
function foo1 (a)
print("foo", a)
return coroutine.yield(2*a)
end
co = coroutine.create(function (a,b)
print("co-body", a, b)
local r = foo1(a+1)
print("co-body", r)
local r, s = coroutine.yield(a+b, a-b)
print("co-body", r, s)
return b, "end"
end)
a, b = coroutine.resume(co, 1, 10)
print("main", a, b)
a, b, c = coroutine.resume(co, "r")
print("main", a, b, c)
a, b, c = coroutine.resume(co, "x", "y")
print("main", a, b, c)
a, b = coroutine.resume(co, "x", "y")
print("main", a, b)
Khi bạn chạy, nó tạo ra kết quả sau :
Code:
Trên đây tớ đã dịch tới phần 2.10 trong hướng dẫn Lua. Phần này dịch nhanh nên thế nào cũng có nhiều lỗi. Tuy nhiên những lỗi này nhỏ và ko ảnh hưởng tới phần chính sẽ nói sau. Phần 3 và 4 ko quan trọng nên bỏ qua ko nói. Phần quan trọng là phần 5 quá dài mà cũng ko quá khó nên tớ sẽ copy/paste ra thay vì trans, sau đó sẽ viết phần chính : Karaoke script Very Happy
Trích dẫn:
5 - Standard Libraries
The standard libraries provide useful functions that are implemented directly through the C API. Some of these functions provide essential services to the language (e.g., type and getmetatable); others provide access to "outside" services (e.g., I/O); and others could be implemented in Lua itself, but are quite useful or have critical performance to deserve an implementation in C (e.g., sort).
All libraries are implemented through the official C API and are provided as separate C modules. Currently, Lua has the following standard libraries:
basic library;
string manipulation;
table manipulation;
mathematical functions (sin, log, etc.);
input and output;
operating system facilities;
debug facilities.
Except for the basic library, each library provides all its functions as fields of a global table or as methods of its objects.
To have access to these libraries, the C host program must first call the functions luaopen_base (for the basic library), luaopen_string (for the string library), luaopen_table (for the table library), luaopen_math (for the mathematical library), luaopen_io (for the I/O and the Operating System libraries), and luaopen_debug (for the debug library). These functions are declared in lualib.h.
5.1 - Basic Functions
The basic library provides some core functions to Lua. If you do not include this library in your application, you should check carefully whether you need to provide some alternative implementation for some of its facilities.
assert (v [, message])
Issues an error when the value of its argument v is nil or false; otherwise, returns this value. message is an error message; when absent, it defaults to "assertion failed!"
collectgarbage ([limit])
Sets the garbage-collection threshold to the given limit (in Kbytes) and checks it against the byte counter. If the new threshold is smaller than the byte counter, then Lua immediately runs the garbage collector (see 2.9). If limit is absent, it defaults to zero (thus forcing a garbage-collection cycle).
dofile (filename)
Opens the named file and executes its contents as a Lua chunk. When called without arguments, dofile executes the contents of the standard input (stdin). Returns any value returned by the chunk. In case of errors, dofile propagates the error to its caller (that is, it does not run in protected mode).
error (message [, level])
Terminates the last protected function called and returns message as the error message. Function error never returns.
The level argument specifies where the error message points the error. With level 1 (the default), the error position is where the error function was called. Level 2 points the error to where the function that called error was called; and so on.
_G
A global variable (not a function) that holds the global environment (that is, _G._G = _G). Lua itself does not use this variable; changing its value does not affect any environment. (Use setfenv to change environments.)
getfenv (f)
Returns the current environment in use by the function. f can be a Lua function or a number, which specifies the function at that stack level: Level 1 is the function calling getfenv. If the given function is not a Lua function, or if f is 0, getfenv returns the global environment. The default for f is 1.
If the environment has a "__fenv" field, returns the associated value, instead of the environment.
getmetatable (object)
If the object does not have a metatable, returns nil. Otherwise, if the object's metatable has a "__metatable" field, returns the associated value. Otherwise, returns the metatable of the given object.
gcinfo ()
Returns two results: the number of Kbytes of dynamic memory that Lua is using and the current garbage collector threshold (also in Kbytes).
ipairs (t)
Returns an iterator function, the table t, and 0, so that the construction
for i,v in ipairs(t) do ... end
will iterate over the pairs (1,t[1]), (2,t[2]), ..., up to the first integer key with a nil value in the table.
loadfile (filename)
Loads a file as a Lua chunk (without running it). If there are no errors, returns the compiled chunk as a function; otherwise, returns nil plus the error message. The environment of the returned function is the global environment.
loadlib (libname, funcname)
Links the program with the dynamic C library libname. Inside this library, looks for a function funcname and returns this function as a C function.
libname must be the complete file name of the C library, including any eventual path and extension.
This function is not supported by ANSI C. As such, it is only available on some platforms (Windows, Linux, Solaris, BSD, plus other Unix systems that support the dlfcn standard).
loadstring (string [, chunkname])
Loads a string as a Lua chunk (without running it). If there are no errors, returns the compiled chunk as a function; otherwise, returns nil plus the error message. The environment of the returned function is the global environment.
The optional parameter chunkname is the name to be used in error messages and debug information.
To load and run a given string, use the idiom
assert(loadstring(s))()
next (table [, index])
Allows a program to traverse all fields of a table. Its first argument is a table and its second argument is an index in this table. next returns the next index of the table and the value associated with the index. When called with nil as its second argument, next returns the first index of the table and its associated value. When called with the last index, or with nil in an empty table, next returns nil. If the second argument is absent, then it is interpreted as nil.
Lua has no declaration of fields; There is no difference between a field not present in a table or a field with value nil. Therefore, next only considers fields with non-nil values. The order in which the indices are enumerated is not specified, even for numeric indices. (To traverse a table in numeric order, use a numerical for or the ipairs function.)
The behavior of next is undefined if, during the traversal, you assign any value to a non-existent field in the table.
pairs (t)
Returns the next function and the table t (plus a nil), so that the construction
for k,v in pairs(t) do ... end
will iterate over all key-value pairs of table t.
pcall (f, arg1, arg2, ...)
Calls function f with the given arguments in protected mode. That means that any error inside f is not propagated; instead, pcall catches the error and returns a status code. Its first result is the status code (a boolean), which is true if the call succeeds without errors. In such case, pcall also returns all results from the call, after this first result. In case of any error, pcall returns false plus the error message.
print (e1, e2, ...)
Receives any number of arguments, and prints their values in stdout, using the tostring function to convert them to strings. This function is not intended for formatted output, but only as a quick way to show a value, typically for debugging. For formatted output, use format (see 5.3).
rawequal (v1, v2)
Checks whether v1 is equal to v2, without invoking any metamethod. Returns a boolean.
rawget (table, index)
Gets the real value of table[index], without invoking any metamethod. table must be a table; index is any value different from nil.
rawset (table, index, value)
Sets the real value of table[index] to value, without invoking any metamethod. table must be a table, index is any value different from nil, and value is any Lua value.
require (packagename)
Loads the given package. The function starts by looking into the table _LOADED to determine whether packagename is already loaded. If it is, then require returns the value that the package returned when it was first loaded. Otherwise, it searches a path looking for a file to load.
If the global variable LUA_PATH is a string, this string is the path. Otherwise, require tries the environment variable LUA_PATH. As a last resort, it uses the predefined path "?;?.lua".
The path is a sequence of templates separated by semicolons. For each template, require will change each interrogation mark in the template to packagename, and then will try to load the resulting file name. So, for instance, if the path is
"./?.lua;./?.lc;/usr/local/?/?.lua;/lasttry"
a require "mod" will try to load the files ./mod.lua, ./mod.lc, /usr/local/mod/mod.lua, and /lasttry, in that order.
The function stops the search as soon as it can load a file, and then it runs the file. After that, it associates, in table _LOADED, the package name with the value that the package returned, and returns that value. If the package returns nil (or no value), require converts this value to true. If the package returns false, require also returns false. However, as the mark in table _LOADED is false, any new attempt to reload the file will happen as if the package was not loaded (that is, the package will be loaded again).
If there is any error loading or running the file, or if it cannot find any file in the path, then require signals an error.
While running a file, require defines the global variable _REQUIREDNAME with the package name. The package being loaded always runs within the global environment.
setfenv (f, table)
Sets the current environment to be used by the given function. f can be a Lua function or a number, which specifies the function at that stack level: Level 1 is the function calling setfenv.
As a special case, when f is 0 setfenv changes the global environment of the running thread.
Code:
loadfile (filename)
Loads a file as a Lua chunk (without running it). If there are no errors, returns the compiled chunk as a function; otherwise, returns nil plus the error message. The environment of the returned function is the global environment.
loadlib (libname, funcname)
Links the program with the dynamic C library libname. Inside this library, looks for a function funcname and returns this function as a C function.
libname must be the complete file name of the C library, including any eventual path and extension.
This function is not supported by ANSI C. As such, it is only available on some platforms (Windows, Linux, Solaris, BSD, plus other Unix systems that support the dlfcn standard).
loadstring (string [, chunkname])
Loads a string as a Lua chunk (without running it). If there are no errors, returns the compiled chunk as a function; otherwise, returns nil plus the error message. The environment of the returned function is the global environment.
The optional parameter chunkname is the name to be used in error messages and debug information.
To load and run a given string, use the idiom
assert(loadstring(s))()
next (table [, index])
Allows a program to traverse all fields of a table. Its first argument is a table and its second argument is an index in this table. next returns the next index of the table and the value associated with the index. When called with nil as its second argument, next returns the first index of the table and its associated value. When called with the last index, or with nil in an empty table, next returns nil. If the second argument is absent, then it is interpreted as nil.
Lua has no declaration of fields; There is no difference between a field not present in a table or a field with value nil. Therefore, next only considers fields with non-nil values. The order in which the indices are enumerated is not specified, even for numeric indices. (To traverse a table in numeric order, use a numerical for or the ipairs function.)
The behavior of next is undefined if, during the traversal, you assign any value to a non-existent field in the table.
pairs (t)
Returns the next function and the table t (plus a nil), so that the construction
for k,v in pairs(t) do ... end
will iterate over all key-value pairs of table t.
pcall (f, arg1, arg2, ...)
Calls function f with the given arguments in protected mode. That means that any error inside f is not propagated; instead, pcall catches the error and returns a status code. Its first result is the status code (a boolean), which is true if the call succeeds without errors. In such case, pcall also returns all results from the call, after this first result. In case of any error, pcall returns false plus the error message.
print (e1, e2, ...)
Receives any number of arguments, and prints their values in stdout, using the tostring function to convert them to strings. This function is not intended for formatted output, but only as a quick way to show a value, typically for debugging. For formatted output, use format (see 5.3).
rawequal (v1, v2)
Checks whether v1 is equal to v2, without invoking any metamethod. Returns a boolean.
rawget (table, index)
Gets the real value of table[index], without invoking any metamethod. table must be a table; index is any value different from nil.
rawset (table, index, value)
Sets the real value of table[index] to value, without invoking any metamethod. table must be a table, index is any value different from nil, and value is any Lua value.
require (packagename)
Loads the given package. The function starts by looking into the table _LOADED to determine whether packagename is already loaded. If it is, then require returns the value that the package returned when it was first loaded. Otherwise, it searches a path looking for a file to load.
If the global variable LUA_PATH is a string, this string is the path. Otherwise, require tries the environment variable LUA_PATH. As a last resort, it uses the predefined path "?;?.lua".
The path is a sequence of templates separated by semicolons. For each template, require will change each interrogation mark in the template to packagename, and then will try to load the resulting file name. So, for instance, if the path is
"./?.lua;./?.lc;/usr/local/?/?.lua;/lasttry"
a require "mod" will try to load the files ./mod.lua, ./mod.lc, /usr/local/mod/mod.lua, and /lasttry, in that order.
The function stops the search as soon as it can load a file, and then it runs the file. After that, it associates, in table _LOADED, the package name with the value that the package returned, and returns that value. If the package returns nil (or no value), require converts this value to true. If the package returns false, require also returns false. However, as the mark in table _LOADED is false, any new attempt to reload the file will happen as if the package was not loaded (that is, the package will be loaded again).
If there is any error loading or running the file, or if it cannot find any file in the path, then require signals an error.
While running a file, require defines the global variable _REQUIREDNAME with the package name. The package being loaded always runs within the global environment.
setfenv (f, table)
Sets the current environment to be used by the given function. f can be a Lua function or a number, which specifies the function at that stack level: Level 1 is the function calling setfenv.
As a special case, when f is 0 setfenv changes the global environment of the running thread.
If the original environment has a "__fenv" field, setfenv raises an error.
setmetatable (table, metatable)
Sets the metatable for the given table. (You cannot change the metatable of a userdata from Lua.) If metatable is nil, removes the metatable of the given table. If the original metatable has a "__metatable" field, raises an error.
tonumber (e [, base])
Tries to convert its argument to a number. If the argument is already a number or a string convertible to a number, then tonumber returns that number; otherwise, it returns nil.
An optional argument specifies the base to interpret the numeral. The base may be any integer between 2 and 36, inclusive. In bases above 10, the letter `A´ (in either upper or lower case) represents 10, `B´ represents 11, and so forth, with `Z´ representing 35. In base 10 (the default), the number may have a decimal part, as well as an optional exponent part (see 2.2.1). In other bases, only unsigned integers are accepted.
tostring (e)
Receives an argument of any type and converts it to a string in a reasonable format. For complete control of how numbers are converted, use format (see 5.3).
If the metatable of e has a "__tostring" field, tostring calls the corresponding value with e as argument, and uses the result of the call as its result.
type (v)
Returns the type of its only argument, coded as a string. The possible results of this function are "nil" (a string, not the value nil), "number", "string", "boolean, "table", "function", "thread", and "userdata".
unpack (list)
Returns all elements from the given list. This function is equivalent to
return list[1], list[2], ..., list[n]
except that the above code can be written only for a fixed n. The number n is the size of the list, as defined for the table.getn function.
_VERSION
A global variable (not a function) that holds a string containing the current interpreter version. The current content of this string is "Lua 5.0".
xpcall (f, err)
This function is similar to pcall, except that you can set a new error handler.
xpcall calls function f in protected mode, using err as the error handler. Any error inside f is not propagated; instead, xpcall catches the error, calls the err function with the original error object, and returns a status code. Its first result is the status code (a boolean), which is true if the call succeeds without errors. In such case, xpcall also returns all results from the call, after this first result. In case of any error, xpcall returns false plus the result from err.
5.2 - Coroutine Manipulation
The operations related to coroutines comprise a sub-library of the basic library and come inside the table coroutine. See 2.10 for a general description of coroutines.
coroutine.create (f)
Creates a new coroutine, with body f. f must be a Lua function. Returns this new coroutine, an object with type "thread".
coroutine.resume (co, val1, ...)
Starts or continues the execution of coroutine co. The first time you resume a coroutine, it starts running its body. The arguments val1, ... go as the arguments to the body function. If the coroutine has yielded, resume restarts it; the arguments val1, ... go as the results from the yield.
If the coroutine runs without any errors, resume returns true plus any values passed to yield (if the coroutine yields) or any values returned by the body function (if the coroutine terminates). If there is any error, resume returns false plus the error message.
coroutine.status (co)
Returns the status of coroutine co, as a string: "running", if the coroutine is running (that is, it called status); "suspended", if the coroutine is suspended in a call to yield, or if it has not started running yet; and "dead" if the coroutine has finished its body function, or if it has stopped with an error.
coroutine.wrap (f)
Creates a new coroutine, with body f. f must be a Lua function. Returns a function that resumes the coroutine each time it is called. Any arguments passed to the function behave as the extra arguments to resume. Returns the same values returned by resume, except the first boolean. In case of error, propagates the error.
coroutine.yield (val1, ...)
Suspends the execution of the calling coroutine. The coroutine cannot be running neither a C function, nor a metamethod, nor an iterator. Any arguments to yield go as extra results to resume.
Code:
OK. Giờ tới phần chính mà nhiều người mong đợi nhất. Chính tớ cũng bất ngờ vì hiện giờ đã đạt được trình độ đủ để hiểu hết tất cả các script karaoke ko thiếu 1 dòng nào Very Happy Đây là 1 script mẫu. Tớ sẽ giải thích từng dòng một.
Code:
-- Include some useful extra functions
include("utils.lua") -- Include script khác vào để sử dụng 1 số hàm, tương tự bên C/C++
version, kind, configuration = 3, "basic_ass", {} -- Dòng này cứ để như lúc tạo file, ko nên sửa
name = "Sample script" -- Tên script
description = "This is an example script." -- Mô tả sơ qua script
-- Phía dưới là hàm chính. Lua automation script bao gồm 1 hàm
function process_lines(meta, styles, lines, config)
aegisub.output_debug(string.format("Number of lines to process: %d", lines.n)) -- Lệnh hỗ trợ debug, ko quan trọng
local output = {} -- Tạo 1 table rỗng để add các line output vào 1 loạt trước khi return về kết quả
output.n = lines.n -- Lấy số dòng của script để chắc chắn là làm hết
for i = 0, lines.n-1 do -- Vòng for dạng số
if lines[i].kind ~= "dialog" then
output[i] = lines[i]
-- Nếu gặp các dòng loại dialog thì bỏ qua, vì các dòng này ko cần thiết. Chỉ cần xử lý các dòng khác là đủ.
else
local newline = copy_line(lines[i])
-- Sử dụng hàm copy_line trong utils.lua được include ở đầu để copy line thứ i sang biến newline
newline.text = string.format("{\\fad(700,300)\\pos(%d,30)\\k100}" , meta.res_x/2)
-- Set text cho line mới bằng hàm string.format. Các ký tự backslash dùng 2 thay vì 1 theo quy ước đã nói ở bài đầu topic.
newline.start_time = newline.start_time - 100
-- Cho thời gian xuất hiện của line lùi lại 100 centisecond (1s)
local cursylpos = 1000 -- Biến cục bộ mới
for j = 1, lines[i].karaoke.n-1 do
-- Vòng for chạy từ 1 tới số syllable - 1 (ko xử lý syllable cuối)
local syl = lines[i].karaoke[j]
-- Biến cục bộ mới được gán = mỗi syllable của câu
newline.text = newline.text .. string.format("{\\t(%d,%d,\\fscx50\\fscy50)}%s", cursylpos, cursylpos + syl.duration*10, syl.text_stripped)
-- %d thứ nhất là cursylpos, %d thứ 2 là cursylpos + syl.duration*10. Cả 2 đều là số nguyên. %s là syl.text_stripped (ko có ass tag). Phần text này được ghép thêm vào cuối newline.text bằng toán tử concatenation (..)
cursylpos = cursylpos + syl.duration*10
-- tăng biến cursylpos lên sau mỗi lần lặp
end
output[i] = newline -- Gán giá trị của newline cho output[i]
end
end
return output -- Trả về kết quả
end
Code:
Chú ý : Lua phân biệt chữ hoa và thường. Việc ghi lùi đầu dòng hay ko ko nhất thiết, tuy nhiên nên căn dòng cho dễ viết.
- Script trên minh họa sơ qua chứ ko xài được vì nó dùng cho version cũ. Sau đây tớ sẽ nói thêm và nhắc lại 1 số điều cần thiết trước khi sử dụng script karaoke thực của automation ver 3. Very Happy
Các kiểu dữ liệu dùng trong Automation v3 : nil, boolean, number, string, function, table (userdata và thread ko dùng tới)
- Các biến sẽ giữ tham chiếu. Phép gán chỉ copy tham chiếu, ko phải giá trị. Như vậy với table phải copy theo từng field. Trừ khi được khai báo local (cục bộ), còn lại tất cả các biến đều là toàn cục (global). Các biến cục bộ có tầm hoạt động xác định.
- Có 2 cách khai báo hàm :
Code:
foo = function(bar)
...
end
và
Code:
function foo(bar)
...
end
Tớ thường xài cách 2. Ai thích xài cách 1 thì tùy.
Khai báo table rỗng :
Code:
foo = {}
Các toán tử :
- Cộng trừ nhân chia tính bình thường (+ - * /)
- Ko có chia lấy dư (%) như trong C
- Lũy thừa (^)
- Nối chuỗi (concatenation) với ".."
- Các phép gán dùng toán tử "=". Cho phép gán nhiều lần với 1 dấu "="
Code:
foo = bar
a, b = c, d
x, y = y, x -- Đổi chỗ 2 giá trị
So sánh : ==, ~=, <=, >=, <, >
Logic : and, or, not
Truy cập table có 2 cách :
- Truy cập kiểu mảng : foo[bar]
- Dùng theo cấu trúc dấu chấm như C, hoặc theo tên trường :
foo.bar
foo["bar"]
Cấu trúc điều khiển :
- Lệnh if :
if foo then
....
elseif bar then
...
else
...
end
Block được generate tự động, độc lập với việc chia dòng, ko nhất thiết phải nằm trong do ... end. Có thể sử dụng nhiều elseif. Phần else là optional. Chỉ có false và nil được tính là false, còn lại 0 hay chuỗi rỗng cũng là true.
- Lệnh for :
for i = a, b do
...
break
...
end
i chạy từ a tới b. Ko có lệnh continue trong vòng như C. Vòng for dạng iterator ko sử dụng trong Automation.
Lệnh return :
return a, b
Lệnh return có thể trả về bao nhiêu giá trị cũng được, kể cả none.
Các library sử dụng trong Automation :
Base (trừ hàm dofile được thay thế bởi include)
Coroutine
String
Table manipulating
Math
Các thư viện File I/O, OS facilities và Debug ko được sử dụng vì lý do an toàn.
process_lines
function process_lines (meta, styles, lines, config)
Hàm này là prototype, kiểu mẫu cho mọi hàm.
Nguyên lý cơ bản ở đây là hàm process_lines được gọi với 1 số dòng sub làm dữ liệu nhập, và trả về 1 thay thế hoàn toàn cho dữ liệu nhập này.
Các đối số :
meta (table) : 1 cấu trúc metadata (dữ liệu cơ bản của 1 file, ở đây là ass)
styles (table) : danh sách các style trong file
Chỉ số -1 chứa số entry. Các entries này được đánh số từ 0 tới n-1.
Vì mỗi style đều có tên nên có thể truy cập thông qua tên. Nên sử dụng cách này để đỡ nhầm lẫn trong quá trình viết.
lines (table) : Danh sách các dòng sub
Chỉ số n chứa số entries. Số entries có chỉ số được đánh dấu từ 0 tới n-1.
config (table) : 1 table chứa configuration setting mà người sử dụng chọn để chạy với script. Xem thêm về configuration system để biết thêm thông tin. Tớ thì ko chú ý cái này mấy vì chả cần.
Return value
1 table có cùng dạng với đối số lines. Nó chứa chỉ số n, mô tả số lines trong kết quả. Bảng sub được đánh số từ 1 tới n ko có "lỗ".
Thay đổi này là trong ver 1.07. Để tương thích, bạn có thể dùng từ 0 tới n-1.
Các biến đặc biệt sau được định nghĩa sẵn :
name (string) : Tên script, hiển thị trong giao diện của Automation manager. Tên này phải ko được trùng vì nó sử dụng để phân biệt các script trong khi export hoặc apply. Khai báo cái này là bắt buộc.
description (string) : Mô tả dài hơn việc script này làm cái gì, và cũng được hiển thị trong Automation manager. Có thể là xâu rỗng hay thế nào tùy ý, tuy nhiên phải có.
version (number) : Version của Automation mà script sẽ sử dụng cho. Bắt buộc phải có. Ở đây là 3.
kind (string) : Trong trường hợp Aegisub về sau hỗ trợ nhiều hơn 1 loại script chuyển đổi file ASS, biến này sẽ cho biết đó là loại script nào. Cái này cũng bắt buộc và phải điền giá trị "basic_ass".
configuration (table) : Định nghĩa 1 số lượng tùy chọn mà người dùng có thể chọn cho script trước khi chạy. Những tùy chọn này sẽ được ghi trên giao diện của Export dialogue, vd như khi dùng k-replacer. Cái này là phụ. Nếu ko đề thì coi như table rỗng.
process_lines (function) : Hàm gọi proces subtitle data. Cái này là bắt buộc. Coi thêm thông tin phía trên để biết rõ.
Cấu trúc dữ liệu :
a/ Meta data :
Chứa các thông tin cơ bản về subtitles. Nó tương tự phần [Script Info] trong file ASS. Hiện tại, chỉ có 2 phần được định nghĩa :
res_x (number)
res_y (number)
Đây là độ phân giải của script, tính theo "màn hình ảo" mà sub được render trên đó. Hiện tại ko có cách nào đưa pixel aspect ratio về đúng aspect ratio của chính video. Vì vậy, trước khi sử dụng script, nhớ là phải resample script resolution bằng Aegisub đã.
b/ Style data :
Style table chứa định nghĩa ASS style. 1 danh sách style tables được chuyển tới hàm process_lines và cũng được sử dụng khi gọi hàm aegisub.text_extents. 1 style table có chứa những trường sau :
name (string) : Tên style, có thể phải dùng nhiều lần trong khi sử dụng effect.
fontname (string) : Tên font. Có phân biệt chữ hoa và chữ thường.
fontsize (number) : Kích thước font
color1 (string) : Primary color của text. Màu ghi theo tag ASS.
color2 (string) : Secondary color của text.
color3 (string) : Màu viền.
color4 (string) : Màu shadow.
bold (boolean)
italic (boolean)
underline (boolean)
strikeout (boolean)
Text có bold/italic/underlined/striked out hay ko.
scale_x (number)
scale_y (number)
Tỉ lệ X và Y của text theo %.
spacing (number) : Khoảng cách giữa các chữ theo pixel
angle (number) : Góc quay của text theo trục Z tính bằng độ.
borderstyle (number) : Border style được sử dụng. (1=viền ngoài, 3=box đặc.)
outline (number) : Độ dày viền tính theo pixel.
shadow (number) : Khoảng cách shadow tính theo pixel
align (number) : Line alignment (Coi lại hướng dẫn Aegisub nếu quên)
margin_l (number)
margin_r (number)
margin_v (number)
Biên phải, trái và dọc tính theo pixels.
encoding (number) : Font encoding.
c/ Subtitle data :
1 subtitle table đại diện cho 1 line trong 1 file ASS. Ngoài các dialog-lines ra, nó còn có thể là 1 comment line dạng dialog, 1 semicolon comment line hay 1 line trống.
1 subtitle table luôn chứa những trường sau :
kind (string) : Là 1 trong số "dialog", "comment", "scomment" và "blank". Trường này cho biết line thuộc loại gì trong cả file sub. Bạn nên luôn kiểm tra lại điều này trước khi thử truy cập vào các trường khác.
Nếu giá trị là "blank", ko cần các field khác nữa, vì nó chỉ là dòng trống.
Nếu giá trị là "scomment", line đó là 1 semicolon comment line, tức là 1 line mà ký tự đầu tiên là dấu chấm phẩy. Trong khi những comment như thế này thường bị bỏ qua trong quá trình xử lý bình thường, bạn có thể sử dụng chúng để chứa thêm các đối số cần thiết cho 1 effect. Với những line loại "scomment", các trường sau được định nghĩa thêm :
text (string) : Text nằm sau dấu chấm phẩy cho tới hết line, bao gồm cả các khoảng trống nhưng ko có dấu chấm phẩy hay ký tự ngắt dòng.
Nếu giá trị là "dialog" hay "comment", line có dạng 1 dialog line bình thường. Điểm khác nhau là 1 "comment" line bắt đầu với từ khóa "Comment:" thay vì "dialog:" và các comment lines ko được render khi play sub.
Các line dạng dialog có chứa thêm các trường sau :
layer (number) : Layer của line được render. Line có layer số lớn hơn được render nằm trên các line có số bé hơn, tương tự như trong PS hay AE.
start_time (number)
end_time (number)
Start và end time của line, tính bằng centiseconds. Để lấy độ dài line tính bằng milliseconds để dùng cho effect sử dụng phép tính sau: (end_time - start_time) * 10
style (string) : Tên của style gắn với line đó. Cái này sẽ cần dùng tới trong nhiều trường hợp cần set style lại khi sử dụng effect.
name (string) : Tên của nhân vật nói trong line đó. Trường này ko sử dụng khi render, nhưng sẽ có ích cho việc chứa thêm dữ liệu dùng làm tham số cho effect.
margin_l (number)
margin_r (number)
margin_v (number)
Override biên trái, phải và dọc cho line, tính bằng pixels.
effect (string) : Các effect đặc biệt áp dụng cho line. Có 3 cái đã nhắc tới trong hướng dẫn Aegisub. Điền giá trị bậy bạ vào cũng chả ảnh hưởng gì, nên có thể dùng nó để chứa thêm tham số ở đây.
text (string) : Nguyên phần text gốc của line, kể cả các ASS override tags.
text_stripped (string) : Text của line đã bỏ đi toàn bộ override tags và drawing tags.
karaoke (table) : 1 danh sách karaoke syllables trong line, luôn chứa ít nhất 1 yếu tố. Các yếu tố này là karaoke tables.
Chỉ số n trong table chứa số syllables. Các syllables được đánh số từ 0 tới n-1. Cái này tương tự bên C/C++
Syllable đầu tiên trong danh sách trước karaoke tag đầu tiên của line, và thường được bỏ qua.
Code:
d/ Karaoke data :
1 karaoke table chứa thông tin về 1 karaoke syllable. 1 karaoke syllable được định nghĩa bắt đầu với 1 override tag, với ký tự đầu tiên của tên tag là k hay K, theo sau có thể có 1 hay vài ký tự khác, sau nữa là các số nguyên. Các số này có thể bằng 0 hoặc lớn hơn. Tên tag, bao gồm cả ký tự đầu (k hoặc K), được gọi là "loại" của syllable, và số là độ dài của syllable.
1 karaoke table chứa những trường sau :
duration (number) : Độ dài của syllable tính theo milliseconds. Syllable đầu tiên của 1 line luôn có độ dài bằng 0.
kind (string) : Loại của syllable. Syllable đầu tiên của 1 line luôn có loại "" (chuỗi rỗng)
text (string) : Text trong syllable, kể cả các override tags. Bất kỳ khối override tag nào tại đầu và cuỗi text đều được coi như đóng. Syllable đầu tiên trong line có tất cả phần text cho đến karaoke tag đầu tiên. Nếu sử dụng cái này thì phải cẩn thận.
text_stripped (string) : Tương tự text nhưng loại bỏ toàn bộ các tag. Trường này thường được sử dụng khi add các tag vô để tạo effect.
Hàm thư viện :
Các hàm thư viện được cung cấp :
function include (filename)
Include hàm đã đặt tên. Script dò đường dẫn được định nghĩa sẵn trong Aegisub sẽ được sử dụng để tìm script.
Nếu tên file là tương đối, đường dẫn tìm kiếm sẽ ko được sử dụng, nhưng thay vì đó, tên file được lấy tương đối với đường dẫn mà script hiện tại của script.
Chú ý là nếu bạn sử dụng include() trong 1 script có include, đường dẫn tương đối vẫn được lấy tương đối với script gốc, và ko tương đối với script được include. Đây là giới hạn của việc thiết kế.
Script được include được load thành 1 hàm anonymous, được thực thi trong môi trường hiện tại. Cái này có 2 ý :
- Bạn có thể include các files dựa trên câu lệnh điều kiện, cho dù trong vòng lặp (để include cùng file nhiều lần)
- Các file đã include có thể trả về giá trị sử dụng lệnh return
@filename (string) : Tên file sẽ include
Return value : Tùy theo file được include
Nếu vài lỗi xuất hiện trong khi include file, việc thực thi script sẽ bị ngắt. (Điều này nghĩa là script có thể load ko được, nếu hàm include được gọi trong khi khởi tạo).
function aegisub.output_debug (text) : Xuất text ra debug console. Ai muốn xài thì tùy. Tớ ko xài.
@text (string) : Text sẽ xuất
Return value : Ko có
function aegisub.set_status (text) : Set status-message hiện thời. Thông báo này sẽ thấy trong quá trình biên dịch script.
@text (string) : Status-message.
Return value : Ko có
function aegisub.colorstring_to_rgb (colorstring) : Chuyển chuỗi màu dạng ASS sang tập giá trị RGB.
@colorstring (string) : Chuỗi màu sẽ convert
Return value : 4 giá trị đều là số, là thành phần màu theo thứ tự : Red, Green, Blue, Alpha
function aegisub.report_progress (progress) : Báo cáo tiến trình của script. Hiệu quả nhất cho script cần nhiều thời gian để tính toán kết quả cuối cùng.
@progress (number) : Quá trình của script theo %. Giá trị này nằm giữa 0 và 100. Nếu giá trị nằm ngoài tầm, nó sẽ được điều chỉnh lại trong khoảng 0 tới 100.
function aegisub.text_extents (style, text) : Tính toán kích thước render của text với style đã cho. Kết quả hiện lên dưới dạng 1 box chứa xung quanh text trước khi rotate, và ko bao gồm viền hay shadow.
Nếu cần cho box chứa text rotate, nên lấy size của cái unrotated đã, rồi tính toán rotated size từ đó.
@style (table) : Style dùng để tính toán size, bắt buộc phải ở dạng table hợp lệ. (Thông thường bạn chỉ index style table được chuyển tới hàm process_lines). Chỉ có những trường sau được sử dụng trong khi tính toán size : fontname, fontsize, bold, italic, underline, strikeout, scale_x, scale_y và spacing.
@text (string) : Text sẽ được sử dụng để tính toán size. Ko nên để nó chứa override tags hay blocks, vì sẽ được coi như 1 phần text và ko sử dụng với ý nghĩa thực của nó. Các ký tự \n, \N và \h cũng thế.
Return value : Hàm này trả về 4 giá trị theo thứ tự sau :
1. Chiều rộng text tính theo pixels (number)
2. Chiều cao của text tính theo pixels (number)
3. Descent của font tính theo pixels (number) <-- WTF is this?
4. Leading ngoài của font tính theo pixels (number)
Tất cả các giá trị trả về được scale đúng bằng làm tròn sử dụng làm tròn C rounding semantic.
Bạn có thể tìm được nhiều thông tin hơn về nhiều kỹ thuật đo in trong tài liệu FreeType 2.
Hàm này hiện được thực thi bằng wxWidgets wxFont và wxDC classes, có nghĩa nó nhận thông tin size từ chính Windows. Trong các phiên bản tương lai, có thể thay đổi để sử dụng thư viện FreeType 2 thay thế. Lúc điều đó xảy ra, nhiều phép đo kích thước sẽ có thể dùng được.
function aegisub.frame_from_ms (ms) : Trả về giá trị của frame trong video (bằng số) tính theo thời điểm đã cho.
@ms (number) : Thời gian tính bằng milisecond để lấy frame number.
Return value : Frame number (dạng số). Nếu ko có framerate data, trả về nil.
function aegisub.ms_from_frame (frame) : Trả về start-time từ video frame-number.
@frame (number) : Frame-number để lấy start-time.
Return value : Start-time của frame (bằng số). Nếu ko có framerate data, trả về nil.
Included script :
Tiếp theo tớ sẽ nói về 1 số script và include files được Aegisub cung cấp sẵn.
- Demo scripts (10 files)
- 1 vài hàm thêm được coi như "thư viện chuẩn" có thể tìm thấy trong file utils.lua
- Để đơn giản hòa việc viết script, có các script Karaskel giúp tăng tốc độ viết lên nhiều, từ đó ta có thể giảm được thời gian căn thời gian hay tọa độ và tập trung vào effect.
a/ Demo scripts :
Hiện tại có 10 file, nằm trong thư mục C:\Program Files\Aegisub\automation\demos\ nếu cài đặt theo như mặc định
1-minimal.lua : Script đơn giản nhất, chỉ gọi hàm output_debug và đưa dữ liệu ra ko thay đổi.
2-dump.lua : Dữ liệu ra cũng ko đổi, nhưng cho thấy làm cách nào đẻ lặp dữ liệu. Hầu hết dữ liệu được kết xuất với hàm output_debug.
3-include.lua : Includes utils.lua và sử dụng hàm copy_line để duplicate line đầu tiên trong script, cho biết làm thế nào để include file và thêm lines vào sub.
4-text_extents.lua : Sử dụng hàm text_extents để tính size của từng syllable riêng biệt trong karaoke và đặt theo đúng thứ tự trên màn hình, center dọc hay ngang. Để hiện effect, các syllable di chuyển lên phía trên 1 chút.
5-configuration.lua : Cho thấy cách định nghĩa mọi loại tùy chọn configuration, và sử dụng 1 số để thay đổi dữ liệu nhập.
6-simple-effect.lua : Hướng dẫn cách tạo 1 effect đơn giản.
7-advanced-effect.lua : Hướng dẫn cách tạo effect phức tạp hơn bằng nhiều line cho mỗi syllable, và đối với mỗi syllable thì làm khác nhau tùy theo loại tag của nó.
8-skeleton.lua : Sử dụng karaskel.lua để tạo effect, giảm thiểu những bước mà hầu hết script đều có. Dùng để tạo effect đơn giản.
9-skeleton-advanced.lua : Tạo ra hiệu ứng phức tạp hơn với karaskel.lua.
10-furigana.lua : Sử dụng furigana bằng karaskel-adv.lua.
b/ Utils.lua :
Để sử dụng thì thêm lệnh sau :
include("utils.lua")
Các hàm sau được định nghĩa trong utils.lua:
function copy_line (input) : Copy nguyên 1 line table.
Đây là hàm được sử dụng rất thường xuyên để tạo line mới dựa trên những line đã có từ file sub. 1 phép gán đơn giản ko thể sử dụng được, vì nó chỉ copy tham chiếu của line table. Mọi thứ trừ karaoketable trong line đã copy có thể được thay đổi 1 cách an toàn mà ko ảnh hưởng tới các bản sao khác của line.
Xem demo file thứ 9 để biết thêm chi tiết.
@input (table) : Line sẽ copy.
Return value : Bản sao của input line.
function ass_color (r, g, b) : Tạo ASS hex color từ giá trị RGB đã cho
@r (number)
@g (number)
@b (number)
Giá trị các thành phần RGB
Return value : Chuỗi ASS hex color code tạo ra từ dữ liệu nhập.
function HSV_to_RGB (H, S, V)
Chuyển màu được ghi dưới định dạng HSV (hue, saturation, luminance) thành giá trị RGB.
@H (number) : Hue (Màu)
@S (number) : Saturation (Độ xám) của màu. Càng thấp càng ngả về xám. Càng cao thì màu càng đậm.
@V (number) : Value (Độ sáng) của màu.
Return value : Trả về 3 số (theo thứ tự) : RGB của HSV đã cho.
function trim (s) : Bỏ khoảng trống ở đầu và cuối chuỗi đã cho.
@s (string) : Chuỗi sẽ loại bỏ khoảng trống.
Return value : Chuỗi ko có khoảng trống ở đầu và cuối.
function next_utf_char (str, off)
Lấy chỉ số của ký tự tiếp theo từ @off trong chuỗi UTF-8 @str. @off bị loại bỏ khỏi giá trị trả về của hàm là số byte bị lấy đi bởi ký tự UTF-8 được chỉ tới bởi @off.
@str (string) : Chuỗi UTF-8 làm tham số
@off (number) : Byte-offset trong chuỗi cho ký tự hiện tại
Return value : Chỉ số trong chuỗi cho ký tự tiếp theo.
function utf_len (str) : Lấy số ký tự (ko phải byte) trong chuỗi UTF-8
@str (string) : Chuỗi sẽ lấy độ dài
Return value : Số ký tự trong chuỗi được cho
function string.headtail (s) : Lấy "head" và "tail" của chuỗi, coi nó như danh sách các từ được tách biệt 1 hay vài khoảng trống.
@s (string) : Chuỗi sẽ lấy head/tail
Return values : 2 chuỗi. 1 là head, 2 là tail.
Nếu @s là chuỗi rỗng, trả về 2 chuỗi rỗng.
Nếu @s ko có khoảng trống, head là @s và tail là chuỗi rỗng.
function xor (a, b) : Tính toán xor của 2 giá trị. (Khác hay giống)
@a, @b (anything) : 2 giá trị sẽ tính xor
Return values :
Nếu chỉ có @a ko phải false, trả về @a (true)
Nếu chỉ có @b ko phải false, trả về @b (true)
Còn lại trả về false.
Code:
c/ Karaskel :
Karaskel là tên của 1 họ những include files được Automation cung cấp. Karaskel script cung cấp phần lớn code là "bộ xương" mà bạn thường xuyên cần đến khi viết script karaoke, cho phép bạn tập trung vào effect, thay vì phải xây dựng code và tính toán tọa độ, vân vân...
Ý tưởng của Karaskel là tính toán trước thông tin position và timing thường cần đến, và cấu trúc dữ liệu xuất bằng cách tự động gọi những hàm nào đó để xử lý lines và syllables. Bạn có thể thấy 1 vài mẫu script cơ bản sử dụng Karaskel trong demo file 8, 9, 10. Coi như demo 6 và 7 là mẫu cho biết làm cách nào ko làm ra karaoke. 2 script này khá khó đọc vì ko xài Karaskel.
Có 2 Karaskel hiện tại : "cơ bản" và "nâng cao". Khả năng bạn sử dụng chúng là rất cao. Sau đây là miêu tả sơ bộ :
Đơn giản :
- 1 line vào, 1 line ra
- Có thể viết 1 hàm thay thế text cho mỗi syllable.
- Có thể tùy ý thêm text vào đầu và cuối mỗi line.
- Ko khác mấy so với simple-k-replacer, nhưng dễ quản lý hơn.
Nâng cao:
- 1 line vào, nhiều line ra (2 trở lên --> n)
- Có thể viết hàm tạo ra nhiều line cho mỗi syllable.
- Hỗ trợ furigana (xem demo 10)
- Tương tự multi-template, nhưng dễ quản lý hơn đối với effect phức tạp.
Code:
Các trường mới được thêm vào quá trình tính toán trước :
Hàm tính toán trước dữ liệu syllable thêm 1 số trường mới cho cả line tables và syllable tables :
meta.res_x (number)
meta.res_y (number)
Có thể bằng 0 hoặc bất định trong vài trường hợp. Trong trường hợp đó, Karaskel bắt chước hành vi của VSFilter, tính toán cái này từ cái kia, hoặc nếu cùng bằng 0, sử dụng mặc định là 384x288.
line.i (number) : Chỉ số dòng, đếm theo mảng lines, bắt đầu từ 0.
line.prev (table)
line.next (table)
Tham chiếu tới các lines tiếp theo và trước, cho phép truy cập các line trong 1 danh sách liên kết đôi. Nhớ chắc chắn rằng chúng ko phải là nil trước khi sử dụng.
line.styleref (table) : Tham chiếu tới style table mô tả style được sử dụng bởi line. Trong trường hợp line có 1 style chưa định nghĩa, style được định nghĩa đầu tiên được thế vào. Điều này là để tránh 1 vài lỗi crash khó sửa. Mỗi khi thay thế xong, sẽ có 1 cảnh báo.
line.text_stripped (string) : Mặc dù đây ko phải là trường mới, tuy nhiên ý nghĩa của nó sẽ thay đổi nếu enable furigana. Khi enable furigana, trường này được thay đổi chỉ để chứa phần text cơ bản mà ko có furigana.
line.width (number)
line.height (number)
line.ascent (number)
line.extlead (number)
Kết quả đơn giản của aegisub.text_extents được áp dụng cho line, sử dụng using line.styleref làm style.
line.centerleft (number)
line.centerright (number)
Cạnh trái và phải của line, khi center ngang trên màn hình. Chú ý rằng hiện chúng ko đúng khi 2 biên phải và trái khác nhau. Các trường này có ích khi liên kết với syl.left, syl.center và syl.right khi định vị trí cho mỗi syllable nằm cân ngay giữa màn hình.
line.duration (number) : Độ dài line tính theo milliseconds.
line.ool_fx (table) : String-indexed table của hiệu ứng kết thúc line. Từ đầu tiên (cách theo khoảng trống) trong mỗi ool fx line định nghĩa tên của ool fx đó, và tên được sử dụng để index ở đây. Xem karaskel.process_lines trong Aegiwiki để biết thêm thông tin.
syl.i (number) : Chỉ số của syllable trên mỗi line, tính từ 0, với chỉ số 0 là trước syllable đầu tiên (Kiểm tra chỉ số 0 có thể là cách đơn giản để tạo ra đoạn bắt đầu line.
syl.inline_fx (string) : Tên của inline fx cho syllable này, được reset lại giá trị mặc định cho mỗi line. Nếu inline fx ko được định nghĩa rõ ràng cho mỗi syllable, nó giống như syllable trước. Hiệu ứng inline được định nghĩa với 1 override tag được định dạng đặc biệt, như là : {\-foo} đặt trước syllable. Ở đây định nghĩa inline fx là "foo". Xem thêm trên Aegiwiki để biết thêm chi tiết.
syl.width (number)
syl.height (number)
syl.ascent (number)
syl.extlead (number)
Kết quả text_extents cho syllable.
syl.left (number)
syl.right (number)
syl.center (number)
Cạnh trái, phải và center của syllable, liên quan tới cạnh trái của line. Chú ý rằng những cái này là cố điều chỉnh lại vị trí cho mỗi syllable, với khoảng trắng bị loại bỏ và 1 vài thứ khác. Hiệu ứng của những cái đó chưa được test, nhưng xem ra hoạt động có hiệu quả.
syl.furigana (table) : Table với furigana data cho syllable, chỉ được tạo ra khi karaskel.engage_furigana = true.
syl.highlights (table) : Table với nhiều highlight data cho syllable, chỉ được tạo ra khi karaskel.engage_furigana = true.
syl.text_stripped (string) : Thay đổi giống như line.text_stripped khi furigana được enabled.
syl.start_time (number)
syl.end_time (number)
Start và end times của syllable, liên quan với line start time theo milliseconds.
Các tùy chọn thêm :
2 Karaskel scripts đều dựa trên 1 nền tảng là file karaskel-base.lua. Quan trọng nhất ở chỗ đây là nơi hàm karaskel.precalc_syllable_data được định nghĩa. Vì vậy nếu bạn muốn hàm đó mà ko có gì khác của Karaskel, bạn chỉ cần include file karaskel-base.lua.
Hơn nữa, Karaskel base định nghĩa 1 số biến mà bạn có thể sử dụng để điều chỉnh hành vi của nó, liên quan nhiều đến cái được tính toán trước, và khi nào thì thông báo debug sẽ xuất hiện. Các tùy chọn sau là có thể dùng được. Nên set chúng trong script có Karaskel, như trong demo 10.
karaskel.engage_positioning (boolean) : Cho phép tính toán tọa độ. Với karaskel=adv, nó được tự enable.
karaskel.precdalc_start_progress (number)
karaskel.precdalc_end_progress (number)
2 biến này định nghĩa tầm mà progress bar bao phủ trong khi syllable data được tính toán trước. Mặc định là 0 tới 50.
karaskel.engage_furigana (boolean) : Cho phép dùng và tính toán furigana. Xem trên Aegiwiki để biết thêm chi tiết.
karaskel.furigana_scale (number) : Định nghĩa tỉ lệ furigana text liên quan tới size của baseline text. Mặc định là 0.4, nghĩa là furigana = 40% size của text thông thường.
Chú ý rằng furigana được tự động "bóc" để tạo khoảng trống cho tất cả các ký tự.
karaskel.inline_fx_default (string) : Tên inline fx mặc định. Xem thêm trên Aegiwiki.
karaskel.ool_fx_style (boolean/string) : Đặt style cho out-of-line. Cũng có thể false, để biểu thị là hiệu ứng out-of-line ko được sử dụng, hoặc 1 chuỗi đặt tên 1 style. Mặc định là false. Xem thêm trên Aegiwiki.
karaske.engage_trace (boolean) : Hiện thông báo debug hay ko. Ít khi sử dụng tới. Cứ để nguyên là false.
Basic karaskel :
Để sử dụng, trước tiên bạn cần include file karaskel.lua vào script. Việc này sẽ định nghĩa hàm process_syllables, và cũng include luôn utils.lua và karaskel-base.lua.
include("karaskel.lua")
Cách sử dụng cơ bản :
Điều đầu tiên và quan trọng nhất phải làm sau khi include là định nghĩa 1 hàm do_syllable :
Code:
function do_syllable(meta, styles, config, line, syl)
return syl.text_stripped
end
Đây là 1 hàm do_syllable rất đơn giản, nhàm chán và loại bỏ tất cả các tag ra khỏi text. Để thấy rõ hiệu ứng, bạn cần add thêm 1 số tag vào (xem demo Cool and cute.
Nói cách khác, ý tưởng cơ bản của hàm do_syllable là bạn thay thế text cho syllable.
Cách sử dụng nâng cao :
Có các hàm khác bạn có thể định nghĩa để thay đổi hành vi của karaskel.
do_line_decide(meta, styles, config, line) : Trả về 1 giá trị boolean. Nếu trả về true, line đó sẽ được xử lý, nếu ko bị loại (ra output ko thay đổi).
do_line_start(meta, styles, config, line) : Trả về 1 chuỗi, thêm chuỗi trả về vào đầu line.
do_line_end(meta, styles, config, line) : Trả về 1 chuỗi và thêm vào cuối line.
do_line(meta, styles, config, line) : Trả về 1 mảng các lines. Bạn thường ko cần override hàm này, chỉ khi cần hành vi rất đặc biệt. Nếu bạn override, nhớ rằng hãy đọc hàm gốc được định nghĩa trong karaskel.lua, hoặc gọi karaskel.do_line ở vị trí nào đó. Nếu bạn cần override hàm này, nhớ sử dụng advanced karaskel thay thế.
1 vd sử dụng karaskel.lua có thể thấy trong simple-k-replacer.lua.
Advanced karaskel :
Include karaskel-adv.lua vào trong script. Nó sẽ định nghĩa hàm process_syllables, và include cả utils.lua và karaskel-base.lua.
include("karaskel-adv.lua")
Cách sử dụng cơ bản :
Tương tự, sau khi include file karaskel-adv.lua, bạn sẽ cần định nghĩa 1 hàm do_syllable.
Code:
function do_syllable(meta, styles, config, line, syl)
local l = copy_line(line)
l.text = string.format("{\\an8\\pos(%d,%d)}%s", line.centerleft+syl.center, line.styleref.margin_v syl.text_stripped)
return { n=1, [1]=l }
end
Hàm do_syllable đơn giản này được đặt chính giữa đỉnh màn hình. Xem demo 9 để biết thêm chi tiết. Ngoài ra có thể tham khảo script line-per-syllable.
Cách sử dụng nâng cao :
Trong nhiều trường hợp, bạn có thể cần dùng 1 vài hàm đặc biệt cho mỗi line trước khi bắt đầu xử lý syllables. Bạn có thể override hàm do_line. Nhớ gọi karaskel.do_line để quay lại quá trình xử lý bình thường sau đó
Code:
function do_line(meta, styles, config, line)
aegisub.output_debug("Processing line: " .. line.i)
return karaskel.do_line(meta, styles, config, line)
end
Nếu sử dụng inline fx, có thể tính đến 1 trick như sau :
Code:
function do_syllable(meta, styles, config, line, syl)
local result = {n=0}
_G["fx_"..syl.inline_fx](meta, line, syl, result)
return result
end
Code:
Đây sẽ là bài cuối cùng của hướng dẫn. Bài này tớ sẽ hướng dẫn viết code theo kinh nghiệm của tớ. Sau khi đọc xong bài này, các cậu sẽ đủ sức để viết nguyên 1 script karaoke. Smile
Khi làm karaoke, luôn luôn nhớ 3 yếu tố : 1 là time control, 2 là positioning, 3 là effect. Nếu nắm bắt được điều này và vận dụng tốt có thể làm được bất cứ effect nào, miễn là trong khả năng của lua.
Trong hình trên, có 4 thời điểm cần nhớ là lúc bắt đầu và kết thúc line, cũng như lúc bắt đầu và kết thúc mỗi syllable.
Sau đây là script mẫu tớ vừa viết. Script này rất đơn giản và ko có nhiều effect phức tạp, chỉ gồm 1 intro, 1 outro, và 1 effect phóng to chữ cho syllable.
Code:
name = "Sample"
description = "Simple multilines karaoke script"
configuration = {}
version, kind = 3, 'basic_ass'
include("karaskel-adv.lua")
br=0
time=1
function do_syllable(meta, styles, config, line, syl)
local result = {n=0}
function result.add()
local l = copy_line(line)
table.insert(result, l)
return l
end
--------Check for empty lines--------
if syl.text == "" then
return { n=0 }
end
if syl.text == " " then
return { n=0 }
end
if syl.text == " " then
return { n=0 }
end
if syl.text == " " then
return { n=0 }
end
-------------------------------------
------------Coordinate definition-------------
local x = syl.center + meta.res_x-line.width - 10
local y = line.styleref.margin_v + 10
----------------------------------------------
----check if a new line has started, and zero the counter if it has; increase the counter----
if time ~= line.start_time then
time = line.start_time
br = -1
end
br = br+1
---------------------------------------------------------------------------------------------
----------Check the line for . , " and don't apply effect if it's one of then----------------
if (syl.text_stripped == ".") then
elseif (syl.text_stripped == ",") then
elseif (syl.text_stripped == "\"") then else
---------------------------------------------------------------------------------------------
-----------Intro-------------------------------------
l = result.add()
l.layer = 1
l.text = string.format("{\\an5\\pos(%d, %d)\\fad(300, 0)}%s", x, y, syl.text_stripped)
l.start_time = line.start_time - 30
l.end_time = line.start_time
-----------Wait to syllable--------------------------
l = result.add()
l.layer = 1
l.text = string.format("{\\an5\\pos(%d, %d)}%s", x, y, syl.text_stripped)
l.start_time = line.start_time
l.end_time = line.start_time + syl.start_time/10
-----------Syllable effect---------------------------
l = result.add()
l.layer = 2
l.text = string.format("{\\an5\\pos(%d, %d)\\t(\\fscx150\\fscy150)}%s", x, y, syl.text_stripped)
l.start_time = line.start_time + syl.start_time/10
l.end_time = line.start_time + syl.start_time/10 + syl.duration
-----------Wait to the end---------------------------
l = result.add()
l.layer = 1
l.text = string.format("{\\an5\\pos(%d, %d)}%s", x, y, syl.text_stripped)
l.start_time = line.start_time + syl.start_time/10 + syl.duration
l.end_time = line.end_time
-----------Outro-------------------------------------
l = result.add()
l.layer = 1
l.text = string.format("{\\an5\\pos(%d, %d)\\fad(0, 300)}%s", x, y, syl.text_stripped)
l.start_time = line.end_time
l.end_time = line.end_time + 30
-----------------------------------------------------
end
return result
end
2 biến toàn cục br và time hiện chưa dùng tới. Tuy nhiên nếu cần cho các syllable xuất hiện theo thứ tự liên tục, ko cùng lúc thì sẽ cộng thêm 1 bội số của br vào l.start_time hay l.end_time.
Biến lục bộ result được khởi tạo là 1 table rỗng, ko có biến.
Hàm result.add sẽ copy line vào và chèn vào cuối table. Cú pháp này tương tự bên C#.
Phần "Check for empty lines" sẽ giúp skip các dòng trống ko cần thiết.
Phần tính toán tọa độ, ở đây text nằm theo chiều ngang nên y sẽ cố định tính từ biên dọc cộng thêm 10 pixel nữa. Về x như trong script nếu muốn align right. Align left và center thì như sau :
local x = syl.center + 10 (Left)
local x = line.centerleft + syl.center (Center)
Đoạn code tiếp theo kiểm tra nếu có 1 line mới bắt đầu rồi thì tăng biến đếm br lên. Biến này rất tiện dụng khi muốn các syllable xuất hiện hay biến mất theo thứ tự.
Đoạn code tiếp theo kiểm tra các dấu và ko apply effect cho các ký tự này.
5 đoạn code tiếp theo là effect, bao gồm 5 giai đoạn như đã ghi. Thời gian được tính toán như đã thấy. Mỗi lần muốn thêm dòng, cứ gọi hàm result.add.
2 chỗ %d đó là 2 số nguyên x và y nằm trong câu lệnh. Nếu đã học hàm printf của C thì phải biết cái này.
Về layer, chỉ có đoạn code thứ 3 có layer 2 để nó nổi lên trên các syllable xung quanh.
Đoạn code 2 và 4 là lúc syllable ko hoạt động nên effect cũng đơn giản. Thông thường sẽ cần thêm 1 tag color và đoạn code thứ 4 để cho thấy hiệu ứng sau syllable. Ở đây để đơn giản nên tớ bỏ đi.
Đoạn code 1 là intro, 5 là outro. Cả 2 đều kéo dài 0.3s với 2 effect ngược nhau (fade in và fade out).
Đoạn 3 là đoạn quan trọng nhất : syllable. Effect chính tập trung ở đây.
Về tính toán thời gian. Cần chú ý là thời gian của line tính bằng centisecond, còn syllable tính bằng milliseconds, nên độ lớn của nó gấp 10 lần. Để tính toán cho đúng, trước khi cộng vào phải chia syl.start_time và syl.end_time cho 10.
Cuối cùng là return result như đã thấy.
Cứ từ từ nghiên cứu cái đoạn code này và xem 10 demo là thoải mái viết code về sau
Last edited: