SDK là JavaScript thuần gắn ở window.WSG, nên engine nào xuất được ra HTML5 cũng dùng được. Cách làm chung: nhúng /sdk/v1/wsg-sdk.js vào index.html của bản export, rồi từ code của engine gọi sang JavaScript ở đúng các thời điểm của vòng đời game.
Phaser 3
Với template trong repo
Game làm trong repo WebSuperGames dùng WsgPhaserPlugin của package nội bộ @wsg/game-kit: plugin tự gọi loadingProgress/loadingFinished theo scene Preload/Menu, gameplayStart/gameplayStop theo scene Game, ngủ vòng lặp và tắt tiếng khi có pause, và cung cấp this.wsg.submitScore() không ném lỗi. Xem Bắt đầu nhanh, phần B.
Phaser không dùng game-kit
@wsg/game-kit không phát hành ra ngoài, nhưng tự nối Phaser với SDK chỉ cần vài dòng. Ví dụ dưới chạy được với Phaser 3 tải bằng thẻ script (phaser.min.js đặt trong bundle) hoặc qua bundler:
const GAME_ID = 'my-phaser-game';
let loadingDone = false;
let userMuted = false;
class Preload extends Phaser.Scene {
constructor() {
super('Preload');
}
preload() {
WSG.loadingStart();
this.load.on('progress', (value) => WSG.loadingProgress(value * 100));
// this.load.image('player', 'assets/player.png'); // chỉ dùng đường dẫn tương đối
}
create() {
this.scene.start('Menu');
}
}
class Menu extends Phaser.Scene {
constructor() {
super('Menu');
}
create() {
if (!loadingDone) {
loadingDone = true;
WSG.loadingFinished(); // chỉ một lần, khi menu đầu tiên hiện ra
}
this.add.text(360, 640, 'Tap to play', { fontSize: '48px' }).setOrigin(0.5);
this.input.once('pointerdown', () => this.scene.start('Game'));
}
}
class Game extends Phaser.Scene {
constructor() {
super('Game');
}
create() {
this.score = 0;
this.scoreText = this.add.text(24, 24, '0', { fontSize: '40px' });
this.input.on('pointerdown', () => this.scoreText.setText(String(++this.score)));
this.time.delayedCall(10000, () => this.gameOver());
WSG.gameplayStart();
this.events.once('shutdown', () => WSG.gameplayStop());
}
async gameOver() {
WSG.gameplayStop();
this.scene.pause();
const result = await WSG.leaderboard.submit(this.score);
this.scene.launch('GameOver', { score: this.score, result });
}
}
class GameOver extends Phaser.Scene {
constructor() {
super('GameOver');
}
create({ score, result }) {
const line = result.accepted ? `#${result.rank}` : result.reason;
this.add.text(360, 600, `${score}\n${line}`, { fontSize: '40px', align: 'center' }).setOrigin(0.5);
this.input.once('pointerdown', async () => {
await WSG.ads.interstitial({ placement: 'between_rounds' });
this.scene.start('Game');
});
}
}
async function boot() {
await WSG.init({ gameId: GAME_ID });
const game = new Phaser.Game({
type: Phaser.AUTO,
parent: 'game',
backgroundColor: '#171430',
scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH, width: 720, height: 1280 },
scene: [Preload, Menu, Game, GameOver],
});
WSG.on('pause', () => {
game.loop.sleep();
game.sound.mute = true;
});
WSG.on('resume', () => {
game.loop.wake();
game.sound.mute = userMuted;
});
WSG.on('audio', (enabled) => {
game.sound.mute = !enabled || userMuted;
});
}
boot();game.loop.sleep()dừng hẳn vòng lặp (đồng hồ, tween, vật lý);game.loop.wake()chạy lại.Menucó thể được tạo nhiều lần, nên dùng cờ đểloadingFinished()chỉ gọi một lần.- Với Vite, đặt
base: './'và giữ thẻ<script src="/sdk/v1/wsg-sdk.js">trongindex.html; Vite sẽ cảnh báo không gộp được thẻ script này, đó là điều mong muốn.
Canvas và JavaScript thuần
Ví dụ đầy đủ ở Bắt đầu nhanh. Những điểm cần giữ:
let paused = false;
let last = 0;
WSG.on('pause', () => (paused = true));
WSG.on('resume', () => {
paused = false;
last = 0; // bỏ qua quãng đã dừng
});
function frame(ts) {
const dt = last ? Math.min(ts - last, 100) : 0; // kẹp dt để không "nhảy cóc" sau khi máy lag
last = ts;
if (!paused) update(dt);
render();
requestAnimationFrame(frame);
}Engine khác qua JavaScript
Với Unity, Construct, Godot hay engine khác, hãy giữ phần nói chuyện với SDK trong JavaScript và chỉ để engine gọi vài hàm đơn giản. Ba việc cần làm trong mọi trường hợp:
- Thêm
<script src="/sdk/v1/wsg-sdk.js"></script>vàoindex.htmlcủa bản export, trước script của engine. - Gọi
WSG.init()sớm,loadingFinished()khi màn hình đầu tiên sẵn sàng,gameplayStart()/gameplayStop()theo lượt chơi,leaderboard.submit()khi hết lượt. - Chuyển
pause/resume/audiocủa SDK thành lệnh dừng vòng lặp và tắt tiếng của engine.
Kiểm tra kích thước bản export: file .wasm và dữ liệu của engine thường chiếm phần lớn bundle, mà giới hạn là 20 MB và 300 file (tính theo kích thước chưa nén). Tắt các module, ngôn ngữ, font không dùng; nếu vẫn vượt, trao đổi với đội vận hành trước khi nộp.
Lưu ý
Tên hàm, tuỳ chọn build và cách export của mỗi engine thay đổi theo phiên bản. Các đoạn dưới đây chỉ minh hoạ cách gọi qua lại với JavaScript; đối chiếu với tài liệu của bản engine bạn dùng.
Unity WebGL
- C# gọi JavaScript qua một plugin
.jslib(đặt trong thư mụcPluginscủa dự án) và các hàm khai báo[DllImport("__Internal")]. Chuỗi từ C# đến JavaScript dưới dạng con trỏ, đọc bằngUTF8ToString(). - JavaScript gọi ngược về C# bằng
SendMessage("TênGameObject", "TênHàm", giáTrị). - Thêm thẻ script SDK vào
index.htmlcủa WebGL template (template tuỳ chỉnh) hoặc sửaindex.htmlsau khi build. - Origin game không gửi
Content-Encoding, nên build với nén Brotli/Gzip sẽ không tải được trừ khi bật cơ chế giải nén dự phòng của Unity; đơn giản nhất là tắt nén khi build cho portal.
mergeInto(LibraryManager.library, {
WsgInit: function (gameIdPtr) {
WSG.on('pause', function () {
SendMessage('WsgBridge', 'OnWsgPause');
});
WSG.on('resume', function () {
SendMessage('WsgBridge', 'OnWsgResume');
});
WSG.init({ gameId: UTF8ToString(gameIdPtr) }).then(function (ctx) {
SendMessage('WsgBridge', 'OnWsgReady', ctx.environment);
});
},
WsgLoadingFinished: function () {
WSG.loadingFinished();
},
WsgGameplayStart: function () {
WSG.gameplayStart();
},
WsgGameplayStop: function () {
WSG.gameplayStop();
},
WsgSubmitScore: function (score) {
WSG.leaderboard.submit(score).then(function (result) {
SendMessage('WsgBridge', 'OnWsgScore', JSON.stringify(result));
});
},
WsgShowRewarded: function () {
WSG.ads.rewarded({ placement: 'revive' }).then(function (result) {
SendMessage('WsgBridge', 'OnWsgRewarded', result.rewarded === true ? 'true' : 'false');
});
},
});using System.Runtime.InteropServices;
using UnityEngine;
// Gắn vào một GameObject tên đúng "WsgBridge" trong scene đầu tiên.
public class WsgBridge : MonoBehaviour
{
[DllImport("__Internal")] private static extern void WsgInit(string gameId);
[DllImport("__Internal")] private static extern void WsgLoadingFinished();
[DllImport("__Internal")] private static extern void WsgGameplayStart();
[DllImport("__Internal")] private static extern void WsgGameplayStop();
[DllImport("__Internal")] private static extern void WsgSubmitScore(int score);
[DllImport("__Internal")] private static extern void WsgShowRewarded();
void Start()
{
#if UNITY_WEBGL && !UNITY_EDITOR
WsgInit("my-unity-game");
#endif
}
// Các hàm dưới được JavaScript gọi bằng SendMessage
void OnWsgReady(string environment) { WsgLoadingFinished(); }
void OnWsgPause() { Time.timeScale = 0f; AudioListener.pause = true; }
void OnWsgResume() { Time.timeScale = 1f; AudioListener.pause = false; }
void OnWsgScore(string resultJson) { Debug.Log("Kết quả nộp điểm: " + resultJson); }
void OnWsgRewarded(string rewarded) { if (rewarded == "true") { /* cấp thưởng */ } }
}Gọi WsgGameplayStart() khi người chơi bắt đầu một lượt, WsgGameplayStop() rồi WsgSubmitScore(score) khi hết lượt. Bản build chạy thử trong Unity Editor không có WSG, nên bọc lời gọi bằng #if UNITY_WEBGL && !UNITY_EDITOR.
Construct 3
- Xuất bằng Web (HTML5), rồi thêm thẻ script SDK vào
index.htmlcủa bản xuất, trước các script của Construct. - Script trong Construct chạy trong Web Worker khi thuộc tính dự án Use worker đang bật, và khi đó không thấy
window.WSG. Đặt Use worker thành No để script gọi được SDK. - Gọi SDK từ khối script trong event sheet; biến toàn cục của dự án đọc qua
runtime.globalVars:
// Khi bắt đầu lượt
WSG.gameplayStart();
// Khi game over (Score là biến toàn cục của dự án)
WSG.gameplayStop();
WSG.leaderboard.submit(Math.floor(runtime.globalVars.Score)).then((result) => {
runtime.globalVars.Rank = result.rank ?? 0;
});Gọi WSG.init() một lần khi dự án khởi động (ví dụ trong script chạy ở layout đầu tiên) và WSG.loadingFinished() khi màn hình đầu tiên hiện ra. Với pause/resume, đặt time scale của dự án về 0 rồi trả lại 1 và tắt tiếng bằng các hành động tương ứng.
Godot
- Xuất bằng preset Web; thêm thẻ script SDK qua tuỳ chọn chèn mã vào
<head>của preset hoặc bằng HTML shell tuỳ chỉnh. - Origin game không gửi các header cross-origin isolation (COOP/COEP), nên bản export cần
SharedArrayBuffer(bật hỗ trợ thread) không chạy được. Dùng bản export không thread (Godot 4.3 trở lên có tuỳ chọn này) hoặc Godot 3. - Godot 4 nói chuyện với JavaScript qua singleton
JavaScriptBridge(Godot 3 làJavaScript):eval()chạy một đoạn mã,get_interface()lấy object JavaScript,create_callback()tạo hàm để JavaScript gọi lại. Giữ tham chiếu tới callback, nếu không nó bị thu hồi.
extends Node
var _on_result # giữ tham chiếu tới callback
func _ready() -> void:
if not OS.has_feature("web"):
return
JavaScriptBridge.eval("WSG.init({ gameId: 'my-godot-game' }).then(() => WSG.loadingFinished())", true)
func round_started() -> void:
JavaScriptBridge.eval("WSG.gameplayStart()", true)
func round_finished(score: int) -> void:
var wsg = JavaScriptBridge.get_interface("WSG")
wsg.gameplayStop()
_on_result = JavaScriptBridge.create_callback(_on_submit_result)
wsg.leaderboard.submit(score).then(_on_result)
func _on_submit_result(args: Array) -> void:
var result = args[0]
print("accepted: ", result.accepted, " rank: ", result.rank)