发射器形状新增

This commit is contained in:
tianmo committed 2026-09-05 17:31:32 +08:00
1 parent 0bde824c88
commit 026ee74d48
11 files changed
+1657 -1336

No files matched your search

+56 -13
View File
@@ -287,23 +287,53 @@
<option value="point">点</option>
<option value="circle">圆形</option>
<option value="rect">矩形</option>
<option value="cone">锥体</option>
<option value="orbit">轨道</option>
<option value="cone">锥形</option>
<option value="path">路径</option>
</select>
</label>
<template v-if="sys.config.shape === 'circle' || sys.config.shape === 'cone'">
<NumSlider label="半径" :min="0" :max="300" :step="1" v-model="sys.config.radius" />
<NumSlider :label="sys.config.shape === 'cone' ? '内径' : '内半径'" :min="0" :max="sys.config.outerRadius" :step="1" v-model="sys.config.innerRadius" />
<NumSlider :label="sys.config.shape === 'cone' ? '外径' : '外半径'" :min="sys.config.innerRadius" :max="300" :step="1" v-model="sys.config.outerRadius" />
</template>
<template v-if="sys.config.shape === 'cone'">
<NumSlider label="锥形朝向" :min="-360" :max="360" :step="1" v-model="sys.config.coneDirection" />
<NumSlider label="锥形开口角度" :min="1" :max="360" :step="1" v-model="sys.config.coneAngle" />
</template>
<template v-else-if="sys.config.shape === 'rect'">
<NumSlider label="宽" :min="1" :max="400" :step="1" v-model="sys.config.rectW" />
<NumSlider label="高" :min="1" :max="400" :step="1" v-model="sys.config.rectH" />
<NumSlider label="内宽" :min="0" :max="sys.config.outerRectW" :step="1" v-model="sys.config.innerRectW" />
<NumSlider label="内高" :min="0" :max="sys.config.outerRectH" :step="1" v-model="sys.config.innerRectH" />
<NumSlider label="外宽" :min="sys.config.innerRectW" :max="400" :step="1" v-model="sys.config.outerRectW" />
<NumSlider label="外高" :min="sys.config.innerRectH" :max="400" :step="1" v-model="sys.config.outerRectH" />
</template>
<template v-else-if="sys.config.shape === 'path'">
<label class="row"><span>选择路径</span>
<select v-model.number="sys.config.spawnPathId" class="inp" @change="onSpawnPathChange">
<option :value="0">无路径(点发射)</option>
<option v-for="path in store.paths" :key="path.id" :value="path.id" :disabled="!path.enabled">{{ path.name }}</option>
</select>
</label>
<label v-if="sys.config.spawnPathId > 0" class="row"><span>出生分布</span>
<select v-model="sys.config.spawnPathDistribution" class="inp">
<option value="fixed">等间距固定分布</option>
<option value="random">随机分布</option>
</select>
</label>
</template>
<div class="divider"></div>
<NumSlider :label="sys.config.shape === 'cone' ? '锥形朝向' : '发射角度'" :min="-360" :max="360" :step="1" v-model="sys.config.direction" />
<NumSlider v-if="sys.config.shape !== 'cone'" label="扩散角度" :min="0" :max="360" :step="1" v-model="sys.config.spread" />
<label class="row"><span>发射角度</span>
<select v-model="sys.config.emissionAngleMode" class="inp">
<option value="fixed">固定</option>
<option value="random">随机</option>
<option v-if="sys.config.shape === 'circle' || sys.config.shape === 'rect' || sys.config.shape === 'cone'" value="radial">径向(中心向外)</option>
<option v-if="sys.config.shape === 'path' && sys.config.spawnPathId > 0" value="path-normal">垂直于路径</option>
<option v-if="sys.config.shape === 'path' && sys.config.spawnPathId > 0" value="path-tangent">沿路径方向</option>
</select>
</label>
<NumSlider v-if="sys.config.emissionAngleMode === 'fixed'" label="固定角度" :min="-360" :max="360" :step="1" v-model="sys.config.direction" />
<template v-else-if="sys.config.emissionAngleMode === 'random'">
<NumSlider label="基准角度" :min="-360" :max="360" :step="1" v-model="sys.config.direction" />
<NumSlider label="随机范围" :min="0" :max="360" :step="1" v-model="sys.config.spread" />
</template>
</div>
</div>
@@ -1245,6 +1275,10 @@ watchEffect(() => {
watchEffect(() => {
for (const system of store.systems) {
const config = ensureEmitterConfig(system.config as any)
if (config.spawnPathId > 0 && !store.paths.some((path) => path.id === config.spawnPathId)) {
config.spawnPathId = 0
if (config.emissionAngleMode === 'path-normal' || config.emissionAngleMode === 'path-tangent') config.emissionAngleMode = 'fixed'
}
if (config.emitterFollowMode !== 'bone') continue
const spine = store.spines.find((item) => item.id === config.emitterFollowSpineId)
const targetExists = !!spine?.bones.some((bone) => bone.name === config.emitterFollowBoneName)
@@ -1404,7 +1438,11 @@ const MODULE_RESET_KEYS: Record<Exclude<ResetModule, 'export'>, string[]> = {
'generateLoopEndAnimation', 'loopEndUseCustomDuration', 'loopEndDurationFrames',
'burstCount', 'burstLoop', 'delay', 'duration',
],
shape: ['shape', 'radius', 'rectW', 'rectH', 'direction', 'spread', 'coneAngle'],
shape: [
'shape', 'shapeRangeVersion',
'innerRadius', 'outerRadius', 'innerRectW', 'innerRectH', 'outerRectW', 'outerRectH', 'spawnPathId', 'spawnPathDistribution',
'radius', 'rectW', 'rectH', 'emissionAngleMode', 'direction', 'spread', 'coneDirection', 'coneAngle',
],
attr: [
'lifeMin', 'lifeMax', 'lifeMode', 'lifeCurve',
'speedMin', 'speedMax', 'speedMode', 'speedCurve',
@@ -1545,11 +1583,11 @@ function setScaleOverLifeMode(mode: 'fixed' | 'random' | 'curve' | 'direction')
// 各发射器形状的默认参数:切换形状时应用(只影响形状相关字段,不牵连其他)
const SHAPE_DEFAULTS: Record<string, Partial<any>> = {
point: { direction: 0, spread: 360 },
circle: { radius: 100, direction: -90, spread: 0 },
rect: { rectW: 200, rectH: 100, direction: -90, spread: 0 },
cone: { radius: 100, coneAngle: 90, direction: 90 },
orbit: {},
point: { emissionAngleMode: 'random', direction: 0, spread: 360 },
circle: { innerRadius: 0, outerRadius: 100, emissionAngleMode: 'fixed', direction: -90, spread: 0 },
rect: { innerRectW: 0, innerRectH: 0, outerRectW: 200, outerRectH: 100, emissionAngleMode: 'fixed', direction: -90, spread: 0 },
cone: { innerRadius: 0, outerRadius: 100, coneAngle: 90, coneDirection: 90, emissionAngleMode: 'random', direction: 90, spread: 90 },
path: { spawnPathId: 0, spawnPathDistribution: 'fixed', emissionAngleMode: 'fixed', direction: 0, spread: 0 },
}
function onShapeChange() {
const cfg = sys.value?.config
@@ -1558,6 +1596,11 @@ function onShapeChange() {
if (!d) return
Object.assign(cfg, d)
}
function onSpawnPathChange() {
const cfg = sys.value?.config
if (!cfg || cfg.spawnPathId > 0) return
if (cfg.emissionAngleMode === 'path-normal' || cfg.emissionAngleMode === 'path-tangent') cfg.emissionAngleMode = 'fixed'
}
// 重置/删除图片 → 回到默认 star.png
const textureFileInput = ref<HTMLInputElement | null>(null)
const sequenceFileInput = ref<HTMLInputElement | null>(null)