精灵遮罩

上次修改时间:2021-07-03 18:41:45

SpriteMask 组件用于对 3D/2D 场景中的精灵实现遮罩效果。

/**
 * @title Sprite Mask
 * @category 2D
 */
import { OrbitControl } from "@oasis-engine/controls";
import {
  AssetType,
  Camera,
  Entity,
  Script,
  Sprite,
  SpriteMask,
  SpriteMaskInteraction,
  SpriteMaskLayer,
  SpriteRenderer,
  Texture2D,
  Vector3,
  WebGLEngine
} from "oasis-engine";

// Create engine.
const engine = new WebGLEngine("canvas");
engine.canvas.resizeByClientSize();

// Create root entity.
const rootEntity = engine.sceneManager.activeScene.createRootEntity();

// Create camera.
const cameraEntity = rootEntity.createChild("Camera");
cameraEntity.transform.setPosition(0, 0, 50);
cameraEntity.addComponent(Camera);
cameraEntity.addComponent(OrbitControl);

// Create sprite and mask.
engine.resourceManager
  .load([
    {
      // Sprite texture
      url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*rgNGR4Vb7lQAAAAAAAAAAAAAARQnAQ",
      type: AssetType.Texture2D
    },
    {
      // Mask texture
      url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*qyhFT5Un5AgAAAAAAAAAAAAAARQnAQ",
      type: AssetType.Texture2D
    },
    {
      // Mask texture
      url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*pgrpQIneqSUAAAAAAAAAAAAAARQnAQ",
      type: AssetType.Texture2D
    }
  ])
  .then((textures: Texture2D[]) => {
    const pos = new Vector3();
    const scale = new Vector3();

    // Create sprites.
    const sprite = new Sprite(engine, textures[0]);
    const maskSprite0 = new Sprite(engine, textures[1]);
    const maksSprite1 = new Sprite(engine, textures[2]);

    // Show inside mask.
    pos.setValue(-5, 0, 0);
    scale.setValue(2, 2, 2);
    addSpriteRenderer(pos, scale, sprite, SpriteMaskInteraction.VisibleInsideMask, SpriteMaskLayer.Layer0);
    addMask(pos, maskSprite0, SpriteMaskLayer.Layer0, ScaleScript);

    // Show outside mask.
    pos.setValue(5, 0, 0);
    scale.setValue(2, 2, 2);
    addSpriteRenderer(pos, scale, sprite, SpriteMaskInteraction.VisibleOutsideMask, SpriteMaskLayer.Layer1);
    addMask(pos, maksSprite1, SpriteMaskLayer.Layer1, RotationScript);
  });

engine.run();

/**
 * Add sprite renderer and set mask interaction and layer.
 */
function addSpriteRenderer(
  pos: Vector3,
  scale: Vector3,
  sprite: Sprite,
  maskInteraction: SpriteMaskInteraction,
  maskLayer: number
): void {
  const entity = rootEntity.createChild("Sprite");
  const renderer = entity.addComponent(SpriteRenderer);
  const { transform } = entity;

  transform.position = pos;
  transform.scale = scale;
  renderer.sprite = sprite;
  renderer.maskInteraction = maskInteraction;
  renderer.maskLayer = maskLayer;
}

/**
 * Add sprite mask and set influence layers, include mask animation script.
 */
function addMask<T extends Script>(
  pos: Vector3,
  sprite: Sprite,
  influenceLayers: number,
  scriptType: new (entity: Entity) => T
): void {
  const entity = rootEntity.createChild("Mask");
  const mask = entity.addComponent(SpriteMask);

  entity.addComponent(scriptType);
  entity.transform.position = pos;
  mask.sprite = sprite;
  mask.influenceLayers = influenceLayers;
}

class ScaleScript extends Script {
  private _scaleSpeed: number = 0.01;

  /**
   * The main loop, called frame by frame.
   * @param deltaTime - The deltaTime when the script update.
   */
  onUpdate(deltaTime: number): void {
    const { transform } = this.entity;
    let curScale = transform.scale.x;

    if (curScale <= 0 || curScale >= 2) {
      this._scaleSpeed *= -1;
    }

    curScale += this._scaleSpeed;
    transform.setScale(curScale, curScale, curScale);
  }
}

class RotationScript extends Script {
  private _rotationSpeed: number = -0.5;

  /**
   * The main loop, called frame by frame.
   * @param deltaTime - The deltaTime when the script update.
   */
  onUpdate(deltaTime: number): void {
    this.entity.transform.rotate(0, 0, this._rotationSpeed);
  }
}

基本使用

1、下载图片纹理(Texture),下载方法请参考资源加载
2、通过 texture 创建 Sprite 对象
3、创建 SpriteMask

// 创建一个遮罩实体
const spriteEntity = rootEntity.createChild(`spriteMask`);
// 给实体添加 SpriteMask 组件
const spriteMask = spriteEntity.addComponent(SpriteMask);
// 通过 texture 创建 sprite 对象
const sprite = new Sprite(engine, texture);
// 设置 sprite
spriteMask.sprite = sprite;
// 设置当前 mask 会对处于哪些遮罩层的精灵有影响
spriteMask.influenceLayers = SpriteMaskLayer.Layer0;

// 设置遮罩类型
spriteRenderer.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
// 设置精灵处于哪个遮罩层
spriteRenderer.maskLayer = SpriteMaskLayer.Layer0;

遮罩层 (SpriteMaskLayer)

SpriteMaskLayer 里面声明了引擎提供的遮罩层,一共声明了 32 个遮罩层,分别是 Layer0~Layer31,遮罩层和渲染无关,只是为了帮助开发者设置 SpriteMaskSpriteRenderer 如何进行关联,一个 SpriteMask 对象要对一个 SpriteRenderer 对象产生遮罩作用的一个前提就是两者的遮罩层有交集。

SpriteMaskinfluenceLayers 表示该 mask 对处于哪些遮罩层内的 SpriteRenderer 会起到遮罩作用,SpriteRenderermaskLayer 表示该精灵处于哪些遮罩层,如下:

070C8B9F-14E2-4A9A-BFEC-4BC3F2BB564F

上图中,spriteMask 对处于 Layer1Layer30 的精灵有遮罩作用,spriteRenderer0 处于 Layer2,不存在交集,所以 spriteRenderer0 不与 spriteMask 起作用,spriteRenderer1 处于 Layer1,和 spriteMask 影响的遮罩层有交集,所以 spriteRenderer1 与 spriteMask 起作用。

参数说明

在基本使用中已经知道了如何创建一个遮罩,接下来就是通过 SpriteMask 提供的参数来控制如何和 精灵 发生作用。

参数类型说明
influenceLayersnumber当前 mask 影响的遮罩层,默认值为 SpriteMaskLayer.Everything,表示对所有遮罩层都有影响
alphaCutoffnumber当前 mask 有效 alpha 值的下限(范围:0~1),即 sprite 的纹理中 alpha 值小于 alphaCutoff 的将被丢弃
// mask 的 sprite 中纹理 alpha 小于 0.5 的将被丢弃
spriteMask.alphaCutoff = 0.5;
// mask 对所有遮罩层的精灵都生效
spriteMask.influenceLayers = SpriteMaskLayer.Everything;
// mask 只对处于遮罩层 Layer0 的精灵有效
spriteMask.influenceLayers = SpriteMaskLayer.Layer0;
// mask 对处于遮罩层 Layer0 和 Layer1 的精灵有效
spriteMask.influenceLayers = SpriteMaskLayer.Layer0 | SpriteMaskLayer.Layer1;