跳到主要内容

PC SDK 高级使用场景

1. 自定义游戏窗口​

创建宿主 createGameHandle 时可以通过 RuntimeGameHandle 的选项配置游戏窗口的大小、位置和样式。

const { TJHostHandle, MultiGameLauncher } = require('webglhost-runtime-pc');

// Initialize host
const hostHandle = await new Promise((resolve, reject) => {
TJHostHandle.initialize(SDK_KEY, SDK_SECRET, (handle, error) => {
if (error) reject(error);
else resolve(handle);
});
});

// Create game handle with custom window options
const windowOptions = {
width: 1280,
height: 720,
x: 100,
y: 100,
show: false, // Don't show window immediately
frame: true, // Show window frame
resizable: true
};

const gameHandle = await MultiGameLauncher.createGameHandle(
hostHandle,
gameId,
{ windowOptions }
);

// Set game start options
const options = {};
options[TJConstants.LAUNCH_KEY] = 'launch-key';
options[TJConstants.USER_ID] = 'user-123';

await gameHandle.setGameStartOptions(options);
await gameHandle.start();

// Play game when ready
gameHandle.setOnFirstFrameRenderedListener(async () => {
await gameHandle.play();
});

2. 支持透明游戏背景​

可在 setGameStartOptions() 时设置参数 ENABLE_TRANSPARENT_MODE 为 true,使得游戏窗口背景支持透明:

const { TJConstants } = require('webglhost-runtime-pc');

const options = {};
options[TJConstants.LAUNCH_KEY] = 'launch-key';
options[TJConstants.USER_ID] = 'user-123';
options[TJConstants.ENABLE_TRANSPARENT_MODE] = true;

await gameHandle.setGameStartOptions(options);
await gameHandle.start();
await gameHandle.play();

注意: 透明模式可能会影响性能,建议仅在必要时使用。

3. 自定义胶囊按钮​

自定义胶囊按钮可通过隐藏默认胶囊按钮并实现自定义 UI 来实现。

在 setGameStartOptions() 时设置参数 SHOW_MENU_LAYOUT 为 false,可隐藏默认胶囊按钮:

const { TJConstants } = require('webglhost-runtime-pc');

const options = {};
options[TJConstants.LAUNCH_KEY] = 'launch-key';
options[TJConstants.USER_ID] = 'user-123';
options[TJConstants.SHOW_MENU_LAYOUT] = false; // Hide default menu button

await gameHandle.setGameStartOptions(options);
await gameHandle.start();

// Add custom menu button UI (using Electron BrowserWindow overlay)
const { BrowserWindow } = require('electron');
const overlayWindow = new BrowserWindow({
parent: gameHandle.getGameWindow(),
modal: false,
show: true,
frame: false,
transparent: true,
width: 200,
height: 60,
x: 10,
y: 10
});

overlayWindow.loadFile('custom-menu-overlay.html');

// Handle menu click in overlay window
// In custom-menu-overlay.html:
// <button onclick="window.parentNotify('menu')">Menu</button>

4. 原生代码与 JS 相互调用​

可参考 CustomCommand API 说明

5. 最近游玩列表​

Host SDK 提供了最近游玩相关的接口,用于监听游戏启动以及查询当前用户的游玩列表。

const { TJHostHandle } = require('webglhost-runtime-pc');

class RecentlyGameListener {
updateGame(game) {
console.log('Game updated:', game);
// Update UI with recently played game
// game is a HistoryModel instance with properties:
// - id, bundleId, name, gameType, iconUrl, tags
// - briefIntro, versionId, userId, launchKey
// - dev, lastLaunchTime
}
}

// Initialize host with recently played game listener
TJHostHandle.initialize(SDK_KEY, SDK_SECRET, (hostHandle, error) => {
if (error) {
console.error('Initialization failed:', error);
return;
}

// Set recently played game listener
const listener = new RecentlyGameListener();
hostHandle.setRecentlyGameListener(listener);

// Get user's game history
const userId = 'user-123';
const playedGames = hostHandle.getUserHistory(userId);
console.log('User played games:', playedGames);
});

PlayedGameModel​

字段数据类型说明
idString游戏 Id
userIdString用户 Id
bundleIdString游戏包 Id
versionIdString游戏版本 Id
nameString游戏名称
gameTypeString游戏类型
iconUrlString游戏图标地址
tagsArray<String>游戏标签
briefIntroString游戏简介
launchKeyString游戏启动参数
devBoolean游戏开发包
lastLaunchTimeNumber游戏最后启动时间 (Unix timestamp)

游戏开发包 dev 字段说明​

MiniHost 控制台在游戏包管理中提供了本地开发测试的功能。开发版游戏包作为临时体验版本,其下载地址是临时的。

因为游戏启动后会被记录进最近游玩列表,而开发包的下载地址是临时的,所以当从最近游玩中启动开发包时,游戏只会启动本地缓存的游戏包,并不会重新下载。

当游戏版本更新时,需要重新加载新版本的游戏包,此时游戏列表会更新 launchKey 替换原有的版本作为下次启动参数。

6. 多窗口游戏管理​

使用 MultiGameLauncher 可以同时管理多个游戏实例,每个实例运行在独立的窗口中:

const { MultiGameLauncher, TJConstants } = require('webglhost-runtime-pc');

// Configure maximum concurrent games
MultiGameLauncher.configMaxGame(3);

// Create first game instance
const gameHandle1 = await MultiGameLauncher.createGameHandle(
hostHandle,
'game-id-1',
{}
);

// Create second game instance
const gameHandle2 = await MultiGameLauncher.createGameHandle(
hostHandle,
'game-id-2',
{}
);

// Start both games
const options1 = {
[TJConstants.LAUNCH_KEY]: 'launch-key-1',
[TJConstants.USER_ID]: 'user-123'
};

const options2 = {
[TJConstants.LAUNCH_KEY]: 'launch-key-2',
[TJConstants.USER_ID]: 'user-123'
};

await gameHandle1.setGameStartOptions(options1);
await gameHandle1.start();
await gameHandle1.play();

await gameHandle2.setGameStartOptions(options2);
await gameHandle2.start();
await gameHandle2.play();

// Pause/resume games
MultiGameLauncher.pause('game-id-1');
MultiGameLauncher.resume('game-id-1');

// Stop specific game
MultiGameLauncher.stop('game-id-1');

// Destroy specific game instance
MultiGameLauncher.destroy('game-id-2');

7. 自定义启动画面​

可以自定义游戏启动时的 Splash 画面:

const { TJConstants } = require('webglhost-runtime-pc');

const options = {};
options[TJConstants.LAUNCH_KEY] = 'launch-key';
options[TJConstants.USER_ID] = 'user-123';
options[TJConstants.SPLASH_ICON_URL] = 'https://example.com/game-icon.png';
options[TJConstants.SPLASH_GAME_TITLE] = 'My Awesome Game';

await gameHandle.setGameStartOptions(options);
await gameHandle.start();

8. 调试和开发工具​

启用 vConsole​

const { TJConstants } = require('webglhost-runtime-pc');

const options = {};
options[TJConstants.LAUNCH_KEY] = 'launch-key';
options[TJConstants.USER_ID] = 'user-123';
options[TJConstants.ENABLE_VCONSOLE] = true; // Enable vConsole for debugging

await gameHandle.setGameStartOptions(options);
await gameHandle.start();

启用 Chrome DevTools Inspector​

const { TJConstants } = require('webglhost-runtime-pc');

const options = {};
options[TJConstants.LAUNCH_KEY] = 'launch-key';
options[TJConstants.USER_ID] = 'user-123';
options[TJConstants.ENABLE_INSPECTOR] = true; // Enable inspector
options[TJConstants.INSPECTOR_WAIT_FOR_INSPECT] = true; // Wait for inspector to attach

await gameHandle.setGameStartOptions(options);
await gameHandle.start();

运行时调试工具​

// Show debug menu
gameHandle.showDebugMenu();

// Toggle debug mode
gameHandle.toggleDebugMode();

// Get debug mode status
const isDebugEnabled = gameHandle.getDebugModeStatus();

// Force garbage collection
gameHandle.invokeForceGC();

// Export logs
gameHandle.exportLogManagerLog();

9. 域名白名单配置​

为了安全性,可以配置允许游戏访问的域名白名单:

const { TJConstants } = require('webglhost-runtime-pc');

const options = {};
options[TJConstants.LAUNCH_KEY] = 'launch-key';
options[TJConstants.USER_ID] = 'user-123';

// Enable domain whitelist
options[TJConstants.ENABLE_DOMAIN_WHITELIST] = true;

// Configure whitelisted domains
options[TJConstants.DOMAIN_WHITELIST_REQUEST] = [
'https://api.example.com',
'https://cdn.example.com'
];

options[TJConstants.DOMAIN_WHITELIST_SOCKET] = [
'wss://socket.example.com'
];

options[TJConstants.DOMAIN_WHITELIST_UPLOAD_FILE] = [
'https://upload.example.com'
];

options[TJConstants.DOMAIN_WHITELIST_DOWNLOAD_FILE] = [
'https://download.example.com'
];

await gameHandle.setGameStartOptions(options);
await gameHandle.start();

10. 游戏状态监听​

监听游戏的各种状态变化:

const { RuntimeState } = require('webglhost-runtime-pc');

// Listen for first frame rendered
gameHandle.setOnFirstFrameRenderedListener(async () => {
console.log('Game first frame rendered');
const state = gameHandle.getGameState();
console.log('Current state:', state);
});

// Listen for game failure
gameHandle.setOnHostFailureListener((error) => {
console.error('Game failed:', error);
// Handle game crash
});

// Monitor state changes
setInterval(() => {
const state = gameHandle.getGameState();

switch (state) {
case RuntimeState.UNAVAILABLE:
console.log('Game is unavailable');
break;
case RuntimeState.WAITING:
console.log('Game is waiting to start');
break;
case RuntimeState.RUNNING:
console.log('Game is running');
break;
case RuntimeState.PLAYING:
console.log('Game is playing');
break;
}
}, 1000);

11. 完整示例​

const {
TJHostHandle,
MultiGameLauncher,
TJConstants,
RuntimeState
} = require('webglhost-runtime-pc');

class AdvancedGameManager {
constructor() {
this.hostHandle = null;
this.games = new Map();
}

async initialize(sdkKey, sdkSecret) {
return new Promise((resolve, reject) => {
TJHostHandle.initialize(sdkKey, sdkSecret, (handle, error) => {
if (error) {
reject(error);
return;
}
this.hostHandle = handle;
this.setupRecentlyPlayedListener();
resolve();
});
});
}

setupRecentlyPlayedListener() {
const listener = {
updateGame: (game) => {
console.log('Recently played game updated:', game);
// Update UI with recently played game
}
};
this.hostHandle.setRecentlyGameListener(listener);
}

async createGame(gameId, launchKey, customOptions = {}) {
// Create game handle
const gameHandle = await MultiGameLauncher.createGameHandle(
this.hostHandle,
gameId,
customOptions
);

// Setup listeners
this.setupGameListeners(gameHandle, gameId);

// Configure game options
const options = {
[TJConstants.LAUNCH_KEY]: launchKey,
[TJConstants.USER_ID]: 'user-123',
[TJConstants.ENABLE_VCONSOLE]: false,
[TJConstants.SHOW_MENU_LAYOUT]: true,
[TJConstants.ENABLE_TRANSPARENT_MODE]: false,
...customOptions
};

await gameHandle.setGameStartOptions(options);

// Store game handle
this.games.set(gameId, gameHandle);

return gameHandle;
}

setupGameListeners(gameHandle, gameId) {
// First frame rendered
gameHandle.setOnFirstFrameRenderedListener(async () => {
console.log(`[${gameId}] First frame rendered`);
await gameHandle.play();
});

// Menu button clicked
gameHandle.setOnTJMenuListener(() => {
console.log(`[${gameId}] Menu clicked`);
this.showGameMenu(gameId);
});

// Close button clicked
gameHandle.setOnTJCloseListener(() => {
console.log(`[${gameId}] Close clicked`);
this.closeGame(gameId);
});

// Game failure
gameHandle.setOnHostFailureListener((error) => {
console.error(`[${gameId}] Game failed:`, error);
this.handleGameFailure(gameId, error);
});
}

async startGame(gameId) {
const gameHandle = this.games.get(gameId);
if (!gameHandle) {
throw new Error(`Game ${gameId} not found`);
}

await gameHandle.start();
}

showGameMenu(gameId) {
const gameHandle = this.games.get(gameId);
if (gameHandle) {
gameHandle.showDebugMenu();
}
}

closeGame(gameId) {
const gameHandle = this.games.get(gameId);
if (gameHandle) {
MultiGameLauncher.stop(gameId);
this.games.delete(gameId);
}
}

handleGameFailure(gameId, error) {
console.error(`Handling game failure for ${gameId}:`, error);
this.closeGame(gameId);
}

getUserHistory(userId) {
return this.hostHandle.getUserHistory(userId);
}

pauseAll() {
MultiGameLauncher.pauseAll();
}

resumeAll() {
for (const gameId of this.games.keys()) {
MultiGameLauncher.resume(gameId);
}
}

stopAll() {
MultiGameLauncher.stopAll();
this.games.clear();
}

destroyAll() {
MultiGameLauncher.destroyAll();
this.games.clear();
}
}

// Usage
async function main() {
const manager = new AdvancedGameManager();

try {
// Initialize
await manager.initialize('sdk-key', 'sdk-secret');

// Configure max concurrent games
MultiGameLauncher.configMaxGame(3);

// Create and start first game
const gameHandle1 = await manager.createGame('game-1', 'launch-key-1', {
[TJConstants.ENABLE_VCONSOLE]: true
});
await manager.startGame('game-1');

// Create and start second game
const gameHandle2 = await manager.createGame('game-2', 'launch-key-2');
await manager.startGame('game-2');

// Get user history
const history = manager.getUserHistory('user-123');
console.log('User history:', history);

} catch (error) {
console.error('Error:', error);
}
}

main().catch(console.error);