JavaScript trong Engine

API script của Engine: vòng đời, các bẫy toạ độ, ba cách tạo một đối tượng, và cách di chuyển một đối tượng mà không xuyên tường.

Trong một câu#

Mọi entity đều có thể mang một script JavaScript. Nó chạy trong trình duyệt, trong tab của bạn, tác động trực tiếp lên scene đang sống, và đó là API script chính thống: dialect BASIC, cùng các view Python, C# và Rust trong trình biên tập, tất cả đều biên dịch xuống cùng một bề mặt này.

Danh sách đầy đủ là tài liệu tham khảo API: 538 thành viên trong 50 namespace, mỗi trang một mục, kèm tìm kiếm. Trang này là những gì bạn cần biết trước khi đọc nó.

Vòng đời#

function start() {}                     // once, when the entity starts
function update(dt) {}                  // every frame, dt in seconds
function fixedUpdate(dt) {}             // fixed timestep, for physics
function onCollisionEnter(other) {}     // first frame of contact
function onCollisionStay(other) {}      // every frame while touching
function onCollisionExit(other) {}      // contact just ended
function onTriggerEnter(other) {}       // entered a trigger volume
function onTriggerExit(other) {}
function onMessage(msg, data, senderId) {}   // entity-to-entity messaging
function onAnimationEvent(name, data) {}
function onDestroy() {}
function main() {}                      // console-game entry, async allowed

deltaTime, time, entityIdentity là các biến toàn cục; getComponent(type) đọc một component từ entity đang sở hữu script.

Va chạm và trigger nổ ra ở cả 2D lẫn 3D, và mỗi bên nhận pháp tuyến tiếp xúc của riêng nó: other.contact.normal trỏ từ entity kia hướng về entity này. Đó là thứ cho phép một script phân biệt "tôi đáp xuống nó" với "nó đáp xuống tôi", một cú nhảy đè kiểu platformer đọc other.contact.normal.y > 0.5.

Hai bẫy toạ độ#

Cả hai đều tốn thời gian debug thật sự, và không cái nào báo lỗi ồn ào.

Chuột: page so với canvas#

input.mousePosition ở toạ độ page. scene.pick() muốn toạ độ canvas. Truyền cái này thay cho cái kia không trượt mục tiêu, nó trúng một điểm bị lệch đi đúng bằng vị trí của viewport trong cửa sổ, nên một cú click-to-move cho nhân vật sẽ đi đến một chỗ nghe có vẻ hợp lý nhưng sai.

Gọi scene.pick() không có tham số (nó nhắm vào con trỏ), hoặc dùng input.mouseViewport khi bạn cần các con số cụ thể.

Virtual control ở đơn vị pixel canvas#

input.addVirtualJoystick(id, x, y, size)input.addVirtualButton(...) đặt tâm của chúng trong cùng không gian với mouseViewport. Một joystick trả về một vector X/Y đã chuẩn hoá sau vùng chết hình tròn của nó; một button phơi bày held, pressed-this-framereleased-this-frame riêng biệt. Con trỏ của chúng bị bắt lấy và tiêu thụ, nên một cú chạm lên control không đồng thời bị báo cáo như một hành động canvas thô.

Joystick mặc định là hợp nhất. input.getJoystickX/Y() chọn ra một vector hoàn chỉnh từ cần điều khiển trên màn hình, một gamepad vật lý, hoặc bàn phím (WASD/mũi tên), tuỳ nguồn nào mạnh hơn. Một pad đã kết nối nhưng đang rảnh vì vậy không vô hiệu hoá di chuyển bằng bàn phím. Trục Y của nó theo không gian màn hình 2D: lên là -1.

Ba cách tạo một đối tượng, và chúng không thể thay thế cho nhau#

Nó tạo ra gìSống trong bao lâu
scene.createEntity(def)một entity có tác giả: xuất hiện trong Scene Graph và Inspectorsuốt dự án, nó được lưu lại
game.spawn(type, pos)một primitive trần trụi: không máu, không AI, không hành viphiên chơi
game.spawnFrom(template, pos, opts)một bản sao của cả một entity: component, physics body, tag, layer, tỉ lệ và cả chồng hành vi đã biên dịchphiên chơi

game.spawn đúng cho mảnh vỡ và bản nháp và vô dụng cho kẻ địch: một bộ sinh được nuôi bằng nó tạo ra những quả bóng vô tri. Template mà spawnFrom sao chép là một entity bình thường bạn dựng trong trình biên tập và tắt đi bằng con mắt trong hierarchy: không có định dạng riêng, không có trình biên tập thứ hai, nên những gì bạn thấy là những gì xuất hiện.

// Once, at start: a missing template should say so, not fail every wave silently
if (!game.hasTemplate('Skeleton')) console.warn('no Skeleton template');

const mob = game.spawnFrom('Skeleton', { x, y, z }, { tag: 'Enemy' });

template nhận một id, một tên hoặc một tag. Các bản sao tạo ra trong lúc chơi chỉ tồn tại trong phạm vi phiên chơi, nên stop() sẽ xoá chúng và scene biên tập không bao giờ bị vấy bẩn.

Tắt một entity nghĩa là ẩn ĐI VÀ im lặng: node bị vô hiệu hoá, script dừng lại, physics body không còn phản hồi. Chỉ ẩn thôi sẽ để lại một kẻ địch vô hình vẫn đang đánh trúng bạn. game.setEnabled(id, on) điều khiển nó từ một script, biểu tượng con mắt điều khiển nó từ hierarchy.

Di chuyển một thứ gì đó: đẩy một body, đừng dịch chuyển tức thời#

Ghi transform.position ở mỗi khung hình sẽ xuyên qua tường, xuyên qua quái vật khác và xuyên qua sàn. Hãy điều khiển velocity thay vào đó, nhưng chỉ khi có gì đó để điều khiển:

if (physics.hasBody) {
  const v = physics.getVelocity();
  physics.setVelocity({ x: vx, y: v.y, z: vz });   // pushes a real body
} else {
  transform.position.x += vx * dt;                 // legitimate for a flyer
}

physics.setVelocity khi không có body sẽ chỉ điền vào một field không ai tích phân, và entity dừng khựng lại. Đó là lý do physics.hasBody tồn tại, và tại sao mọi hành vi di chuyển trong catalogue đều hỏi câu hỏi đó thay vì giả định câu trả lời. Dừng lại nghĩa là huỷ velocity: không làm gì cả sẽ để body tiếp tục trôi.

Ghi một vị trí giờ đây thật sự di chuyển một physics body. Havok điều khiển transform, không bao giờ theo chiều ngược lại, nên việc gán transform.position trên một body động từng bị ghi đè ở bước tiếp theo: mọi lần dịch chuyển tức thời từng âm thầm thất bại trên một entity có vật lý. Proxy vị trí giờ đồng bộ lại body, theo cách mà proxy xoay vẫn luôn làm.

Pathfinding là một bản chụp nhanh#

pathfinding.createGrid() bắn ray một lưới một lần duy nhất, lấy tâm ở nơi nó được gọi. Rời khỏi vùng đó trả về null, và hầu hết các bên gọi sau đó sẽ dùng lại một đường thẳng, xuyên qua tường, mà không báo một lời nào.

Lưới tự canh giữa lại theo mặc định cho mỗi chuyến đi được yêu cầu. setAutoRecenter(false) cho phép bỏ qua điều này trên một thế giới cố định, nơi việc dựng lại lưới tốn cả một lượt bắn ray một cách vô ích.

console.log ghi log. Print vẽ.#

Console là console.log / warn / error, như ở bất kỳ đâu khác. Print của BASIC vẽ lên màn hình và được gọi ở mỗi khung hình, hai lệnh này cố tình tách biệt nhau, và nhầm lẫn giữa chúng là thứ làm đầy một console.

Script chạy ở đâu#

Trong trình biên tập và trong một bản build web, script chạy trong trình duyệt. Trong một bản build desktop hay mobile native, chúng chạy không đổi trên một runtime QuickJS nhúng sẵn, cùng một mã nguồn, không phải một bản port.

Script được lưu trong cơ sở dữ liệu riêng của trình duyệt trong lúc bạn chỉnh sửa, nên một buffer chưa lưu sống sót qua một lần tải lại; chúng được lưu vào dự án cùng với mọi thứ khác.

Giới hạn và các lỗi thường gặp#

  • scene.pick() khi có tham số muốn toạ độ canvas. Xem ở trên; đó là lỗi sai-chỗ phổ biến nhất.
  • game.spawn tạo một primitive, không phải một nhân vật. Dùng spawnFrom.
  • Một body được tạo cho các entity sinh ra giữa lúc chơi. Physics từng chỉ được dựng ở lúc Play, nên bất cứ thứ gì sinh ra sau đó từng âm thầm rơi xuyên qua thế giới trong khi hàng xóm được đặt sẵn bằng tay lại hoạt động bình thường.
  • Tám bước là giới hạn của đội ngũ AI, không phải của bạn. Một vòng lặp script không có giới hạn như vậy; một while chạy trốn sẽ treo khung hình giống như ở bất cứ đâu khác.
  • Bốn view ngôn ngữ còn lại được transpile từ view này. Chỉnh sửa một view Python hay Rust rồi quay lại sẽ đi qua đúng bản JavaScript đã lưu, xem trang BASIC để biết lý do.