147 lines
9.9 KiB
JavaScript
147 lines
9.9 KiB
JavaScript
// Isolated Cocos preview: production reward renderer, Win prefab geometry and original artwork.
|
|
// node tools/preview-level-reward.cjs [--check] (PLAYWRIGHT_MODULE can point to an installed package).
|
|
const http = require('node:http'), fs = require('node:fs'), path = require('node:path');
|
|
const { loadMembers } = require('./new-player-ab-test-support.cjs');
|
|
const root = path.resolve(__dirname, '..');
|
|
const creator = process.env.COCOS_CREATOR_PATH || 'C:/ProgramData/cocos/editors/Creator/2.4.15';
|
|
const port = Number(process.env.REWARD_PREVIEW_PORT || 8795);
|
|
const images = ['assets/publicUI/font.png', 'assets/res/move/jibijj.png',
|
|
'assets/win/texture/juli.png', 'assets/win/texture/newMode1.png'];
|
|
const data = {
|
|
prefab: JSON.parse(fs.readFileSync(path.join(root, 'assets/win/prefab/Win.prefab'))),
|
|
atlases: images.map(file => ({ file, meta: JSON.parse(fs.readFileSync(path.join(root,
|
|
file === images[0] ? 'assets/publicUI/font.plist.meta' : file + '.meta'))) })),
|
|
};
|
|
const code = `window.NumberToImage=${loadMembers('assets/Script/NumberToImage.ts', ['numberToImageNodes']).toString()};
|
|
window.RewardDisplay=${loadMembers('assets/Script/SceneManager.ts', ['updateWinCoin', 'updateWinCoinIconState']).toString()};`;
|
|
|
|
async function boot() {
|
|
window.errors = [];
|
|
window.addEventListener('error', event => errors.push(String(event.error || event.message)));
|
|
window.addEventListener('unhandledrejection', event => errors.push(String(event.reason)));
|
|
const data = await fetch('/data').then(r => r.json());
|
|
cc.assetManager.init({ importBase: '/imports', nativeBase: '/native' });
|
|
cc.assetManager.loadBundle('/internal', error => {
|
|
if (error) throw error;
|
|
cc.game.run({ id: 'GameCanvas', debugMode: 1, frameRate: 30, renderMode: 0,
|
|
groupList: ['default'], collisionMatrix: [[true]] }, async () => {
|
|
const scene = new cc.Scene(), canvas = new cc.Node('Canvas');
|
|
scene.addChild(canvas); canvas.setContentSize(720, 650);
|
|
const component = canvas.addComponent(cc.Canvas);
|
|
component.designResolution = cc.size(720, 650); component.fitWidth = component.fitHeight = true;
|
|
cc.view.setDesignResolutionSize(720, 650, cc.ResolutionPolicy.SHOW_ALL);
|
|
cc.view.resizeWithBrowserSize(true); cc.director.runScene(scene);
|
|
const cameraNode = new cc.Node('Camera'); canvas.addChild(cameraNode);
|
|
const camera = cameraNode.addComponent(cc.Camera);
|
|
camera.backgroundColor = cc.color(236, 221, 189);
|
|
cc.fx = { GameConfig: { LEGACY_LAYER_TWO_VARIANT_B_ENABLED: false } };
|
|
const frames = {}; NumberToImage.font = { _spriteFrames: {} };
|
|
for (const { file, meta } of data.atlases) {
|
|
const texture = await new Promise((resolve, reject) => cc.assetManager.loadRemote('/' + file,
|
|
(e, asset) => e ? reject(e) : resolve(asset)));
|
|
for (const [name, m] of Object.entries(meta.subMetas)) {
|
|
const frame = new cc.SpriteFrame(texture, cc.rect(m.trimX, m.trimY, m.width, m.height),
|
|
!!m.rotated, cc.v2(m.offsetX, m.offsetY), cc.size(m.rawWidth, m.rawHeight));
|
|
frames[m.uuid] = frame;
|
|
NumberToImage.font._spriteFrames[name.replace(/\.png$/, '')] = frame;
|
|
}
|
|
}
|
|
function build(index) {
|
|
const row = data.prefab[index], node = new cc.Node(row._name), trs = row._trs.array;
|
|
node.setPosition(trs[0], trs[1]); node.scaleX = trs[7]; node.scaleY = trs[8];
|
|
node.setAnchorPoint(row._anchorPoint.x, row._anchorPoint.y);
|
|
node.setContentSize(row._contentSize.width, row._contentSize.height); node.active = row._active;
|
|
for (const ref of row._components) {
|
|
const component = data.prefab[ref.__id__];
|
|
if (component.__type__ === 'cc.Sprite' && component._spriteFrame) {
|
|
const sprite = node.addComponent(cc.Sprite); sprite.spriteFrame = frames[component._spriteFrame.__uuid__];
|
|
sprite.sizeMode = cc.Sprite.SizeMode.CUSTOM; sprite.enabled = component._enabled;
|
|
node.setContentSize(row._contentSize.width, row._contentSize.height);
|
|
}
|
|
}
|
|
for (const child of row._children) node.addChild(build(child.__id__));
|
|
return node;
|
|
}
|
|
const reward = build(data.prefab.findIndex(row => row._name === 'tiaodik'));
|
|
canvas.addChild(reward); reward.y = -40;
|
|
const renderer = new RewardDisplay(); renderer.winCoin = reward.getChildByName('coin');
|
|
window.showReward = amount => renderer.updateWinCoin(amount);
|
|
window.measureReward = () => {
|
|
const digits = renderer.winCoin.children.map(node => node.getBoundingBoxToWorld());
|
|
const symbol = reward.getChildByName('New Sprite').getBoundingBoxToWorld();
|
|
const artwork = reward.getChildByName('beijing').getBoundingBoxToWorld();
|
|
const left = symbol.xMin, right = Math.max(...digits.map(rect => rect.xMax));
|
|
return { count: digits.length, width: right - left, left, right,
|
|
center: (left + right) / 2, artworkCenter: artwork.center.x,
|
|
gap: Math.min(...digits.map(rect => rect.xMin)) - symbol.xMax,
|
|
insideArtworkWidth: left >= artwork.xMin && right <= artwork.xMax,
|
|
digitGaps: digits.slice(1).map((rect, i) => rect.xMin - digits[i].xMax) };
|
|
};
|
|
showReward(Number(new URLSearchParams(location.search).get('amount')) || 120);
|
|
window.ready = true;
|
|
});
|
|
});
|
|
}
|
|
const html = `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<style>html,body{margin:0;height:100%;overflow:hidden;background:#ecddbd}canvas{display:block}</style>
|
|
<canvas id="GameCanvas"></canvas><script src="/engine.js"></script><script src="/code.js"></script><script src="/boot.js"></script>`;
|
|
const server = http.createServer((req, res) => {
|
|
const url = new URL(req.url, 'http://localhost').pathname;
|
|
const send = (type, body) => { res.setHeader('Content-Type', type); res.end(body); };
|
|
if (url === '/favicon.ico') { res.statusCode = 204; return res.end(); }
|
|
if (url === '/') return send('text/html; charset=utf-8', html);
|
|
if (url === '/data') return send('application/json', JSON.stringify(data));
|
|
if (url === '/code.js') return send('text/javascript', code);
|
|
if (url === '/boot.js') return send('text/javascript', '(' + boot.toString() + ')();');
|
|
let target;
|
|
if (images.includes(url.slice(1))) target = path.join(root, url.slice(1));
|
|
if (url === '/engine.js') target = path.join(creator, 'resources/engine/bin/cocos2d-js-for-preview.js');
|
|
if (/^\/imports\/[a-f0-9]{2}\/[a-f0-9-]+\.json$/.test(url)) target = path.join(root, 'library', url.slice(1));
|
|
if (/^\/native\/[a-f0-9]{2}\/[a-f0-9-]+\.png$/.test(url)) target = path.join(root, 'library/imports', url.slice('/native/'.length));
|
|
if (url.startsWith('/internal/') || url.startsWith('/assets/internal/')) {
|
|
const base = path.join(creator, 'resources/cocos2d-x/simulator/win32/assets');
|
|
const candidate = path.resolve(base, url.replace(/^\/(assets\/)?/, ''));
|
|
if (candidate.startsWith(base + path.sep)) target = candidate;
|
|
}
|
|
if (target && fs.existsSync(target)) {
|
|
res.setHeader('Content-Type', target.endsWith('.js') ? 'text/javascript' : target.endsWith('.png') ? 'image/png' : 'application/json');
|
|
return fs.createReadStream(target).pipe(res);
|
|
}
|
|
res.statusCode = 404; res.end('not found');
|
|
});
|
|
server.listen(port, '127.0.0.1', async () => {
|
|
console.log(`http://127.0.0.1:${port}`);
|
|
if (!process.argv.includes('--check')) return;
|
|
const assert = require('node:assert/strict');
|
|
let browser;
|
|
try {
|
|
const { chromium } = require(process.env.PLAYWRIGHT_MODULE || 'playwright');
|
|
browser = await chromium.launch({ headless: true, channel: 'msedge' });
|
|
const output = path.join(root, 'temp/level-reward'); fs.mkdirSync(output, { recursive: true });
|
|
for (const viewport of [{ width: 390, height: 844 }, { width: 720, height: 1280 }]) {
|
|
const page = await browser.newPage({ viewport });
|
|
const renderErrors = [];
|
|
page.on('console', message => { if (message.type() === 'error') renderErrors.push(message.text()); });
|
|
await page.goto(`http://127.0.0.1:${port}`);
|
|
await page.waitForFunction(() => window.ready);
|
|
await page.evaluate(() => new Promise(resolve => cc.director.once(cc.Director.EVENT_AFTER_DRAW, resolve)));
|
|
for (const amount of [40, 80, 120, 160, 240, 480, 120, 40]) {
|
|
await page.evaluate(value => showReward(value), amount);
|
|
await page.evaluate(() => new Promise(resolve => cc.director.once(cc.Director.EVENT_AFTER_DRAW, resolve)));
|
|
const result = await page.evaluate(() => measureReward());
|
|
assert.equal(result.count, String(amount).length);
|
|
assert.ok(Math.abs(result.center - result.artworkCenter) < 0.01, JSON.stringify(result));
|
|
assert.equal(result.gap, 20);
|
|
assert.ok(result.digitGaps.every(gap => gap >= 0));
|
|
assert.equal(result.insideArtworkWidth, true);
|
|
if ([40, 120, 480].includes(amount)) await page.screenshot({ path: path.join(output, `${viewport.width}-${amount}.png`) });
|
|
console.log(viewport.width, amount, JSON.stringify(result));
|
|
}
|
|
assert.deepEqual(await page.evaluate(() => errors), []);
|
|
assert.deepEqual(renderErrors, []);
|
|
await page.close();
|
|
}
|
|
} catch (error) { console.error(error); process.exitCode = 1; }
|
|
finally { if (browser) await browser.close(); server.close(); }
|
|
});
|