- 캔버스 종횡비에 따른 원형 영역 클릭 감지 로직 수정 - SpriteEffectManager의 원형 판정 함수에 aspect ratio 계산 추가 - 패키지 버전을 1.5.4로 업데이트
148 lines
4.5 KiB
TypeScript
148 lines
4.5 KiB
TypeScript
import * as THREE from 'three';
|
|
import type { Point } from '@/types';
|
|
import type { SpriteEffectArea } from '@/types/spriteEffect';
|
|
import { SpriteEffectInstance } from './SpriteEffectInstance';
|
|
|
|
/**
|
|
* 터치/마우스 상태 (스프라이트 이펙트 전용)
|
|
*/
|
|
export interface SpriteEffectTouchState {
|
|
/** 마우스/터치 위치 (정규화 좌표, null이면 미접촉) */
|
|
position: Point | null;
|
|
/** 드래그 중 여부 */
|
|
isDragging: boolean;
|
|
}
|
|
|
|
/**
|
|
* 점이 원 안에 있는지 확인
|
|
*/
|
|
const isPointInCircle = (point: Point, center: Point, radius: number, resolution?: { x: number; y: number }): boolean => {
|
|
const aspect = resolution ? resolution.x / resolution.y : 1;
|
|
const dx = point.x - center.x;
|
|
const dy = (point.y - center.y) / aspect;
|
|
return dx * dx + dy * dy <= radius * radius;
|
|
};
|
|
|
|
/**
|
|
* 스프라이트 이펙트 전체 관리자
|
|
* ImageDistortion 컴포넌트에서 생성하여 사용하는 최상위 진입점
|
|
* 왜곡 영역(DistortionArea)과 독립적으로 이펙트 영역을 관리
|
|
*/
|
|
export class SpriteEffectManager {
|
|
/** 모든 이펙트 메쉬를 담는 그룹 */
|
|
private effectGroup: THREE.Group;
|
|
/** 영역ID+이펙트ID → 인스턴스 맵 */
|
|
private instances: Map<string, SpriteEffectInstance> = new Map();
|
|
/** 이전 프레임에서 터치 중이던 영역 ID 세트 (버스트 감지용) */
|
|
private previousTouchingAreas: Set<string> = new Set();
|
|
|
|
constructor() {
|
|
this.effectGroup = new THREE.Group();
|
|
this.effectGroup.renderOrder = 1;
|
|
}
|
|
|
|
/**
|
|
* Three.js 씬에 이펙트 그룹 추가
|
|
*/
|
|
attachToScene(scene: THREE.Scene): void {
|
|
scene.add(this.effectGroup);
|
|
}
|
|
|
|
/**
|
|
* 이펙트 영역 설정 변경을 감지하여 인스턴스 생성/제거
|
|
*/
|
|
syncEffects(effectAreas: SpriteEffectArea[]): void {
|
|
console.log('[SpriteEffectManager] syncEffects 호출:', effectAreas.length, '개 영역');
|
|
const activeKeys = new Set<string>();
|
|
|
|
for (const area of effectAreas) {
|
|
for (const effectConfig of area.effects) {
|
|
const key = `${area.id}::${effectConfig.id}`;
|
|
activeKeys.add(key);
|
|
|
|
// 이미 존재하면 설정만 업데이트
|
|
const existing = this.instances.get(key);
|
|
if (existing) {
|
|
existing.updateConfig(effectConfig);
|
|
continue;
|
|
}
|
|
|
|
// 새 인스턴스 생성
|
|
console.log('[SpriteEffectManager] 인스턴스 생성:', key, effectConfig.emoji ?? effectConfig.spriteUrl);
|
|
const instance = new SpriteEffectInstance(effectConfig);
|
|
this.instances.set(key, instance);
|
|
this.effectGroup.add(instance.group);
|
|
}
|
|
}
|
|
|
|
// 더 이상 사용되지 않는 인스턴스 제거
|
|
for (const [key, instance] of this.instances) {
|
|
if (!activeKeys.has(key)) {
|
|
instance.dispose();
|
|
this.effectGroup.remove(instance.group);
|
|
this.instances.delete(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 매 프레임 업데이트
|
|
* @param effectAreas 이펙트 영역 배열
|
|
* @param deltaTime 초 단위 프레임 시간
|
|
* @param touchState 마우스/터치 상태
|
|
*/
|
|
update(effectAreas: SpriteEffectArea[], deltaTime: number, touchState: SpriteEffectTouchState, resolution?: { x: number; y: number }): void {
|
|
// 현재 터치 중인 영역 감지
|
|
const currentTouchingAreas = new Set<string>();
|
|
|
|
if (touchState.isDragging && touchState.position) {
|
|
for (const area of effectAreas) {
|
|
const radius = area.radius ?? 0.1;
|
|
if (isPointInCircle(touchState.position, area.position, radius, resolution)) {
|
|
currentTouchingAreas.add(area.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 각 영역의 이펙트 업데이트
|
|
for (const area of effectAreas) {
|
|
for (const effectConfig of area.effects) {
|
|
const key = `${area.id}::${effectConfig.id}`;
|
|
const instance = this.instances.get(key);
|
|
if (!instance) continue;
|
|
|
|
// touch 이펙트: 새로 터치된 영역이면 버스트
|
|
if (effectConfig.trigger === 'touch') {
|
|
const isNewTouch = currentTouchingAreas.has(area.id)
|
|
&& !this.previousTouchingAreas.has(area.id);
|
|
if (isNewTouch) {
|
|
instance.triggerBurst(touchState.position ?? area.position);
|
|
}
|
|
}
|
|
|
|
// 매 프레임 업데이트 (ambient 방출 + 파티클 물리)
|
|
instance.update(deltaTime, area.position, resolution);
|
|
}
|
|
}
|
|
|
|
// 터치 상태 갱신
|
|
this.previousTouchingAreas = currentTouchingAreas;
|
|
}
|
|
|
|
/**
|
|
* 리소스 정리
|
|
*/
|
|
dispose(): void {
|
|
for (const [, instance] of this.instances) {
|
|
instance.dispose();
|
|
}
|
|
this.instances.clear();
|
|
this.previousTouchingAreas.clear();
|
|
|
|
// effectGroup을 부모에서 제거
|
|
if (this.effectGroup.parent) {
|
|
this.effectGroup.parent.remove(this.effectGroup);
|
|
}
|
|
}
|
|
}
|