ADD : 요요 보스 레이드 기능 페이셜 필터 강화 기능 ik 포인트 해결

This commit is contained in:
user 2026-03-29 00:14:40 +09:00
parent 3929be8974
commit 074c11eb8a
1676 changed files with 78605 additions and 134 deletions

View File

@ -4,12 +4,57 @@ using UnityEngine;
[CustomEditor(typeof(OptitrackSkeletonAnimator_Mingle))]
public class OptitrackSkeletonAnimatorEditor : Editor
{
private static readonly string[] k_FilterLabels = { "Off", "Low", "Medium", "High", "Custom" };
public override void OnInspectorGUI()
{
DrawDefaultInspector();
var anim = (OptitrackSkeletonAnimator_Mingle)target;
// 필터 강도 버튼 나열
EditorGUILayout.Space(4);
EditorGUILayout.LabelField("필터 강도", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
for (int i = 0; i < k_FilterLabels.Length; i++)
{
var strength = (OptitrackSkeletonAnimator_Mingle.FilterStrength)i;
bool isSelected = anim.filterStrength == strength;
var prevBgBtn = GUI.backgroundColor;
GUI.backgroundColor = isSelected ? new Color(0.4f, 0.7f, 1f) : Color.white;
if (GUILayout.Button(k_FilterLabels[i], GUILayout.Height(24)))
{
Undo.RecordObject(anim, "Change Filter Strength");
anim.SetFilterStrength(strength);
EditorUtility.SetDirty(anim);
}
GUI.backgroundColor = prevBgBtn;
}
EditorGUILayout.EndHorizontal();
// Custom일 때 슬라이더 표시
if (anim.filterStrength == OptitrackSkeletonAnimator_Mingle.FilterStrength.Custom)
{
EditorGUI.indentLevel++;
EditorGUI.BeginChangeCheck();
float minCutoff = EditorGUILayout.Slider("Min Cutoff (Hz)", anim.filterMinCutoff, 0.1f, 10f);
float beta = EditorGUILayout.Slider("Beta", anim.filterBeta, 0f, 5f);
float maxCutoff = EditorGUILayout.Slider("Max Cutoff (Hz)", anim.filterMaxCutoff, 5f, 120f);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(anim, "Change Filter Custom Values");
anim.filterMinCutoff = minCutoff;
anim.filterBeta = beta;
anim.filterMaxCutoff = maxCutoff;
EditorUtility.SetDirty(anim);
}
EditorGUI.indentLevel--;
}
// 연결 상태 배지
EditorGUILayout.Space(8);
var prevBg = GUI.backgroundColor;

View File

@ -33,19 +33,53 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour
[Tooltip("에디터에서 캡처한 T-포즈 데이터. 비어 있으면 런타임 CacheRestPose()를 사용합니다.")]
public OptitrackRestPoseData RestPoseAsset;
public enum FilterStrength { Off, Low, Medium, High, Custom }
[Header("본 1€ 필터 (속도 적응형 저역통과)")]
[Tooltip("활성화 시 빠른 움직임은 그대로, 정지/느린 움직임의 노이즈를 제거합니다.\n단순 EMA보다 모션 보존이 훨씬 우수합니다.")]
public bool enableBoneFilter = true;
[Tooltip("최소 차단 주파수 (Hz). 정지 시 노이즈 제거 강도. 낮을수록 강함. 권장: 2~4 Hz")]
[Range(0.1f, 10f)]
public float filterMinCutoff = 3.0f;
[Tooltip("속도 계수. 빠른 동작에서 cutoff 상승 속도. 높을수록 지연 감소. 권장: 0.5~2.0")]
[Range(0f, 5f)]
public float filterBeta = 1.5f;
[Tooltip("최대 차단 주파수 상한 (Hz). 빠른 동작에서도 이 이상 주파수는 항상 제거됩니다.\n" +
"MagicaCloth2 지터가 빠른 동작에서 발생하면 낮추세요. 권장: 10~20 Hz")]
[Range(5f, 120f)]
public float filterMaxCutoff = 15.0f;
[HideInInspector]
public FilterStrength filterStrength = FilterStrength.Medium;
[HideInInspector] public float filterMinCutoff = 3.0f;
[HideInInspector] public float filterBeta = 1.5f;
[HideInInspector] public float filterMaxCutoff = 15.0f;
// 프리셋별 파라미터 (minCutoff, beta, maxCutoff)
private static readonly (float minCutoff, float beta, float maxCutoff)[] k_FilterPresets =
{
(0f, 0f, 0f), // Off (사용 안 함)
(5.0f, 2.0f, 25.0f), // Low
(3.0f, 1.5f, 15.0f), // Medium
(1.5f, 0.8f, 10.0f), // High
(0f, 0f, 0f), // Custom (프리셋 적용 안 함)
};
[HideInInspector] public bool enableBoneFilter = true;
/// <summary>
/// 런타임에서 필터 강도를 변경합니다. StreamDeck/핫키 등에서 호출.
/// </summary>
public void SetFilterStrength(FilterStrength strength)
{
filterStrength = strength;
if (strength != FilterStrength.Off && strength != FilterStrength.Custom)
{
var preset = k_FilterPresets[(int)strength];
filterMinCutoff = preset.minCutoff;
filterBeta = preset.beta;
filterMaxCutoff = preset.maxCutoff;
}
// 프리셋 전환 시 필터 상태 리셋 (이전 값과 불연속 방지)
m_filterStates.Clear();
}
/// <summary>
/// 현재 프리셋에서 다음 프리셋으로 순환. 버튼 하나로 Off→Low→Medium→High→Custom→Off...
/// </summary>
public void CycleFilterStrength()
{
int next = ((int)filterStrength + 1) % System.Enum.GetValues(typeof(FilterStrength)).Length;
SetFilterStrength((FilterStrength)next);
}
private OptitrackSkeletonDefinition m_skeletonDef;
private string previousSkeletonName;
@ -239,6 +273,9 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour
return;
}
// 필터 활성화 상태 동기화 (프리셋 값은 SetFilterStrength()에서만 적용)
enableBoneFilter = filterStrength != FilterStrength.Off;
// MirrorMode 변경 감지 → 필터 상태 리셋 (불연속 튐 방지)
bool currentMirrorMode = StreamingClient != null && StreamingClient.MirrorMode;
if (currentMirrorMode != m_lastMirrorMode)

View File

@ -17,6 +17,9 @@ public class StreamingleFacialReceiverEditor : Editor
private VisualElement statusContainer;
private VisualElement emaFields;
private VisualElement euroFields;
private VisualElement medianEuroFields;
private VisualElement sharedPortInfo;
private Label masterStatusValue;
public override VisualElement CreateInspectorGUI()
{
@ -40,6 +43,9 @@ public class StreamingleFacialReceiverEditor : Editor
portButtonsContainer = root.Q("portButtonsContainer");
emaFields = root.Q("emaFields");
euroFields = root.Q("euroFields");
medianEuroFields = root.Q("medianEuroFields");
sharedPortInfo = root.Q("sharedPortInfo");
masterStatusValue = root.Q<Label>("masterStatusValue");
// Auto-find button
var autoFindBtn = root.Q<Button>("autoFindBtn");
@ -50,7 +56,7 @@ public class StreamingleFacialReceiverEditor : Editor
// Build dynamic port buttons
RebuildPortButtons();
// Track filterMode for conditional visibility of EMA/Euro fields
// Track filterMode for conditional visibility of filter fields
var filterModeProp = serializedObject.FindProperty("filterMode");
UpdateFilterModeVisibility(filterModeProp.enumValueIndex);
@ -59,6 +65,15 @@ public class StreamingleFacialReceiverEditor : Editor
UpdateFilterModeVisibility(prop.enumValueIndex);
});
// Track useSharedPort for info visibility
var sharedPortProp = serializedObject.FindProperty("useSharedPort");
UpdateSharedPortVisibility(sharedPortProp.boolValue);
root.TrackPropertyValue(sharedPortProp, prop =>
{
UpdateSharedPortVisibility(prop.boolValue);
});
// Track availablePorts and activePortIndex changes to rebuild port buttons
var portsProp = serializedObject.FindProperty("availablePorts");
root.TrackPropertyValue(portsProp, _ => RebuildPortButtons());
@ -74,11 +89,19 @@ public class StreamingleFacialReceiverEditor : Editor
private void UpdateFilterModeVisibility(int modeIndex)
{
// FilterMode: 0=None, 1=EMA, 2=OneEuro
// FilterMode: 0=None, 1=EMA, 2=OneEuro, 3=MedianOneEuro
if (emaFields != null)
emaFields.style.display = modeIndex == 1 ? DisplayStyle.Flex : DisplayStyle.None;
if (euroFields != null)
euroFields.style.display = modeIndex == 2 ? DisplayStyle.Flex : DisplayStyle.None;
if (medianEuroFields != null)
medianEuroFields.style.display = modeIndex == 3 ? DisplayStyle.Flex : DisplayStyle.None;
}
private void UpdateSharedPortVisibility(bool useShared)
{
if (sharedPortInfo != null)
sharedPortInfo.style.display = useShared ? DisplayStyle.Flex : DisplayStyle.None;
}
private void RebuildPortButtons()
@ -189,5 +212,30 @@ public class StreamingleFacialReceiverEditor : Editor
statusContainer.AddToClassList("facial-status-container--visible");
else if (!isPlaying && statusContainer.ClassListContains("facial-status-container--visible"))
statusContainer.RemoveFromClassList("facial-status-container--visible");
// 마스터 상태 업데이트 (플레이 모드에서만)
if (isPlaying && masterStatusValue != null && receiver != null && receiver.useSharedPort)
{
try
{
var status = StreamingleFacialUdpMaster.Instance.GetPortStatus();
if (status.TryGetValue(receiver.LOCAL_PORT, out int count))
{
masterStatusValue.text = $"Port {receiver.LOCAL_PORT}: {count} Receiver(s)";
}
else
{
masterStatusValue.text = "Not connected";
}
}
catch
{
masterStatusValue.text = "---";
}
}
else if (masterStatusValue != null)
{
masterStatusValue.text = receiver != null && receiver.useSharedPort ? "Play 모드에서 활성화" : "---";
}
}
}

View File

@ -72,6 +72,39 @@
margin-left: 8px;
}
/* ---- Shared Port (Master Mode) ---- */
.facial-shared-port-info {
padding: 6px 8px;
margin-top: 4px;
margin-bottom: 4px;
background-color: rgba(99, 102, 241, 0.1);
border-radius: 4px;
border-left-width: 3px;
border-left-color: rgba(99, 102, 241, 0.5);
}
.facial-info-text {
font-size: 10px;
color: #94a3b8;
-unity-font-style: italic;
white-space: normal;
margin-top: 2px;
}
.facial-master-status-row {
flex-direction: row;
align-items: center;
margin-top: 4px;
}
.facial-master-status-value {
-unity-font-style: bold;
font-size: 11px;
color: #a5b4fc;
margin-left: 8px;
}
/* ---- Port Hot-Swap ---- */
.facial-active-port-row {
@ -137,6 +170,19 @@
margin-top: 4px;
}
#medianEuroFields {
padding-left: 16px;
margin-top: 4px;
}
.facial-filter-separator-label {
font-size: 10px;
color: #64748b;
-unity-text-align: middle-center;
margin-top: 6px;
margin-bottom: 2px;
}
/* ---- Facial Intensity ---- */
.facial-separator {

View File

@ -21,6 +21,20 @@
</ui:Foldout>
</ui:VisualElement>
<!-- Shared Port (Master Mode) -->
<ui:VisualElement class="section">
<ui:Foldout text="Shared Port (Master Mode)" value="true" class="section-foldout">
<uie:PropertyField binding-path="useSharedPort" label="Use Shared Port"/>
<ui:VisualElement name="sharedPortInfo" class="facial-shared-port-info">
<ui:Label text="같은 포트에 여러 Receiver를 연결하여 동일한 모캡 데이터를 공유 수신합니다." class="facial-info-text"/>
</ui:VisualElement>
<ui:VisualElement name="masterStatusRow" class="facial-master-status-row">
<ui:Label text="Master Status" class="facial-port-label"/>
<ui:Label name="masterStatusValue" text="---" class="facial-master-status-value"/>
</ui:VisualElement>
</ui:Foldout>
</ui:VisualElement>
<!-- Port Hot-Swap (port buttons built dynamically in C#) -->
<ui:VisualElement class="section">
<ui:Foldout text="Port Hot-Swap" value="true" class="section-foldout">
@ -51,6 +65,15 @@
<uie:PropertyField binding-path="euroBeta" label="Beta (Speed Coeff)"/>
<uie:PropertyField binding-path="euroDCutoff" label="D Cutoff (Hz)"/>
</ui:VisualElement>
<!-- Median+Euro Filter Fields -->
<ui:VisualElement name="medianEuroFields">
<uie:PropertyField binding-path="medianWindowSize" label="Median Window Size"/>
<ui:Label text="── OneEuro Params ──" class="facial-filter-separator-label"/>
<uie:PropertyField binding-path="euroMinCutoff" label="Min Cutoff (Hz)"/>
<uie:PropertyField binding-path="euroBeta" label="Beta (Speed Coeff)"/>
<uie:PropertyField binding-path="euroDCutoff" label="D Cutoff (Hz)"/>
<ui:Label text="Median(스파이크제거) → OneEuro(적응형스무딩)" class="facial-info-text"/>
</ui:VisualElement>
</ui:Foldout>
</ui:VisualElement>

View File

@ -0,0 +1,46 @@
using UnityEngine;
/// <summary>
/// Median Filter - 임펄스 노이즈(스파이크) 제거에 탁월
/// 윈도우 내 중앙값을 출력하여 이상치를 자연스럽게 배제
///
/// OneEuroFilter 앞에 전처리로 사용하면 (MedianOneEuro 모드)
/// 스파이크 제거 + 적응형 스무딩의 조합으로 최고 품질의 필터링 가능
///
/// windowSize: 홀수 권장 (3=최소 지연, 5=일반, 7+=강한 스파이크 제거)
/// </summary>
public class MedianFilter
{
private readonly float[] buffer;
private readonly float[] sorted;
private int count;
private int writeIndex;
public MedianFilter(int windowSize = 5)
{
int size = Mathf.Max(3, windowSize | 1); // 홀수로 강제
buffer = new float[size];
sorted = new float[size];
count = 0;
writeIndex = 0;
}
public float Filter(float value)
{
buffer[writeIndex] = value;
writeIndex = (writeIndex + 1) % buffer.Length;
if (count < buffer.Length) count++;
// 현재 윈도우를 정렬하여 중앙값 반환
System.Array.Copy(buffer, sorted, count);
System.Array.Sort(sorted, 0, count);
return sorted[count / 2];
}
public void Reset()
{
count = 0;
writeIndex = 0;
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1c90c541f662e9d489f79aa99fea0162

View File

@ -3,6 +3,7 @@ using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Globalization;
@ -21,6 +22,11 @@ public class StreamingleFacialReceiver : MonoBehaviour
private string messageString = "";
private string lastProcessedMessage = ""; // 이전 메시지 저장용
// ── 공유 포트 (마스터 모드) ──
[Header("Shared Port (Master Mode)")]
[Tooltip("활성화 시 UdpMaster를 통해 같은 포트의 데이터를 여러 Receiver가 공유 수신")]
public bool useSharedPort = true;
// 포트 핫스왑 시스템
[Header("Port Hot-Swap")]
[Tooltip("사용 가능한 아이폰 포트 목록")]
@ -31,11 +37,11 @@ public class StreamingleFacialReceiver : MonoBehaviour
public int LOCAL_PORT => availablePorts != null && activePortIndex < availablePorts.Length ? availablePorts[activePortIndex] : 49983;
// 필터 모드 설정
public enum FilterMode { None, EMA, OneEuro }
public enum FilterMode { None, EMA, OneEuro, MedianOneEuro }
[Header("Data Filtering")]
[Tooltip("필터링 모드: None=필터없음, EMA=기존 스무딩+스파이크, OneEuro=1€ 적응형 필터")]
public FilterMode filterMode = FilterMode.OneEuro;
[Tooltip("필터링 모드: None=필터없음, EMA=스무딩+스파이크, OneEuro=1€ 적응형, Kalman=칼만필터, MedianOneEuro=메디안+1€ 복합")]
public FilterMode filterMode = FilterMode.MedianOneEuro;
// EMA 필터 설정 (기존 호환)
[Header("EMA Filter Settings")]
@ -67,16 +73,26 @@ public class StreamingleFacialReceiver : MonoBehaviour
[Range(0.1f, 5f)]
public float euroDCutoff = 1.0f;
// Euro 필터 인스턴스 (블렌드쉐이프별)
// Median+OneEuro 복합 필터 설정
[Header("Median+Euro Filter Settings")]
[Tooltip("Median 윈도우 크기 (홀수). 스파이크 제거용. 3=최소 지연, 5=일반, 7+=강한 제거")]
[Range(3, 11)]
public int medianWindowSize = 5;
// ── 필터 인스턴스 ──
// Euro 필터
private Dictionary<string, OneEuroFilter> euroFilters = new Dictionary<string, OneEuroFilter>();
private float euroMinCutoffPrev, euroBetaPrev, euroDCutoffPrev;
// EMA 필터링용 이전 값 저장
// EMA 필터링용 이전 값
private Dictionary<string, float> prevBlendShapeValues = new Dictionary<string, float>();
// 연속 스파이크 추적 (같은 방향으로 연속이면 실제 움직임)
// 연속 스파이크 추적
private Dictionary<string, int> blendShapeSpikeCount = new Dictionary<string, int>();
private Dictionary<string, float> blendShapeSpikeDirection = new Dictionary<string, float>();
// Median 필터 (MedianOneEuro용)
private Dictionary<string, MedianFilter> medianFilters = new Dictionary<string, MedianFilter>();
// MedianOneEuro용 Euro 필터 (별도 인스턴스)
private Dictionary<string, OneEuroFilter> medianEuroFilters = new Dictionary<string, OneEuroFilter>();
private int medianWindowSizePrev;
// 빠르게 변하는 BlendShape 목록 (눈 깜빡임, 입 등)
private static readonly HashSet<string> FastBlendShapes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
@ -153,6 +169,18 @@ public class StreamingleFacialReceiver : MonoBehaviour
}
}
// 재접속 코루틴 중복 방지
private Coroutine reconnectCoroutine;
// ── 마스터 모드에서 호출되는 메서드 ──
/// <summary>
/// UdpMaster가 메시지를 분배할 때 호출. 직접 호출하지 마세요.
/// </summary>
public void SetMessageFromMaster(string msg)
{
messageString = msg;
}
// Start is called
void StartFunction()
{
@ -165,10 +193,18 @@ public class StreamingleFacialReceiver : MonoBehaviour
// BlendShape 인덱스 캐싱 초기화
InitializeBlendShapeCache();
//Recieve udp from iOS
if (useSharedPort)
{
// 마스터 모드: UdpMaster에 등록하여 공유 수신
StreamingleFacialUdpMaster.Instance.Register(LOCAL_PORT, this);
}
else
{
// 독립 모드: 자체 UDP 서버 생성
CreateUdpServer();
}
}
}
void Start()
{
@ -248,9 +284,12 @@ public class StreamingleFacialReceiver : MonoBehaviour
if (euroFilters != null)
{
foreach (var filter in euroFilters.Values)
{
filter.UpdateParams(euroMinCutoff, euroBeta, euroDCutoff);
}
if (medianEuroFilters != null)
{
foreach (var filter in medianEuroFilters.Values)
filter.UpdateParams(euroMinCutoff, euroBeta, euroDCutoff);
}
euroMinCutoffPrev = euroMinCutoff;
euroBetaPrev = euroBeta;
@ -446,6 +485,11 @@ public class StreamingleFacialReceiver : MonoBehaviour
Debug.LogError($"[iFacialMocap] 데이터 수신 오류: {e.Message}");
}
}
catch (ObjectDisposedException)
{
// UDP 소켓이 닫힌 경우 (정상 종료 시 발생)
break;
}
catch (Exception e)
{
// 스레드 종료 중이면 로그 생략
@ -455,7 +499,6 @@ public class StreamingleFacialReceiver : MonoBehaviour
}
// CPU를 양보하는 Sleep 사용 (5ms 대기)
// Busy waiting 대신 Thread.Sleep으로 CPU 사용률 감소
Thread.Sleep(5);
}
}
@ -488,9 +531,21 @@ public class StreamingleFacialReceiver : MonoBehaviour
if (StartFlag == false)
{
StartFlag = true;
if (useSharedPort)
{
// 마스터에서 해제
if (StreamingleFacialUdpMaster.Instance != null)
{
StreamingleFacialUdpMaster.Instance.Unregister(LOCAL_PORT, this);
}
}
else
{
StopUDP();
}
}
}
public void StopUDP()
@ -500,21 +555,21 @@ public class StreamingleFacialReceiver : MonoBehaviour
// UDP 종료
if (udp != null)
{
try
{
udp.Close();
udp.Dispose();
}
catch (Exception) { }
udp = null;
}
// 스레드가 종료될 때까지 대기 (최대 100ms)
// 스레드가 종료될 때까지 대기 (최대 300ms)
if (thread != null && thread.IsAlive)
{
thread.Join(100);
// 그래도 종료되지 않으면 강제 종료
if (thread.IsAlive)
{
thread.Abort();
}
thread.Join(300);
thread = null;
}
}
@ -529,39 +584,62 @@ public class StreamingleFacialReceiver : MonoBehaviour
return;
}
int oldPort = LOCAL_PORT;
activePortIndex = portIndex;
Debug.Log($"[iFacialMocap] 포트 전환: {availablePorts[portIndex]}");
if (useSharedPort)
{
// 마스터 모드: 포트 전환 요청
StreamingleFacialUdpMaster.Instance.SwitchPort(oldPort, LOCAL_PORT, this);
}
else
{
Reconnect();
}
}
/// <summary>
/// 페이셜 모션 캡처 재접속
/// 페이셜 모션 캡처 재접속 (프리징 없는 코루틴 방식)
/// </summary>
public void Reconnect()
{
if (reconnectCoroutine != null)
{
StopCoroutine(reconnectCoroutine);
}
reconnectCoroutine = StartCoroutine(ReconnectCoroutine());
}
private IEnumerator ReconnectCoroutine()
{
Debug.Log("[iFacialMocap] 재접속 시도 중...");
try
if (useSharedPort)
{
// 기존 연결 종료
// 마스터 모드: 해제 후 재등록
StreamingleFacialUdpMaster.Instance.Unregister(LOCAL_PORT, this);
yield return null; // 1프레임 대기
StreamingleFacialUdpMaster.Instance.Register(LOCAL_PORT, this);
}
else
{
// 독립 모드: UDP 종료 후 코루틴으로 대기 (프리징 없음)
StopUDP();
// 잠시 대기
Thread.Sleep(500);
// OS가 포트를 해제할 시간을 코루틴으로 대기 (메인 스레드 블로킹 없음)
yield return new WaitForSeconds(0.5f);
// 플래그 리셋
StartFlag = true;
// 재시작
StartFunction();
}
reconnectCoroutine = null;
Debug.Log("[iFacialMocap] 재접속 완료");
}
catch (Exception e)
{
Debug.LogError($"[iFacialMocap] 재접속 실패: {e.Message}");
}
}
/// <summary>
/// 프레임레이트 독립적 EMA 계수 계산
@ -575,14 +653,21 @@ public class StreamingleFacialReceiver : MonoBehaviour
}
/// <summary>
/// BlendShape 값 필터링: filterMode에 따라 EMA 또는 1€ Euro Filter 적용
/// BlendShape 값 필터링: filterMode에 따라 적절한 필터 적용
/// </summary>
float FilterBlendShapeValue(string name, float rawValue)
{
if (filterMode == FilterMode.OneEuro)
switch (filterMode)
{
case FilterMode.OneEuro:
return FilterOneEuro(name, rawValue);
case FilterMode.MedianOneEuro:
return FilterMedianOneEuro(name, rawValue);
case FilterMode.EMA:
return FilterEMA(name, rawValue);
default:
return rawValue;
}
}
/// <summary>
@ -610,6 +695,48 @@ public class StreamingleFacialReceiver : MonoBehaviour
return Mathf.Clamp(filter.Filter(rawValue, t), 0f, 100f);
}
/// <summary>
/// Median + OneEuro 복합 필터:
/// 1단계: Median 필터로 스파이크/이상치 제거
/// 2단계: OneEuro 필터로 적응형 스무딩
/// → 스파이크에 강하면서도 빠른 움직임에 반응하는 최고 품질 필터링
/// </summary>
float FilterMedianOneEuro(string name, float rawValue)
{
// Median 윈도우 크기 변경 감지 → 필터 재생성
if (medianWindowSizePrev != medianWindowSize)
{
medianFilters.Clear();
medianWindowSizePrev = medianWindowSize;
}
// 1단계: Median 필터
if (!medianFilters.TryGetValue(name, out var medianFilter))
{
medianFilter = new MedianFilter(medianWindowSize);
medianFilters[name] = medianFilter;
}
float medianValue = medianFilter.Filter(rawValue);
// 2단계: OneEuro 필터
float t = Time.time;
if (!medianEuroFilters.TryGetValue(name, out var euroFilter))
{
euroFilter = new OneEuroFilter(60f, euroMinCutoff, euroBeta, euroDCutoff);
medianEuroFilters[name] = euroFilter;
euroFilter.Filter(medianValue, t);
return medianValue;
}
// 파라미터 변경 감지
if (euroMinCutoffPrev != euroMinCutoff || euroBetaPrev != euroBeta || euroDCutoffPrev != euroDCutoff)
{
SyncEuroParams();
}
return Mathf.Clamp(euroFilter.Filter(medianValue, t), 0f, 100f);
}
/// <summary>
/// EMA 필터: 연속 스파이크 판별 + 카테고리별 임계값 + 프레임독립 EMA (기존 로직)
/// </summary>

View File

@ -0,0 +1,249 @@
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
/// <summary>
/// 중앙 UDP 수신 매니저. 하나의 포트에서 수신한 데이터를 여러 StreamingleFacialReceiver에 분배.
/// 싱글톤으로 동작하며, Receiver가 Register 시 자동 생성됨.
///
/// 사용법:
/// StreamingleFacialReceiver에서 useSharedPort = true로 설정하면
/// 자동으로 이 마스터를 통해 UDP 데이터를 공유 수신합니다.
/// 같은 포트에 여러 Receiver를 등록하면 동일한 데이터를 모든 Receiver가 받습니다.
/// </summary>
public class StreamingleFacialUdpMaster : MonoBehaviour
{
private static StreamingleFacialUdpMaster _instance;
public static StreamingleFacialUdpMaster Instance
{
get
{
if (_instance == null)
{
var go = new GameObject("[StreamingleFacialUdpMaster]");
go.hideFlags = HideFlags.HideInHierarchy;
DontDestroyOnLoad(go);
_instance = go.AddComponent<StreamingleFacialUdpMaster>();
}
return _instance;
}
}
private class PortListener
{
public int port;
public UdpClient udp;
public Thread thread;
public volatile bool isRunning;
public volatile string latestMessage = "";
public string lastDistributedMessage = "";
public readonly HashSet<StreamingleFacialReceiver> receivers = new HashSet<StreamingleFacialReceiver>();
public readonly object lockObj = new object();
}
private readonly Dictionary<int, PortListener> listeners = new Dictionary<int, PortListener>();
/// <summary>
/// Receiver를 지정 포트에 등록. 해당 포트의 리스너가 없으면 자동 생성.
/// </summary>
public void Register(int port, StreamingleFacialReceiver receiver)
{
if (!listeners.TryGetValue(port, out var listener))
{
listener = new PortListener { port = port };
listeners[port] = listener;
StartListener(listener);
}
lock (listener.lockObj)
{
listener.receivers.Add(receiver);
}
Debug.Log($"[FacialUdpMaster] Port {port} 등록 (총 {listener.receivers.Count}개 Receiver)");
}
/// <summary>
/// Receiver를 지정 포트에서 해제. 해당 포트에 Receiver가 없으면 리스너 종료.
/// </summary>
public void Unregister(int port, StreamingleFacialReceiver receiver)
{
if (!listeners.TryGetValue(port, out var listener)) return;
bool shouldStop = false;
lock (listener.lockObj)
{
listener.receivers.Remove(receiver);
shouldStop = listener.receivers.Count == 0;
}
if (shouldStop)
{
StopListener(listener);
listeners.Remove(port);
Debug.Log($"[FacialUdpMaster] Port {port} 리스너 종료 (Receiver 없음)");
}
}
/// <summary>
/// 포트 변경 시 호출. 기존 포트 해제 → 새 포트 등록.
/// </summary>
public void SwitchPort(int oldPort, int newPort, StreamingleFacialReceiver receiver)
{
Unregister(oldPort, receiver);
Register(newPort, receiver);
}
/// <summary>
/// 특정 포트의 리스너를 재시작 (포트 문제 시 복구용)
/// </summary>
public void RestartPort(int port)
{
if (!listeners.TryGetValue(port, out var listener)) return;
StopListener(listener);
// 리스너 상태 리셋 후 재시작
listener.latestMessage = "";
listener.lastDistributedMessage = "";
StartListener(listener);
Debug.Log($"[FacialUdpMaster] Port {port} 리스너 재시작");
}
/// <summary>
/// 현재 등록된 포트와 각 포트의 Receiver 수를 반환 (디버그용)
/// </summary>
public Dictionary<int, int> GetPortStatus()
{
var status = new Dictionary<int, int>();
foreach (var kvp in listeners)
{
status[kvp.Key] = kvp.Value.receivers.Count;
}
return status;
}
private void StartListener(PortListener listener)
{
try
{
listener.udp = new UdpClient(listener.port);
listener.udp.Client.ReceiveTimeout = 5;
listener.isRunning = true;
listener.thread = new Thread(() => ListenThread(listener))
{
IsBackground = true
};
listener.thread.Start();
Debug.Log($"[FacialUdpMaster] Port {listener.port} 수신 시작");
}
catch (Exception e)
{
Debug.LogError($"[FacialUdpMaster] Port {listener.port} 시작 실패: {e.Message}");
}
}
private void StopListener(PortListener listener)
{
listener.isRunning = false;
try
{
if (listener.udp != null)
{
listener.udp.Close();
listener.udp.Dispose();
listener.udp = null;
}
}
catch (Exception) { }
if (listener.thread != null && listener.thread.IsAlive)
{
listener.thread.Join(300);
listener.thread = null;
}
}
private void ListenThread(PortListener listener)
{
while (listener.isRunning)
{
try
{
IPEndPoint remoteEP = null;
byte[] data = listener.udp.Receive(ref remoteEP);
if (data != null && data.Length > 0)
{
listener.latestMessage = Encoding.ASCII.GetString(data);
}
}
catch (SocketException e)
{
if (!listener.isRunning) break;
if (e.SocketErrorCode != SocketError.TimedOut)
{
Debug.LogError($"[FacialUdpMaster] Port {listener.port} 수신 오류: {e.Message}");
}
}
catch (ObjectDisposedException)
{
break;
}
catch (Exception e)
{
if (!listener.isRunning) break;
Debug.LogError($"[FacialUdpMaster] Port {listener.port} 오류: {e.Message}");
}
Thread.Sleep(5);
}
}
void Update()
{
foreach (var kvp in listeners)
{
var listener = kvp.Value;
string msg = listener.latestMessage;
if (string.IsNullOrEmpty(msg) || msg == listener.lastDistributedMessage)
continue;
listener.lastDistributedMessage = msg;
lock (listener.lockObj)
{
foreach (var receiver in listener.receivers)
{
if (receiver != null && receiver.isActiveAndEnabled)
{
receiver.SetMessageFromMaster(msg);
}
}
}
}
}
void OnDestroy()
{
foreach (var kvp in listeners)
{
StopListener(kvp.Value);
}
listeners.Clear();
if (_instance == this)
_instance = null;
}
void OnApplicationQuit()
{
OnDestroy();
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5f84ad022978ca14bb7fa383b1e86b3d

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 027db6e30adb2054687426649457fbbc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/BossRaidWeb/bossraid_script.txt (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a468efa52276a384db63282e727b704b
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/BossRaidWeb/bossraid_style.txt (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: af43c7dd8dc851f4eaa92ccd2dfa3fa3
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/Resources/BossRaidWeb/bossraid_template.txt (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f88baa51ad375d946a06b4217d25ddec
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 98485dcc15d43456ba5d755247e30747
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/ResourcesData/Etc/Particle/Confetti/8colors.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,90 @@
fileFormatVersion: 2
guid: 4f7f159d7b449401197b9532fd225c70
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: -1
wrapV: -1
wrapW: -1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 2
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,273 @@
using UnityEngine;
using Kayac;
public class Confetti : MonoBehaviour
{
[SerializeField] SkinnedInstancingRenderer instanceRenderer;
[SerializeField] int pieceCount = 500;
[SerializeField] float emitPiecePerSecond = 30f;
[SerializeField] bool emissionLooped = true;
[SerializeField] float positionRandomizeRadius;
[SerializeField] float velocityRandomizeRadius;
[SerializeField] float pieceWidth = 1f;
[SerializeField] float pieceLength = 1f;
[SerializeField] float normalBendRatio = 0.5f;
[SerializeField] float resistance = 5f;
[SerializeField] Vector3 gravity = new Vector3(0f, -9.81f, 0f);
[SerializeField] Vector3 wind = Vector3.zero;
[SerializeField] bool autoStart;
public int PieceCount { get { return pieceCount; } }
public float PositionRandomizeRadius
{
get
{
return positionRandomizeRadius;
}
set
{
positionRandomizeRadius = value;
}
}
public float VelocityRandomizeRadius
{
get
{
return velocityRandomizeRadius;
}
set
{
velocityRandomizeRadius = value;
}
}
public float PieceWidth
{
get
{
return pieceWidth;
}
set
{
pieceWidth = value;
}
}
public float PieceLength
{
get
{
return pieceLength;
}
set
{
pieceLength = value;
}
}
public float NormalBendRatio
{
get
{
return normalBendRatio;
}
set
{
normalBendRatio = value;
}
}
public float Resistance
{
get
{
return resistance;
}
set
{
resistance = value;
}
}
public Vector3 Gravity
{
get
{
return gravity;
}
set
{
gravity = value;
}
}
public Vector3 Wind
{
get
{
return wind;
}
set
{
wind = value;
}
}
public bool EmissionLooped
{
get
{
return emissionLooped;
}
set
{
emissionLooped = value;
}
}
public float EmitPiecePerSecond
{
get
{
return emitPiecePerSecond;
}
set
{
emitPiecePerSecond = value;
}
}
Mesh originalMesh;
ConfettiPiece[] pieces;
int emitIndex;
float emitCarry; // 端数持ち越し
private void Start()
{
if (autoStart)
{
ManualStart();
}
}
public void ManualStart(int pieceCountOverride = 0)
{
if (pieceCountOverride > 0)
{
pieceCount = pieceCountOverride;
}
originalMesh = new Mesh()
{
name = "original"
};
MeshGenerator.GenerateQuad(
originalMesh,
Vector3.zero,
new Vector3(1f, 0f, 0f),
new Vector3(0f, 0f, 1f),
Vector2.zero,
Vector2.zero,
doubleSided: true);
var uvOffsets = new Vector2[pieceCount];
for (int i = 0; i < pieceCount; i++)
{
uvOffsets[i] = new Vector2(Random.Range(0.125f, 1f), 0f);
}
instanceRenderer.ManualStart(originalMesh, uvOffsets.Length, uvOffsets);
// 実際にこの数で作れたとは限らないので確認して上書きする
pieceCount = instanceRenderer.Count;
pieces = new ConfettiPiece[pieceCount];
}
public void StartEmission()
{
if (originalMesh == null)
{
ManualStart();
}
emitCarry = 0f;
for (int i = 0; i < pieces.Length; i++)
{
pieces[i].Init(
Vector3.zero,
Vector3.zero,
0f,
Quaternion.identity,
0f,
0f);
}
}
void EmitPiece(int count)
{
for (int i = 0; i < count; i++)
{
var q = Quaternion.identity;
var qz = Quaternion.AngleAxis(Random.Range(-10f, 10f), new Vector3(0f, 0f, 1f));
var qy = Quaternion.AngleAxis(Random.Range(-180f, 180f), new Vector3(0f, 1f, 0f));
var qx = Quaternion.AngleAxis(Random.Range(-180f, 180f), new Vector3(1f, 0f, 0f));
q *= qz;
q *= qy;
q *= qx;
var position = new Vector3(
Random.Range(-positionRandomizeRadius, positionRandomizeRadius),
Random.Range(-positionRandomizeRadius, positionRandomizeRadius),
Random.Range(-positionRandomizeRadius, positionRandomizeRadius));
var velocity = new Vector3(
Random.Range(-velocityRandomizeRadius, velocityRandomizeRadius),
Random.Range(velocityRandomizeRadius, velocityRandomizeRadius),
Random.Range(-velocityRandomizeRadius, velocityRandomizeRadius));
var randomizedNormalBendRatio = Random.Range(normalBendRatio * 0.5f, normalBendRatio);
pieces[emitIndex].Init(
position,
velocity,
Time.deltaTime,
q,
pieceLength * 0.5f,
randomizedNormalBendRatio);
emitIndex++;
if (emitIndex >= pieces.Length)
{
emitIndex = 0;
if (!emissionLooped)
{
emitPiecePerSecond = 0f;
break;
}
}
}
}
void Update()
{
if (pieces == null)
{
return;
}
float dt = Time.deltaTime;
if (emitPiecePerSecond > 0f)
{
var countF = (emitPiecePerSecond * dt) + emitCarry;
var countI = (int)countF;
EmitPiece(countI);
emitCarry = countF - (float)countI;
}
float halfZSize = pieceLength * 0.5f;
for (int i = 0; i < pieces.Length; i++)
{
pieces[i].Update(dt, ref wind, ref gravity, resistance, halfZSize);
}
// 行列反映
var poses = instanceRenderer.BeginUpdatePoses();
for (int i = 0; i < pieces.Length; i++)
{
pieces[i].GetTransform(ref poses[i], pieceLength, pieceWidth);
}
instanceRenderer.EndUpdatePoses();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c8afa2659287b4ab4a056bece52bb6ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,77 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Confetti
m_Shader: {fileID: 10752, guid: 0000000000000000f000000000000000, type: 0}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 2800000, guid: 4f447d9f9c5234f49b436e395e10e9a0, type: 3}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 2800000, guid: 4f7f159d7b449401197b9532fd225c70, type: 3}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _BumpScale: 1
- _Cutoff: 0.5
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0.5019608, g: 0.5019608, b: 0.5019608, a: 1}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d48b83c48ac2c48339a66b0dbdc5aeeb
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/ResourcesData/Etc/Particle/Confetti/Confetti.prefab (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 514c7e149f71f456988bc9816d5ef692
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,99 @@
using UnityEngine;
struct ConfettiPiece
{
public void Init(
Vector3 position,
Vector3 velocity,
float deltaTime,
Quaternion orientation, //Z軸方向に
float halfZSize,
float normalBendRatio)
{
this.normalBendRatio = normalBendRatio;
this.orientation = orientation;
this.position = position;
var forward = orientation * new Vector3(0f, 0f, 1f);
var t = forward * halfZSize;
var dp = velocity * deltaTime;
prevPosition0 = position - t;
prevPosition1 = position + t;
prevPosition0 -= dp;
prevPosition1 -= dp;
}
public void Update(
float deltaTime,
ref Vector3 wind,
ref Vector3 gravity,
float resistance,
float halfZSize)
{
var forward = orientation * new Vector3(0f, 0f, 1f);
var t = forward * halfZSize;
var p0 = position - t;
var p1 = position + t;
// 相対的な風ベクトルを出す
var v0 = (p0 - prevPosition0) / deltaTime;
var v1 = (p1 - prevPosition1) / deltaTime;
var relativeWind0 = wind - v0;
var relativeWind1 = wind - v1;
// 頂点ごとの法線を生成
var n = orientation * new Vector3(0f, 1f, 0f);
// 曲げる
t = forward * normalBendRatio;
var n0 = n + t;
var n1 = n - t;
// 正規化。n1はn0と同じ長さなので長さを計算して使い回す
n0.Normalize();
n1.Normalize();
// 風ベクトルの法線方向成分を、加速度とする
var dot0 = Vector3.Dot(n0, relativeWind0);
var dot1 = Vector3.Dot(n1, relativeWind1);
if (dot0 < 0)
{
n0 = -n0;
dot0 = -dot0;
}
if (dot1 < 0)
{
n1 = -n1;
dot1 = -dot1;
}
var accel0 = n0 * (dot0 * resistance);
var accel1 = n1 * (dot1 * resistance);
// 重力を追加
accel0 += gravity;
accel1 += gravity;
// 独立に積分
var dt2 = deltaTime * deltaTime;
var dp0 = p0 - prevPosition0 + (accel0 * dt2);
var dp1 = p1 - prevPosition1 + (accel1 * dt2);
var nextP0 = p0 + dp0;
var nextP1 = p1 + dp1;
prevPosition0 = p0;
prevPosition1 = p1;
p0 = nextP0;
p1 = nextP1;
// 拘束
var newForward = (p1 - p0).normalized;
position = (p0 + p1) * 0.5f;
// 姿勢更新
// forward -> newForwardに向くような、回転軸右ベクタの回転を作用させる
var dq = Quaternion.FromToRotation(forward, newForward);
orientation = dq * orientation; // dqは時間的に後の回転だから、ベクタから遠い方、つまり前から乗算
orientation.Normalize();
}
public void GetTransform(
ref Matrix4x4 matrix,
float ZSize,
float XSize)
{
matrix.SetTRS(position, orientation, new Vector3(XSize, 1f, ZSize));
}
public Vector3 position;
Vector3 prevPosition0;
Vector3 prevPosition1;
Quaternion orientation;
float normalBendRatio;
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bfb6fb60322ae4e7e855e73b08d27331
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 976974e4d3018034e87caeb9f51f20f6

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c865e48369a325f4784ecd8444d1ec51

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f348a2fb2f8a7fe4e82fb08818415884

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 715add6d7a2eff443bf0aee3cf26e7fc

View File

@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 77ec3c4e07a36814fb3169ecef41b50a
labels:
- ball
- bubble
- cartoonfx
- electric
- particles
- toon
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX Prefabs/Electric/CFX_ElectricityBall.prefab
uploadId: 545941

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: c41f95c98750b744bb429ee2fc6a1d70
labels:
- cartoonfx

View File

@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 46876a2780737ec45a19d62f88e5ce56
timeCreated: 1505140479
licenseType: Store
NativeFormatImporter:
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX Prefabs/Explosions/CFX_Explosion_B_Smoke+Text.prefab
uploadId: 545941

View File

@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 4adffb0018aeda44e9263f901167af1f
timeCreated: 1505138997
licenseType: Store
NativeFormatImporter:
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX Prefabs/Explosions/CFX_Firework_Trails_Gravity.prefab
uploadId: 545941

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: 45653d11d228e594498b3d9119285e35
labels:
- cartoonfx

View File

@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: f95ff734540a0d749be6571b6cb5b37f
labels:
- cartoonfx
- hit
- particles
- text
- toon
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX Prefabs/Hits/CFX_Hit_A Red+RandomText.prefab
uploadId: 545941

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: f06e78284f831ae469c74624ab5d513b
labels:
- cartoonfx
- particles
- hit
- toon
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX Prefabs/Hits/CFX_Hit_C White.prefab
uploadId: 545941

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: f991151302bbc8440bc158a1be7d008d
labels:
- cartoonfx

View File

@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: a40b64bf23366dc42a892af2c6ff6140
labels:
- cartoonfx
- particles
- poof
- smoke
- toon
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX Prefabs/Misc/CFX_MagicPoof.prefab
uploadId: 545941

View File

@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 9e4577abe56a95f4a91d29a0473cd30c
timeCreated: 1505142609
licenseType: Store
NativeFormatImporter:
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX Prefabs/Misc/CFX_Magical_Source.prefab
uploadId: 545941

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 3156817f64ec68948ad6bb26569b9ef5
labels:
- cartoonfx
- particles
- poof
- toon
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX Prefabs/Misc/CFX_Poof.prefab
uploadId: 545941

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 352304b7fbe11f445970df6ae36e6227
labels:
- cartoonfx
- particles
- tornado
- toon
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX Prefabs/Misc/CFX_Tornado.prefab
uploadId: 545941

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 2a474af3f4c27be4e9bdce39cfe4315d
labels:
- cartoonfx
- particles
- virus
- toon
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX Prefabs/Misc/CFX_Virus.prefab
uploadId: 545941

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 84d86077ce20b3d408f08f7cc5cfb4ea

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9ef0e39c32aae7b4480fbdb6d2efb0a1

View File

@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: bc98890f6028f444eaa7789bde5e2eeb
labels:
- cartoonfx
- particles
- blood
- directional
- toon
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX2 Prefabs/Blood/CFX2_Blood.prefab
uploadId: 545941

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7e71da46ca428824c9e9e4598154d6d3

View File

@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: a97415c6b57076f41a6621a7b51d63a3
timeCreated: 1506674220
licenseType: Store
NativeFormatImporter:
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX2 Prefabs/Debris Hits/CFX2_RockHit.prefab
uploadId: 545941

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c21f3643835b3b34e897f4b378531f37

View File

@ -0,0 +1,17 @@
fileFormatVersion: 2
guid: 4961a16ba01e09547be3e1c9d16d5ad0
timeCreated: 1506673851
licenseType: Store
NativeFormatImporter:
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX2 Prefabs/Electric/CFX2_SparksHit_B
Sphere.prefab
uploadId: 545941

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: efc53651f7e42d84d86caee3be88073e

View File

@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: aa8d6482d70a23445b4b2a928ed1b845
timeCreated: 1506670975
licenseType: Store
NativeFormatImporter:
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX2 Prefabs/Explosions/CFX2_WWExplosion_C.prefab
uploadId: 545941

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 86fef6e72f4589f428dccc24bd05cf0b

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: d4f5579fb5da2f646a0c2bee91bdf6b5
labels:
- cartoonfx
- particles
- symbol
- toon
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX2 Prefabs/Misc/CFX2_BatsCloud.prefab
uploadId: 545941

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: bcaff8fc36e95584bb27cc3cf7c6cb14
labels:
- cartoonfx
- particles
- toon
- symbol
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX2 Prefabs/Misc/CFX2_BrokenHeart.prefab
uploadId: 545941

View File

@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: daa6cceb1b087d74aad6916dac2805ea
timeCreated: 1505487254
licenseType: Store
NativeFormatImporter:
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX2 Prefabs/Misc/CFX2_Wandering_Spirits.prefab
uploadId: 545941

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fdee0cda8f0d2094fa5ca47abb4ffd95

View File

@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: aa3fed151f536ee49b583c6a3ac4a2e1
labels:
- cartoonfx
- particles
- pickup
- symbol
- toon
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX2 Prefabs/Pickup Items/CFX2_PickupDiamond2.prefab
uploadId: 545941

View File

@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: 3af6f68141914ca43ba39b2403757bd0
labels:
- cartoonfx
- particles
- pickup
- symbol
- toon
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX2 Prefabs/Pickup Items/CFX2_PickupHeart.prefab
uploadId: 545941

View File

@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: 1bc4a709f24528b40984e62f32e398a0
labels:
- cartoonfx
- particles
- pickup
- symbol
- toon
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX2 Prefabs/Pickup Items/CFX2_PickupSmiley2.prefab
uploadId: 545941

View File

@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: 8fc06027b01064e4f885fd8dd2929310
labels:
- cartoonfx
- particles
- pickup
- symbol
- toon
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX2 Prefabs/Pickup Items/CFX2_PickupStar.prefab
uploadId: 545941

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e8472d517d4584f41ac28b96b812564f

View File

@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: 7867af58a6ec48846ae807eb4022fbaa
labels:
- cartoonfx
- particles
- smoke
- toon
- symbol
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX2 Prefabs/Skull & Ghosts Effects/CFX2_EnemyDeathSkull.prefab
uploadId: 545941

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fda99d3a72bd62d44b37eef4da0ac074

View File

@ -0,0 +1,17 @@
fileFormatVersion: 2
guid: 77c69ec12208c4c4380d15e30b6f8f68
timeCreated: 1507212856
licenseType: Store
NativeFormatImporter:
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX2 Prefabs/Water/CFX2_Big_Splash
(No Collision).prefab
uploadId: 545941

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: 9358d72e9ab0589469b7c81a782cb400
DefaultImporter:
userData:

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: 6d8820046d052af4583d35be43c7a959
DefaultImporter:
userData:

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 1ca16f2a31fb6a548ad8954bed1d321f
labels:
- CartoonFX
- Toon
- Particles
- Hit
- Electric
- Ground
NativeFormatImporter:
userData:
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX3 Prefabs/Electric/CFX3_Hit_Electric_A_Ground.prefab
uploadId: 545941

View File

@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: e243632bb3aff9549abdcfcc2ef0c4f0
timeCreated: 1504710934
licenseType: Store
NativeFormatImporter:
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX3 Prefabs/Electric/CFX3_Hit_Electric_C_Air.prefab
uploadId: 545941

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: d15fe19a363a70749ba9f690ab060547
DefaultImporter:
userData:

View File

@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: de07bf378e7247444a0715ad2ff509e3
labels:
- CartoonFX
- Toon
- Particles
- Snow
NativeFormatImporter:
userData:
AssetOrigin:
serializedVersion: 1
productId: 109565
packageName: Cartoon FX Remaster Free
packageVersion: R 1.0.2
assetPath: Assets/JMO Assets/Cartoon FX (legacy)/CFX3 Prefabs/Environment/CFX3_Snow_Dense.prefab
uploadId: 545941

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: 5d25cb644a933ed4bad4bc0b015fa1f6
DefaultImporter:
userData:

Some files were not shown because too many files have changed in this diff Show More