From f0b6a55649c1b53fc1e04f58fe44d37c2d3d1c2b Mon Sep 17 00:00:00 2001 From: "DESKTOP-S4BOTN2\\user" Date: Sat, 18 Apr 2026 12:17:27 +0900 Subject: [PATCH] =?UTF-8?q?ADD:=20=EC=8B=A0=EA=B7=9C=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80.=20=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=A6=AC=EB=B0=8D=EA=B8=80-=EC=98=A4=EB=B8=8C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=EB=AC=B4=EB=B2=84=20,=20=EC=95=BC=EB=AA=A8-=EC=98=A4?= =?UTF-8?q?=EB=B8=8C=EC=A0=9D=ED=8A=B8=ED=8A=B8=EB=9E=98=ED=82=B9=ED=88=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/ObjectMover.cs | 39 +++ .../Controllers/ObjectMover.cs.meta | 2 + Assets/YAMO.meta | 8 + Assets/YAMO/Editor.meta | 8 + Assets/YAMO/Editor/ElasticTrackerEditor.cs | 222 ++++++++++++++ .../YAMO/Editor/ElasticTrackerEditor.cs.meta | 2 + Assets/YAMO/ElasticTracker.cs | 283 ++++++++++++++++++ Assets/YAMO/ElasticTracker.cs.meta | 2 + 8 files changed, 566 insertions(+) create mode 100644 Assets/Scripts/Streamingle/StreamingleControl/Controllers/ObjectMover.cs create mode 100644 Assets/Scripts/Streamingle/StreamingleControl/Controllers/ObjectMover.cs.meta create mode 100644 Assets/YAMO.meta create mode 100644 Assets/YAMO/Editor.meta create mode 100644 Assets/YAMO/Editor/ElasticTrackerEditor.cs create mode 100644 Assets/YAMO/Editor/ElasticTrackerEditor.cs.meta create mode 100644 Assets/YAMO/ElasticTracker.cs create mode 100644 Assets/YAMO/ElasticTracker.cs.meta diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Controllers/ObjectMover.cs b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/ObjectMover.cs new file mode 100644 index 000000000..a84f732f6 --- /dev/null +++ b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/ObjectMover.cs @@ -0,0 +1,39 @@ +using UnityEngine; +using System.Collections.Generic; + +public class ObjectMover : MonoBehaviour +{ + [Header("이동할 오브젝트 리스트")] + public List targetObjects = new List(); + + [Header("프리셋 위치 (GameObjects)")] + public List presetPositions = new List(); + + /// + /// targetObjects를 지정한 프리셋 위치로 이동합니다. + /// + public void Set(int presetIndex) + { + if (presetIndex < 0 || presetIndex >= presetPositions.Count) + { + Debug.LogWarning($"[ObjectMover] 잘못된 프리셋 인덱스: {presetIndex}, 유효 범위: 0-{presetPositions.Count - 1}"); + return; + } + + var preset = presetPositions[presetIndex]; + if (preset == null) + { + Debug.LogWarning($"[ObjectMover] 프리셋 위치가 null입니다: {presetIndex}"); + return; + } + + foreach (var obj in targetObjects) + { + if (obj == null) continue; + obj.transform.position = preset.transform.position; + obj.transform.rotation = preset.transform.rotation; + } + + Debug.Log($"[ObjectMover] {targetObjects.Count}개 오브젝트를 프리셋 {presetIndex} ({preset.name})으로 이동"); + } +} diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Controllers/ObjectMover.cs.meta b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/ObjectMover.cs.meta new file mode 100644 index 000000000..2852d990a --- /dev/null +++ b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/ObjectMover.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8db7f272a7063a34e9e9abd932adffef \ No newline at end of file diff --git a/Assets/YAMO.meta b/Assets/YAMO.meta new file mode 100644 index 000000000..32fa1584e --- /dev/null +++ b/Assets/YAMO.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dd539ee6303e28f44aa5a7604af84aca +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YAMO/Editor.meta b/Assets/YAMO/Editor.meta new file mode 100644 index 000000000..8a14817d8 --- /dev/null +++ b/Assets/YAMO/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d6780301a63522f479d7f559b12916fd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YAMO/Editor/ElasticTrackerEditor.cs b/Assets/YAMO/Editor/ElasticTrackerEditor.cs new file mode 100644 index 000000000..d018db407 --- /dev/null +++ b/Assets/YAMO/Editor/ElasticTrackerEditor.cs @@ -0,0 +1,222 @@ +using UnityEditor; +using UnityEngine; + +[CustomEditor(typeof(ElasticTracker))] +public class ElasticTrackerEditor : Editor +{ + private SerializedProperty enableFollow, followTargets, positionOffset; + private SerializedProperty followSmoothSpeed, followDistanceElasticity, followFrameInterval; + private SerializedProperty followX, followY, followZ; + private SerializedProperty moveRatioX, moveRatioY, moveRatioZ; + + private SerializedProperty enableLookAt, lookAtTargets, lookAtOffset; + private SerializedProperty lookAtSmoothSpeed, worldUp, lookAtFrameInterval; + private SerializedProperty rotateX, rotateY, rotateZ; + private SerializedProperty rotateRatioX, rotateRatioY, rotateRatioZ; + + private SerializedProperty enableOrbital, orbitCenters; + private SerializedProperty orbitHorizontalRadius, orbitHorizontalSpeed, orbitHorizontalPhaseOffset; + private SerializedProperty orbitVerticalRadius, orbitVerticalSpeed, orbitVerticalPhaseOffset; + private SerializedProperty orbitVerticalAngleMin, orbitVerticalAngleMax; + private SerializedProperty orbitHeightOffset; + + private SerializedProperty enableNoise; + private SerializedProperty posNoiseAmplitude, posNoiseFrequency; + private SerializedProperty posNoiseX, posNoiseY, posNoiseZ; + private SerializedProperty rotNoiseAmplitude, rotNoiseFrequency; + private SerializedProperty rotNoiseX, rotNoiseY, rotNoiseZ; + + private SerializedProperty updateInEditMode; + + private void OnEnable() + { + enableFollow = serializedObject.FindProperty("enableFollow"); + followTargets = serializedObject.FindProperty("followTargets"); + positionOffset = serializedObject.FindProperty("positionOffset"); + followSmoothSpeed = serializedObject.FindProperty("followSmoothSpeed"); + followDistanceElasticity = serializedObject.FindProperty("followDistanceElasticity"); + followFrameInterval = serializedObject.FindProperty("followFrameInterval"); + followX = serializedObject.FindProperty("followX"); + followY = serializedObject.FindProperty("followY"); + followZ = serializedObject.FindProperty("followZ"); + moveRatioX = serializedObject.FindProperty("moveRatioX"); + moveRatioY = serializedObject.FindProperty("moveRatioY"); + moveRatioZ = serializedObject.FindProperty("moveRatioZ"); + + enableLookAt = serializedObject.FindProperty("enableLookAt"); + lookAtTargets = serializedObject.FindProperty("lookAtTargets"); + lookAtOffset = serializedObject.FindProperty("lookAtOffset"); + lookAtSmoothSpeed = serializedObject.FindProperty("lookAtSmoothSpeed"); + worldUp = serializedObject.FindProperty("worldUp"); + lookAtFrameInterval = serializedObject.FindProperty("lookAtFrameInterval"); + rotateX = serializedObject.FindProperty("rotateX"); + rotateY = serializedObject.FindProperty("rotateY"); + rotateZ = serializedObject.FindProperty("rotateZ"); + rotateRatioX = serializedObject.FindProperty("rotateRatioX"); + rotateRatioY = serializedObject.FindProperty("rotateRatioY"); + rotateRatioZ = serializedObject.FindProperty("rotateRatioZ"); + + enableOrbital = serializedObject.FindProperty("enableOrbital"); + orbitCenters = serializedObject.FindProperty("orbitCenters"); + orbitHorizontalRadius = serializedObject.FindProperty("orbitHorizontalRadius"); + orbitHorizontalSpeed = serializedObject.FindProperty("orbitHorizontalSpeed"); + orbitHorizontalPhaseOffset = serializedObject.FindProperty("orbitHorizontalPhaseOffset"); + orbitVerticalRadius = serializedObject.FindProperty("orbitVerticalRadius"); + orbitVerticalSpeed = serializedObject.FindProperty("orbitVerticalSpeed"); + orbitVerticalPhaseOffset = serializedObject.FindProperty("orbitVerticalPhaseOffset"); + orbitVerticalAngleMin = serializedObject.FindProperty("orbitVerticalAngleMin"); + orbitVerticalAngleMax = serializedObject.FindProperty("orbitVerticalAngleMax"); + orbitHeightOffset = serializedObject.FindProperty("orbitHeightOffset"); + + enableNoise = serializedObject.FindProperty("enableNoise"); + posNoiseAmplitude = serializedObject.FindProperty("posNoiseAmplitude"); + posNoiseFrequency = serializedObject.FindProperty("posNoiseFrequency"); + posNoiseX = serializedObject.FindProperty("posNoiseX"); + posNoiseY = serializedObject.FindProperty("posNoiseY"); + posNoiseZ = serializedObject.FindProperty("posNoiseZ"); + rotNoiseAmplitude = serializedObject.FindProperty("rotNoiseAmplitude"); + rotNoiseFrequency = serializedObject.FindProperty("rotNoiseFrequency"); + rotNoiseX = serializedObject.FindProperty("rotNoiseX"); + rotNoiseY = serializedObject.FindProperty("rotNoiseY"); + rotNoiseZ = serializedObject.FindProperty("rotNoiseZ"); + + updateInEditMode = serializedObject.FindProperty("updateInEditMode"); + } + + public override void OnInspectorGUI() + { + serializedObject.Update(); + + // ── Follow Section ── + DrawSectionHeader("Follow", enableFollow); + if (enableFollow.boolValue) + { + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(followTargets, new GUIContent("Targets"), true); + EditorGUILayout.PropertyField(positionOffset, new GUIContent("Position Offset")); + EditorGUILayout.Space(4); + + EditorGUILayout.PropertyField(followSmoothSpeed, new GUIContent("Smooth Speed")); + EditorGUILayout.PropertyField(followDistanceElasticity, new GUIContent("Distance Elasticity")); + EditorGUILayout.PropertyField(followFrameInterval, new GUIContent("Frame Interval")); + EditorGUILayout.Space(4); + + EditorGUILayout.LabelField("Axis On/Off", EditorStyles.miniLabel); + EditorGUILayout.BeginHorizontal(); + followX.boolValue = EditorGUILayout.ToggleLeft("X", followX.boolValue, GUILayout.Width(40)); + followY.boolValue = EditorGUILayout.ToggleLeft("Y", followY.boolValue, GUILayout.Width(40)); + followZ.boolValue = EditorGUILayout.ToggleLeft("Z", followZ.boolValue, GUILayout.Width(40)); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(2); + + EditorGUILayout.LabelField("Axis Move Ratio", EditorStyles.miniLabel); + if (followX.boolValue) EditorGUILayout.Slider(moveRatioX, 0f, 100f, "X Ratio %"); + if (followY.boolValue) EditorGUILayout.Slider(moveRatioY, 0f, 100f, "Y Ratio %"); + if (followZ.boolValue) EditorGUILayout.Slider(moveRatioZ, 0f, 100f, "Z Ratio %"); + EditorGUI.indentLevel--; + } + + EditorGUILayout.Space(8); + + // ── LookAt Section ── + DrawSectionHeader("LookAt", enableLookAt); + if (enableLookAt.boolValue) + { + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(lookAtTargets, new GUIContent("Targets"), true); + EditorGUILayout.PropertyField(lookAtOffset, new GUIContent("Offset")); + EditorGUILayout.Space(4); + + EditorGUILayout.PropertyField(lookAtSmoothSpeed, new GUIContent("Smooth Speed")); + EditorGUILayout.PropertyField(worldUp, new GUIContent("World Up")); + EditorGUILayout.PropertyField(lookAtFrameInterval, new GUIContent("Frame Interval")); + EditorGUILayout.Space(4); + + EditorGUILayout.LabelField("Axis On/Off", EditorStyles.miniLabel); + EditorGUILayout.BeginHorizontal(); + rotateX.boolValue = EditorGUILayout.ToggleLeft("X", rotateX.boolValue, GUILayout.Width(40)); + rotateY.boolValue = EditorGUILayout.ToggleLeft("Y", rotateY.boolValue, GUILayout.Width(40)); + rotateZ.boolValue = EditorGUILayout.ToggleLeft("Z", rotateZ.boolValue, GUILayout.Width(40)); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(2); + + EditorGUILayout.LabelField("Axis Rotate Ratio", EditorStyles.miniLabel); + if (rotateX.boolValue) EditorGUILayout.Slider(rotateRatioX, 0f, 100f, "X Ratio %"); + if (rotateY.boolValue) EditorGUILayout.Slider(rotateRatioY, 0f, 100f, "Y Ratio %"); + if (rotateZ.boolValue) EditorGUILayout.Slider(rotateRatioZ, 0f, 100f, "Z Ratio %"); + EditorGUI.indentLevel--; + } + + EditorGUILayout.Space(8); + + // ── Orbital Section ── + DrawSectionHeader("Orbital", enableOrbital); + if (enableOrbital.boolValue) + { + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(orbitCenters, new GUIContent("Centers (비워두면 Follow Targets)"), true); + EditorGUILayout.PropertyField(orbitHeightOffset, new GUIContent("Height Offset")); + EditorGUILayout.Space(4); + + EditorGUILayout.LabelField("Horizontal (360° Loop)", EditorStyles.miniLabel); + EditorGUILayout.PropertyField(orbitHorizontalRadius, new GUIContent("Radius")); + EditorGUILayout.PropertyField(orbitHorizontalSpeed, new GUIContent("Speed (°/s)")); + EditorGUILayout.PropertyField(orbitHorizontalPhaseOffset, new GUIContent("Phase Offset (°)")); + EditorGUILayout.Space(4); + + EditorGUILayout.LabelField("Vertical (Ping-Pong)", EditorStyles.miniLabel); + EditorGUILayout.PropertyField(orbitVerticalRadius, new GUIContent("Radius")); + EditorGUILayout.PropertyField(orbitVerticalSpeed, new GUIContent("Speed (°/s)")); + EditorGUILayout.PropertyField(orbitVerticalPhaseOffset, new GUIContent("Phase Offset (°)")); + EditorGUILayout.PropertyField(orbitVerticalAngleMin, new GUIContent("Angle Min (°)")); + EditorGUILayout.PropertyField(orbitVerticalAngleMax, new GUIContent("Angle Max (°)")); + EditorGUI.indentLevel--; + } + + EditorGUILayout.Space(8); + + // ── Noise Section ── + DrawSectionHeader("Noise (Hand-held)", enableNoise); + if (enableNoise.boolValue) + { + EditorGUI.indentLevel++; + + EditorGUILayout.LabelField("Position Noise", EditorStyles.miniLabel); + EditorGUILayout.PropertyField(posNoiseAmplitude, new GUIContent("Amplitude (m)")); + EditorGUILayout.PropertyField(posNoiseFrequency, new GUIContent("Frequency")); + EditorGUILayout.BeginHorizontal(); + posNoiseX.boolValue = EditorGUILayout.ToggleLeft("X", posNoiseX.boolValue, GUILayout.Width(40)); + posNoiseY.boolValue = EditorGUILayout.ToggleLeft("Y", posNoiseY.boolValue, GUILayout.Width(40)); + posNoiseZ.boolValue = EditorGUILayout.ToggleLeft("Z", posNoiseZ.boolValue, GUILayout.Width(40)); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(4); + + EditorGUILayout.LabelField("Rotation Noise", EditorStyles.miniLabel); + EditorGUILayout.PropertyField(rotNoiseAmplitude, new GUIContent("Amplitude (°)")); + EditorGUILayout.PropertyField(rotNoiseFrequency, new GUIContent("Frequency")); + EditorGUILayout.BeginHorizontal(); + rotNoiseX.boolValue = EditorGUILayout.ToggleLeft("X", rotNoiseX.boolValue, GUILayout.Width(40)); + rotNoiseY.boolValue = EditorGUILayout.ToggleLeft("Y", rotNoiseY.boolValue, GUILayout.Width(40)); + rotNoiseZ.boolValue = EditorGUILayout.ToggleLeft("Z", rotNoiseZ.boolValue, GUILayout.Width(40)); + EditorGUILayout.EndHorizontal(); + + EditorGUI.indentLevel--; + } + + EditorGUILayout.Space(8); + + // ── Editor Options ── + EditorGUILayout.LabelField("Editor Options", EditorStyles.boldLabel); + EditorGUILayout.PropertyField(updateInEditMode); + + serializedObject.ApplyModifiedProperties(); + } + + private void DrawSectionHeader(string label, SerializedProperty toggle) + { + EditorGUILayout.BeginHorizontal(); + toggle.boolValue = EditorGUILayout.Toggle(toggle.boolValue, GUILayout.Width(16)); + EditorGUILayout.LabelField(label, EditorStyles.boldLabel); + EditorGUILayout.EndHorizontal(); + } +} diff --git a/Assets/YAMO/Editor/ElasticTrackerEditor.cs.meta b/Assets/YAMO/Editor/ElasticTrackerEditor.cs.meta new file mode 100644 index 000000000..d8bd45e02 --- /dev/null +++ b/Assets/YAMO/Editor/ElasticTrackerEditor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 11b450839bcc20a49989f3fbe201825a \ No newline at end of file diff --git a/Assets/YAMO/ElasticTracker.cs b/Assets/YAMO/ElasticTracker.cs new file mode 100644 index 000000000..21d838ae7 --- /dev/null +++ b/Assets/YAMO/ElasticTracker.cs @@ -0,0 +1,283 @@ +using UnityEngine; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +[ExecuteAlways] +public class ElasticTracker : MonoBehaviour +{ + // ── Follow ── + public bool enableFollow = true; + public Transform[] followTargets = new Transform[1]; + public Vector3 positionOffset; + [Min(0f)] public float followSmoothSpeed = 5f; + [Min(0f)] public float followDistanceElasticity = 1.5f; + [Min(1)] public int followFrameInterval = 1; + public bool followX = true; + public bool followY = true; + public bool followZ = true; + [Range(0f, 100f)] public float moveRatioX = 100f; + [Range(0f, 100f)] public float moveRatioY = 100f; + [Range(0f, 100f)] public float moveRatioZ = 100f; + + // ── LookAt ── + public bool enableLookAt = true; + public Transform[] lookAtTargets = new Transform[1]; + public Vector3 lookAtOffset; + [Min(0f)] public float lookAtSmoothSpeed = 5f; + public Vector3 worldUp = Vector3.up; + [Min(1)] public int lookAtFrameInterval = 1; + public bool rotateX = true; + public bool rotateY = true; + public bool rotateZ = true; + [Range(0f, 100f)] public float rotateRatioX = 100f; + [Range(0f, 100f)] public float rotateRatioY = 100f; + [Range(0f, 100f)] public float rotateRatioZ = 100f; + + // ── Orbital ── + public bool enableOrbital = false; + public Transform[] orbitCenters = new Transform[0]; + [Min(0f)] public float orbitHorizontalRadius = 5f; + public float orbitHorizontalSpeed = 15f; + public float orbitHorizontalPhaseOffset = 0f; + [Min(0f)] public float orbitVerticalRadius = 1f; + public float orbitVerticalSpeed = 8f; + public float orbitVerticalPhaseOffset = 0f; + public float orbitVerticalAngleMin = -20f; + public float orbitVerticalAngleMax = 40f; + public float orbitHeightOffset = 2f; + + // ── Noise (Hand-held) ── + public bool enableNoise = false; + + [Min(0f)] public float posNoiseAmplitude = 0.003f; + [Min(0f)] public float posNoiseFrequency = 0.4f; + public bool posNoiseX = true; + public bool posNoiseY = true; + public bool posNoiseZ = true; + + [Min(0f)] public float rotNoiseAmplitude = 0.25f; + [Min(0f)] public float rotNoiseFrequency = 0.3f; + public bool rotNoiseX = true; + public bool rotNoiseY = true; + public bool rotNoiseZ = false; + + // ── Editor ── + public bool updateInEditMode = true; + + private int _followFrameCounter; + private float _followAccDelta; + private int _lookAtFrameCounter; + private float _lookAtAccDelta; + private float _orbitalTime; + private float _noiseTime; + private float _noiseSeedX, _noiseSeedY, _noiseSeedZ; + private float _noiseSeedRX, _noiseSeedRY, _noiseSeedRZ; + +#if UNITY_EDITOR + private double _lastEditorTime; +#endif + + private void OnEnable() + { + _followFrameCounter = 0; + _followAccDelta = 0f; + _lookAtFrameCounter = 0; + _lookAtAccDelta = 0f; + _orbitalTime = 0f; + _noiseTime = 0f; + _noiseSeedX = Random.Range(0f, 1000f); + _noiseSeedY = Random.Range(0f, 1000f); + _noiseSeedZ = Random.Range(0f, 1000f); + _noiseSeedRX = Random.Range(0f, 1000f); + _noiseSeedRY = Random.Range(0f, 1000f); + _noiseSeedRZ = Random.Range(0f, 1000f); + +#if UNITY_EDITOR + _lastEditorTime = EditorApplication.timeSinceStartup; + EditorApplication.update -= EditorTick; + EditorApplication.update += EditorTick; +#endif + } + + private void OnDisable() + { +#if UNITY_EDITOR + EditorApplication.update -= EditorTick; +#endif + } + + private void LateUpdate() + { + if (!Application.isPlaying) return; + + float dt = Time.deltaTime; + + if (enableFollow) + { + _followAccDelta += dt; + _followFrameCounter++; + if (_followFrameCounter >= followFrameInterval) + { + ApplyFollow(_followAccDelta); + _followFrameCounter = 0; + _followAccDelta = 0f; + } + } + + if (enableOrbital) + { + ApplyOrbital(dt); + } + + if (enableLookAt) + { + _lookAtAccDelta += dt; + _lookAtFrameCounter++; + if (_lookAtFrameCounter >= lookAtFrameInterval) + { + ApplyLookAt(_lookAtAccDelta); + _lookAtFrameCounter = 0; + _lookAtAccDelta = 0f; + } + } + + if (enableNoise) + { + ApplyNoise(dt); + } + } + +#if UNITY_EDITOR + private void EditorTick() + { + if (Application.isPlaying || !updateInEditMode || this == null || !isActiveAndEnabled) return; + + double now = EditorApplication.timeSinceStartup; + float deltaTime = Mathf.Max(0.0001f, (float)(now - _lastEditorTime)); + _lastEditorTime = now; + + if (enableFollow) ApplyFollow(deltaTime); + if (enableOrbital) ApplyOrbital(deltaTime); + if (enableLookAt) ApplyLookAt(deltaTime); + if (enableNoise) ApplyNoise(deltaTime); + EditorApplication.QueuePlayerLoopUpdate(); + } +#endif + + private Vector3 GetCenterPosition(Transform[] targets) + { + Vector3 sum = Vector3.zero; + int count = 0; + for (int i = 0; i < targets.Length; i++) + { + if (targets[i] != null) + { + sum += targets[i].position; + count++; + } + } + if (count == 0) return transform.position; + return sum / count; + } + + private void ApplyFollow(float deltaTime) + { + Vector3 centerPos = GetCenterPosition(followTargets); + if (centerPos == transform.position && followTargets.Length > 0) return; + + Vector3 targetPos = centerPos + positionOffset; + Vector3 currentPos = transform.position; + + float distance = Vector3.Distance(currentPos, targetPos); + float speedMultiplier = 1f + (distance * followDistanceElasticity); + float t = 1f - Mathf.Exp(-followSmoothSpeed * speedMultiplier * deltaTime); + + Vector3 fullNextPos = Vector3.Lerp(currentPos, targetPos, t); + + Vector3 nextPos = currentPos; + if (followX) nextPos.x = Mathf.Lerp(currentPos.x, fullNextPos.x, moveRatioX * 0.01f); + if (followY) nextPos.y = Mathf.Lerp(currentPos.y, fullNextPos.y, moveRatioY * 0.01f); + if (followZ) nextPos.z = Mathf.Lerp(currentPos.z, fullNextPos.z, moveRatioZ * 0.01f); + + transform.position = nextPos; + } + + private void ApplyOrbital(float deltaTime) + { + // orbitCenters가 비어있으면 followTargets를 폴백으로 사용 + Transform[] centers = (orbitCenters != null && orbitCenters.Length > 0) ? orbitCenters : followTargets; + Vector3 centerPos = GetCenterPosition(centers); + + _orbitalTime += deltaTime; + + // Horizontal: continuous 360° loop + float hAngleDeg = (orbitHorizontalPhaseOffset + orbitHorizontalSpeed * _orbitalTime) % 360f; + float hAngleRad = hAngleDeg * Mathf.Deg2Rad; + + // Vertical: ping-pong with sine easing for smooth turnaround + float vCycle = orbitVerticalSpeed * _orbitalTime + orbitVerticalPhaseOffset; + float vNormalized = (Mathf.Sin(vCycle * Mathf.Deg2Rad) + 1f) * 0.5f; + float vAngleDeg = Mathf.Lerp(orbitVerticalAngleMin, orbitVerticalAngleMax, vNormalized); + float vAngleRad = vAngleDeg * Mathf.Deg2Rad; + + // Spherical to Cartesian offset + float cosV = Mathf.Cos(vAngleRad); + Vector3 orbitOffset = new Vector3( + Mathf.Sin(hAngleRad) * orbitHorizontalRadius * cosV, + Mathf.Sin(vAngleRad) * orbitVerticalRadius + orbitHeightOffset, + Mathf.Cos(hAngleRad) * orbitHorizontalRadius * cosV + ); + + transform.position = centerPos + orbitOffset; + } + + private void ApplyLookAt(float deltaTime) + { + Vector3 centerPos = GetCenterPosition(lookAtTargets); + Vector3 lookPoint = centerPos + lookAtOffset; + Vector3 direction = lookPoint - transform.position; + if (direction.sqrMagnitude < 0.0001f) return; + + Quaternion targetRot = Quaternion.LookRotation(direction.normalized, worldUp); + + float t = 1f - Mathf.Exp(-lookAtSmoothSpeed * deltaTime); + Quaternion fullRot = Quaternion.Slerp(transform.rotation, targetRot, t); + + Vector3 currentEuler = transform.rotation.eulerAngles; + Vector3 fullEuler = fullRot.eulerAngles; + + Vector3 resultEuler = currentEuler; + if (rotateX) resultEuler.x = Mathf.LerpAngle(currentEuler.x, fullEuler.x, rotateRatioX * 0.01f); + if (rotateY) resultEuler.y = Mathf.LerpAngle(currentEuler.y, fullEuler.y, rotateRatioY * 0.01f); + if (rotateZ) resultEuler.z = Mathf.LerpAngle(currentEuler.z, fullEuler.z, rotateRatioZ * 0.01f); + + transform.rotation = Quaternion.Euler(resultEuler); + } + + private void ApplyNoise(float deltaTime) + { + _noiseTime += deltaTime; + + // Position noise + if (posNoiseAmplitude > 0f) + { + float pt = _noiseTime * posNoiseFrequency; + float nx = posNoiseX ? (Mathf.PerlinNoise(_noiseSeedX + pt, 0f) - 0.5f) * 2f * posNoiseAmplitude : 0f; + float ny = posNoiseY ? (Mathf.PerlinNoise(_noiseSeedY + pt, 0f) - 0.5f) * 2f * posNoiseAmplitude : 0f; + float nz = posNoiseZ ? (Mathf.PerlinNoise(_noiseSeedZ + pt, 0f) - 0.5f) * 2f * posNoiseAmplitude : 0f; + transform.position += transform.rotation * new Vector3(nx, ny, nz); + } + + // Rotation noise + if (rotNoiseAmplitude > 0f) + { + float rt = _noiseTime * rotNoiseFrequency; + float rx = rotNoiseX ? (Mathf.PerlinNoise(_noiseSeedRX + rt, 0f) - 0.5f) * 2f * rotNoiseAmplitude : 0f; + float ry = rotNoiseY ? (Mathf.PerlinNoise(_noiseSeedRY + rt, 0f) - 0.5f) * 2f * rotNoiseAmplitude : 0f; + float rz = rotNoiseZ ? (Mathf.PerlinNoise(_noiseSeedRZ + rt, 0f) - 0.5f) * 2f * rotNoiseAmplitude : 0f; + transform.rotation *= Quaternion.Euler(rx, ry, rz); + } + } +} diff --git a/Assets/YAMO/ElasticTracker.cs.meta b/Assets/YAMO/ElasticTracker.cs.meta new file mode 100644 index 000000000..b89b535ee --- /dev/null +++ b/Assets/YAMO/ElasticTracker.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 28712e1e7e1e78048b27efeed7cfe027 \ No newline at end of file