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 相互调用
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
| 字段 | 数据类型 | 说明 |
|---|---|---|
id | String | 游戏 Id |
userId | String | 用户 Id |
bundleId | String | 游戏包 Id |
versionId | String | 游戏版本 Id |
name | String | 游戏名称 |
gameType | String | 游戏类型 |
iconUrl | String | 游戏图标地址 |
tags | Array<String> | 游戏标签 |
briefIntro | String | 游戏简介 |
launchKey | String | 游戏启动参数 |
dev | Boolean | 游戏开发包 |
lastLaunchTime | Number | 游戏最后启动时间 (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);