Engine3
Selepas bertahun-tahun membuat konsep, kini tiba masa untuk nombor 3

Engine3
Dalam artikel tentang alat pembangunan anda sudah boleh membaca apa yang akan saya gunakan untuk membuat permainan Signal From The Stars.
Oleh sebab Love2d ialah rangka kerja yang hanya mempunyai beberapa fungsi asas yang ringkas, saya mendapat idea untuk membuat enjin asas dahulu, kemudian permainan di atasnya. Oleh sebab saya sudah beberapa kali membuat konsep seperti ini dalam hidup saya, ini akan menjadi enjin ketiga, sebab itu namanya engine3.
Kelebihannya, saya mungkin boleh melancarkan engine3 sebagai open source, dan juga suatu hari nanti membuat permainan yang sama sekali berbeza. Semua ini berada dalam repositori git yang berasingan, yang akan digunakan oleh permainan kemudian.
Jadi pada awalnya saya belum benar-benar mengerjakan permainan, tetapi bermula dengan menulis sedikit logik. Sudah tentu saya melakukannya dengan memikirkan apa yang sebenarnya akan digunakan dalam permainan. Malah, semua yang tidak saya perlukan tidak akan saya masukkan ke dalam rangka kerja (less is more).
Dari segi fail, struktur engine3 kelihatan seperti ini:

Struktur asas engine3
Pustaka, modul dan mod
Ada perkara yang anda mahu buat sendiri, ada yang tidak. Sebabnya, mencipta semula roda sangat mahal. Anda kehilangan masa tambahan dan perlu menguji serta menyelenggaranya sendiri.
Sebab itu ada folder lib dalam engine3. Di dalamnya ada beberapa pustaka yang bukan saya buat sendiri, tetapi diterbitkan contohnya di bawah lesen MIT.
- Binser https://github.com/bakpakin/binser
- Saya gunakan ini untuk menyimpan data, seperti simpanan permainan.
- Json https://github.com/rxi/json.lua
- Digunakan untuk membaca data json (sandaran)
- Msgpack https://framagit.org/fperrad/lua-MessagePack
- Json memang bagus, tetapi msgpack memproses fail berkali-kali ganda lebih pantas
- Tick https://github.com/rxi/tick
- Dengan ini anda boleh memanggil fungsi pada masa yang ditetapkan, contohnya selepas 3 saat
- Vector https://github.com/themousery/vector.lua
- Semua objek bergerak dengan vektor
Ada juga folder lib-dev yang mengandungi modul pihak ketiga yang hanya diperlukan semasa pembangunan.
- ProFi https://gist.github.com/perky/2838755
- Dengan ini anda boleh melihat sama ada anda menggunakan terlalu banyak memori
Menulis kod
Sangat penting untuk menulis kod secara konsisten dalam penamaan kelas, fungsi, pemboleh ubah dan nama fail. Begitu juga dalam penggunaan format kod dan anotasi. Dengan itu kod kekal mudah dibaca dan kemudian lebih mudah melihat di mana sesuatu tidak kena. Selain itu, orang lain juga lebih mudah memahami kod. Untuk itu saya menggunakan https://luals.github.io/wiki/.
Secara peribadi saya rasa nama program itu, lua language server, agak mengelirukan, tetapi ia melakukan tepat apa yang saya mahu.
Selepas pemasangan, anda boleh menulis baris — dalam kod seperti di bawah.
---@param str string The string to check
---@param ending string The chararacter to check
local function endsWith(str, ending)
return ending == "" or str:sub(-(#ending)) == ending
end
Ini nampak tidak penting, tetapi membuatkan Visual Studio Code memberi amaran jika saya contohnya melakukan ini:
local something = endsWith({something = "a"}, "b") -- invalid str
Salah satu kelemahan Lua berbanding contohnya GO ialah Lua tidak mempunyai semakan sebegini dan oleh itu mudah berlaku ralat.
Dokumentasi
Tiada siapa membaca dokumentasi… Tetapi saya tahu ia akan diperlukan dalam jangka panjang, terutamanya jika enjin ini menjadi open source. Nasib baik lua language server juga ada penjana .md yang menghasilkan keseluruhan dokumentasi. Saya melakukannya dengan skrip shell berikut, create_code_doc.sh
#!/bin/bash
echo "Generate code documentation"
cd ../
lua-language-server --logpath=temp --doc=src/ --doc_out_path=codedoc
Semakan tambahan
Ujian boleh sangat mengganggu dan memakan masa, terutamanya apabila anda membina sesuatu dari kosong. Anda masih sentiasa menguji dan mengubah banyak perkara. Saya rasa perkataan refactor ialah perkataan yang paling banyak digunakan dalam git log.
Walaupun begitu saya tetap menggunakan unit test dan integration test.

Struktur asas ujian engine3
Skrip shell check_code.sh kini kelihatan begini, dan saya boleh menjalankannya bila-bila masa, contohnya sebelum setiap git push atau kemudian dalam pipeline.
#!/bin/bash
cd ../
luacheck --no-cache --config scripts/.luacheckrc src
echo "UNIT TEST"
./unittest.sh
echo "INTEGRATION TEST"
./integrationtest.sh
Semakan kod
Dengan program https://github.com/mpeterv/luacheck anda boleh menyemak (atau sengaja tidak menyemak) pelbagai perkara.
Unit test
Untuk ini saya menggunakan https://github.com/bluebird75/luaunit, dan setiap unit test mempunyai fail sendiri dengan fungsi yang berkaitan.
test-order.lua
local lu = require("luaunit")
TestClass = {}
local numKeyTable = {
[1] = {name = "a", order = 1},
[2] = {name = "b", order = 2},
[3] = {name = "d", order = 3}
}
function TestClass:test1Sort()
table.sort(
numKeyTable,
function(a, b)
return a.order < b.order
end
)
lu.assertEquals(
numKeyTable,
{
[1] = {name = "a", order = 1},
[2] = {name = "b", order = 2},
[3] = {name = "d", order = 3}
}
)
end
os.exit(lu.LuaUnit.run())
Integration test
Menjadikannya berfungsi agak lebih sukar tetapi berguna, kerana ia perlu memulakan love2d dan melakukan sesuatu dengan input dan output. Di sini juga saya menggunakan luaunit, tetapi sebagai main.lua untuk memulakan love2d, seperti di bawah
local lu = require("luaunit")
local runner = lu.LuaUnit.new()
function love.load()
runner:runSuiteByInstances(
{
{"rect-helper", require "rect-helper"},
{"image-assets", require "image-assets"},
{"audio-assets", require "audio-assets"},
{"entity-manager", require "entity-manager"},
{"callback", require "callback"},
{"frameset", require "frameset"}
}
)
love.event.push("quit", 0)
end
Bina, bina, bina
Di tempat saya tinggal ada kekurangan rumah yang besar, dan slogan yang sering didengar ialah:
Bina, bina, bina
Engine3 sudah tentu juga perlu membina permainan, tetapi nasib baik saya hanya perlu melakukannya apabila permainan sudah siap atau apabila saya mahu mengujinya pada platform lain.
Skrip shell yang saya buat untuk ini buat masa ini ialah build.sh, dan ia akan diubah kemudian.
#!/bin/bash
source config
osx_CFBundleName=${APP_NAME}
osx_CFBundleIdentifier="com.SuperCompany."${APP_NAME}
ios_CFBundleVersion="11.3"
src_dir="../src/"
osx_build_with=${ENGINE3_SRC_DIR}"/scripts/osx-love-build-source/love.app"
win64_build_with=${ENGINE3_SRC_DIR}"/scripts/win-love-build-source/love-win64/"
win32_build_with=${ENGINE3_SRC_DIR}"/scripts/win-love-build-source/love-win32/"
build_dir="../build/"
build_love_file=${build_dir}${APP_NAME}".love"
build_temp_dir=${build_dir}"temp/"
build_osx_dir=${build_dir}"osx/"
build_win64_dir=${build_dir}"win64/"
build_win32_dir=${build_dir}"win32/"
build_linux_dir=${build_dir}"linux/"
current_script_dir=$(pwd)
echo "[1/7] Remove old build ${build_dir}"
rm -Rf ${build_dir}
mkdir -p ${build_dir}
mkdir -p ${build_temp_dir}
echo "[2/7] Copy only the files that we actual need"
cd ${src_dir}
# We don't want/need all files in the actuale build, for the end-user.
rsync --relative -rpKL \
--exclude=lib/engine3/archive \
--exclude=lib/engine3/codedoc \
--exclude=lib/engine3/docs \
--exclude=lib/engine3/scripts \
--exclude=lib/engine3/temp \
--exclude=lib/engine3/README.MD \
--exclude=lib/engine3/src/framework/helper/dump.lua \
--exclude=lib/engine3/src/lib-dev \
--exclude=lib/engine3/src/test \
--exclude=assets/*/*/*.json \
--exclude=assets/*/*.json \
--exclude=unittest \
--exclude=*.DS_Store* \
--exclude=*.gitignore* \
* \
${build_temp_dir}
# some modules are exclude, check for example if the dump( function is in use
check1=$(grep -rnw ${build_temp_dir} -e 'dump(')
if [ ! -z "$check1" ] ; then
echo "dump() function found, remove it in the source"
echo ${check1}
exit 3
fi
echo "[3/7] Switch to the temp directory"
cd ${build_temp_dir}
echo "[4/7] Create love file: ${build_dir}${APP_NAME}.love"
zip -9 -r --quiet ${APP_NAME}".love" .
cd ${current_script_dir}
mv ${build_temp_dir}${APP_NAME}".love" ${build_love_file}
echo "[5/7] Create osx executable: ${build_osx_dir}"
mkdir -p ${build_osx_dir}
cp -R ${osx_build_with} ${build_osx_dir}
mv ${build_osx_dir}"love.app" ${build_osx_dir}${APP_NAME}".app"
cp ${build_love_file} ${build_osx_dir}${APP_NAME}".app/Contents/Resources"
plutil -replace CFBundleName -string ${osx_CFBundleName} ${build_osx_dir}${APP_NAME}".app/Contents/Info.plist"
plutil -replace CFBundleIdentifier -string ${osx_CFBundleIdentifier} ${build_osx_dir}${APP_NAME}".app/Contents/Info.plist"
plutil -remove UTExportedTypeDeclarations ${build_osx_dir}${APP_NAME}".app/Contents/Info.plist"
echo "[6/7] Create win 64 executable: ${build_win64_dir}"
mkdir -p ${build_win64_dir}
cp -R ${win64_build_with} ${build_win64_dir}
cat ${build_win64_dir}"love.exe" ${build_love_file} > ${build_win64_dir}${APP_NAME}".exe"
echo "[7/7] Create win 32 executable: ${build_win32_dir}"
mkdir -p ${build_win32_dir}
cp -R ${win32_build_with} ${build_win32_dir}
cat ${build_win32_dir}"love.exe" ${build_love_file} > ${build_win32_dir}${APP_NAME}".exe"
# don't need this file anymore
#rm ${build_love_file}
# comment this line below, to see/debug what files are used for the actual build.
rm -rf ${build_temp_dir}
exit 0
Akhir kata
Seperti yang anda lihat, anda perlu pastikan asasnya kukuh dahulu. Sebelum sampai ke tahap itu memang mengambil masa, tetapi kemudian anda akan mendapat ganjarannya. Dahulu misalnya saya tiada ujian atau semakan sintaks. Pada mulanya masih okey, tetapi anda akan sampai ke satu tahap di mana anda sudah menulis begitu banyak kod sehingga semuanya menjadi kabur.
Anda perlu menganggap bahawa orang lain akan melihat kod itu dan perlu dapat memahaminya. Atau bahawa anda sendiri masih memahaminya 3 atau 10 tahun lagi.