초기 커밋.
This commit is contained in:
55
Assets/Scripts/UVC/util/ColorUtil.cs
Normal file
55
Assets/Scripts/UVC/util/ColorUtil.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Util
|
||||
{
|
||||
public static class ColorUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// hex code to color #FFFFFF
|
||||
/// </summary>
|
||||
/// <param name="hexCode">#rrggbb</param>
|
||||
/// <returns>Color °´Ăź</returns>
|
||||
/// <example>
|
||||
/// // Example usage:
|
||||
/// Color color = ColorUtil.FromHex("#FF5733");
|
||||
/// Debug.Log($"Color: {color}");
|
||||
/// </example>
|
||||
public static Color FromHex(string hexCode)
|
||||
{
|
||||
Color color;
|
||||
if (ColorUtility.TryParseHtmlString(hexCode, out color))
|
||||
{
|
||||
return color;
|
||||
}
|
||||
|
||||
Debug.LogError("[UnityExtension::HexColor]invalid hex code - " + hexCode);
|
||||
return Color.white;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// hex code to color with alpha #FFFFFF
|
||||
/// </summary>
|
||||
/// <param name="hexCode">#rrggbb</param>
|
||||
/// <param name="alpha">0.0~1.0</param>
|
||||
/// <returns>Color °´Ăź</returns>
|
||||
/// <example>
|
||||
/// // Example usage:
|
||||
/// Color colorWithAlpha = ColorUtil.FromHex("#FF5733", 0.5f);
|
||||
/// Debug.Log($"Color with alpha: {colorWithAlpha}");
|
||||
/// </example>
|
||||
public static Color FromHex(string hexCode, float alpha)
|
||||
{
|
||||
Color color;
|
||||
if (ColorUtility.TryParseHtmlString(hexCode, out color))
|
||||
{
|
||||
color.a = alpha;
|
||||
return color;
|
||||
}
|
||||
|
||||
Debug.LogError("[UnityExtension::HexColor]invalid hex code - " + hexCode);
|
||||
color = Color.white;
|
||||
color.a = alpha;
|
||||
return color;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/util/ColorUtil.cs.meta
Normal file
2
Assets/Scripts/UVC/util/ColorUtil.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21f0437a07bf58842a9db3a313d4766a
|
||||
63
Assets/Scripts/UVC/util/Debounce.cs
Normal file
63
Assets/Scripts/UVC/util/Debounce.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace UVC.Util
|
||||
{
|
||||
public class Debounce : IDisposable
|
||||
{
|
||||
private CancellationTokenSource cts = new CancellationTokenSource();
|
||||
|
||||
/// <summary>
|
||||
/// 마지막 호출 된 함수 한번 실행. milliseconds 이후 실행 되어 추가로 호추 되면 이전 함수 취소 함.
|
||||
/// Debounce debounce = new Debounce();
|
||||
/// debounce.DebounceAction(1000, repository.SaveToolBarData, data);
|
||||
/// </summary>
|
||||
/// <typeparam name="T">param Type</typeparam>
|
||||
/// <param name="milliseconds"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="param"></param>
|
||||
public void DebounceAction<T>(int milliseconds, Action<T> action, T param)
|
||||
{
|
||||
resetCancellationToken();
|
||||
UniTask.Delay(TimeSpan.FromMilliseconds(milliseconds), cancellationToken: cts.Token, ignoreTimeScale: false).ContinueWith(() =>
|
||||
{
|
||||
action.Invoke(param);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 마지막 호출 된 함수 한번 실행. milliseconds 이후 실행 되어 추가로 호추 되면 이전 함수 취소 함.
|
||||
/// Debounce debounce = new Debounce();
|
||||
/// debounce.DebounceUniTask(1000, repository.SaveToolBarData, data);
|
||||
/// </summary>
|
||||
/// <typeparam name="T">param Type</typeparam>
|
||||
/// <param name="milliseconds"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="param"></param>
|
||||
public void DebounceUniTask<T>(int milliseconds, Func<T, UniTaskVoid> action, T param)
|
||||
{
|
||||
resetCancellationToken();
|
||||
UniTask.Delay(TimeSpan.FromMilliseconds(milliseconds), cancellationToken: cts.Token, ignoreTimeScale: false).ContinueWith(() =>
|
||||
{
|
||||
action.Invoke(param).Forget();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void resetCancellationToken()
|
||||
{
|
||||
cts.Cancel();
|
||||
cts = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
cts.Cancel();
|
||||
cts.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/util/Debounce.cs.meta
Normal file
2
Assets/Scripts/UVC/util/Debounce.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee6e5e9af2f9c724489f5afabf85257b
|
||||
43
Assets/Scripts/UVC/util/DriveUtil.cs
Normal file
43
Assets/Scripts/UVC/util/DriveUtil.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.IO;
|
||||
|
||||
namespace UVC.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// DriveUtil 클래스는 시스템 드라이브의 가용 공간을 계산하는 유틸리티를 제공합니다.
|
||||
/// </summary>
|
||||
public static class DriveUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// 지정된 드라이브의 가용 GigaByte 크기를 반환합니다.
|
||||
/// </summary>
|
||||
/// <param name="driveName">드라이브 이름 (예: "C"). 기본값은 "C"입니다.</param>
|
||||
/// <returns>가용 공간(GigaByte) 또는 드라이브가 없을 경우 -1을 반환합니다.</returns>
|
||||
/// <example>
|
||||
/// // Example usage:
|
||||
/// long freeSpace = DriveUtil.GetDriveGigaBytes("C");
|
||||
/// if (freeSpace != -1)
|
||||
/// {
|
||||
/// Debug.Log($"C 드라이브의 가용 공간: {freeSpace} GB");
|
||||
/// }
|
||||
/// else
|
||||
/// {
|
||||
/// Debug.LogError("드라이브를 찾을 수 없습니다.");
|
||||
/// }
|
||||
/// </example>
|
||||
public static long GetDriveGigaBytes(string driveName = "C")
|
||||
{
|
||||
DriveInfo[] drivers = DriveInfo.GetDrives();
|
||||
foreach (var item in drivers)
|
||||
{
|
||||
if (item.DriveType == DriveType.Fixed)
|
||||
{
|
||||
if (item.Name.StartsWith(driveName))
|
||||
{
|
||||
return item.AvailableFreeSpace / 1024 / 1024 / 1024;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/util/DriveUtil.cs.meta
Normal file
2
Assets/Scripts/UVC/util/DriveUtil.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: acbe413c2ea538646af2f2ce0d8bd54f
|
||||
50
Assets/Scripts/UVC/util/GameObjectUtil.cs
Normal file
50
Assets/Scripts/UVC/util/GameObjectUtil.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// Unity에서 프리팹 경로를 통해 GameObject를 생성하거나 특정 컴포넌트를 가져오는 유틸리티 클래스입니다.
|
||||
/// </summary>
|
||||
public static class GameObjectUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// 주어진 프리팹 경로를 통해 GameObject를 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="prefabPath">프리팹의 Resources 경로</param>
|
||||
/// <param name="parent">생성된 GameObject의 부모 Transform (선택 사항)</param>
|
||||
/// <returns>생성된 GameObject</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// string prefabPath = "Prefabs/MyPrefab";
|
||||
/// GameObject newObject = prefabPath.GetGameObjectFromPrefabPath();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static GameObject GetGameObjectFromPrefabPath(this string prefabPath, Transform parent = null)
|
||||
{
|
||||
GameObject prefab = Resources.Load(prefabPath, typeof(GameObject)) as GameObject;
|
||||
return UnityEngine.Object.Instantiate(prefab);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 주어진 프리팹 경로를 통해 특정 타입의 컴포넌트를 가져옵니다.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">가져올 컴포넌트 타입</typeparam>
|
||||
/// <param name="prefabPath">프리팹의 Resources 경로</param>
|
||||
/// <param name="parent">생성된 GameObject의 부모 Transform (선택 사항)</param>
|
||||
/// <returns>생성된 GameObject에서 가져온 컴포넌트</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// string prefabPath = "Prefabs/MyPrefab";
|
||||
/// MyComponent component = prefabPath.GetComponentFromPrefabPath<MyComponent>();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static T GetComponentFromPrefabPath<T>(this string prefabPath, Transform parent = null)
|
||||
{
|
||||
GameObject prefab = Resources.Load(prefabPath, typeof(GameObject)) as GameObject;
|
||||
GameObject go = UnityEngine.Object.Instantiate(prefab);
|
||||
if (parent != null) go.transform.SetParent(parent, false);
|
||||
return go.GetComponent<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/util/GameObjectUtil.cs.meta
Normal file
2
Assets/Scripts/UVC/util/GameObjectUtil.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8fe5ea7a4ba58e4099f712f5d3cf133
|
||||
105
Assets/Scripts/UVC/util/GizmoDraw.cs
Normal file
105
Assets/Scripts/UVC/util/GizmoDraw.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// GizmoDraw 클래스는 Unity의 Gizmo를 사용하여 Sphere, Box, 또는 Mesh를 그리는 기능을 제공합니다.
|
||||
/// </summary>
|
||||
public class GizmoDraw : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Gizmo의 타입을 정의합니다. Sphere, Box, 또는 Mesh 중 하나를 선택할 수 있습니다.
|
||||
/// </summary>
|
||||
public enum GizmoType
|
||||
{
|
||||
Sphere,
|
||||
Box,
|
||||
Mesh
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 Gizmo의 타입을 설정합니다.
|
||||
/// </summary>
|
||||
public GizmoType gizmoType;
|
||||
|
||||
/// <summary>
|
||||
/// Gizmo를 화면에 표시할지 여부를 설정합니다.
|
||||
/// </summary>
|
||||
public bool show = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gizmo를 와이어프레임으로 그릴지 여부를 설정합니다.
|
||||
/// </summary>
|
||||
public bool wire = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gizmo의 색상을 설정합니다.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
private Color color = Color.yellow;
|
||||
|
||||
/// <summary>
|
||||
/// Sphere Gizmo의 반지름을 설정합니다.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
private float radius = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Box Gizmo의 경계(Bounds)를 설정합니다.
|
||||
/// </summary>
|
||||
public Bounds bounds = new Bounds(Vector3.zero, Vector3.one);
|
||||
|
||||
/// <summary>
|
||||
/// Mesh Gizmo를 그릴 때 사용할 Mesh를 설정합니다.
|
||||
/// </summary>
|
||||
public Mesh mesh;
|
||||
|
||||
/// <summary>
|
||||
/// Mesh Gizmo의 스케일을 설정합니다.
|
||||
/// </summary>
|
||||
public float scale = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Unity의 Gizmo를 그리는 메소드입니다. Unity 에디터에서만 동작합니다.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// GizmoDraw 컴포넌트를 사용하여 Sphere를 그리는 예제:
|
||||
/// <code>
|
||||
/// var gizmoDraw = gameObject.AddComponent<GizmoDraw>();
|
||||
/// gizmoDraw.gizmoType = GizmoDraw.GizmoType.Sphere;
|
||||
/// gizmoDraw.show = true;
|
||||
/// gizmoDraw.wire = false;
|
||||
/// gizmoDraw.radius = 2f;
|
||||
/// </code>
|
||||
/// </example>
|
||||
void OnDrawGizmos()
|
||||
{
|
||||
if (this.show)
|
||||
{
|
||||
Gizmos.color = color;
|
||||
|
||||
if (gizmoType == GizmoType.Sphere)
|
||||
{
|
||||
if (wire)
|
||||
Gizmos.DrawWireSphere(transform.position, radius);
|
||||
else
|
||||
Gizmos.DrawSphere(transform.position, radius);
|
||||
}
|
||||
else if (gizmoType == GizmoType.Box)
|
||||
{
|
||||
if (wire)
|
||||
Gizmos.DrawWireCube(transform.position + bounds.center, bounds.size);
|
||||
else
|
||||
Gizmos.DrawCube(transform.position + bounds.center, bounds.size);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (wire)
|
||||
Gizmos.DrawWireMesh(mesh, transform.position, Quaternion.identity, new Vector3(scale, scale, scale));
|
||||
else
|
||||
Gizmos.DrawMesh(mesh, transform.position, Quaternion.identity, new Vector3(scale, scale, scale));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/util/GizmoDraw.cs.meta
Normal file
2
Assets/Scripts/UVC/util/GizmoDraw.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e951c873a7dc88e42b201fca48bb08dd
|
||||
34
Assets/Scripts/UVC/util/MathUtil.cs
Normal file
34
Assets/Scripts/UVC/util/MathUtil.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// 수학 관련 유틸리티 메소드를 제공하는 클래스입니다.
|
||||
/// </summary>
|
||||
public static class MathUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// 중심점(`center`)을 기준으로 반지름(`radius`) 거리에서 각도(`angle`)에 해당하는 위치를 계산합니다.
|
||||
/// </summary>
|
||||
/// <param name="center">회전의 중심점</param>
|
||||
/// <param name="radius">회전 반지름</param>
|
||||
/// <param name="angle">회전 각도 (0~360도)</param>
|
||||
/// <returns>계산된 위치를 나타내는 `Vector3`</returns>
|
||||
/// <example>
|
||||
/// 다음은 `GetCircleLocationXY` 메소드를 사용하는 예제입니다:
|
||||
/// <code>
|
||||
/// Vector3 center = new Vector3(0, 0, 0);
|
||||
/// float radius = 5f;
|
||||
/// float angle = 90f;
|
||||
/// Vector3 position = MathUtil.GetCircleLocationXY(center, radius, angle);
|
||||
/// Debug.Log(position); // 출력: (0.0, 5.0, 0.0)
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static Vector3 GetCircleLocationXY(Vector3 center, float radius, float angle)
|
||||
{
|
||||
Vector3 pos = new Vector3(math.cos(angle * Mathf.Deg2Rad) * radius, math.sin(angle * Mathf.Deg2Rad) * radius, 0);
|
||||
return pos + center;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/util/MathUtil.cs.meta
Normal file
2
Assets/Scripts/UVC/util/MathUtil.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0fb243d5835026d4bb990bd68bd32bb1
|
||||
72
Assets/Scripts/UVC/util/ScreenCapture.cs
Normal file
72
Assets/Scripts/UVC/util/ScreenCapture.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace UVC.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// 화면 캡처를 위한 유틸리티 클래스입니다.
|
||||
/// 지정된 카메라 또는 화면 전체를 캡처하여 Texture2D로 반환합니다.
|
||||
/// </summary>
|
||||
public static class ScreenCapture
|
||||
{
|
||||
/// <summary>
|
||||
/// 화면을 캡처하여 Texture2D로 반환합니다.
|
||||
/// 대상 카메라가 RenderTexture를 사용하는 경우 해당 크기로 캡처하며,
|
||||
/// 그렇지 않은 경우 화면 전체를 캡처합니다.
|
||||
/// </summary>
|
||||
/// <returns>캡처된 Texture2D 객체</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 화면 캡처 실행
|
||||
/// Texture2D capturedImage = ScreenCapture.GetScreenShot(GetComponent<Camera>());
|
||||
/// if (capturedImage != null)
|
||||
/// {
|
||||
/// Debug.Log("화면 캡처 성공!");
|
||||
/// // 캡처된 이미지를 파일로 저장하거나 UI에 표시 가능
|
||||
/// }
|
||||
/// else
|
||||
/// {
|
||||
/// Debug.LogError("화면 캡처 실패: 대상 카메라가 설정되지 않았습니다.");
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static Texture2D GetScreenShot(Camera camera)
|
||||
{
|
||||
if (camera == null) return null;
|
||||
|
||||
if (camera.targetTexture != null)
|
||||
{
|
||||
Texture2D screenShot = new Texture2D(camera.targetTexture.width, camera.targetTexture.height, TextureFormat.RGB24, false);
|
||||
screenShot.wrapMode = TextureWrapMode.Clamp;
|
||||
|
||||
RenderTexture rt = camera.targetTexture;
|
||||
camera.Render();
|
||||
|
||||
RenderTexture.active = rt;
|
||||
screenShot.ReadPixels(new Rect(0, 0, camera.targetTexture.width, camera.targetTexture.height), 0, 0);
|
||||
screenShot.Apply(false);
|
||||
RenderTexture.active = null;
|
||||
|
||||
return screenShot;
|
||||
}
|
||||
else
|
||||
{
|
||||
Texture2D screenShot = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
|
||||
screenShot.wrapMode = TextureWrapMode.Clamp;
|
||||
|
||||
RenderTexture rt = new RenderTexture(Screen.width, Screen.height, 24);
|
||||
|
||||
camera.targetTexture = rt;
|
||||
camera.Render();
|
||||
camera.targetTexture = null;
|
||||
|
||||
RenderTexture.active = rt;
|
||||
screenShot.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
|
||||
screenShot.Apply(false);
|
||||
RenderTexture.active = null;
|
||||
|
||||
return screenShot;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/util/ScreenCapture.cs.meta
Normal file
2
Assets/Scripts/UVC/util/ScreenCapture.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 277b6a259a675b34d9a9a502ccb36aa7
|
||||
66
Assets/Scripts/UVC/util/Throttle.cs
Normal file
66
Assets/Scripts/UVC/util/Throttle.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using System;
|
||||
|
||||
namespace UVC.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// 특정 시간 동안 함수 실행을 제한하는 유틸리티 클래스
|
||||
/// </summary>
|
||||
public class Throttle
|
||||
{
|
||||
private DateTime timerStarted = DateTime.UtcNow.AddYears(-1);
|
||||
|
||||
/// <summary>
|
||||
/// 비동기 함수 실행 후 지정된 milliseconds 동안 추가 실행을 모두 취소합니다.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">함수에 전달할 매개변수의 타입</typeparam>
|
||||
/// <param name="milliseconds">제한 시간(밀리초)</param>
|
||||
/// <param name="action">실행할 비동기 함수</param>
|
||||
/// <param name="param">비동기 함수에 전달할 매개변수</param>
|
||||
/// <example>
|
||||
/// 아래는 ThrottleUniTask 메소드를 사용하는 예제입니다:
|
||||
/// <code>
|
||||
/// var throttle = new Throttle();
|
||||
/// await throttle.ThrottleUniTask(1000, async (param) =>
|
||||
/// {
|
||||
/// Console.WriteLine($"실행됨: {param}");
|
||||
/// await UniTask.Delay(500);
|
||||
/// }, "테스트 매개변수");
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void ThrottleUniTask<T>(int milliseconds, Func<T, UniTaskVoid> action, T param)
|
||||
{
|
||||
if (DateTime.UtcNow - timerStarted > TimeSpan.FromMilliseconds(milliseconds))
|
||||
{
|
||||
timerStarted = DateTime.UtcNow;
|
||||
action.Invoke(param).Forget();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 동기 함수 실행 후 지정된 milliseconds 동안 추가 실행을 모두 취소합니다.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">함수에 전달할 매개변수의 타입</typeparam>
|
||||
/// <param name="milliseconds">제한 시간(밀리초)</param>
|
||||
/// <param name="action">실행할 동기 함수</param>
|
||||
/// <param name="param">동기 함수에 전달할 매개변수</param>
|
||||
/// <example>
|
||||
/// 아래는 ThrottleAction 메소드를 사용하는 예제입니다:
|
||||
/// <code>
|
||||
/// var throttle = new Throttle();
|
||||
/// throttle.ThrottleAction(1000, (param) =>
|
||||
/// {
|
||||
/// Console.WriteLine($"실행됨: {param}");
|
||||
/// }, "테스트 매개변수");
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void ThrottleAction<T>(int milliseconds, Action<T> action, T param)
|
||||
{
|
||||
if (DateTime.UtcNow - timerStarted > TimeSpan.FromMilliseconds(milliseconds))
|
||||
{
|
||||
timerStarted = DateTime.UtcNow;
|
||||
action.Invoke(param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/util/Throttle.cs.meta
Normal file
2
Assets/Scripts/UVC/util/Throttle.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c421342b11ca02d44ac9baeb45f794e1
|
||||
630
Assets/Scripts/UVC/util/WindowTools.cs
Normal file
630
Assets/Scripts/UVC/util/WindowTools.cs
Normal file
@@ -0,0 +1,630 @@
|
||||
// Copyright (C) 2017 d.mind - All Rights Reserved
|
||||
// Version 1.0.1
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
using UVC.Core;
|
||||
|
||||
|
||||
namespace UVC.Util
|
||||
{
|
||||
public enum GWLWindowStyles : long
|
||||
{
|
||||
GWL_EXSTYLE = -20, // 새로운 확장 창 스타일을 설정합니다.
|
||||
GWL_HINSTANCE = -6, // 새로운 애플리케이션 인스턴스 핸들을 설정합니다.
|
||||
GWL_ID = -12, // 자식 창의 새로운 식별자를 설정합니다. 창은 최상위 창일 수 없습니다.
|
||||
GWL_STYLE = -16, // 새로운 창 스타일을 설정합니다.
|
||||
GWL_USERDATA = -21, // 창과 연관된 사용자 데이터를 설정합니다. 이 데이터는 창을 생성한 애플리케이션에서 사용하기 위한 것입니다. 초기 값은 0입니다.
|
||||
GWL_WNDPROC = -4 // 창 프로시저의 새로운 주소를 설정합니다. 호출 스레드와 동일한 프로세스에 속하지 않는 창의 속성은 변경할 수 없습니다.
|
||||
}
|
||||
|
||||
public enum WindowStyles : long
|
||||
{
|
||||
WS_BORDER = 0x00800000, // 창에 얇은 선의 테두리가 있습니다.
|
||||
WS_CAPTION = 0x00C00000, // 창에 제목 표시줄이 있습니다(WS_BORDER 스타일 포함).
|
||||
WS_CHILD = 0x40000000, // 창이 자식 창입니다. 이 스타일을 가진 창은 메뉴 표시줄을 가질 수 없습니다. 이 스타일은 WS_POPUP 스타일과 함께 사용할 수 없습니다.
|
||||
WS_CHILDWINDOW = 0x40000000, // WS_CHILD 스타일과 동일합니다.
|
||||
WS_CLIPCHILDREN = 0x02000000, // 부모 창 내에서 그리기가 발생할 때 자식 창이 차지하는 영역을 제외합니다. 이 스타일은 부모 창을 생성할 때 사용됩니다.
|
||||
WS_CLIPSIBLINGS = 0x04000000, // 자식 창을 서로 상대적으로 클리핑합니다. 특정 자식 창이 WM_PAINT 메시지를 받을 때 WS_CLIPSIBLINGS 스타일은 다른 겹치는 자식 창을 업데이트할 영역에서 제외합니다.
|
||||
WS_DISABLED = 0x08000000, // 창이 처음에 비활성화됩니다. 비활성화된 창은 사용자로부터 입력을 받을 수 없습니다.
|
||||
WS_DLGFRAME = 0x00400000, // 창에 대화 상자 스타일의 테두리가 있습니다. 이 스타일을 가진 창은 제목 표시줄을 가질 수 없습니다.
|
||||
WS_GROUP = 0x00020000, // 창이 컨트롤 그룹의 첫 번째 컨트롤입니다. 그룹은 이 첫 번째 컨트롤과 WS_GROUP 스타일이 있는 다음 컨트롤까지 정의된 모든 컨트롤로 구성됩니다.
|
||||
WS_HSCROLL = 0x00100000, // 창에 가로 스크롤바가 있습니다.
|
||||
WS_ICONIC = 0x20000000, // 창이 처음에 최소화됩니다. WS_MINIMIZE 스타일과 동일합니다.
|
||||
WS_MAXIMIZE = 0x01000000, // 창이 처음에 최대화됩니다.
|
||||
WS_MAXIMIZEBOX = 0x00010000, // 창에 최대화 버튼이 있습니다. WS_EX_CONTEXTHELP 스타일과 함께 사용할 수 없습니다.
|
||||
WS_MINIMIZE = 0x20000000, // 창이 처음에 최소화됩니다. WS_ICONIC 스타일과 동일합니다.
|
||||
WS_MINIMIZEBOX = 0x00020000, // 창에 최소화 버튼이 있습니다. WS_EX_CONTEXTHELP 스타일과 함께 사용할 수 없습니다. WS_SYSMENU 스타일도 지정해야 합니다.
|
||||
WS_OVERLAPPED = 0x00000000, // 창이 겹쳐진 창입니다. 겹쳐진 창은 제목 표시줄과 테두리를 가집니다. WS_TILED 스타일과 동일합니다.
|
||||
WS_OVERLAPPEDWINDOW = 0x00000000 | 0x00C00000 | 0x00080000 | 0x00040000 | 0x00020000 | 0x00010000, // 창이 겹쳐진 창입니다. WS_TILEDWINDOW 스타일과 동일합니다.
|
||||
WS_POPUP = 0x80000000, // 창이 팝업 창입니다. 이 스타일은 WS_CHILD 스타일과 함께 사용할 수 없습니다.
|
||||
WS_POPUPWINDOW = 0x80000000 | 0x00800000 | 0x00080000, // 창이 팝업 창입니다. WS_CAPTION 및 WS_POPUPWINDOW 스타일을 결합하여 창 메뉴를 표시할 수 있습니다.
|
||||
WS_SIZEBOX = 0x00040000, // 창에 크기 조정 테두리가 있습니다. WS_THICKFRAME 스타일과 동일합니다.
|
||||
WS_SYSMENU = 0x00080000, // 창의 제목 표시줄에 시스템 메뉴가 있습니다. WS_CAPTION 스타일도 지정해야 합니다.
|
||||
WS_TABSTOP = 0x00010000, // 창이 사용자가 TAB 키를 눌렀을 때 키보드 포커스를 받을 수 있는 컨트롤입니다. TAB 키를 누르면 WS_TABSTOP 스타일이 있는 다음 컨트롤로 키보드 포커스가 이동합니다.
|
||||
WS_THICKFRAME = 0x00040000, // 창에 크기 조정 테두리가 있습니다. WS_SIZEBOX 스타일과 동일합니다.
|
||||
WS_TILED = 0x00000000, // 창이 겹쳐진 창입니다. 겹쳐진 창은 제목 표시줄과 테두리를 가집니다. WS_OVERLAPPED 스타일과 동일합니다.
|
||||
WS_TILEDWINDOW = 0x00000000 | 0x00C00000 | 0x00080000 | 0x00040000 | 0x00020000 | 0x00010000, // 창이 겹쳐진 창입니다. WS_OVERLAPPEDWINDOW 스타일과 동일합니다.
|
||||
WS_VISIBLE = 0x10000000, // 창이 처음에 표시됩니다.
|
||||
WS_VSCROLL = 0x00200000 // 창에 세로 스크롤바가 있습니다.
|
||||
}
|
||||
|
||||
public enum ExtendedWindowStyles : long
|
||||
{
|
||||
WS_EX_ACCEPTFILES = 0x00000010, // 창이 드래그 앤 드롭 파일을 허용합니다.
|
||||
WS_EX_APPWINDOW = 0x00040000, // 창이 보일 때 최상위 창을 작업 표시줄에 강제로 표시합니다.
|
||||
WS_EX_CLIENTEDGE = 0x00000200, // 창에 오목한 가장자리 테두리가 있습니다.
|
||||
WS_EX_COMPOSITED = 0x02000000, // 모든 하위 창을 이중 버퍼링을 사용하여 아래에서 위로 그립니다.
|
||||
WS_EX_CONTEXTHELP = 0x00000400, // 창의 제목 표시줄에 물음표가 포함됩니다. 사용자가 물음표를 클릭하면 커서가 물음표와 포인터로 변경됩니다. 사용자가 자식 창을 클릭하면 WM_HELP 메시지를 받습니다.
|
||||
WS_EX_CONTROLPARENT = 0x00010000, // 창 자체가 대화 상자 탐색에 참여해야 하는 자식 창을 포함합니다.
|
||||
WS_EX_DLGMODALFRAME = 0x00000001, // 창에 이중 테두리가 있습니다. 선택적으로 WS_CAPTION 스타일을 dwStyle 매개변수에 지정하여 제목 표시줄을 생성할 수 있습니다.
|
||||
WS_EX_LAYERED = 0x00080000, // 창이 레이어드 창입니다.
|
||||
WS_EX_LAYOUTRTL = 0x00400000, // 셸 언어가 히브리어, 아랍어 또는 읽기 순서 정렬을 지원하는 다른 언어인 경우 창의 수평 원점이 오른쪽 가장자리에 있습니다. 수평 값을 증가시키면 왼쪽으로 이동합니다.
|
||||
WS_EX_LEFT = 0x00000000, // 창에 일반적인 왼쪽 정렬 속성이 있습니다. 기본값입니다.
|
||||
WS_EX_LEFTSCROLLBAR = 0x00004000, // 셸 언어가 히브리어, 아랍어 또는 읽기 순서 정렬을 지원하는 다른 언어인 경우 수직 스크롤바(있는 경우)가 왼쪽에 있습니다.
|
||||
WS_EX_LTRREADING = 0x00000000, // 창 텍스트가 왼쪽에서 오른쪽 읽기 순서 속성을 사용하여 표시됩니다. 기본값입니다.
|
||||
WS_EX_MDICHILD = 0x00000040, // 창이 MDI 자식 창입니다.
|
||||
WS_EX_NOACTIVATE = 0x08000000, // 이 스타일로 생성된 최상위 창은 사용자가 클릭해도 포그라운드 창이 되지 않습니다.
|
||||
WS_EX_NOINHERITLAYOUT = 0x00100000, // 창이 창 레이아웃을 자식 창에 전달하지 않습니다.
|
||||
WS_EX_NOPARENTNOTIFY = 0x00000004, // 이 스타일로 생성된 자식 창은 생성되거나 삭제될 때 WM_PARENTNOTIFY 메시지를 부모 창에 보내지 않습니다.
|
||||
WS_EX_NOREDIRECTIONBITMAP = 0x00200000, // 창이 리디렉션 표면에 렌더링되지 않습니다. 이는 보이는 콘텐츠가 없거나 표면 이외의 메커니즘을 사용하여 시각적 콘텐츠를 제공하는 창에 해당합니다.
|
||||
WS_EX_OVERLAPPEDWINDOW = 0x00000300, // 창이 겹쳐진 창입니다.
|
||||
WS_EX_PALETTEWINDOW = 0x00000188, // 창이 팔레트 창입니다. 이는 명령 배열을 제공하는 모달리스 대화 상자입니다.
|
||||
WS_EX_RIGHT = 0x00001000, // 창에 일반적인 "오른쪽 정렬" 속성이 있습니다. 이는 창 클래스에 따라 다릅니다.
|
||||
WS_EX_RIGHTSCROLLBAR = 0x00000000, // 수직 스크롤바(있는 경우)가 클라이언트 영역의 오른쪽에 있습니다. 기본값입니다.
|
||||
WS_EX_RTLREADING = 0x00002000, // 셸 언어가 히브리어, 아랍어 또는 읽기 순서 정렬을 지원하는 다른 언어인 경우 창 텍스트가 오른쪽에서 왼쪽 읽기 순서 속성을 사용하여 표시됩니다.
|
||||
WS_EX_STATICEDGE = 0x00020000, // 창에 사용자 입력을 받지 않는 항목에 사용하기 위한 3차원 테두리 스타일이 있습니다.
|
||||
WS_EX_TOOLWINDOW = 0x00000080, // 창이 플로팅 도구 모음으로 사용되도록 의도되었습니다. 도구 창은 일반 제목 표시줄보다 짧은 제목 표시줄을 가지며 창 제목은 더 작은 글꼴로 그려집니다.
|
||||
WS_EX_TOPMOST = 0x00000008, // 창이 모든 비최상위 창 위에 배치되며 비활성화되더라도 그 위에 유지됩니다. 이 스타일을 추가하거나 제거하려면 SetWindowPos 함수를 사용하십시오.
|
||||
WS_EX_TRANSPARENT = 0x00000020, // 창이 동일한 스레드에서 생성된 아래의 형제 창이 그려질 때까지 그려지지 않습니다. 창은 투명하게 보입니다.
|
||||
WS_EX_WINDOWEDGE = 0x00000100 // 창에 돌출된 가장자리 테두리가 있습니다.
|
||||
}
|
||||
|
||||
public enum WindowToolsFlags : long
|
||||
{
|
||||
SWP_NOSIZE = 0x0001, //Binary: 0000 0000 0000 0001
|
||||
SWP_NOMOVE = 0x0002, //Binary: 0000 0000 0000 0010
|
||||
SWP_NOZORDER = 0x0004, //Binary: 0000 0000 0000 0100
|
||||
SWP_DRAWFRAME = 0x0020, //Binary: 0000 0000 0010 0000
|
||||
SWP_HIDEWINDOW = 0x0080, //Binary: 0000 0000 1000 0000
|
||||
SWP_NOACTIVATE = 0x0010, //Binary: 0000 0000 0001 0000
|
||||
SWP_SHOWWINDOW = 0x0040 //Binary: 0000 0000 0100 0000
|
||||
}
|
||||
|
||||
public enum WindowToolsAlphaFlag : long
|
||||
{
|
||||
LWA_ALPHA = 0x00000002, //Binary: 0000 0000 0000 0000 0000 0000 0000 0010
|
||||
LWA_COLORKEY = 0x00000001 //Binary: 0000 0000 0000 0000 0000 0000 0000 0001
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// WindowTools 클래스는 Unity 애플리케이션에서 창의 위치, 크기, 스타일 등을 제어하는 기능을 제공합니다.
|
||||
/// 이 클래스는 Windows API를 활용하여 창의 속성을 변경하며, Unity 환경에서 실행 시 유용한 창 관리 기능을 제공합니다.
|
||||
/// </summary>
|
||||
public class WindowTools : SingletonApp<WindowTools>
|
||||
{
|
||||
[Serializable]
|
||||
public class WindowInfoData
|
||||
{
|
||||
public class RectInfo
|
||||
{
|
||||
public int x; // 창의 X 좌표
|
||||
public int y; // 창의 Y 좌표
|
||||
public int width; // 창의 너비
|
||||
public int height; // 창의 높이
|
||||
}
|
||||
|
||||
[Tooltip("기능 사용 여부")]
|
||||
public bool use = true;// 창 관리 기능 활성화 여부
|
||||
|
||||
[Tooltip("창 표시 영역")]
|
||||
public RectInfo rect = new RectInfo() { x = 0, y = 0, width = 1920, height = 1080 }; // 창의 초기 위치 및 크기
|
||||
|
||||
[Tooltip("항상 최상위에 놓을지 여부")]
|
||||
public bool alwaysTop = false; // 창을 항상 최상위로 유지할지 여부
|
||||
|
||||
[Tooltip("창 테두리 표시 여부")]
|
||||
public bool showBorder = true; // 창 테두리 표시 여부
|
||||
|
||||
[Tooltip("첫 실행 시 창 크기, 1:기본, 2:최소화, 3:최대화"), Range(1, 3)]
|
||||
public int initWindowState = 1; // 창의 초기 상태 (기본, 최소화, 최대화)
|
||||
|
||||
[Tooltip("실행 딜레이 (단위:초)")]
|
||||
public float startDelay = 0f; // 창 초기화 지연 시간
|
||||
|
||||
[Tooltip("새 타이틀 명 (비어있으면 반영안됨)")]
|
||||
public string newTitle = "Octopus"; // 창의 새 제목
|
||||
}
|
||||
|
||||
private WindowInfoData WindowInfo = new WindowInfoData();
|
||||
|
||||
private IntPtr _hwnd; // 창 핸들
|
||||
private long _old; // 이전 창 스타일
|
||||
private string _filePath; // 파일 경로
|
||||
private int screenWidth; // 화면 너비
|
||||
private int screenHeight; // 화면 높이
|
||||
private int startWidth; // 초기 창 너비
|
||||
private int startHeight; // 초기 창 높이
|
||||
|
||||
/// <summary>
|
||||
/// Unity 애플리케이션 시작 시 창의 초기 위치와 크기를 설정합니다.
|
||||
/// </summary>
|
||||
private void Start()
|
||||
{
|
||||
|
||||
DisplayInfo displayInfo = Screen.mainWindowDisplayInfo;
|
||||
|
||||
// 화면 해상도 계산
|
||||
screenWidth = int.Parse(displayInfo.workArea.width.ToString()) + 14;// Screen.width; 14:그림자
|
||||
screenHeight = int.Parse(displayInfo.workArea.height.ToString()) + 7;// creen.height; 7:그림자
|
||||
|
||||
startWidth = 350 + 14;
|
||||
startHeight = 606 + 7;
|
||||
int centerX = ((screenWidth - startWidth) / 2);
|
||||
int centerY = ((screenHeight - startHeight) / 2);
|
||||
|
||||
WindowInfo.rect.x = centerX;
|
||||
WindowInfo.rect.y = centerY;
|
||||
WindowInfo.rect.width = startWidth;
|
||||
WindowInfo.rect.height = startHeight;
|
||||
|
||||
#if !UNITY_EDITOR || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN
|
||||
|
||||
Screen.MoveMainWindowTo(displayInfo, new Vector2Int(centerX, centerY));
|
||||
|
||||
|
||||
// 창 관리 기능 활성화 시 초기화 수행
|
||||
if (WindowInfo.use)
|
||||
{
|
||||
StartCoroutine(Initialize());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// 애플리케이션 종료 시 창의 크기를 저장합니다.
|
||||
/// </summary>
|
||||
private void OnApplicationQuit()
|
||||
{
|
||||
PlayerPrefs.SetInt("Screenmanager Resolution Width", startWidth);
|
||||
PlayerPrefs.SetInt("Screenmanager Resolution Height", startHeight);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
//private void Update()
|
||||
//{
|
||||
// if (Input.GetKeyDown(KeyCode.Space))
|
||||
// {
|
||||
// SaveData();
|
||||
// }
|
||||
|
||||
// if (Input.GetKeyDown(KeyCode.Q))
|
||||
// {
|
||||
// SetWindowPosition(100, 100);
|
||||
// }
|
||||
// if (Input.GetKeyDown(KeyCode.W))
|
||||
// {
|
||||
// SetWindowPosition(0, 0);
|
||||
// }
|
||||
// if (Input.GetKeyDown(KeyCode.A))
|
||||
// {
|
||||
// SetWindowSize(800, 600);
|
||||
// }
|
||||
// if (Input.GetKeyDown(KeyCode.S))
|
||||
// {
|
||||
// SetWindowSize(1280, 900);
|
||||
// }
|
||||
|
||||
// if (Input.GetKeyDown(KeyCode.Z))
|
||||
// {
|
||||
// ShowBorder();
|
||||
// }
|
||||
// if (Input.GetKeyDown(KeyCode.X))
|
||||
// {
|
||||
// HideBorder();
|
||||
// }
|
||||
|
||||
// if (Input.GetKeyDown(KeyCode.Alpha1))
|
||||
// {
|
||||
// NormalizeWindow();
|
||||
// }
|
||||
// if (Input.GetKeyDown(KeyCode.Alpha2))
|
||||
// {
|
||||
// MinimizeWindow();
|
||||
// }
|
||||
// if (Input.GetKeyDown(KeyCode.Alpha3))
|
||||
// {
|
||||
// MaximizeWindow();
|
||||
// }
|
||||
|
||||
// if (Input.GetKeyDown(KeyCode.Alpha4))
|
||||
// {
|
||||
// SetWindowTitle("Window1");
|
||||
// }
|
||||
// if (Input.GetKeyDown(KeyCode.Alpha5))
|
||||
// {
|
||||
// SetWindowTitle("Window2");
|
||||
// }
|
||||
|
||||
// if (Input.GetKeyDown(KeyCode.Alpha6))
|
||||
// {
|
||||
// SetAlphaWindow(0.3f);
|
||||
// }
|
||||
// if (Input.GetKeyDown(KeyCode.Alpha7))
|
||||
// {
|
||||
// SetAlphaWindow(1f);
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 창 초기화 작업을 수행합니다.
|
||||
/// </summary>
|
||||
/// <returns>코루틴</returns>
|
||||
private IEnumerator Initialize()
|
||||
{
|
||||
#if UNITY_STANDALONE_WIN && !UNITY_EDITOR
|
||||
_hwnd = GetWindowHandle();//FindWindow("UnityWndClass", Application.productName);
|
||||
_old = GetWindowLong(_hwnd, (long)GWLWindowStyles.GWL_STYLE);
|
||||
|
||||
yield return new WaitForSeconds(this.WindowInfo.startDelay);
|
||||
|
||||
|
||||
int hWndInsertAfter = (this.WindowInfo.alwaysTop) ? -1 : 0;
|
||||
Rect winRect = new Rect(WindowInfo.rect.x, WindowInfo.rect.y, WindowInfo.rect.width, WindowInfo.rect.height);
|
||||
SetActiveWindow(_hwnd);
|
||||
|
||||
if (!WindowInfo.showBorder)
|
||||
{
|
||||
SetWindowLong(_hwnd, (int)GWLWindowStyles.GWL_STYLE, 1);
|
||||
}
|
||||
|
||||
SetWindowPos(_hwnd, hWndInsertAfter, (int)winRect.x, (int)winRect.y, (int)winRect.width, (int)winRect.height, (uint)WindowToolsFlags.SWP_SHOWWINDOW);
|
||||
|
||||
if(!string.IsNullOrEmpty(WindowInfo.newTitle.Trim()))
|
||||
{
|
||||
SetWindowTitle(WindowInfo.newTitle);
|
||||
}
|
||||
|
||||
if(WindowInfo.initWindowState == 2)
|
||||
{
|
||||
MinimizeWindow();
|
||||
}
|
||||
else if(WindowInfo.initWindowState == 3)
|
||||
{
|
||||
MaximizeWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
NormalizeWindow();
|
||||
}
|
||||
#endif
|
||||
yield return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 윈도우 창의 위치를 지정합니다.
|
||||
/// </summary>
|
||||
/// <param name="x">창의 X 좌표</param>
|
||||
/// <param name="y">창의 Y 좌표</param>
|
||||
public void SetWindowPosition(int x, int y)
|
||||
{
|
||||
WindowInfo.rect.x = x;
|
||||
WindowInfo.rect.y = y;
|
||||
|
||||
#if UNITY_STANDALONE_WIN && !UNITY_EDITOR
|
||||
int hWndInsertAfter = (this.WindowInfo.alwaysTop) ? -1 : 0;
|
||||
SetWindowPos(_hwnd, hWndInsertAfter, x, y, 0, 0, (UInt32)(WindowToolsFlags.SWP_NOSIZE | WindowToolsFlags.SWP_NOZORDER | WindowToolsFlags.SWP_DRAWFRAME));
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 윈도우 창의 크기를 지정합니다.
|
||||
/// </summary>
|
||||
/// <param name="w">창의 너비</param>
|
||||
/// <param name="h">창의 높이</param>
|
||||
public void SetWindowSize(int w, int h)
|
||||
{
|
||||
WindowInfo.rect.width = w;
|
||||
WindowInfo.rect.height = h;
|
||||
|
||||
#if UNITY_STANDALONE_WIN && !UNITY_EDITOR
|
||||
int hWndInsertAfter = (this.WindowInfo.alwaysTop) ? -1 : 0;
|
||||
SetWindowPos(_hwnd, hWndInsertAfter, 0, 0, w, h, (UInt32)(WindowToolsFlags.SWP_NOMOVE | WindowToolsFlags.SWP_NOZORDER | WindowToolsFlags.SWP_DRAWFRAME));
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 윈도우 창의 크기를 화면 해상도에 맞게 최대 크기로 설정합니다.
|
||||
/// </summary>
|
||||
public void SetWindowMaxSize()
|
||||
{
|
||||
SetWindowSize(screenWidth, screenHeight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 윈도우 창의 테두리를 활성화합니다.
|
||||
/// </summary>
|
||||
public void ShowBorder()
|
||||
{
|
||||
this.WindowInfo.showBorder = true;
|
||||
|
||||
#if UNITY_STANDALONE_WIN && !UNITY_EDITOR
|
||||
SetActiveWindow(_hwnd);
|
||||
SetWindowLong(_hwnd, (int)GWLWindowStyles.GWL_STYLE, (int)_old);
|
||||
RefreshWindow();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 윈도우 창의 테두리를 숨깁니다.
|
||||
/// </summary>
|
||||
public void HideBorder()
|
||||
{
|
||||
this.WindowInfo.showBorder = false;
|
||||
|
||||
#if UNITY_STANDALONE_WIN && !UNITY_EDITOR
|
||||
SetActiveWindow(_hwnd);
|
||||
SetWindowLong(_hwnd, (int)GWLWindowStyles.GWL_STYLE, 1);
|
||||
RefreshWindow();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 윈도우 창을 새로고침하여 변경 사항을 적용합니다.
|
||||
/// </summary>
|
||||
private void RefreshWindow()
|
||||
{
|
||||
#if UNITY_STANDALONE_WIN && !UNITY_EDITOR
|
||||
int hWndInsertAfter = (this.WindowInfo.alwaysTop) ? -1 : 0;
|
||||
|
||||
Rect2 windowRect = new Rect2();
|
||||
GetWindowRect(_hwnd, ref windowRect);
|
||||
|
||||
int screenW = (int)Screen.width;
|
||||
int screenH = (int)Screen.height;
|
||||
SetWindowPos(_hwnd, hWndInsertAfter, (int)windowRect.Left, (int)windowRect.Top, screenW, screenH, (int)(WindowToolsFlags.SWP_SHOWWINDOW));
|
||||
#endif
|
||||
}
|
||||
|
||||
//public void SetAlwaysTop(bool alwaysTop)
|
||||
//{
|
||||
// this.WindowInfo.alwaysTop = alwaysTop;
|
||||
// StopAllCoroutines();
|
||||
// StartCoroutine(Init());
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 윈도우 창을 기본 상태로 설정합니다.
|
||||
/// </summary>
|
||||
public void NormalizeWindow()
|
||||
{
|
||||
this.WindowInfo.initWindowState = 1;
|
||||
|
||||
#if UNITY_STANDALONE_WIN && !UNITY_EDITOR
|
||||
ShowWindowAsync(_hwnd, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 윈도우 창을 최소화합니다.
|
||||
/// </summary>
|
||||
public void MinimizeWindow()
|
||||
{
|
||||
this.WindowInfo.initWindowState = 2;
|
||||
|
||||
#if UNITY_STANDALONE_WIN && !UNITY_EDITOR
|
||||
ShowWindowAsync(_hwnd, 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 윈도우 창을 최대화합니다.
|
||||
/// </summary>
|
||||
public void MaximizeWindow()
|
||||
{
|
||||
this.WindowInfo.initWindowState = 3;
|
||||
|
||||
#if UNITY_STANDALONE_WIN && !UNITY_EDITOR
|
||||
ShowWindowAsync(_hwnd, 3);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 윈도우 창의 제목을 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="newTitle">새로운 창 제목</param>
|
||||
public void SetWindowTitle(string newTitle)
|
||||
{
|
||||
this.WindowInfo.newTitle = newTitle;
|
||||
|
||||
#if UNITY_STANDALONE_WIN && !UNITY_EDITOR
|
||||
SetWindowText(_hwnd, newTitle);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 윈도우 창의 투명도를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="opacity">투명도 값 (0.0 ~ 1.0)</param>
|
||||
public void SetAlphaWindow(float opacity)
|
||||
{
|
||||
#if UNITY_STANDALONE_WIN && !UNITY_EDITOR
|
||||
opacity = Mathf.Clamp01(opacity);
|
||||
int alpha = Mathf.RoundToInt(opacity * 255);
|
||||
SetWindowLong(_hwnd, (int)GWLWindowStyles.GWL_EXSTYLE, GetWindowLong(_hwnd, (long)GWLWindowStyles.GWL_EXSTYLE) | (long)ExtendedWindowStyles.WS_EX_LAYERED);
|
||||
SetLayeredWindowAttributes(_hwnd, 0, (byte)alpha, (uint)WindowToolsAlphaFlag.LWA_ALPHA);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 메시지 박스를 표시합니다.
|
||||
/// </summary>
|
||||
/// <param name="title">메시지 박스 제목</param>
|
||||
/// <param name="message">메시지 내용</param>
|
||||
/// <param name="type">메시지 박스 유형 (AbortRetryIgnore, CancelTryContinue, Help, OK, OkCancel, RetryCancel, YesNo, YesNoCancel)</param>
|
||||
/// <returns>사용자가 선택한 버튼의 결과 (OK, CANCEL, ABORT, RETRY, IGNORE, YES, NO, TRY AGAIN)</returns>
|
||||
public static string Alert(string title, string message, string type = "OK")
|
||||
{
|
||||
//#if UNITY_STANDALONE_WIN && !UNITY_EDITOR
|
||||
try
|
||||
{
|
||||
string DialogResult = string.Empty;
|
||||
uint MB_ABORTRETRYIGNORE = (uint)(0x00000002L | 0x00000010L);
|
||||
uint MB_CANCELTRYCONTINUE = (uint)(0x00000006L | 0x00000030L);
|
||||
uint MB_HELP = (uint)(0x00004000L | 0x00000040L);
|
||||
uint MB_OK = (uint)(0x00000000L | 0x00000040L);
|
||||
uint MB_OKCANCEL = (uint)(0x00000001L | 0x00000040L);
|
||||
uint MB_RETRYCANCEL = (uint)0x00000005L;
|
||||
uint MB_YESNO = (uint)(0x00000004L | 0x00000040L);
|
||||
uint MB_YESNOCANCEL = (uint)(0x00000003L | 0x00000040L);
|
||||
int intresult = -1;
|
||||
string strResult = string.Empty;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case "AbortRetryIgnore":
|
||||
intresult = MessageBox(GetWindowHandle(), message, title, MB_ABORTRETRYIGNORE);
|
||||
break;
|
||||
case "CancelTryContinue":
|
||||
intresult = MessageBox(GetWindowHandle(), message, title, MB_CANCELTRYCONTINUE);
|
||||
break;
|
||||
case "Help":
|
||||
intresult = MessageBox(GetWindowHandle(), message, title, MB_HELP);
|
||||
break;
|
||||
case "OK":
|
||||
intresult = MessageBox(GetWindowHandle(), message, title, MB_OK);
|
||||
break;
|
||||
case "OkCancel":
|
||||
intresult = MessageBox(GetWindowHandle(), message, title, MB_OKCANCEL);
|
||||
break;
|
||||
case "RetryCancel":
|
||||
intresult = MessageBox(GetWindowHandle(), message, title, MB_RETRYCANCEL);
|
||||
break;
|
||||
case "YesNo":
|
||||
intresult = MessageBox(GetWindowHandle(), message, title, MB_YESNO);
|
||||
break;
|
||||
case "YesNoCancel":
|
||||
intresult = MessageBox(GetWindowHandle(), message, title, MB_YESNOCANCEL);
|
||||
break;
|
||||
default:
|
||||
intresult = MessageBox(GetWindowHandle(), message, title, (uint)(0x00000000L | 0x00000010L));
|
||||
break;
|
||||
}
|
||||
|
||||
switch (intresult)
|
||||
{
|
||||
case 1:
|
||||
strResult = "OK";
|
||||
break;
|
||||
case 2:
|
||||
strResult = "CANCEL";
|
||||
break;
|
||||
case 3:
|
||||
strResult = "ABORT";
|
||||
break;
|
||||
case 4:
|
||||
strResult = "RETRY";
|
||||
break;
|
||||
case 5:
|
||||
strResult = "IGNORE";
|
||||
break;
|
||||
case 6:
|
||||
strResult = "YES";
|
||||
break;
|
||||
case 7:
|
||||
strResult = "NO";
|
||||
break;
|
||||
case 10:
|
||||
strResult = "TRY AGAIN";
|
||||
break;
|
||||
default:
|
||||
strResult = "OK";
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return strResult;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
//#endif
|
||||
}
|
||||
|
||||
|
||||
private static System.IntPtr GetWindowHandle()
|
||||
{
|
||||
return GetActiveWindow();
|
||||
}
|
||||
|
||||
#region User32
|
||||
#if UNITY_STANDALONE_WIN && !UNITY_EDITOR
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
|
||||
static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern long GetWindowText(IntPtr hWnd, StringBuilder text, int count);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool SetWindowText(System.IntPtr hwnd, System.String lpString);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr SetWindowLong(IntPtr hwnd, long nIndex, long dwNewLong);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr SetWindowLong(IntPtr hwnd, int _nIndex, int dwNewLong);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern long GetWindowLong(IntPtr hwnd, long nIndex);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
||||
|
||||
[DllImport("user32")]
|
||||
static extern int SetActiveWindow(IntPtr hwnd);
|
||||
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool GetWindowRect(IntPtr hwnd, ref Rect2 rectangle);
|
||||
|
||||
|
||||
|
||||
public struct Rect2
|
||||
{
|
||||
public int Left { get; set; }
|
||||
public int Top { get; set; }
|
||||
public int Right { get; set; }
|
||||
public int Bottom { get; set; }
|
||||
}
|
||||
#endif
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr GetActiveWindow();
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
static extern int MessageBox(IntPtr hwnd, String lpText, String lpCaption, uint uType);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/util/WindowTools.cs.meta
Normal file
2
Assets/Scripts/UVC/util/WindowTools.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7db14290c261fca4483a39761e5bd49f
|
||||
333
Assets/Scripts/UVC/util/XmlDataUtil.cs
Normal file
333
Assets/Scripts/UVC/util/XmlDataUtil.cs
Normal file
@@ -0,0 +1,333 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.Serialization;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace UVC.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// XML 데이터 처리 유틸리티 클래스
|
||||
/// </summary>
|
||||
public class XmlDataUtil : MonoBehaviour
|
||||
{
|
||||
private static XmlDataUtil __instance = null;
|
||||
|
||||
public static XmlDataUtil Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if(__instance == null)
|
||||
{
|
||||
__instance = new GameObject("_XmlDataUtil").AddComponent<XmlDataUtil>();
|
||||
}
|
||||
return __instance;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 데이터 직렬화 + XML 파일 저장
|
||||
/// </summary>
|
||||
/// <param name="filePath">저장될 파일 경로</param>
|
||||
/// <param name="data">직렬화할 데이터</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var data = new MyData { Name = "Test", Age = 25 };
|
||||
/// XmlDataUtil.Instance.SerializeXmlData("path/to/file.xml", data);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void SerializeXmlData(string filePath, object data)
|
||||
{
|
||||
Debug.Log("SerializeXmlData");
|
||||
|
||||
XmlSerializer serializer = new XmlSerializer(data.GetType());
|
||||
|
||||
using (TextWriter writer = new StreamWriter(filePath))
|
||||
{
|
||||
serializer.Serialize(writer, data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 데이터 비직렬화 + XML 파일 로드
|
||||
/// </summary>
|
||||
/// <typeparam name="T">비직렬화할 데이터 타입</typeparam>
|
||||
/// <param name="filePath">XML 파일 경로</param>
|
||||
/// <param name="completeAction">로드 완료 후 실행할 콜백</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// XmlDataUtil.Instance.DeserializeXmlData<MyData>("path/to/file.xml", (data) =>
|
||||
/// {
|
||||
/// Debug.Log($"Name: {data.Name}, Age: {data.Age}");
|
||||
/// });
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void DeserializeXmlData<T>(string filePath, Action<T> completeAction)
|
||||
{
|
||||
XmlSerializer serializer = new XmlSerializer(typeof(T));
|
||||
|
||||
using (FileStream fs = new FileStream(filePath, FileMode.Open))
|
||||
{
|
||||
XmlReader reader = XmlReader.Create(fs);
|
||||
|
||||
if (completeAction != null)
|
||||
{
|
||||
completeAction.Invoke((T)serializer.Deserialize(reader));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// XML 데이터 저장
|
||||
/// </summary>
|
||||
/// <param name="filePath">저장될 파일 경로</param>
|
||||
/// <param name="data">저장할 데이터</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var data = new MyData { Name = "Test", Age = 25 };
|
||||
/// XmlDataUtil.Instance.SaveXmlData("path/to/file.xml", data);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void SaveXmlData(string filePath, object data)
|
||||
{
|
||||
//Debug.Log(filePath + " , " + data);
|
||||
//Debug.Log("File.Exist: " + File.Exists(filePath));
|
||||
|
||||
if (data == null) return;
|
||||
|
||||
//파일이 있으면 기존 파일 로드 후 수정
|
||||
if(File.Exists(filePath))
|
||||
{
|
||||
StartCoroutine(LoadXml(filePath, (XDocument xdoc) =>
|
||||
{
|
||||
//Debug.Log(xdoc.ToString());
|
||||
|
||||
UpdateData(xdoc, data, (isChanged, isDataError) =>
|
||||
{
|
||||
//Debug.Log("isChanged: " + isChanged + " , isDataError: " + isDataError);
|
||||
|
||||
if (isChanged && !isDataError)
|
||||
{
|
||||
xdoc.Save(filePath);
|
||||
Debug.Log("SaveXmlData!");
|
||||
}
|
||||
else if (isDataError)
|
||||
{
|
||||
Debug.Log("XML 데이터 노드 순서가 잘못되었다.! 다시 강제로 직렬화한다.");
|
||||
SerializeXmlData(filePath, data);
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
//파일이 없으면 새롭게 직렬화, 저장
|
||||
else
|
||||
{
|
||||
SerializeXmlData(filePath, data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// XML 데이터 로드
|
||||
/// </summary>
|
||||
/// <param name="filePath">XML 파일 경로</param>
|
||||
/// <param name="OnLoadComplete">로드 완료 이벤트 핸들러</param>
|
||||
/// <param name="OnError">에러 핸들러</param>
|
||||
/// <returns>코루틴</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// StartCoroutine(XmlDataUtil.Instance.LoadXml("path/to/file.xml", (xdoc) =>
|
||||
/// {
|
||||
/// Debug.Log(xdoc.ToString());
|
||||
/// }, (error) =>
|
||||
/// {
|
||||
/// Debug.LogError(error);
|
||||
/// }));
|
||||
/// </code>
|
||||
/// </example>
|
||||
public IEnumerator LoadXml(string filePath, Action<XDocument> OnLoadComplete, Action<string> OnError = null)
|
||||
{
|
||||
if (!filePath.Contains("://"))
|
||||
{
|
||||
filePath = "file://" + filePath;
|
||||
}
|
||||
using (UnityWebRequest www = UnityWebRequest.Get(filePath))
|
||||
{
|
||||
www.timeout = 5;//5초
|
||||
yield return www.SendWebRequest();
|
||||
|
||||
if (www.isNetworkError || www.isHttpError)
|
||||
{
|
||||
if (OnError != null)
|
||||
{
|
||||
OnError(www.error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (MemoryStream memStream = new MemoryStream())
|
||||
{
|
||||
memStream.Write(www.downloadHandler.data, 0, www.downloadHandler.data.Length);
|
||||
memStream.Position = 0;
|
||||
|
||||
XmlReader reader = XmlReader.Create(memStream);
|
||||
XDocument xdoc = XDocument.Load(reader);
|
||||
|
||||
if (OnLoadComplete != null)
|
||||
{
|
||||
OnLoadComplete(xdoc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// XML 데이터를 비직렬화하여 로드합니다.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">비직렬화할 데이터 타입</typeparam>
|
||||
/// <param name="filePath">XML 파일 경로</param>
|
||||
/// <param name="OnLoadComplete">로드 완료 후 실행할 콜백 (비직렬화된 데이터 전달)</param>
|
||||
/// <param name="OnError">에러 발생 시 실행할 콜백 (에러 메시지 전달)</param>
|
||||
/// <returns>코루틴</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// StartCoroutine(XmlDataUtil.Instance.LoadXml<MyData>("path/to/file.xml", (data) =>
|
||||
/// {
|
||||
/// Debug.Log($"Name: {data.Name}, Age: {data.Age}");
|
||||
/// }, (error) =>
|
||||
/// {
|
||||
/// Debug.LogError($"Error loading XML: {error}");
|
||||
/// }));
|
||||
/// </code>
|
||||
/// </example>
|
||||
[Obsolete]
|
||||
public IEnumerator LoadXml<T>(string filePath, Action<T> OnLoadComplete, Action<string> OnError = null)
|
||||
{
|
||||
if (!filePath.Contains("://"))
|
||||
{
|
||||
filePath = "file://" + filePath;
|
||||
}
|
||||
using (UnityWebRequest www = UnityWebRequest.Get(filePath))
|
||||
{
|
||||
www.timeout = 5;//5초
|
||||
yield return www.SendWebRequest();
|
||||
|
||||
if (www.isNetworkError || www.isHttpError)
|
||||
{
|
||||
if (OnError != null)
|
||||
{
|
||||
OnError(www.error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
using (MemoryStream memStream = new MemoryStream())
|
||||
{
|
||||
memStream.Write(www.downloadHandler.data, 0, www.downloadHandler.data.Length);
|
||||
memStream.Position = 0;
|
||||
|
||||
XmlSerializer serializer = new XmlSerializer(typeof(T));
|
||||
XmlReader reader = XmlReader.Create(memStream);
|
||||
T result = (T)serializer.Deserialize(reader);
|
||||
|
||||
if (OnLoadComplete != null)
|
||||
{
|
||||
OnLoadComplete(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void UpdateData(XDocument xdoc, object data, Action<bool, bool> OnComplete)
|
||||
{
|
||||
XmlSerializer xmlSerializer = new XmlSerializer(data.GetType());
|
||||
using (StringWriter textWriter = new StringWriter())
|
||||
{
|
||||
xmlSerializer.Serialize(textWriter, data);
|
||||
var currentData = XElement.Parse(textWriter.ToString());
|
||||
bool isChanged = false;
|
||||
bool isDataError = false;
|
||||
|
||||
var currentItemList = currentData.Descendants().ToList();
|
||||
var originItemList = xdoc.Root.Descendants().ToList();
|
||||
|
||||
foreach (var currentItem in currentItemList)
|
||||
{
|
||||
foreach (var originItem in originItemList)
|
||||
{
|
||||
if (currentItemList.IndexOf(currentItem) == originItemList.IndexOf(originItem))
|
||||
{
|
||||
if (currentItem.Name.Equals(originItem.Name))
|
||||
{
|
||||
if (!currentItem.Value.Equals(originItem.Value) && !originItem.HasElements)
|
||||
{
|
||||
originItem.SetValue(currentItem.Value);
|
||||
isChanged = true;
|
||||
}
|
||||
|
||||
foreach (var currentAtt in currentItem.Attributes())
|
||||
{
|
||||
foreach (var originAtt in originItem.Attributes())
|
||||
{
|
||||
if (!currentAtt.Value.Equals(originAtt.Value))
|
||||
{
|
||||
originAtt.SetValue(currentAtt.Value);
|
||||
isChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isDataError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(OnComplete != null)
|
||||
{
|
||||
OnComplete(isChanged, isDataError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// XML 데이터를 문자열로 직렬화
|
||||
/// </summary>
|
||||
/// <param name="toSerialize">직렬화할 객체</param>
|
||||
/// <returns>XML 문자열</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var xmlString = XmlDataUtil.ToXmlString(new MyData { Name = "Test", Age = 25 });
|
||||
/// Debug.Log(xmlString);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static string ToXmlString(object toSerialize)
|
||||
{
|
||||
XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());
|
||||
|
||||
using (StringWriter textWriter = new StringWriter())
|
||||
{
|
||||
xmlSerializer.Serialize(textWriter, toSerialize);
|
||||
return textWriter.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/util/XmlDataUtil.cs.meta
Normal file
2
Assets/Scripts/UVC/util/XmlDataUtil.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6852d598b0f731548ac4b408f055c8aa
|
||||
247
Assets/Scripts/UVC/util/Zipper.cs
Normal file
247
Assets/Scripts/UVC/util/Zipper.cs
Normal file
@@ -0,0 +1,247 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using System;
|
||||
|
||||
namespace UVC.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// Zipper 클래스는 압축 파일(Zip 및 7z)의 압축 해제 기능을 제공합니다.
|
||||
/// 7zip 라이브러리를 사용하며, 압축 해제 진행률을 실시간으로 추적할 수 있습니다.
|
||||
/// 7zip 사용. https://assetstore.unity.com/packages/tools/input-management/7zip-lzma-lz4-fastlz-zip-gzip-brotli-multiplatform-plugins-12674
|
||||
/// </summary>
|
||||
public class Zipper
|
||||
{
|
||||
|
||||
// zip 보관 파일을 압축하지 않은 현재 파일 수로 변경하는 단일 항목 정수 배열입니다.
|
||||
// decompress_File 기능을 실행할 때 getTotalFiles 기능으로 반환된 총 파일 수와 이 int를 비교하여 zip에 여러 개의 파일이 포함된 경우 추출의 진행 상황을 파악한다.
|
||||
// 여러 개의 스레드를 사용하는 경우 다른 스레드에 다른 진행 정수를 사용해야 합니다.그렇지 않으면 공유 위반이 발생합니다.
|
||||
private int[] progress;
|
||||
|
||||
/// <summary>
|
||||
/// 개별 파일의 압축 해제 진행률(바이트 단위)을 나타내는 배열입니다.
|
||||
/// </summary>
|
||||
private ulong[] progress2;
|
||||
/// <summary>
|
||||
/// 압축 파일의 경로를 저장합니다.
|
||||
/// </summary>
|
||||
private string zipFilePath;
|
||||
/// <summary>
|
||||
/// 압축 해제가 완료되었는지 여부를 나타냅니다.
|
||||
/// </summary>
|
||||
private bool isComplete = false;
|
||||
/// <summary>
|
||||
/// 압축 해제 진행률(0.0 ~ 1.0)을 나타냅니다.
|
||||
/// </summary>
|
||||
private float percent = 0;
|
||||
/// <summary>
|
||||
/// 압축 해제 진행 상황을 콜백으로 전달하는 액션입니다.
|
||||
/// </summary>
|
||||
private Action<long, long, float> OnProgress;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 압축 파일을 해제합니다.
|
||||
/// 파일 형식(Zip 또는 7z)에 따라 적절한 메서드를 호출합니다.
|
||||
/// </summary>
|
||||
/// <param name="zipFilePath">압축 파일 경로</param>
|
||||
/// <param name="decompressFolderPath">압축 해제 대상 폴더 경로</param>
|
||||
/// <param name="OnProgress">압축 해제 진행 상황을 전달하는 콜백</param>
|
||||
/// <returns>압축 해제 결과 메시지</returns>
|
||||
/// <example>
|
||||
/// Zipper zipper = new Zipper();
|
||||
/// string result = await zipper.Decompress("path/to/file.zip", "path/to/output", (current, total, percent) =>
|
||||
/// {
|
||||
/// Debug.Log($"Progress: {percent * 100}%");
|
||||
/// });
|
||||
/// if (result == null)
|
||||
/// {
|
||||
/// Debug.Log("Decompression successful!");
|
||||
/// }
|
||||
/// else
|
||||
/// {
|
||||
/// Debug.LogError($"Decompression failed: {result}");
|
||||
/// }
|
||||
/// </example>
|
||||
public async UniTask<string> Decompress(string zipFilePath, string decompressFolderPath, Action<long, long, float> OnProgress)
|
||||
{
|
||||
if (zipFilePath.ToLower().Contains(".zip"))
|
||||
{
|
||||
return await DecompressZip(zipFilePath, decompressFolderPath, OnProgress);
|
||||
}
|
||||
else if (zipFilePath.ToLower().Contains(".7z"))
|
||||
{
|
||||
return await Decompress7Zip(zipFilePath, decompressFolderPath, OnProgress);
|
||||
}
|
||||
return "Unsupported compressed file";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Zip 파일을 압축 해제합니다.
|
||||
/// </summary>
|
||||
/// <param name="zipFilePath">압축 파일 경로</param>
|
||||
/// <param name="decompressFolderPath">압축 해제 대상 폴더 경로</param>
|
||||
/// <param name="OnProgress">압축 해제 진행 상황을 전달하는 콜백</param>
|
||||
/// <returns>압축 해제 결과 메시지</returns>
|
||||
/// <example>
|
||||
/// Zipper zipper = new Zipper();
|
||||
/// string result = await zipper.DecompressZip("path/to/file.zip", "path/to/output", (current, total, percent) =>
|
||||
/// {
|
||||
/// Debug.Log($"Progress: {percent * 100}%");
|
||||
/// });
|
||||
/// if (result == null)
|
||||
/// {
|
||||
/// Debug.Log("Decompression successful!");
|
||||
/// }
|
||||
/// else
|
||||
/// {
|
||||
/// Debug.LogError($"Decompression failed: {result}");
|
||||
/// }
|
||||
/// </example>
|
||||
public async UniTask<string> DecompressZip(string zipFilePath, string decompressFolderPath, Action<long, long, float> OnProgress)
|
||||
{
|
||||
progress = new int[1];
|
||||
progress2 = new ulong[1];
|
||||
isComplete = false;
|
||||
this.zipFilePath = zipFilePath;
|
||||
this.OnProgress = OnProgress;
|
||||
|
||||
if (lzip.validateFile(zipFilePath))
|
||||
{
|
||||
//getFileInfo zip 내의 콘텐츠의 총 압축되지 않은 바이트를 반환한다.
|
||||
ulong totalBytes = lzip.getFileInfo(zipFilePath);
|
||||
CountPercentZipAsync(totalBytes).Forget();
|
||||
int result = await UniTask.RunOnThreadPool(() =>
|
||||
{
|
||||
return lzip.decompress_File(zipFilePath, decompressFolderPath, progress, null, progress2);
|
||||
});
|
||||
isComplete = true;
|
||||
if (result == 1) //success
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
//-1 = could not initialize zip archive.
|
||||
//-2 = failed extraction
|
||||
//- 6 = IntPtr buffer had no valid buffer length parameter passed.
|
||||
string errorMessage = "Unknown error";
|
||||
if (result == -1) errorMessage = "could not initialize zip archive.";
|
||||
else if (result == -2) errorMessage = "failed extraction.";
|
||||
else if (result == -6) errorMessage = "IntPtr buffer had no valid buffer length parameter passed.";
|
||||
return errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
return "invalidate File";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Zip 파일의 압축 해제 진행률을 계산합니다.
|
||||
/// </summary>
|
||||
/// <param name="totalBytes">압축 해제 대상 파일의 총 바이트 수</param>
|
||||
private async UniTaskVoid CountPercentZipAsync(ulong totalBytes)
|
||||
{
|
||||
|
||||
percent = (float)progress2[0] / (float)totalBytes;
|
||||
|
||||
//Debug.Log($"CountPercentAsync totalBytes:{totalBytes} progress:{progress[0]} progress2:{progress2[0]} {percent}");
|
||||
if (OnProgress != null) OnProgress((long)progress2[0], (long)totalBytes, percent);
|
||||
if (percent == 1) isComplete = true;
|
||||
if (!isComplete)
|
||||
{
|
||||
await UniTask.Delay(TimeSpan.FromMilliseconds(100));//0.1초에 한번씩
|
||||
CountPercentZipAsync(totalBytes).Forget();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 7z 파일을 압축 해제합니다.
|
||||
/// </summary>
|
||||
/// <param name="zipFilePath">압축 파일 경로</param>
|
||||
/// <param name="decompressFolderPath">압축 해제 대상 폴더 경로</param>
|
||||
/// <param name="OnProgress">압축 해제 진행 상황을 전달하는 콜백</param>
|
||||
/// <returns>압축 해제 결과 메시지</returns>
|
||||
/// <example>
|
||||
/// Zipper zipper = new Zipper();
|
||||
/// string result = await zipper.Decompress7Zip("path/to/file.7z", "path/to/output", (current, total, percent) =>
|
||||
/// {
|
||||
/// Debug.Log($"Progress: {percent * 100}%");
|
||||
/// });
|
||||
/// if (result == null)
|
||||
/// {
|
||||
/// Debug.Log("Decompression successful!");
|
||||
/// }
|
||||
/// else
|
||||
/// {
|
||||
/// Debug.LogError($"Decompression failed: {result}");
|
||||
/// }
|
||||
/// </example>
|
||||
public async UniTask<string> Decompress7Zip(string zipFilePath, string decompressFolderPath, Action<long, long, float> OnProgress)
|
||||
{
|
||||
progress = new int[1] { 0 };
|
||||
isComplete = false;
|
||||
this.zipFilePath = zipFilePath;
|
||||
this.OnProgress = OnProgress;
|
||||
|
||||
// 참조된 progress int는 압축 해제 중인 파일의 현재 인덱스를 나타냅니다. 별도의 스레드에서 사용하여 실시간으로 표시합니다.
|
||||
// (thread에서) 실시간 바이트 레벨 압축해제 진행을 얻기 위해서는 두 가지 방법이 있습니다.
|
||||
// 1. lzma.get7zSize 함수를 사용하여 파일의 총 압축되지 않은 크기를 구하고 lzma.getBytesWritten 함수를 호출하여 실시간으로 작성된 바이트들과 비교한다.
|
||||
// 2. lzma.getFileSize(또는 FileBuffers의 버퍼 길이)를 사용하여 파일 크기를 구하고 lzma.getBytesRead 기능을 호출하여 실시간으로 읽는 바이트들과 비교한다.
|
||||
//FileInfo fileInfo = new(zipFilePath);
|
||||
//FileSecurity fsecurity = fileInfo.GetAccessControl();
|
||||
//var id = System.Security.Principal.WindowsIdentity.GetCurrent().Name.Trim();
|
||||
//var name = id.Split("\\");
|
||||
//fsecurity.AddAccessRule(new FileSystemAccessRule(new NTAccount(name[1]), FileSystemRights.FullControl, InheritanceFlags.ObjectInherit | InheritanceFlags.ContainerInherit, PropagationFlags.None, AccessControlType.Allow));
|
||||
//fileInfo.SetAccessControl(fsecurity);
|
||||
long totalBytes = lzma.getFileSize(zipFilePath);
|
||||
CountPercent7ZipAsync(totalBytes, true).Forget();
|
||||
int result = await UniTask.RunOnThreadPool(() =>
|
||||
{
|
||||
return lzma.doDecompress7zip(zipFilePath, decompressFolderPath, progress, true, true);
|
||||
});
|
||||
isComplete = true;
|
||||
if (result == 1) //success
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
//1 : OK
|
||||
//2 : Could not find requested file in archive
|
||||
//- 1 : Could not open input(7z) file
|
||||
//- 2 : Decoder doesn't support this archive
|
||||
//- 3 : Can not allocate memory
|
||||
//-5 : Unknown error
|
||||
//-6 : File IO error
|
||||
string errorMessage = "Unknown error";
|
||||
if (result == 2) errorMessage = "Could not find requested file in archive";
|
||||
else if (result == -1) errorMessage = "Could not open input(7z) file";
|
||||
else if (result == -2) errorMessage = "Decoder doesn't support this archive";
|
||||
else if (result == -3) errorMessage = "Can not allocate memory";
|
||||
else if (result == -6) errorMessage = "File IO error";
|
||||
return errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 7z 파일의 압축 해제 진행률을 계산합니다.
|
||||
/// </summary>
|
||||
/// <param name="totalBytes">압축 해제 대상 파일의 총 바이트 수</param>
|
||||
/// <param name="isFirst">첫 번째 호출 여부</param>
|
||||
private async UniTaskVoid CountPercent7ZipAsync(long totalBytes, bool isFirst = false)
|
||||
{
|
||||
if (!isFirst)
|
||||
{
|
||||
long readByted = (long)lzma.getBytesRead();
|
||||
percent = (float)readByted / (float)totalBytes;
|
||||
//Debug.Log($"CountPercent7ZipAsync totalBytes:{totalBytes} progress:{progress}");
|
||||
if (OnProgress != null) OnProgress(readByted, totalBytes, percent);
|
||||
if (percent == 1) isComplete = true;
|
||||
}
|
||||
if (!isComplete)
|
||||
{
|
||||
await UniTask.Delay(TimeSpan.FromMilliseconds(100));//0.1초에 한번씩
|
||||
CountPercent7ZipAsync(totalBytes).Forget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/util/Zipper.cs.meta
Normal file
2
Assets/Scripts/UVC/util/Zipper.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2814ffe18bfd10a48b2fc4f027b6046b
|
||||
Reference in New Issue
Block a user