index.js 3.7 KB
// @drama/render-pixi —— Renderer 接口的 PixiJS v8 后端,公共面。
//
// 唯一公共入口(架构「禁止深导入」红线)。只导出 createPixiRenderer + CharacterSkin/SlotSkin
// typedef(render-pixi §3 / §5.6 公共面最小化)。
//
// Pixi 只在本包出现(render-pixi §5.2)。本包对外暴露的方法签名零 Pixi 入参:core 调
// applyActorPose/applyCamera 等时只传 SlotTransform / CameraRenderState / string / number。
// PIXI 命名空间在工厂处一次性解析(不外溢到接口):
//   - 若 `opts.PIXI` 给了就用它(测试注入 stub、或调用方已持有的命名空间);
//   - 否则动态 import 'pixi.js'。动态而非顶层静态 import,是为了让无 GPU 的 headless
//     单测(注入 stub Sprite)无需安装 / 加载真实 pixi.js 即可跑 §8 的纯映射用例。

import { createPixiRenderer as _createPixiRenderer } from './renderer.js'

/**
 * @typedef {{
 *   rig: import('@drama/rig').RigDefinition,
 *   PIXI?: *,
 *   loadTexture?: (url:string)=>Promise<*>,
 *   resolveEffect?: (name:string)=>(import('./effects.js').EffectDef|null),
 *   faceFrameDt?: number,
 * }} RendererOpts   工厂注入项:rig 必填;PIXI 在工厂处一次注入(不外溢到接口)。
 */

/**
 * @typedef {import('@drama/core').Renderer & {
 *   addActor:(actorId:string, skin:CharacterSkin, placement:object)=>void,
 *   removeActor:(actorId:string)=>void,
 *   loadScene:(def?:object)=>Promise<void>,
 *   updateEffects:(dtMs:number)=>void,
 * }} PixiRenderer   @drama/core Renderer(零 Pixi 入参)+ 出场 / 换场扩展方法
 */

/**
 * 创建满足 @drama/core Renderer 接口的 PixiJS 渲染后端。
 * @param {import('pixi.js').Application} app  已 init 的 Pixi Application(由 player-vue / composition root 建)
 * @param {RendererOpts} opts
 * @returns {PixiRenderer}
 */
export function createPixiRenderer(app, opts) {
  if (opts && opts.PIXI) return _createPixiRenderer(app, opts)
  throw new Error(
    'createPixiRenderer: opts.PIXI is required. Pass the imported `* as PIXI` namespace ' +
      '(e.g. `import * as PIXI from "pixi.js"`). Renderer 方法签名仍零 Pixi——PIXI 仅在工厂处一次注入。',
  )
}

/**
 * 与 createPixiRenderer 等价,但自动 `import('pixi.js')` 注入 PIXI(浏览器 / 已装 pixi.js 环境)。
 * 给 player-vue / studio 直接用;headless 单测走 createPixiRenderer + 注入 stub。
 * @param {import('pixi.js').Application} app
 * @param {Omit<RendererOpts,'PIXI'>} opts  同 createPixiRenderer(无需传 PIXI)
 * @returns {Promise<PixiRenderer>}
 */
export async function createPixiRendererAuto(app, opts) {
  const PIXI = await import('pixi.js')
  return _createPixiRenderer(app, { ...opts, PIXI })
}

// ───────────────────────── 公共形状契约(render-pixi §3) ─────────────────────────

/**
 * @typedef {Object} CharacterSkin   渲染需要的贴皮数据(来自 @drama/assets 的 character 出包)。
 * @property {string} rigId
 * @property {Record<string, SlotSkin>} parts   slot 名 -> 该部件贴皮
 */

/**
 * @typedef {Object} SlotSkin
 * @property {string=} url                普通部件贴图
 * @property {{x:number,y:number}=} anchor
 * @property {number=} scale
 * @property {number=} rotation
 * @property {boolean=} mirror
 * @property {number=} zOrder             覆盖 slot 默认层级
 * @property {{x:number,y:number}=} bonePos   关节位置覆盖(对齐用)
 * @property {Record<string,{static:string,talk?:string,mirror?:boolean,scale?:number}>=} emotions  face 专用情绪帧
 * @property {number=} talkFps            口型轮播帧率(face 用)
 */