정보창, 에디트 구조 작성
This commit is contained in:
33
Assets/Scripts/UVC/Edit/DropZone.cs
Normal file
33
Assets/Scripts/UVC/Edit/DropZone.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UVC.Edit
|
||||
{
|
||||
public class DropZone : MonoBehaviour, IDropHandler
|
||||
{
|
||||
public void OnDrop(PointerEventData eventData)
|
||||
{
|
||||
if (LibraryItem.DraggedPrefab != null)
|
||||
{
|
||||
GameObject newObj = Instantiate(LibraryItem.DraggedPrefab, GetDropPosition(eventData), Quaternion.identity);
|
||||
|
||||
// 여기서 newObj의 EditableObject 컴포넌트를 찾아 초기화 로직을 실행할 수 있습니다.
|
||||
// newObj.GetComponent<EditableObject>()?.Initialize(System.Guid.NewGuid().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 GetDropPosition(PointerEventData eventData)
|
||||
{
|
||||
// 3D Stage의 경우, 화면 좌표를 월드 좌표로 변환해야 합니다.
|
||||
Ray ray = Camera.main.ScreenPointToRay(eventData.position);
|
||||
if (new Plane(Vector3.up, Vector3.zero).Raycast(ray, out float enter))
|
||||
{
|
||||
return ray.GetPoint(enter);
|
||||
}
|
||||
return Vector3.zero;
|
||||
|
||||
// UI Canvas의 경우, RectTransformUtility를 사용합니다.
|
||||
// RectTransformUtility.ScreenPointToLocalPointInRectangle(...)
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Edit/DropZone.cs.meta
Normal file
2
Assets/Scripts/UVC/Edit/DropZone.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea2b0e8422a022f4a9a6df278a857d4c
|
||||
19
Assets/Scripts/UVC/Edit/EditableObject.cs
Normal file
19
Assets/Scripts/UVC/Edit/EditableObject.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Edit
|
||||
{
|
||||
public abstract class EditableObject : MonoBehaviour, ISelectable
|
||||
{
|
||||
[field: SerializeField]
|
||||
public string ItemId { get; private set; }
|
||||
|
||||
public abstract void OnSelect();
|
||||
public abstract void OnDeselect();
|
||||
|
||||
public virtual void Initialize(string id)
|
||||
{
|
||||
this.ItemId = id;
|
||||
this.name = $"{GetType().Name}_{id}";
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Edit/EditableObject.cs.meta
Normal file
2
Assets/Scripts/UVC/Edit/EditableObject.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ddfd4794affdf44fb5e8dadcd13284d
|
||||
22
Assets/Scripts/UVC/Edit/EditableObject3D.cs
Normal file
22
Assets/Scripts/UVC/Edit/EditableObject3D.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Edit
|
||||
{
|
||||
public class EditableObject3D : EditableObject
|
||||
{
|
||||
// 3D 객체 고유의 속성 및 로직
|
||||
// 예: Material, Mesh 등
|
||||
|
||||
public override void OnSelect()
|
||||
{
|
||||
// 외곽선 표시 로직 호출
|
||||
Debug.Log($"{name} selected.");
|
||||
}
|
||||
|
||||
public override void OnDeselect()
|
||||
{
|
||||
// 외곽선 숨김 로직 호출
|
||||
Debug.Log($"{name} deselected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Edit/EditableObject3D.cs.meta
Normal file
2
Assets/Scripts/UVC/Edit/EditableObject3D.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2bbff96f4152cac46a16c5343f40e5fa
|
||||
40
Assets/Scripts/UVC/Edit/GizmoController.cs
Normal file
40
Assets/Scripts/UVC/Edit/GizmoController.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Edit
|
||||
{
|
||||
public class GizmoController : MonoBehaviour
|
||||
{
|
||||
public Transform targetObject; // 선택된 객체의 Transform
|
||||
// private RuntimeTransformHandle _transformHandle;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
InteractionController.OnObjectSelected += OnObjectSelected;
|
||||
}
|
||||
void OnDisable()
|
||||
{
|
||||
InteractionController.OnObjectSelected -= OnObjectSelected;
|
||||
}
|
||||
|
||||
void OnObjectSelected(EditableObject obj)
|
||||
{
|
||||
if (obj != null)
|
||||
{
|
||||
targetObject = obj.transform;
|
||||
// _transformHandle.target = targetObject;
|
||||
// _transformHandle.gameObject.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetObject = null;
|
||||
// _transformHandle.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 툴바에서 이동/회전/크기 툴 선택 시 아래와 같은 함수 호출
|
||||
public void SetMoveMode()
|
||||
{
|
||||
// _transformHandle.type = HandleType.Position;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Edit/GizmoController.cs.meta
Normal file
2
Assets/Scripts/UVC/Edit/GizmoController.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8cd05d9b2d558f46aeeef881d1175e1
|
||||
8
Assets/Scripts/UVC/Edit/ISelectable.cs
Normal file
8
Assets/Scripts/UVC/Edit/ISelectable.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace UVC.Edit
|
||||
{
|
||||
public interface ISelectable
|
||||
{
|
||||
void OnSelect();
|
||||
void OnDeselect();
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Edit/ISelectable.cs.meta
Normal file
2
Assets/Scripts/UVC/Edit/ISelectable.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 958cc5e553c04b7429a00214ce6a263a
|
||||
77
Assets/Scripts/UVC/Edit/InteractionController.cs
Normal file
77
Assets/Scripts/UVC/Edit/InteractionController.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UVC.Edit
|
||||
{
|
||||
public class InteractionController : MonoBehaviour
|
||||
{
|
||||
public static event System.Action<EditableObject> OnObjectSelected;
|
||||
public static event System.Action OnBackgroundClicked;
|
||||
|
||||
private Camera _mainCamera;
|
||||
private EditableObject _selectedObject;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_mainCamera = Camera.main;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (Input.GetMouseButtonDown(0))
|
||||
{
|
||||
// UI 위에서 클릭했는지 먼저 확인
|
||||
if (EventSystem.current.IsPointerOverGameObject())
|
||||
{
|
||||
return; // UI 클릭 시 월드 객체 선택 방지
|
||||
}
|
||||
|
||||
HandleSelection();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSelection()
|
||||
{
|
||||
Ray ray = _mainCamera.ScreenPointToRay(Input.mousePosition);
|
||||
// 3D 객체 선택 (Physics Raycast)
|
||||
if (Physics.Raycast(ray, out RaycastHit hit))
|
||||
{
|
||||
// EditableObject 컴포넌트를 가진 객체인지 확인
|
||||
if (hit.collider.TryGetComponent<EditableObject>(out var target))
|
||||
{
|
||||
SetSelectedObject(target);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 2D 객체 선택 (Physics2D Raycast) - 필요 시 카메라 설정에 따라 추가
|
||||
// RaycastHit2D hit2D = Physics2D.GetRayIntersection(ray);
|
||||
// if (hit2D.collider != null && hit2D.collider.TryGetComponent<EditableObject>(out var target2D))
|
||||
// {
|
||||
// SetSelectedObject(target2D);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// 아무것도 선택되지 않았을 경우
|
||||
SetSelectedObject(null);
|
||||
}
|
||||
|
||||
private void SetSelectedObject(EditableObject target)
|
||||
{
|
||||
if (_selectedObject == target) return;
|
||||
|
||||
_selectedObject?.OnDeselect();
|
||||
_selectedObject = target;
|
||||
_selectedObject?.OnSelect();
|
||||
|
||||
if (_selectedObject != null)
|
||||
{
|
||||
OnObjectSelected?.Invoke(_selectedObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnBackgroundClicked?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Edit/InteractionController.cs.meta
Normal file
2
Assets/Scripts/UVC/Edit/InteractionController.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce86abe153e9a8742b1643cc28166c16
|
||||
30
Assets/Scripts/UVC/Edit/LibraryItem.cs
Normal file
30
Assets/Scripts/UVC/Edit/LibraryItem.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UVC.Edit
|
||||
{
|
||||
public class LibraryItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
|
||||
{
|
||||
public GameObject prefabToSpawn; // 이 아이템이 생성할 프리팹
|
||||
public static GameObject DraggedPrefab; // 현재 드래그 중인 프리팹 (static으로 공유)
|
||||
|
||||
public void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
DraggedPrefab = prefabToSpawn;
|
||||
// 드래그 시 시각적 효과 (예: 아이콘 반투명화)
|
||||
}
|
||||
|
||||
public void OnDrag(PointerEventData eventData) { } // 드래그 중 로직 (필요 시)
|
||||
|
||||
public void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
DraggedPrefab = null;
|
||||
// 드래그 종료 시 시각적 효과 원상복구
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Edit/LibraryItem.cs.meta
Normal file
2
Assets/Scripts/UVC/Edit/LibraryItem.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c02ea26fd607fed4aaa07ca57e73a451
|
||||
35
Assets/Scripts/UVC/Edit/OutlineEffect.cs
Normal file
35
Assets/Scripts/UVC/Edit/OutlineEffect.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Edit
|
||||
{
|
||||
[RequireComponent(typeof(Renderer))]
|
||||
public class OutlineEffect : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Material _outlineMaterial;
|
||||
private Renderer _renderer;
|
||||
private bool _isOutlined = false;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_renderer = GetComponent<Renderer>();
|
||||
}
|
||||
|
||||
public void SetOutline(bool visible)
|
||||
{
|
||||
if (_isOutlined == visible) return;
|
||||
|
||||
_isOutlined = visible;
|
||||
var materials = _renderer.sharedMaterials.ToList();
|
||||
if (_isOutlined)
|
||||
{
|
||||
materials.Add(_outlineMaterial);
|
||||
}
|
||||
else
|
||||
{
|
||||
materials.Remove(_outlineMaterial);
|
||||
}
|
||||
_renderer.materials = materials.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Edit/OutlineEffect.cs.meta
Normal file
2
Assets/Scripts/UVC/Edit/OutlineEffect.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2659b57b886087046bbf148a542a83d1
|
||||
49
Assets/Scripts/UVC/Edit/PropertyWindow.cs
Normal file
49
Assets/Scripts/UVC/Edit/PropertyWindow.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using SFB;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UVC.Edit
|
||||
{
|
||||
public class PropertyWindow : MonoBehaviour
|
||||
{
|
||||
public Image uiImageTarget; // UI 객체에 적용할 Image
|
||||
public SpriteRenderer spriteTarget; // 2D 객체에 적용할 SpriteRenderer
|
||||
|
||||
public void OpenImageFileBrowser()
|
||||
{
|
||||
var extensions = new[] {
|
||||
new ExtensionFilter("Image Files", "png", "jpg", "jpeg")
|
||||
};
|
||||
|
||||
// 비동기 방식으로 파일 브라우저 열기
|
||||
StandaloneFileBrowser.OpenFilePanelAsync("Select an Image", "", extensions, false, (string[] paths) =>
|
||||
{
|
||||
if (paths.Length > 0 && !string.IsNullOrEmpty(paths[0]))
|
||||
{
|
||||
LoadAndApplyTexture(paths[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void LoadAndApplyTexture(string path)
|
||||
{
|
||||
byte[] fileData = File.ReadAllBytes(path);
|
||||
Texture2D texture = new Texture2D(2, 2);
|
||||
// 이미지 데이터로 텍스처 로드
|
||||
if (texture.LoadImage(fileData))
|
||||
{
|
||||
if (uiImageTarget != null)
|
||||
{
|
||||
Sprite newSprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
|
||||
uiImageTarget.sprite = newSprite;
|
||||
}
|
||||
if (spriteTarget != null)
|
||||
{
|
||||
Sprite newSprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
|
||||
spriteTarget.sprite = newSprite;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Edit/PropertyWindow.cs.meta
Normal file
2
Assets/Scripts/UVC/Edit/PropertyWindow.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 170fcace70e26454e80a7e0c972f1e6f
|
||||
19
Assets/Scripts/UVC/EditableObject.cs
Normal file
19
Assets/Scripts/UVC/EditableObject.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Editor
|
||||
{
|
||||
public abstract class EditableObject : MonoBehaviour, ISelectable
|
||||
{
|
||||
[field: SerializeField]
|
||||
public string ItemId { get; private set; }
|
||||
|
||||
public abstract void OnSelect();
|
||||
public abstract void OnDeselect();
|
||||
|
||||
public virtual void Initialize(string id)
|
||||
{
|
||||
this.ItemId = id;
|
||||
this.name = $"{GetType().Name}_{id}";
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/EditableObject.cs.meta
Normal file
2
Assets/Scripts/UVC/EditableObject.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0224adc9ffbf484eadece57e5583624
|
||||
22
Assets/Scripts/UVC/EditableObject3D.cs
Normal file
22
Assets/Scripts/UVC/EditableObject3D.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Editor
|
||||
{
|
||||
public class EditableObject3D : EditableObject
|
||||
{
|
||||
// 3D 객체 고유의 속성 및 로직
|
||||
// 예: Material, Mesh 등
|
||||
|
||||
public override void OnSelect()
|
||||
{
|
||||
// 외곽선 표시 로직 호출
|
||||
Debug.Log($"{name} selected.");
|
||||
}
|
||||
|
||||
public override void OnDeselect()
|
||||
{
|
||||
// 외곽선 숨김 로직 호출
|
||||
Debug.Log($"{name} deselected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/EditableObject3D.cs.meta
Normal file
2
Assets/Scripts/UVC/EditableObject3D.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f0f7e0e4ee826a409789e44ac3584fb
|
||||
@@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace UVC.Extention
|
||||
@@ -153,5 +156,159 @@ namespace UVC.Extention
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 두 사전의 키와 값을 비교하여 두 사전이 같은지 확인합니다.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">사전의 키 유형입니다.</typeparam>
|
||||
/// <typeparam name="TValue">사전의 값 유형입니다.</typeparam>
|
||||
/// <param name="dic">비교할 첫 번째 사전입니다.</param>
|
||||
/// <param name="other">비교할 두 번째 사전입니다.</param>
|
||||
/// <paramref name="dic"/>의 모든 키가 <paramref name="other"/>에 존재하고 해당 값이 같으면 <see langword="true"/>를 반환합니다.
|
||||
/// 그렇지 않으면 <see langword="false"/>를 반환합니다.</returns>
|
||||
public static bool IsSame<TKey, TValue>(this IDictionary<TKey, TValue> dic, IDictionary<TKey, TValue> other)
|
||||
{
|
||||
return !dic.Keys.Any(key => !object.Equals(dic[key], other[key]));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 두 사전을 비교하여 두 번째 사전의 키와 값을 포함하는 새 사전을 반환합니다.
|
||||
/// 두 사전의 값이 첫 번째 사전의 값과 다릅니다.
|
||||
/// </summary>
|
||||
/// <remarks>이 메서드는 <see
|
||||
/// cref="object.Equals(object, object)"/>를 사용하여 얕은 값 비교를 수행합니다. 키가 <paramref name="oldDict"/>에는 있지만 <paramref
|
||||
/// name="newDict"/>에는 없는 경우, 또는 그 반대의 경우, 해당 키는 무시됩니다.</remarks>
|
||||
/// <typeparam name="TKey">사전에 있는 키의 타입입니다.</typeparam>
|
||||
/// <typeparam name="TValue">사전에 있는 값의 타입입니다.</typeparam>
|
||||
/// <param name="oldDict">비교할 원본 사전입니다.</param>
|
||||
/// <param name="newDict">비교할 업데이트된 사전입니다.</param>
|
||||
/// <returns> <paramref name="newDict"/>의 키가 포함된 사전입니다. 해당 키의 값이 <paramref name="oldDict"/>의 해당 값과 다른 경우입니다.
|
||||
/// 반환된 사전의 값은 <paramref name="newDict"/>에서 가져옵니다.</returns>
|
||||
public static IDictionary<TKey, TValue> Differences<TKey, TValue>(this IDictionary<TKey, TValue> oldDict, IDictionary<TKey, TValue> newDict)
|
||||
{
|
||||
var result = new Dictionary<TKey, TValue>();
|
||||
foreach (var key in oldDict.Keys)
|
||||
{
|
||||
TValue oldValue = oldDict[key];
|
||||
TValue newValue = newDict[key];
|
||||
|
||||
if (!object.Equals(oldValue, newValue))
|
||||
{
|
||||
result.Add(key, newValue);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 두 사전을 비교하여 두 번째 사전의 키와 값을 포함하는 새 사전을 반환합니다.
|
||||
/// 두 사전의 값이 첫 번째 사전의 값과 다릅니다.
|
||||
/// </summary>
|
||||
/// <remarks>이 메서드는 심층 비교를 사용하여 값이 다른지 확인합니다. 비교
|
||||
/// 로직은 <c>DeepEquals</c> 메서드로 정의됩니다. <paramref name="dict1"/>에는 있지만
|
||||
/// <paramref name="dict2"/>에는 없는 키는 결과에 포함되지 않습니다.</remarks>
|
||||
/// <typeparam name="TKey">사전에 있는 키의 타입입니다.</typeparam>
|
||||
/// <typeparam name="TValue">사전에 있는 값의 타입입니다.</typeparam>
|
||||
/// <param name="dict1">비교할 첫 번째 사전입니다.</param>
|
||||
/// <param name="dict2">비교할 두 번째 사전입니다.</param>
|
||||
/// <returns><paramref name="dict2"/>의 키와 값을 포함하는 사전으로, 값이 <paramref name="dict1"/>의 값과 다른 경우
|
||||
/// <paramref name="dict1"/>에 키가 있지만 값이 같지 않으면
|
||||
/// <paramref name="dict2"/>의 키와 해당 값이 포함됩니다.</returns>
|
||||
public static IDictionary<TKey, TValue> DeepDifferences<TKey, TValue>(this IDictionary<TKey, TValue> dict1, IDictionary<TKey, TValue> dict2)
|
||||
{
|
||||
var result = new Dictionary<TKey, TValue>();
|
||||
foreach (var key in dict1.Keys)
|
||||
{
|
||||
TValue val1 = dict1[key];
|
||||
TValue val2 = dict2[key];
|
||||
if (!DeepEquals(val1, val2))
|
||||
{
|
||||
result.Add(key, val2);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 두 객체의 값, 속성 및 중첩 구조를 비교하여 두 객체가 깊이 동일한지 여부를 확인합니다.
|
||||
///
|
||||
// </summary>
|
||||
/// <remarks>이 메서드는 제공된 객체와 중첩 구조를 재귀적으로 비교합니다.
|
||||
/// 기본 유형, 문자열, 소수, 컬렉션(예: 배열,리스트), 사전 및 복합 객체의 비교를 지원합니다.
|
||||
/// 컬렉션의 경우, 이 메서드는 요소를 순서대로 비교합니다.
|
||||
/// 사전의 경우, 키와 연관된 값을 비교합니다. 객체의 유형이 다르거나 일치하지 않는 구조를 가진 경우,
|
||||
/// 이 메서드는 <see langword="false"/>를 반환합니다.</remarks>
|
||||
/// <param name="obj1">비교할 첫 번째 객체입니다. null일 수 있습니다.</param>
|
||||
/// <param name="obj2">비교할 두 번째 객체입니다. null일 수 있습니다.</param>
|
||||
/// 객체가 깊이 동일하면 <returns><see langword="true"/>를 반환하고, 그렇지 않으면 <see langword="false"/>를 반환합니다. 깊이 동일성
|
||||
///에는 기본 값, 문자열, 컬렉션, 사전 및 복합 객체의 공개 속성 비교가 포함됩니다.
|
||||
///</returns>
|
||||
private static bool DeepEquals(object obj1, object obj2)
|
||||
{
|
||||
if (ReferenceEquals(obj1, obj2))
|
||||
return true;
|
||||
|
||||
if (obj1 == null || obj2 == null)
|
||||
return false;
|
||||
|
||||
if (obj1.Equals(obj2))
|
||||
return true;
|
||||
|
||||
var type1 = obj1.GetType();
|
||||
var type2 = obj2.GetType();
|
||||
|
||||
if (type1 != type2)
|
||||
return false;
|
||||
|
||||
// String or primitive
|
||||
if (type1.IsPrimitive || obj1 is string || obj1 is decimal)
|
||||
return obj1.Equals(obj2);
|
||||
|
||||
// List or Array
|
||||
if (obj1 is IEnumerable enumerable1 && obj2 is IEnumerable enumerable2)
|
||||
{
|
||||
var list1 = enumerable1.Cast<object>().ToList();
|
||||
var list2 = enumerable2.Cast<object>().ToList();
|
||||
|
||||
if (list1.Count != list2.Count)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < list1.Count; i++)
|
||||
{
|
||||
if (!DeepEquals(list1[i], list2[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Dictionary
|
||||
if (obj1 is IDictionary dict1 && obj2 is IDictionary dict2)
|
||||
{
|
||||
if (dict1.Count != dict2.Count)
|
||||
return false;
|
||||
|
||||
foreach (var key in dict1.Keys)
|
||||
{
|
||||
if (!dict2.Contains(key)) return false;
|
||||
if (!DeepEquals(dict1[key], dict2[key])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Complex object
|
||||
var props = type1.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
foreach (var prop in props)
|
||||
{
|
||||
var val1 = prop.GetValue(obj1);
|
||||
var val2 = prop.GetValue(obj2);
|
||||
if (!DeepEquals(val1, val2))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8
Assets/Scripts/UVC/ISelectable.cs
Normal file
8
Assets/Scripts/UVC/ISelectable.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace UVC.Editor
|
||||
{
|
||||
public interface ISelectable
|
||||
{
|
||||
void OnSelect();
|
||||
void OnDeselect();
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/ISelectable.cs.meta
Normal file
2
Assets/Scripts/UVC/ISelectable.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9156b89e72147142831eb6d58934e49
|
||||
77
Assets/Scripts/UVC/InteractionController.cs
Normal file
77
Assets/Scripts/UVC/InteractionController.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UVC.Editor
|
||||
{
|
||||
public class InteractionController : MonoBehaviour
|
||||
{
|
||||
public static event System.Action<EditableObject> OnObjectSelected;
|
||||
public static event System.Action OnBackgroundClicked;
|
||||
|
||||
private Camera _mainCamera;
|
||||
private EditableObject _selectedObject;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_mainCamera = Camera.main;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (Input.GetMouseButtonDown(0))
|
||||
{
|
||||
// UI 위에서 클릭했는지 먼저 확인
|
||||
if (EventSystem.current.IsPointerOverGameObject())
|
||||
{
|
||||
return; // UI 클릭 시 월드 객체 선택 방지
|
||||
}
|
||||
|
||||
HandleSelection();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSelection()
|
||||
{
|
||||
Ray ray = _mainCamera.ScreenPointToRay(Input.mousePosition);
|
||||
// 3D 객체 선택 (Physics Raycast)
|
||||
if (Physics.Raycast(ray, out RaycastHit hit))
|
||||
{
|
||||
// EditableObject 컴포넌트를 가진 객체인지 확인
|
||||
if (hit.collider.TryGetComponent<EditableObject>(out var target))
|
||||
{
|
||||
SetSelectedObject(target);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 2D 객체 선택 (Physics2D Raycast) - 필요 시 카메라 설정에 따라 추가
|
||||
// RaycastHit2D hit2D = Physics2D.GetRayIntersection(ray);
|
||||
// if (hit2D.collider != null && hit2D.collider.TryGetComponent<EditableObject>(out var target2D))
|
||||
// {
|
||||
// SetSelectedObject(target2D);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// 아무것도 선택되지 않았을 경우
|
||||
SetSelectedObject(null);
|
||||
}
|
||||
|
||||
private void SetSelectedObject(EditableObject target)
|
||||
{
|
||||
if (_selectedObject == target) return;
|
||||
|
||||
_selectedObject?.OnDeselect();
|
||||
_selectedObject = target;
|
||||
_selectedObject?.OnSelect();
|
||||
|
||||
if (_selectedObject != null)
|
||||
{
|
||||
OnObjectSelected?.Invoke(_selectedObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnBackgroundClicked?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/InteractionController.cs.meta
Normal file
2
Assets/Scripts/UVC/InteractionController.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f8a7b0a02822c942a8c2846d5a2432a
|
||||
8
Assets/Scripts/UVC/Object3d.meta
Normal file
8
Assets/Scripts/UVC/Object3d.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d91472c3f70d9d4c84c8b50283e0548
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
55
Assets/Scripts/UVC/Object3d/InteractionController.cs
Normal file
55
Assets/Scripts/UVC/Object3d/InteractionController.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UVC.Extention;
|
||||
using UVC.UI.Info;
|
||||
|
||||
namespace UVC.Object3d
|
||||
{
|
||||
/// <summary>
|
||||
/// 사용자의 마우스 클릭 입력을 감지하고 3D 객체와 상호작용합니다.
|
||||
/// </summary>
|
||||
public class InteractionController : InteractiveObject
|
||||
{
|
||||
// InfoWindow 인스턴스에 대한 참조
|
||||
private InfoWindow infoWindow;
|
||||
|
||||
private Camera mainCamera;
|
||||
|
||||
private Dictionary<string, object>? infoData;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
mainCamera = Camera.main;
|
||||
|
||||
// 씬에 있는 InfoWindow 인스턴스를 동적으로 찾습니다.
|
||||
// FindObjectOfType은 씬에서 해당 타입의 활성화된 첫 번째 객체를 반환합니다.
|
||||
infoWindow = InfoWindow.Create();
|
||||
|
||||
if (infoWindow == null)
|
||||
{
|
||||
Debug.LogError("씬에서 InfoWindow 컴포넌트를 찾을 수 없습니다. InfoWindow가 씬에 존재하고 활성화되어 있는지 확인해주세요.");
|
||||
enabled = false; // infoWindow가 없으면 이 스크립트를 비활성화합니다.
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
Dictionary<string, object> info = new Dictionary<string, object>
|
||||
{
|
||||
{ "objectName", gameObject.name },
|
||||
{ "objectPosition", transform.position },
|
||||
{ "objectRotation", transform.rotation },
|
||||
{ "objectScale", transform.localScale }
|
||||
};
|
||||
// 변경 된 정보가 있을 때 클릭된 객체의 정보를 InfoWindow에 전달합니다.
|
||||
if (!infoWindow.IsVisible && (infoData != null && !infoData.IsSame(info)))
|
||||
{
|
||||
infoWindow.Show(transform, info);
|
||||
infoData = info;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06c8fe736f6ab0d4787d720284f536ea
|
||||
37
Assets/Scripts/UVC/Object3d/InteractiveObject.cs
Normal file
37
Assets/Scripts/UVC/Object3d/InteractiveObject.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UVC.Object3d
|
||||
{
|
||||
/// <summary>
|
||||
/// Unity의 Event System을 사용하여 마우스/포인터 상호작용을 처리하는 3D 객체를 위한 기본 클래스입니다.
|
||||
/// 이 클래스를 상속받는 객체는 클릭, 포인터 진입/이탈 이벤트를 수신할 수 있습니다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 이 컴포넌트가 올바르게 작동하려면 다음 조건이 충족되어야 합니다:
|
||||
/// 1. 이 컴포넌트가 부착된 게임 오브젝트에 반드시 <see cref="Collider"/> 컴포넌트가 있어야 합니다.
|
||||
/// 2. 씬에 <see cref="EventSystem"/>이 존재해야 합니다.
|
||||
/// 3. 메인 카메라에 <see cref="PhysicsRaycaster"/> 컴포넌트가 부착되어 있어야 합니다.
|
||||
/// </remarks>
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public class InteractiveObject : MonoBehaviour, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// 포인터로 이 객체를 클릭했을 때 호출됩니다.
|
||||
/// </summary>
|
||||
/// <param name="eventData">클릭 이벤트와 관련된 데이터입니다.</param>
|
||||
public virtual void OnPointerClick(PointerEventData eventData) { }
|
||||
|
||||
/// <summary>
|
||||
/// 포인터가 이 객체 위로 들어왔을 때 호출됩니다. 하이라이트 효과 등에 사용할 수 있습니다.
|
||||
/// </summary>
|
||||
/// <param name="eventData">포인터 이벤트와 관련된 데이터입니다.</param>
|
||||
public virtual void OnPointerEnter(PointerEventData eventData) { }
|
||||
|
||||
/// <summary>
|
||||
/// 포인터가 이 객체에서 벗어났을 때 호출됩니다.
|
||||
/// </summary>
|
||||
/// <param name="eventData">포인터 이벤트와 관련된 데이터입니다.</param>
|
||||
public virtual void OnPointerExit(PointerEventData eventData) { }
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Object3d/InteractiveObject.cs.meta
Normal file
2
Assets/Scripts/UVC/Object3d/InteractiveObject.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 369f10656ae555b4983fa3147fc56818
|
||||
35
Assets/Scripts/UVC/OutlineEffect.cs
Normal file
35
Assets/Scripts/UVC/OutlineEffect.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Editor
|
||||
{
|
||||
[RequireComponent(typeof(Renderer))]
|
||||
public class OutlineEffect : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Material _outlineMaterial;
|
||||
private Renderer _renderer;
|
||||
private bool _isOutlined = false;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_renderer = GetComponent<Renderer>();
|
||||
}
|
||||
|
||||
public void SetOutline(bool visible)
|
||||
{
|
||||
if (_isOutlined == visible) return;
|
||||
|
||||
_isOutlined = visible;
|
||||
var materials = _renderer.sharedMaterials.ToList();
|
||||
if (_isOutlined)
|
||||
{
|
||||
materials.Add(_outlineMaterial);
|
||||
}
|
||||
else
|
||||
{
|
||||
materials.Remove(_outlineMaterial);
|
||||
}
|
||||
_renderer.materials = materials.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/OutlineEffect.cs.meta
Normal file
2
Assets/Scripts/UVC/OutlineEffect.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 575f56d1e1229a543a23502e94149f71
|
||||
8
Assets/Scripts/UVC/UI/Info.meta
Normal file
8
Assets/Scripts/UVC/UI/Info.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8822a5b71db85f64483ede945af84867
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
115
Assets/Scripts/UVC/UI/Info/InfoWindow.cs
Normal file
115
Assets/Scripts/UVC/UI/Info/InfoWindow.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UVC.Core;
|
||||
|
||||
namespace UVC.UI.Info
|
||||
{
|
||||
/// <summary>
|
||||
/// 3D 객체를 따라다니며 정보를 표시하는 UI 창입니다.
|
||||
/// 이 컴포넌트는 World Space Canvas 내의 UI 요소에 추가되어야 합니다.
|
||||
/// </summary>
|
||||
public class InfoWindow : MonoBehaviour
|
||||
{
|
||||
|
||||
[Tooltip("InfoWindow의 프리팹")]
|
||||
[SerializeField]
|
||||
private static GameObject infoWindowPrefab;
|
||||
|
||||
[Tooltip("정보 텍스트를 표시할 UI 요소")]
|
||||
[SerializeField]
|
||||
private TextMeshProUGUI infoText;
|
||||
|
||||
[Tooltip("정보 창을 닫을 버튼")]
|
||||
[SerializeField]
|
||||
private Button closeButton;
|
||||
|
||||
[Tooltip("UI가 객체를 가리지 않도록 할 월드 좌표계 오프셋")]
|
||||
[SerializeField]
|
||||
private Vector3 worldOffset = new Vector3(0, 1.5f, 0);
|
||||
|
||||
// 정보 창이 따라다닐 3D 객체의 Transform
|
||||
private Transform? target;
|
||||
|
||||
// 메인 카메라 참조
|
||||
private Camera mainCamera;
|
||||
|
||||
/// <summary>
|
||||
/// 정보 창이 현재 화면에 표시되고 있는지 여부를 반환합니다.
|
||||
/// </summary>
|
||||
public bool IsVisible => gameObject.activeSelf;
|
||||
|
||||
/// <summary>
|
||||
/// 정보 창을 생성하여 반환합니다.
|
||||
/// </summary>
|
||||
public static InfoWindow? Create()
|
||||
{
|
||||
if(infoWindowPrefab == null)
|
||||
{
|
||||
Debug.LogError("InfoWindow 프리팹이 할당되지 않았습니다. Inspector에서 할당해주세요.");
|
||||
return null;
|
||||
}
|
||||
return Instantiate(infoWindowPrefab).GetComponent<InfoWindow>();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
mainCamera = Camera.main;
|
||||
|
||||
// 닫기 버튼이 할당되었으면 클릭 이벤트를 연결합니다.
|
||||
if (closeButton != null)
|
||||
{
|
||||
closeButton.onClick.AddListener(Hide);
|
||||
}
|
||||
|
||||
// 처음에는 정보 창을 숨깁니다.
|
||||
if (gameObject.activeSelf)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
// target이 설정되어 있고 활성화 상태일 때만 위치와 방향을 업데이트합니다.
|
||||
if (target != null && gameObject.activeSelf)
|
||||
{
|
||||
// 위치 업데이트
|
||||
transform.position = target.position + worldOffset;
|
||||
|
||||
// 항상 카메라를 바라보도록 방향 업데이트 (빌보드 효과)
|
||||
transform.rotation = mainCamera.transform.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 정보 창을 특정 대상에 대해 표시합니다.
|
||||
/// </summary>
|
||||
/// <param name="targetObject">정보를 표시할 3D 객체의 Transform</param>
|
||||
/// <param name="information">표시할 정보 문자열</param>
|
||||
public void Show(Transform targetObject, Dictionary<string, object> information)
|
||||
{
|
||||
target = targetObject;
|
||||
if (infoText != null)
|
||||
{
|
||||
infoText.text = information.ToString();
|
||||
}
|
||||
gameObject.SetActive(true);
|
||||
|
||||
// 즉시 위치와 방향을 업데이트합니다.
|
||||
LateUpdate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 정보 창을 숨깁니다.
|
||||
/// </summary>
|
||||
public void Hide()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
target = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/UI/Info/InfoWindow.cs.meta
Normal file
2
Assets/Scripts/UVC/UI/Info/InfoWindow.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 620e52b408949c340adef1110323cb7c
|
||||
Reference in New Issue
Block a user