/** * Render layers and overlay cameras (#302). The main camera sees only `Default`, an overlay camera * 2000 units to the right sees only `UI`. Each draws its own picture, the overlay's laid on top: * a green sprite of the overlay with z -10 covers a red one of the main camera with z 1000, and * what a camera does not see stays away – a yellow `UI` sprite in front of the main camera, a blue * `Default` one in front of the overlay. Switched to no overlay, the second camera draws nothing. * * Everything it adds carries an `rl63` id and goes again at the end. */ import { MAKE_OBJECT } from './lib/scaffold.mjs'; const OVERLAY = 'rl63-cam'; export async function renderLayers(t) { const { check, json, evaluate, mutate, sleep, shot, showTab, play, stop, clickText, press } = t; await showTab('scene'); await mutate(` const p = __gm.state.project; const scene = __gm.state.activeScene; const main = scene.objects.find((o) => o.id === 'o3'); window.__rl63 = { layers: p.renderLayers, overrides: structuredClone(main.overrides) }; p.renderLayers = ['Default', 'UI']; main.overrides = { ...main.overrides, sees: 0b01 }; ${MAKE_OBJECT} const sprite = p.assets.find((a) => a.type === 'sprite'); const square = (id, name, dx, z, size, color, layer, from) => { const o = make(id, sprite.id, '', 0, name); Object.assign(o.position, { x: from.x + dx, y: from.y, z }); o.overrides = { width: size, height: size, color, texture: '', shader: '', renderLayer: layer }; return o; }; const overlay = structuredClone(main); Object.assign(overlay, { id: '${OVERLAY}', name: 'Overlay 63', instanceEdits: {} }); overlay.position = { ...main.position, x: main.position.x + 2000 }; overlay.overrides = { ...main.overrides, overlay: true, sees: 0b10 }; scene.objects.push( overlay, square('rl63-a', 'A 63', 70, 1000, 60, 0xff0000, -1, main.position), square('rl63-d', 'D 63', -70, 1000, 60, 0xffff00, 1, main.position), square('rl63-b', 'B 63', 0, -10, 40, 0x00ff00, 1, overlay.position), square('rl63-c', 'C 63', 70, 1000, 60, 0x0000ff, 0, overlay.position), ); `); await evaluate('__gm.state.select(null)'); await showTab('game'); await sleep(300); const channels = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16)); const is = (hex, [r, g, b]) => { const [pr, pg, pb] = channels(hex); return Math.abs(pr - r) < 30 && Math.abs(pg - g) < 30 && Math.abs(pb - b) < 30; }; /** The three points of the picture the squares sit on: the centre and 70 units either side. */ const picture = async () => { const { frame, scale } = await json('__gm.gameScale()'); const cx = Math.round(frame.x + frame.w / 2); const cy = Math.round(frame.y + frame.h / 2); const at = (dx) => json(`__gm.pixelAt(${Math.round(cx + dx * scale)}, ${cy})`); return { centre: await at(0), right: await at(70), left: await at(-70), main: await json('__gm.gameDrawOrder()'), overlays: await json('__gm.gameOverlays()'), }; }; const GREEN = [0, 255, 0]; const RED = [255, 0, 0]; const YELLOW = [255, 255, 0]; const stacked = (seen) => seen.overlays.length === 1 && seen.overlays[0].camera === OVERLAY && seen.overlays[0].drawOrder.includes('rl63-b') && !seen.overlays[0].drawOrder.includes('rl63-c') && seen.main.includes('rl63-a') && !seen.main.includes('rl63-d') && !seen.main.includes('rl63-b') && is(seen.centre, GREEN) && is(seen.right, RED) && !is(seen.left, YELLOW); // 63a. The preview of the Game tab: each camera draws what it sees, the overlay on top. const preview = await picture(); await shot('63-overlay-preview'); check( 'each camera draws only its layers, and the overlay lies on top whatever the z', stacked(preview), JSON.stringify(preview), ); // 63b. Without Overlay the second camera is a plain one after the first: it draws nothing. await mutate(` const cam = __gm.state.activeScene.objects.find((o) => o.id === '${OVERLAY}'); cam.overrides = { ...cam.overrides, overlay: false }; `); await sleep(200); const plain = await picture(); check( 'two plain cameras switched on draw through the first one only', plain.overlays.length === 0 && !is(plain.centre, GREEN) && is(plain.right, RED), JSON.stringify(plain), ); await mutate(` const cam = __gm.state.activeScene.objects.find((o) => o.id === '${OVERLAY}'); cam.overrides = { ...cam.overrides, overlay: true }; `); // 63c. The running game stacks them the same way. await play(); await sleep(400); const running = await picture(); check( 'the running game lays the overlay camera on top the same way', stacked(running), JSON.stringify(running), ); await stop(); // 63d. The project settings list the render layers, apart from the collision layers. await t.clickToolbar('menu'); await clickText('Project settings…'); const opened = await json('__gm.renderLayers()'); await clickText('Add render layer'); const added = await json('__gm.renderLayers()'); const collision = await json('__gm.collisionLayers()'); await press('Escape'); await sleep(150); check( 'the project settings list the render layers and add one at the end', JSON.stringify(opened.dialog) === JSON.stringify(['Default', 'UI']) && JSON.stringify(added.dialog) === JSON.stringify(['Default', 'UI', 'Layer 2']) && !collision.dialog.includes('UI'), JSON.stringify({ opened, added, collision }), ); // Clean up. await showTab('scene'); await mutate(` const p = __gm.state.project; const scene = __gm.state.activeScene; scene.objects = scene.objects.filter((o) => !o.id.startsWith('rl63-')); scene.objects.find((o) => o.id === 'o3').overrides = window.__rl63.overrides; p.renderLayers = window.__rl63.layers; `); }