Skip to content

透明度与颜色 ​

Node2D 有三个颜色属性,都可以写在构造参数里,也都可以补间:

属性默认作用范围说明
alpha1自己和子节点不透明度 0–1,父子相乘。为 0 时仍能被点中,要隐藏用 visible
modulate0xffffff自己和子节点颜色乘子 0xRRGGBB,每个像素乘上它:0xff6666 偏红、0x888888 变暗。只能变暗或偏色,不能变亮
selfModulate0xffffff只有自己的贴图 / 文字同上,但不影响子节点(Boss 变红而血条不变);和 modulate 叠乘
ts
// 受伤变红再恢复:颜色属性按 RGB 通道补间
enemy.modulate = 0xff4040
enemy.createTween().to(enemy, { modulate: 0xffffff }, 0.2)
// 淡出后销毁
fx.createTween().to(fx, { alpha: 0 }, 0.3).call(() => fx.queueFree())

受击闪白:Sprite2D.flash ​

modulate 是乘法,没法把角色变成白色。受击闪白用 flash:

ts
import { Ease, Scene, Sprite2D, tex, v, type Tween } from 'sapling2d'

class Enemy extends Sprite2D {
  hp = 3
  private _flashTween: Tween | null = null

  hit() {
    this.hp--
    // 闪白:整个形状变成白色,再淡回原样(modulate 只能变暗,做不出这个效果)
    this._flashTween?.kill()
    this.flash = 1
    this._flashTween = this.createTween().to(this as Enemy, { flash: 0 }, 0.12, Ease.QuadOut) // 子类的属性:to(this as 类名, …)
  }

  /** 中毒:闪绿色。 */
  poison() {
    this.flashColor = 0x60ff60
    this.flash = 0.6
  }
}

export class Arena extends Scene {
  static override assets = { enemy: tex('enemy.png') }
  enemy!: Enemy

  override ready() {
    this.enemy = this.add(new Enemy({ texture: Arena.assets.enemy, position: v(375, 400) }))
  }
}
ts
import { expect, it } from 'vitest'
import { createTestGame } from 'sapling2d/testing'

it('flash', async () => {
  const g = await createTestGame({ main: Arena })
  const enemy = g.scene.enemy
  enemy.hit()
  expect(enemy.flash).toBe(1)
  g.step(3)
  expect(enemy.flash).toBeGreaterThan(0)
  expect(enemy.flash).toBeLessThan(1)
  g.stepSeconds(0.2)
  expect(enemy.flash).toBe(0) // 淡回原样
  enemy.poison()
  expect(g.dump()).toContain('flash=0.6 flashColor=#60ff60')
})
  • flash(0–1,默认 0)在贴图的形状上叠一层 flashColor(默认白色)。为 1 时整个形状变成纯色,透明的地方仍然透明。
  • 受击时设成 1,再补间回 0;反复受击时先 kill() 上一个补间。flashColor 也能补间。
  • modulate、alpha、blendMode 照常作用在闪光上:被 modulate 染蓝的节点闪出来是淡蓝。
  • 帧动画、图集的帧、翻转、offset 都照常对齐。只作用于这个精灵自己,不影响子节点。
  • 代价:一张图第一次闪白时,显存里会生成一张同样大小的白色剪影,之后这张图的所有帧、所有精灵共用。大图集闪一次就多占一份显存,所以给要闪白的角色单独打一张图集。

叠加发光:blendMode ​

火光、爆炸、刀光、魔法这类发光的特效,用 blendMode: 'add':颜色相加,叠在别的东西上会把它照亮。普通混合只会盖上一层颜色,看起来发灰。

ts
import { Ease, Node2D, Particles2D, Scene, Sprite2D, tex, v, type Vector2 } from 'sapling2d'

export class Arena extends Scene {
  // glow.png:白色、中心亮边缘透明的圆,用 selfModulate 染色
  static override assets = { glow: tex('glow.png'), spark: tex('spark.png') }
  /** 所有发光特效的父节点:设一次 'add',下面的光圈和火花都叠加发光;挨在一起绘制也更容易合批。 */
  fx!: Node2D

  override ready() {
    this.fx = this.add(new Node2D({ name: 'Fx', blendMode: 'add', zIndex: 100 }))
  }

  /** 爆炸:一个放大淡出的光圈 + 一把火花。叠在怪物上会把它照亮,而不是盖上一层不透明的橙色。 */
  explode(at: Vector2) {
    const glow = this.fx.add(new Sprite2D({ texture: Arena.assets.glow, position: at, scale: v(0.5, 0.5), selfModulate: 0xff9030 }))
    glow.createTween().to(glow, { scale: v(2.5, 2.5), alpha: 0 }, 0.3, Ease.QuadOut).call(() => glow.queueFree())
    const sparks = this.fx.add(
      new Particles2D({ texture: Arena.assets.spark, position: at, emitting: false, amount: 12, lifetime: 0.3, speedMin: 150, speedMax: 350, alphaEnd: 0, selfModulate: 0xffd060 }),
    )
    sparks.emit()
    sparks.finished.connect(() => sparks.queueFree())
  }
}
ts
import { expect, it } from 'vitest'
import { createTestGame } from 'sapling2d/testing'

it('blend mode', async () => {
  const g = await createTestGame({ main: Arena })
  g.scene.explode(v(375, 600))
  g.step()
  expect(g.dump()).toContain('Fx (Node2D) position=(0, 0) zIndex=100 blendMode=add')
  const glow = g.scene.fx.children[0] as Sprite2D
  expect(glow.blendMode).toBe('inherit') // 子节点跟随父节点:实际按 'add' 绘制
  g.stepSeconds(0.5)
  expect(g.scene.fx.children).toHaveLength(0) // 光圈淡出、火花散完后都删掉了
})
  • 取值:'inherit'(默认,跟随父节点)、'normal'、'add'。作用于自己和整棵子树,子节点可以设回 'normal'。
  • 不传进 CanvasLayer:界面层自成一层,要发光就在层里面的节点上设。
  • 贴图、ColorRect、文字、粒子、图块地图都按它绘制。alpha 照常起作用(淡出时光也变弱),selfModulate 给光染色。
  • 发光贴图做成白色、黑底或透明底、中心亮边缘暗,颜色交给 selfModulate。
  • 背景越暗效果越好;同一处叠太多层会过曝成一片白。
  • 混合模式不同的相邻节点不能合批:同屏很多发光特效时,把它们放在同一个父节点下,让它们在绘制顺序上挨在一起。

MIT 许可证