Update particle interpolation and effect management

- 파티클 스케일 및 투명도에 다중 키프레임 보간 기능 추가
- 스케일 보간을 초기값 기준 배율 방식으로 변경
- 기존 이펙트 인스턴스의 설정을 실시간으로 업데이트하도록 개선
- 다중 구간 보간을 위한 lerpKeyframes 유틸리티 함수 구현
- SpriteParticle 인터페이스에 초기 스케일 상태 저장 추가
This commit is contained in:
BaekRyang 2026-03-11 08:55:34 +09:00
parent 530e6d0396
commit f3c5ae3669
4 changed files with 38 additions and 9 deletions

View File

@ -15,6 +15,21 @@ const randomRange = (min: number, max: number): number =>
const lerp = (a: number, b: number, t: number): number => const lerp = (a: number, b: number, t: number): number =>
a + (b - a) * t; a + (b - a) * t;
/**
* (2개: 선형, 3개: 전반/ )
*/
const lerpKeyframes = (values: number[], t: number): number => {
if (values.length === 2) {
return lerp(values[0], values[1], t);
}
// 3개 이상: 균등 구간 분할
const segments = values.length - 1;
const segT = t * segments;
const idx = Math.min(Math.floor(segT), segments - 1);
const localT = segT - idx;
return lerp(values[idx], values[idx + 1], localT);
};
/** /**
* SpriteEffectConfig에 * SpriteEffectConfig에
* , , / * , , /
@ -67,6 +82,11 @@ export class SpriteEffectInstance {
this.loadTexture(config.spriteUrl); this.loadTexture(config.spriteUrl);
} }
/** 런타임 설정 업데이트 (maxParticles 제외) */
updateConfig(config: SpriteEffectConfig): void {
this.config = config;
}
/** 텍스처 로드 */ /** 텍스처 로드 */
private loadTexture(url: string): void { private loadTexture(url: string): void {
const loader = new THREE.TextureLoader(); const loader = new THREE.TextureLoader();
@ -139,7 +159,8 @@ export class SpriteEffectInstance {
particle.velocity.y = Math.sin(angleRad) * speed; particle.velocity.y = Math.sin(angleRad) * speed;
// 초기 속성 // 초기 속성
particle.scale = randomRange(config.initialScale[0], config.initialScale[1]); particle.initialScale = randomRange(config.initialScale[0], config.initialScale[1]);
particle.scale = particle.initialScale;
particle.rotation = 0; particle.rotation = 0;
particle.opacity = 1; particle.opacity = 1;
particle.lifetime = randomRange(config.lifetime[0], config.lifetime[1]); particle.lifetime = randomRange(config.lifetime[0], config.lifetime[1]);
@ -212,10 +233,11 @@ export class SpriteEffectInstance {
// overLifetime 보간 적용 // overLifetime 보간 적용
if (overLifetime) { if (overLifetime) {
if (overLifetime.scale) { if (overLifetime.scale) {
particle.scale = lerp(overLifetime.scale[0], overLifetime.scale[1], lifeRatio); // overLifetime.scale은 initialScale에 대한 배율로 적용
particle.scale = particle.initialScale * lerpKeyframes(overLifetime.scale, lifeRatio);
} }
if (overLifetime.opacity) { if (overLifetime.opacity) {
particle.opacity = lerp(overLifetime.opacity[0], overLifetime.opacity[1], lifeRatio); particle.opacity = lerpKeyframes(overLifetime.opacity, lifeRatio);
} }
if (overLifetime.rotationSpeed) { if (overLifetime.rotationSpeed) {
particle.rotation += overLifetime.rotationSpeed * deltaTime; particle.rotation += overLifetime.rotationSpeed * deltaTime;

View File

@ -59,8 +59,12 @@ export class SpriteEffectManager {
const key = `${area.id}::${effectConfig.id}`; const key = `${area.id}::${effectConfig.id}`;
activeKeys.add(key); activeKeys.add(key);
// 이미 존재하면 스킵 // 이미 존재하면 설정만 업데이트
if (this.instances.has(key)) continue; const existing = this.instances.get(key);
if (existing) {
existing.updateConfig(effectConfig);
continue;
}
// 새 인스턴스 생성 // 새 인스턴스 생성
console.log('[SpriteEffectManager] 인스턴스 생성:', key, effectConfig.spriteUrl); console.log('[SpriteEffectManager] 인스턴스 생성:', key, effectConfig.spriteUrl);

View File

@ -14,6 +14,8 @@ export interface SpriteParticle {
velocity: Point; velocity: Point;
/** 현재 스케일 */ /** 현재 스케일 */
scale: number; scale: number;
/** 방출 시 초기 스케일 (overLifetime 배율 기준값) */
initialScale: number;
/** 현재 회전 (라디안) */ /** 현재 회전 (라디안) */
rotation: number; rotation: number;
/** 현재 투명도 */ /** 현재 투명도 */
@ -48,6 +50,7 @@ export class SpriteParticlePool {
position: { x: 0, y: 0 }, position: { x: 0, y: 0 },
velocity: { x: 0, y: 0 }, velocity: { x: 0, y: 0 },
scale: 1, scale: 1,
initialScale: 1,
rotation: 0, rotation: 0,
opacity: 1, opacity: 1,
age: 0, age: 0,

View File

@ -10,10 +10,10 @@ export type SpriteBlendMode = 'normal' | 'additive';
* *
*/ */
export interface SpriteParticleOverLifetime { export interface SpriteParticleOverLifetime {
/** [시작, 끝] 스케일 */ /** 스케일 보간: [시작, 끝] 또는 [시작, 중간, 끝] */
scale?: [number, number]; scale?: number[];
/** [시작, 끝] 투명도 */ /** 투명도 보간: [시작, 끝] 또는 [시작, 중간, 끝] */
opacity?: [number, number]; opacity?: number[];
/** 회전 속도 (라디안/초) */ /** 회전 속도 (라디안/초) */
rotationSpeed?: number; rotationSpeed?: number;
/** 속도 감쇠 (0-1, 매 프레임 속도에 곱해짐) */ /** 속도 감쇠 (0-1, 매 프레임 속도에 곱해짐) */