✋ Hãy tự làm trước. Lời giải chỉ có ích khi bạn đã thử và bị kẹt ít nhất 15 phút. Đọc lời giải quá sớm sẽ lấy mất phần "à, ra thế!", mà đó lại là lúc bạn học được nhiều nhất.
Quay lại bài học: 00-bat-dau.md
⭐ Bản đồ namespace #
var withInit = Object.keys(OG).filter(function (k) {
return OG[k] && typeof OG[k].init === 'function';
});
console.log(withInit);
var notInList = withInit.filter(function (k) { return OG.FEATURE_INIT_ORDER.indexOf(k) < 0; });
console.log(notInList);Kết quả notInList gồm các module core: DOM, Input, Progress, cùng với Journeys.
Giải thích. Đọc hàm boot trong src/core/main.js:
OG.DOM.init(),OG.Input.init(canvas),OG.Progress.init()được gọi trực tiếp, đúng thứ tự cố định. Đây là phần core: thiếu chúng thì game không chạy, nên không cần "gọi mềm".OG.Journeys.init()được gọi riêng bằngOG.util.call('Journeys', 'init')sau mọi module tính năng. Lý do (comment trongboot): nó kiểm tra dữ liệu hành trình, mà dữ liệu đó tham chiếu tới trùm/nhân vật do module khác đăng ký.- Còn
OG.Savethì không cóinitmà cóload(), cũng đượcbootgọi trực tiếp.
Ý chính rút ra: core init cứng, tính năng init mềm. Bài 01 giải thích "mềm" nghĩa là gì.
⭐⭐ Cố ý làm sai thứ tự nạp #
Sau khi đổi chỗ, phần đầu index.html thành:
<script src="src/core/events.js"></script>
<script src="src/core/namespace.js"></script>Điều quan sát được:
Console trình duyệt báo lỗi đầu tiên ở
events.js, kiểuUncaught TypeError: Cannot set properties of undefined (setting 'EV'). Câu chữ có thể khác một chút tuỳ trình duyệt.Sau đó là hàng loạt lỗi dây chuyền ở các file sau:
OG.Eventskhông tồn tại nên mọi file gọiOG.Events.on(...)lúc nạp đều lỗi theo.Smoke test in một khối như dưới đây (dòng đầu là thông báo lỗi, các dòng sau là vị trí trong file) và kết thúc ĐỎ:
text✗ Lỗi khi load src/core/events.js: TypeError: Cannot set properties of undefined (setting 'EV') src/core/events.js:31 OG.EV = { ^Số dòng có thể lệch nếu
events.jsđã được sửa. Vì sao dòng thông báo lỗi lại đứng trước vị trí nguồn? Xem⚠️ Bẫyở hàmerrTextđầutests/smoke.js.
Giải thích. events.js mở đầu bằng var OG = window.OG;. Lúc này namespace.js chưa chạy nên window.OG là undefined.
Lệnh gán đầu tiên vào OG.EV ném TypeError và cả IIFE dừng lại: OG.Events không bao giờ được tạo. Không có import,
trình duyệt không biết events.js cần namespace.js. Ta phải tự xếp thứ tự.
Bài học rút ra: lỗi đầu tiên mới là lỗi thật, các lỗi sau thường chỉ là hệ quả. Luôn đọc console từ trên xuống.
Nhớ trả index.html về như cũ và chạy node tests/smoke.js cho XANH.
⭐⭐⭐ Thêm file src/dev/scene-logger.js #
/* ============================================================================
* scene-logger.js — IN RA CONSOLE MỖI LẦN ĐỔI SCENE (công cụ học tập)
* ----------------------------------------------------------------------------
* Vai trò: Nghe 'scene:change' và in "[scene] từ → tới". Không sửa trạng thái game.
* Khái niệm: module chỉ NGHE event (observer) · đăng ký lúc nạp file
* Bài học: docs/learn/00-bat-dau.md
* Phụ thuộc: OG.Events.on (đọc LÚC NẠP → file phải đứng sau src/core/events.js)
* Event: phát: (không) · nghe: 'scene:change'
* ========================================================================== */
(function () {
'use strict';
var OG = window.OG;
OG.Events.on('scene:change', function (e) {
// push/pop: scene đè lên (pause, câu đố...) hay gỡ xuống — in thêm để thấy scene stack hoạt động.
var kind = e.push ? ' (push)' : e.pop ? ' (pop)' : '';
console.log('[scene] ' + (e.from || '(chưa có)') + ' → ' + e.to + kind);
});
})();Thêm thẻ script vào index.html, ví dụ ngay trước nhóm 12 (main.js):
<script src="src/dev/scene-logger.js"></script>Vì sao đặt ở đây?
- File gọi
OG.Events.onngay lúc nạp, nên điều kiện tối thiểu là đứng sausrc/core/events.js. - Nhưng đặt cạnh các file tính năng, trước
main.js, thì người đọcindex.htmlhiểu ngay đây là một "module phụ". Hơn nữa nó nghe được cả lầnScenes.go('boot')đầu tiên trongboot(). - Nếu đặt sau
main.js: trên trình duyệtbootvẫn chờDOMContentLoadednên thường vẫn kịp. Nhưngindex.htmlđã ghi rõ quy ước "main.js LUÔN CUỐI", và khiboot()chạy ngay (trang đã nạp xong) thì file của bạn sẽ bỏ lỡ event đầu tiên. Đừng dựa vào may mắn.
Kiểm tra: mở game → console in [scene] (chưa có) → boot, [scene] boot → menu... Bấm Esc trong màn chơi để thấy (push)
và (pop). Chạy node tests/smoke.js: vẫn XANH và không thấy dòng [scene] nào, vì smoke test thay console.log của game bằng
bản giả chỉ in khi có cờ --verbose. Chạy node tests/smoke.js --verbose thì sẽ thấy hàng trăm dòng [scene] vì test đi qua
hàng trăm lần đổi scene.
Đây là lý do code thật nên có cờ bật/tắt. Thử bọc console.log trong if (OG.Config.DEBUG) xem sao.
Xong bài tập thì xoá thẻ script đó (hoặc giữ lại có cờ DEBUG).
src/đã được đóng gói khi deploy (.github/workflows/pages.yml) nên không cần sửa workflow.