背景也参与颜色混合显示

This commit is contained in:
tianmo committed 2026-11-09 17:56:12 +08:00
1 parent d4ecb1f896
commit 4b1a329e1b
7 files changed
+152 -131

No files matched your search

+31 -10
View File
@@ -1,6 +1,6 @@
<template>
<div class="stage-wrap">
<div ref="holder" class="stage-holder" :class="{ grid: store.settings.showGrid }" tabindex="0" @wheel.prevent="onWheelEvent" @pointerdown="onPanDown" @dblclick.prevent="onStageDoubleClick"></div>
<div ref="holder" class="stage-holder" tabindex="0" @wheel.prevent="onWheelEvent" @pointerdown="onPanDown" @dblclick.prevent="onStageDoubleClick"></div>
<img
class="stage-watermark"
:class="{ 'watermark-left-panel': !store.settings.parameterPanelOnRight }"
@@ -51,7 +51,7 @@ import { drawBoneFollowTargets, drawGizmo, drawCollisionBodies, drawPaths } from
import { appendStateBones, drawBones } from './stage/boneGuides'
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { Application, Container, Graphics, type Text, type Texture } from 'pixi.js'
import { Application, Container, Graphics, Texture, TilingSprite, type Text } from 'pixi.js'
import { ParticleEmitter, ensureEmitterConfig, type ParticleState } from '../core/particleEmitter'
import { bakeLoopFrames } from '../core/loopFrameBaker'
import { loopPlaybackRange } from '../core/loopSegmentTiming'
@@ -101,6 +101,7 @@ if (store.settings.showGrid == null) store.settings.showGrid = true
let app: Application | null = null
let stageResizeObserver: ResizeObserver | null = null
let world: Container | null = null
let canvasGrid: TilingSprite | null = null
let originGfx: Graphics | null = null
let boneFollowTargetGfx: Graphics | null = null
let boneGfx: Graphics | null = null
@@ -136,6 +137,16 @@ let previewColliders: CollisionBody[] = []
const emitterPreviewTransform = new Map<number, { x: number; y: number; rotation: number }>()
function createLayers() {
// 棋盘格必须在 WebGL 底部,粒子混合模式才能读取它。
const checker = document.createElement('canvas')
checker.width = checker.height = 28
const checkerContext = checker.getContext('2d')!
checkerContext.fillStyle = '#141a26'
checkerContext.fillRect(14, 0, 14, 14)
checkerContext.fillRect(0, 14, 14, 14)
canvasGrid = new TilingSprite(Texture.from(checker), app!.screen.width, app!.screen.height)
canvasGrid.visible = store.settings.showGrid
app!.stage.addChild(canvasGrid)
world = new Container()
app!.stage.addChild(world)
axisLabelLayer = new Container() // 独立顶层,不随 world 缩放
@@ -604,19 +615,33 @@ function loop() {
onMounted(async () => {
const el = holder.value!
app = new Application({ width: el.clientWidth || 800, height: el.clientHeight || 600, backgroundAlpha: 0, antialias: true })
app = new Application({
width: el.clientWidth || 800,
height: el.clientHeight || 600,
backgroundColor: 0x0e1219,
backgroundAlpha: 1,
antialias: true,
})
el.appendChild(app.view as unknown as HTMLCanvasElement)
stageResizeObserver = new ResizeObserver(() => {
if (!app) return
const nextWidth = Math.max(1, el.clientWidth)
const previousHeight = app.screen.height
const nextHeight = Math.max(1, el.clientHeight)
// Stage 高度变化时补偿视图中心,避免底部面板折叠导致画布内容整体跳动。
if (Math.abs(nextHeight - previousHeight) > 0.5) store.editor.viewY -= (nextHeight - previousHeight) / 2
app.renderer.resize(Math.max(1, el.clientWidth), nextHeight)
app.renderer.resize(nextWidth, nextHeight)
if (canvasGrid) {
canvasGrid.width = nextWidth
canvasGrid.height = nextHeight
}
})
stageResizeObserver.observe(el)
createLayers()
watch(() => store.settings.backgroundImageData, (value) => background.syncTexture(value), { immediate: true })
watch(() => store.settings.showGrid, (visible) => {
if (canvasGrid) canvasGrid.visible = visible
})
const textures = await loadDefaultTextures()
dotTex = textures.dotTex
trailTex = textures.trailTex
@@ -663,6 +688,8 @@ onUnmounted(() => {
spineRuntime?.destroy()
spineRuntime = null
background.destroy()
canvasGrid?.texture.destroy(true)
canvasGrid = null
if (world && app) emitterSync.destroy(world)
if (app) { app.destroy(true, { children: true }); app = null }
})
@@ -672,16 +699,10 @@ watch(() => [store.activeObjectType, store.activeObjectId], () => pathEditing.re
<style scoped>
.stage-wrap { position: relative; display: flex; width: 100%; height: 100%; min-height: 0; flex-direction: column; overflow: hidden; }
/* 画布:默认纯色;开启网格时显示深色棋盘格 */
.stage-holder {
width: 100%; min-height: 0; flex: 1; cursor: grab; touch-action: none;
background-color: #0e1219;
outline: none;
}
.stage-holder.grid {
background: conic-gradient(#141a26 90deg, transparent 90deg 180deg, #141a26 180deg 270deg, #141a26 180deg 270deg, transparent 270deg);
background-size: 28px 28px;
}
.stage-holder:active { cursor: grabbing; }
.stage-watermark {
position: absolute; top: 14px; left: 14px; z-index: 1;