Compare commits

...

15 Commits

Author SHA1 Message Date
이정수 8669da076c [Fix] 사용 용도에 맞춰서 디렉토리 배치 수정 2025-06-21 11:23:45 +09:00
이정수 ce58621316 [feat] 비동기 상태 머신 및 싱글톤 추가 2025-06-21 11:07:16 +09:00
이정수 abc2cfb3ca [add] unitask 추가 2025-06-21 10:57:48 +09:00
김판돌 a2796087f1 Excel 코드 수정 2025-06-06 23:19:52 +09:00
김판돌 e04fc50fb6 임시 ui 배치완료 2025-05-28 21:19:17 +09:00
김판돌 c1a7485647 Merge branch 'master' of https://gitea.pandoli365.com/Team.wak/Wizard_Of_Wak 2025-05-27 21:32:16 +09:00
김판돌 01106842b0 Main UI 임의배치 2025-05-27 21:32:11 +09:00
이정수 c1cbd2c3ff [Add] 이벤트 시스템 추가 2025-05-17 16:16:53 +09:00
이정수 4a1c2c344e [add] Logger 추가
Debug.Log 최적화 및 추적용 래퍼 클래스 추가
2025-05-17 15:58:50 +09:00
김판돌 6f0ecab261 Excel 코드 추가 2025-05-15 20:50:51 +09:00
김판돌 f3506c37b5 Merge remote-tracking branch 'origin/master' 2025-05-15 20:50:06 +09:00
이정수 97b6749fa6 [chore] 명명규칙 추가 2025-05-13 00:41:55 +09:00
이정수 94e8fcd6ef [chore] readme에 컨벤션 규칙 추가 2025-05-13 00:39:47 +09:00
김판돌 34867dc03f Merge branch 'feat/map-generate' 2025-05-09 20:48:24 +09:00
hy c2843200a7
feat: MapGenerator
### 알고리즘 작동 방식
1. 원형내 랜덤으로 방 배치
2. 서로 겹치는 방은 떨어트리기
3. 방 고르기
4. 복도 이어주기
2025-05-09 01:24:00 +09:00
547 changed files with 368768 additions and 44 deletions
.gitignore
Assets
07_Excel.meta
07_Excel
0_Scenes
1_Script
3_Prefab
NuGet.configNuGet.config.metaPackages.meta
Packages

1
.gitignore vendored
View File

@ -72,3 +72,4 @@ crashlytics-build.properties
/[Aa]ssets/[Ss]treamingAssets/aa.meta
/[Aa]ssets/[Ss]treamingAssets/aa/*
/.idea

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 36e847eb252a40b47a7a08ce6018325e
guid: e042717589682a54f9ce41ea0e4f0259
folderAsset: yes
DefaultImporter:
externalObjects: {}

BIN
Assets/07_Excel/Datas.xlsx Normal file

Binary file not shown.

View File

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

View File

@ -0,0 +1,275 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_OcclusionBakeSettings:
smallestOccluder: 5
smallestHole: 0.25
backfaceThreshold: 100
m_SceneGUID: 00000000000000000000000000000000
m_OcclusionCullingData: {fileID: 0}
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 9
m_Fog: 0
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
m_FogMode: 3
m_FogDensity: 0.01
m_LinearFogStart: 0
m_LinearFogEnd: 300
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
m_AmbientIntensity: 1
m_AmbientMode: 3
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
m_SkyboxMaterial: {fileID: 0}
m_HaloStrength: 0.5
m_FlareStrength: 1
m_FlareFadeSpeed: 3
m_HaloTexture: {fileID: 0}
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
m_DefaultReflectionMode: 0
m_DefaultReflectionResolution: 128
m_ReflectionBounces: 1
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 12
m_GIWorkflowMode: 1
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
m_IndirectOutputScale: 1
m_AlbedoBoost: 1
m_EnvironmentLightingMode: 0
m_EnableBakedLightmaps: 0
m_EnableRealtimeLightmaps: 0
m_LightmapEditorSettings:
serializedVersion: 12
m_Resolution: 2
m_BakeResolution: 40
m_AtlasSize: 1024
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
m_ExtractAmbientOcclusion: 0
m_Padding: 2
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 1
m_PVRSampling: 1
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 512
m_PVRBounces: 2
m_PVREnvironmentSampleCount: 256
m_PVREnvironmentReferencePointCount: 2048
m_PVRFilteringMode: 1
m_PVRDenoiserTypeDirect: 1
m_PVRDenoiserTypeIndirect: 1
m_PVRDenoiserTypeAO: 1
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
m_PVREnvironmentMIS: 1
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 5
m_PVRFilteringGaussRadiusAO: 2
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
m_ExportTrainingData: 0
m_TrainingDataDestination: TrainingData
m_LightProbeSampleCountMultiplier: 4
m_LightingDataAsset: {fileID: 0}
m_LightingSettings: {fileID: 0}
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
serializedVersion: 3
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
agentSlope: 45
agentClimb: 0.4
ledgeDropHeight: 0
maxJumpAcrossDistance: 0
minRegionArea: 2
manualCellSize: 0
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
buildHeightMesh: 0
maxJobWorkers: 0
preserveTilesOutsideBounds: 0
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
--- !u!1 &1802181444
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1802181447}
- component: {fileID: 1802181446}
- component: {fileID: 1802181445}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!81 &1802181445
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1802181444}
m_Enabled: 1
--- !u!20 &1802181446
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1802181444}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
m_projectionMatrixMode: 1
m_GateFitMode: 2
m_FOVAxisMode: 0
m_Iso: 200
m_ShutterSpeed: 0.005
m_Aperture: 16
m_FocusDistance: 10
m_FocalLength: 50
m_BladeCount: 5
m_Curvature: {x: 2, y: 11}
m_BarrelClipping: 0.25
m_Anamorphism: 0
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
near clip plane: 0.3
far clip plane: 1000
field of view: 60
orthographic: 1
orthographic size: 5
m_Depth: -1
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
m_TargetEye: 3
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 0
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
--- !u!4 &1802181447
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1802181444}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1830898465
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1830898467}
- component: {fileID: 1830898466}
m_Layer: 0
m_Name: MapGenerator
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1830898466
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1830898465}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8fdb3db0de214c69a1c56245514dcc63, type: 3}
m_Name:
m_EditorClassIdentifier:
totalRooms: 20
mainRoomsCount: 10
mapRadius: 60
minRoomWidth: 8
maxRoomWidth: 16
minRoomHeight: 8
maxRoomHeight: 16
separationIterations: 50
roomColor: {r: 0, g: 0, b: 1, a: 1}
mainRoomColor: {r: 1, g: 0, b: 0, a: 1}
corridorColor: {r: 0.06642128, g: 1, b: 0.043137252, a: 1}
--- !u!4 &1830898467
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1830898465}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
m_Roots:
- {fileID: 1802181447}
- {fileID: 1830898467}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4dc33b9f5eb3ae34f927ff4f2aeab6cc
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 5d8307864ef5554429dd27fdad1d1f7b
guid: 569493318424c974cb7bad6f56e8939d
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6dec96d472af453888b1a805041198c5
timeCreated: 1746711648

View File

@ -0,0 +1,299 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class MapGenerator : MonoBehaviour
{
public int totalRooms = 20;
public int mainRoomsCount = 10;
public float mapRadius = 50f;
public float minRoomWidth = 4f;
public float maxRoomWidth = 10f;
public float minRoomHeight = 4f;
public float maxRoomHeight = 10f;
public int separationIterations = 10;
private readonly List<Room> _rooms = new();
private List<Room> _mainRooms = new();
private readonly List<Connection> _connections = new();
private readonly HashSet<Vector2Int> _corridorTiles = new();
private void Start()
{
GenerateMap();
}
public void GenerateMap()
{
ClearAll();
GenerateRooms();
SeparateRooms();
SelectMainRooms();
ConnectRoomsWithMST();
CreateCorridors();
Debug.Log($"생성된 전체 방 수: {_rooms.Count}");
Debug.Log($"메인 방 수: {_mainRooms.Count}");
Debug.Log($"연결 수: {_connections.Count}");
}
private void ClearAll()
{
_rooms.Clear();
_mainRooms.Clear();
_connections.Clear();
}
private void GenerateRooms()
{
for (var i = 0; i < totalRooms; i++)
{
// 랜덤 위치 선택
var angle = Random.Range(0f, Mathf.PI * 2);
var distance = Random.Range(0f, mapRadius);
var position = new Vector2(
Mathf.Cos(angle) * distance,
Mathf.Sin(angle) * distance
);
// 랜덤 크기 방 생성
var width = Random.Range(minRoomWidth, maxRoomWidth);
var height = Random.Range(minRoomHeight, maxRoomHeight);
_rooms.Add(new Room(position, new Vector2(width, height)));
}
}
private void SeparateRooms()
{
for (var iter = 0; iter < separationIterations; iter++)
{
var stillOverlapping = false;
for (var i = 0; i < _rooms.Count; i++)
for (var j = i + 1; j < _rooms.Count; j++)
if (_rooms[i].Overlaps(_rooms[j]))
{
stillOverlapping = true;
// 방들이 겹치면 서로 밀어냄
var direction = (_rooms[i].Position - _rooms[j].Position).normalized;
var overlap = _rooms[i].GetOverlapDistance(_rooms[j]);
_rooms[i].Position += direction * overlap * 0.5f;
_rooms[j].Position -= direction * overlap * 0.5f;
}
// 더 이상 겹치는 방이 없으면 종료
if (!stillOverlapping) break;
}
}
private void SelectMainRooms()
{
// 방 크기에 따라 정렬 (큰 것부터)
_rooms.Sort((a, b) => (b.Size.x * b.Size.y).CompareTo(a.Size.x * a.Size.y));
// 가장 큰 방 mainRoomsCount개만 선택
_mainRooms = _rooms.Take(Mathf.Min(mainRoomsCount, _rooms.Count)).ToList();
}
private void ConnectRoomsWithMST()
{
var edges = new List<Edge>();
// 모든 방 쌍에 대해 거리 계산
for (var i = 0; i < _mainRooms.Count; i++)
for (var j = i + 1; j < _mainRooms.Count; j++)
{
var dist = Vector2.Distance(_mainRooms[i].Position, _mainRooms[j].Position);
edges.Add(new Edge(i, j, dist));
}
// 크루스칼 MST 알고리즘
edges.Sort((a, b) => a.Weight.CompareTo(b.Weight));
var ds = new DisjointSet(_mainRooms.Count);
foreach (var edge in edges)
if (ds.Find(edge.U) != ds.Find(edge.V))
{
ds.Union(edge.U, edge.V);
_connections.Add(new Connection(_mainRooms[edge.U], _mainRooms[edge.V]));
}
}
private void CreateCorridors()
{
foreach (var conn in _connections)
{
var roomA = conn.RoomA;
var roomB = conn.RoomB;
// 두 방의 중심점
var startPos = Vector2Int.RoundToInt(roomA.Position);
var endPos = Vector2Int.RoundToInt(roomB.Position);
// 두 가지 L자 패턴
if (Random.Range(0, 2) == 0)
{
// 수평 먼저, 수직 나중 ㄱ
CreateCorridor(_corridorTiles, startPos, new Vector2Int(endPos.x, startPos.y));
CreateCorridor(_corridorTiles, new Vector2Int(endPos.x, startPos.y), endPos);
}
else
{
// 수직 먼저, 수평 나중 ┌
CreateCorridor(_corridorTiles, startPos, new Vector2Int(startPos.x, endPos.y));
CreateCorridor(_corridorTiles, new Vector2Int(startPos.x, endPos.y), endPos);
}
}
}
/// <summary>
/// 중복 없는 복도 생성
/// </summary>
/// <param name="corridorTiles"></param>
/// <param name="start"></param>
/// <param name="end"></param>
private void CreateCorridor(HashSet<Vector2Int> corridorTiles, Vector2Int start, Vector2Int end)
{
var position = start;
var direction = new Vector2Int(
Mathf.Clamp(end.x - start.x, -1, 1),
Mathf.Clamp(end.y - start.y, -1, 1)
);
while (position != end)
{
corridorTiles.Add(position);
// 다음 위치로 이동
if (position.x != end.x)
position.x += direction.x;
else if (position.y != end.y)
position.y += direction.y;
}
}
#region Debug
public Color roomColor = Color.blue;
public Color mainRoomColor = Color.red;
public Color corridorColor = Color.green;
private void OnDrawGizmos()
{
// 방
Gizmos.color = roomColor;
foreach (var room in _rooms)
{
if (_mainRooms.Contains(room))
Gizmos.color = mainRoomColor;
else
Gizmos.color = roomColor;
Gizmos.DrawWireCube(room.Position, room.Size);
}
// 복도
Gizmos.color = corridorColor;
var tileSize = 1f; // 타일 크기
if (_corridorTiles != null)
foreach (var pos in _corridorTiles)
Gizmos.DrawWireCube(new Vector3(pos.x, pos.y, 0),
new Vector3(tileSize, tileSize, 0));
}
#endregion
#region Class
// 방 클래스
public class Room
{
public Vector2 Position;
public Vector2 Size;
public Room(Vector2 pos, Vector2 size)
{
Position = pos;
Size = size;
}
public bool Overlaps(Room other)
{
return !(Position.x + Size.x / 2 < other.Position.x - other.Size.x / 2 ||
Position.x - Size.x / 2 > other.Position.x + other.Size.x / 2 ||
Position.y + Size.y / 2 < other.Position.y - other.Size.y / 2 ||
Position.y - Size.y / 2 > other.Position.y + other.Size.y / 2);
}
public float GetOverlapDistance(Room other)
{
var dx = Mathf.Min(Position.x + Size.x / 2, other.Position.x + other.Size.x / 2) -
Mathf.Max(Position.x - Size.x / 2, other.Position.x - other.Size.x / 2);
var dy = Mathf.Min(Position.y + Size.y / 2, other.Position.y + other.Size.y / 2) -
Mathf.Max(Position.y - Size.y / 2, other.Position.y - other.Size.y / 2);
// 실제 겹치는 거리 계산
if (dx > 0 && dy > 0)
return Mathf.Min(dx, dy) + 0.1f; // 약간의 여유 공간 추가
return 0;
}
}
// 연결 클래스
private class Connection
{
public readonly Room RoomA, RoomB;
public Connection(Room a, Room b)
{
RoomA = a;
RoomB = b;
}
}
// MST용 간선 클래스
private class Edge
{
public readonly int U, V;
public readonly float Weight;
public Edge(int u, int v, float w)
{
U = u;
V = v;
Weight = w;
}
}
#endregion
// Disjoint Set 자료구조
private class DisjointSet
{
private readonly int[] _parent;
public DisjointSet(int n)
{
_parent = new int[n];
for (var i = 0; i < n; i++) _parent[i] = i;
}
public int Find(int x)
{
if (_parent[x] == x) return x;
return _parent[x] = Find(_parent[x]);
}
public void Union(int a, int b)
{
var rootA = Find(a);
var rootB = Find(b);
if (rootA != rootB) _parent[rootB] = rootA;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8fdb3db0de214c69a1c56245514dcc63
timeCreated: 1746712260

View File

@ -21,7 +21,6 @@ public class PlayerAttack : MonoBehaviour
controller = GetComponent<PlayerController>();
animator = GetComponentInChildren<Animator>();
PlayManager.Instance.leftMouve += Attack;
Debug.Log(123);
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 2ae509d2e75631f4b9fcbd8dca5522ca
guid: f5ce1188e2abd3c45b15851146565423
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -0,0 +1,94 @@
using Cysharp.Threading.Tasks;
using System;
using System.Collections.Generic;
using UnityEngine;
public interface IAsyncState
{
UniTask Enter();
UniTask Execute();
UniTask Exit();
}
public class AsyncStateMachine
{
private IAsyncState currentState;
private Dictionary<IAsyncState, List<Transition>> transitions = new Dictionary<IAsyncState, List<Transition>>();
private List<Transition> globalTransitions = new List<Transition>();
public async UniTask ChangeState(IAsyncState newState)
{
if (newState == currentState)
{
return;
}
if (currentState != null)
{
await currentState.Exit();
}
currentState = newState;
if (currentState != null)
{
await currentState.Enter();
}
}
public async UniTask Update()
{
foreach (var transition in globalTransitions)
{
if (transition.Condition())
{
await ChangeState(transition.TargetState);
return;
}
}
if (currentState != null && transitions.ContainsKey(currentState))
{
foreach (var transition in transitions[currentState])
{
if (transition.Condition())
{
await ChangeState(transition.TargetState);
return;
}
}
}
if (currentState != null)
{
await currentState.Execute();
}
}
public void AddTransition(IAsyncState fromState, IAsyncState toState, Func<bool> condition)
{
if (!transitions.ContainsKey(fromState))
{
transitions[fromState] = new List<Transition>();
}
transitions[fromState].Add(new Transition(toState, condition));
}
public void AddGlobalTransition(IAsyncState toState, Func<bool> condition)
{
globalTransitions.Add(new Transition(toState, condition));
}
private class Transition
{
public IAsyncState TargetState { get; }
public Func<bool> Condition { get; }
public Transition(IAsyncState targetState, Func<bool> condition)
{
TargetState = targetState;
Condition = condition;
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: bef09ddb9b84450459beff37469910fa
guid: 990671db329eb1043bab4ecac29c9221
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -0,0 +1,103 @@
using Cysharp.Threading.Tasks;
using System;
using System.Collections.Generic;
using UnityEngine;
public interface IAsyncState<T>
{
UniTask Enter(T context);
UniTask Execute(T context);
UniTask Exit(T context);
}
public class AsyncStateMachine<T>
{
private IAsyncState<T> currentState;
private Dictionary<IAsyncState<T>, List<Transition>> transitions = new Dictionary<IAsyncState<T>, List<Transition>>();
private List<Transition> globalTransitions = new List<Transition>();
public async UniTask ChangeState(IAsyncState<T> newState, T context)
{
if (newState == currentState)
{
return;
}
if (currentState != null)
{
await currentState.Exit(context);
}
//Debug.Log($"{context} -> {currentState} -> {newState}");
currentState = newState;
if (currentState != null)
{
await currentState.Enter(context);
}
}
public async UniTask Update(T context)
{
foreach (var transition in globalTransitions)
{
if (transition.Condition())
{
await ChangeState(transition.TargetState, context);
return;
}
}
if (currentState != null && transitions.ContainsKey(currentState))
{
foreach (var transition in transitions[currentState])
{
if (transition.Condition())
{
await ChangeState(transition.TargetState, context);
return;
}
}
}
if (currentState != null)
{
await currentState.Execute(context);
}
}
public void AddTransition(IAsyncState<T> fromState, IAsyncState<T> toState, Func<bool> condition)
{
if (!transitions.ContainsKey(fromState))
{
transitions[fromState] = new List<Transition>();
}
transitions[fromState].Add(new Transition(toState, condition));
}
public void AddGlobalTransition(IAsyncState<T> toState, Func<bool> condition)
{
globalTransitions.Add(new Transition(toState, condition));
}
/// <summary>
/// 상태머신 내부 데이터를 초기화합니다.
/// </summary>
public void Clear()
{
currentState = null;
transitions.Clear();
globalTransitions.Clear();
}
private class Transition
{
public IAsyncState<T> TargetState { get; }
public Func<bool> Condition { get; }
public Transition(IAsyncState<T> targetState, Func<bool> condition)
{
TargetState = targetState;
Condition = condition;
}
}
}

View File

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

View File

@ -0,0 +1,91 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Unity.Profiling;
using UnityEngine;
public static class EventAggregator
{
// 타입별 SubscriberList<T> 저장
static readonly Dictionary<Type, object> subscribers = new Dictionary<Type, object>();
// Publish 성능 측정을 위한 profiler marker
static readonly ProfilerMarker publishMarker = new ProfilerMarker("EventAggregator.Publish");
/// <summary>
/// 이벤트 타입 T를 구독합니다.
/// </summary>
public static void Subscribe<T>(Action<T> handler)
{
var key = typeof(T);
if (!subscribers.TryGetValue(key, out var listObj))
{
listObj = new SubscriberList<T>(initialCapacity: 4);
subscribers[key] = listObj;
}
((SubscriberList<T>)listObj).Add(handler);
Logger.LogDebug($"[EA] Subscribe<{key.Name}> → {handler.Target?.GetType().Name}.{handler.Method.Name}");
}
/// <summary>
/// 이벤트 타입 T의 구독을 해제합니다.
/// </summary>
public static void Unsubscribe<T>(Action<T> handler)
{
var key = typeof(T);
if (subscribers.TryGetValue(key, out var listObj))
((SubscriberList<T>)listObj).Remove(handler);
Logger.LogDebug($"[EA] Unsubscribe<{key.Name}> ← {handler.Target?.GetType().Name}.{handler.Method.Name}");
}
/// <summary>
/// 이벤트를 발행합니다.
/// </summary>
public static void Publish<T>(in T payload)
{
var key = typeof(T);
Logger.LogDebug($"[EA] Publish<{key.Name}> payload={payload}");
publishMarker.Begin();
if (subscribers.TryGetValue(key, out var listObj))
((SubscriberList<T>)listObj).Invoke(payload);
publishMarker.End();
}
class SubscriberList<T>
{
readonly List<Action<T>> handlers;
public SubscriberList(int initialCapacity)
{
handlers = new List<Action<T>>(initialCapacity);
}
public void Add(Action<T> handler) => handlers.Add(handler);
public void Remove(Action<T> handler) => handlers.Remove(handler);
public void Invoke(in T payload)
{
for (int i = 0, cnt = handlers.Count; i < cnt; i++)
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
try
{
handlers[i].Invoke(payload);
}
catch (Exception ex)
{
// 오류는 항상 남기되, 로깅 메커니즘을 통일
Logger.LogError($"[EA] Exception in <{typeof(T).Name}> handler: {ex}");
}
#else
_handlers[i].Invoke(payload);
#endif
}
}
}
}

View File

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

View File

@ -0,0 +1,53 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using UnityEngine;
/// <summary>
/// Debug.Log을 좀 더 편하게 사용하기 위한 헬퍼 클래스입니다.
/// </summary>
public static class Logger
{
public enum Level { Debug, Info, Warning, Error }
// 릴리즈 빌드에서 불필요한 스택트레이스 끄기
static Logger()
{
#if !UNITY_EDITOR && !DEVELOPMENT_BUILD
Application.SetStackTraceLogType(LogType.Log, StackTraceLogType.None);
Application.SetStackTraceLogType(LogType.Warning,StackTraceLogType.None);
Application.SetStackTraceLogType(LogType.Error, StackTraceLogType.ScriptOnly);
#endif
}
// 에디터/개발 빌드에서만 컴파일됨 → 릴리즈에선 이 호출 자체가 사라집니다
[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]
public static void LogDebug(object message, UnityEngine.Object context = null) => LogInternal(Level.Debug, message, context);
[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]
public static void LogInfo(object message, UnityEngine.Object context = null) => LogInternal(Level.Info, message, context);
[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]
public static void LogWarning(object message, UnityEngine.Object context = null) => LogInternal(Level.Warning, message, context);
// 에러는 항상 남겨둡니다
public static void LogError(object message, UnityEngine.Object context = null) => LogInternal(Level.Error, message, context);
static void LogInternal(Level level, object message, UnityEngine.Object context = null)
{
switch (level)
{
case Level.Debug:
case Level.Info:
UnityEngine.Debug.Log(message, context);
break;
case Level.Warning:
UnityEngine.Debug.LogWarning(message, context);
break;
case Level.Error:
UnityEngine.Debug.LogError(message, context);
break;
}
}
}

View File

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

View File

@ -0,0 +1,20 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class Singleton<T> where T : class, new()
{
private static readonly Lazy<T> instance = new Lazy<T>(() => new T(), true);
public static T Instance => instance.Value;
protected Singleton()
{
if (instance.IsValueCreated)
{
throw new InvalidOperationException("Singleton instance already created.");
}
}
}

View File

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

View File

@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using ClosedXML.Excel;
public class Excel
{
private List<Sheet> datas;
public List<Sheet> getDatas { get { return datas; } }
#region Dynamic
//이러한 형태로 사용할것
public Dynamic dynamic;
public class Dynamic
{
List<Data> datas;
public float selectVelue(string name)
{
return datas.Find(n => n.name.Equals(name)).velue;
}
public Dynamic(Sheet sheet)
{
datas = new List<Data>();
foreach (var item in sheet.dicViewer)
{
Data data = new Data((string)item.Value["name"], (float)item.Value["velue"]);
datas.Add(data);
}
}
public class Data
{
public string name;
public float velue;
public Data(string name, float velue)
{
this.name = name;
this.velue = velue;
}
}
}
#endregion
public Excel(List<string> fileNames)
{
ExcelManager excel = new ExcelManager();
excel.Add(fileNames);
datas = excel.Play();
dynamic = new Dynamic(datas.Find(n => n.name.Equals("Dynamic")));
}
public class Sheet
{
string _name;
public string name { get { return _name; } }
List<string> _variable;
public List<string> variable { get { return _variable; } }
List<string> _dataEnum;
public List<string> dataEnum { get { return _dataEnum; } }
List<string> _type;
public List<string> type { get { return _type; } }
Dictionary<long, Dictionary<string, object>> _dicViewer;
public Dictionary<long, Dictionary<string, object>> dicViewer { get { return _dicViewer; } }
public Sheet(string name, List<string> variable, List<string> dataEnum, List<string> type, Dictionary<long, Dictionary<string, object>> dicViewer)
{
this._name = name;
this._variable = variable;
this._dataEnum = dataEnum;
this._type = type;
this._dicViewer = dicViewer;
}
public Sheet(Sheet sheet)
{
this._name = new string(sheet.name);
this._variable = new List<string>(sheet.variable);
this._dataEnum = new List<string>(sheet.dataEnum);
this._type = new List<string>(type);
this._dicViewer = new Dictionary<long, Dictionary<string, object>>(dicViewer);
}
}
class ExcelManager
{
readonly string path = Application.dataPath + "/07_Excel/";
List<Sheet> _sheets;
List<string> readList;
public ExcelManager()
{
_sheets = new List<Sheet>();
readList = new List<string>();
}
public void Add(List<string> files)
{
foreach (string file in files)
{
readList.Add(path + file);
}
}
public List<Sheet> Play()
{
for (int n = 0; n < readList.Count; n++)
if (!ExcelLoad(readList[n]))
return null;
return _sheets;
}
public bool ExcelLoad(string pathFile)
{
_sheets = new List<Sheet>();
// 엑셀 파일을 엽니다.
using (var workbook = new XLWorkbook(pathFile))
{
// 모든 워크시트를 반복합니다.
foreach (var worksheet in workbook.Worksheets)
{
// 변수 이름, 데이터 타입, 데이터 열거형, 딕셔너리 초기화
var variable = new List<string>(); // 변수명
var dataEnum = new List<string>(); // server와 client를 나눌 기준
var type = new List<string>(); // 변수 타입
var dicViewer = new Dictionary<long, Dictionary<string, object>>();
// 행과 열의 수 얻기
var vertical = worksheet.RangeUsed().RowCount() - 1; // 데이터가 있는 행의 수
var horizontal = worksheet.RangeUsed().ColumnCount() - 1; // 데이터가 있는 열의 수
// 변수 이름과 타입을 삽입
for (int n = 0; n <= horizontal; n++)
{
variable.Add(worksheet.Cell(1, n + 1).GetValue<string>());
type.Add(worksheet.Cell(4, n + 1).GetValue<string>().ToLower());
dataEnum.Add(worksheet.Cell(3, n + 1).GetValue<string>().ToLower());
}
bool isIndex = variable[0] == "index";
for (int n = 5; n <= vertical + 1; n++)
{
var dataList = new Dictionary<string, object>();
for (int m = 0; m <= horizontal; m++)
{
object getData;
switch (type[m])
{
case "bool":
getData = (worksheet.Cell(n, m + 1).Value.ToString() == "true");
break;
case "int":
case "enum":
getData = (int)worksheet.Cell(n, m + 1).Value;
break;
case "long":
getData = (long)worksheet.Cell(n, m + 1).Value;
break;
case "float":
getData = (float)worksheet.Cell(n, m + 1).Value;
break;
case "time":
getData = (DateTime)worksheet.Cell(n, m + 1).Value;
break;
default:
getData = worksheet.Cell(n, m + 1).Value.ToString();
break;
}
dataList.Add(variable[m], getData);
}
dicViewer.Add((isIndex ? (long)dataList["index"] : n - 4), dataList);
}
var sheet = new Sheet(worksheet.Name, variable, dataEnum, type, dicViewer);
_sheets.Add(sheet);
}
}
return true;
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,33 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
/// <summary>
/// Window → EventAggregator Debugger 메뉴에서 구독 현황을 확인할 수 있는 에디터 창입니다.
/// </summary>
public class EventAggregatorDebugger : EditorWindow
{
Vector2 scroll;
[MenuItem("Window/EventAggregator Debugger")]
static void Open() => GetWindow<EventAggregatorDebugger>("EA Debugger");
void OnGUI()
{
GUILayout.Label("EventAggregator Subscriptions", EditorStyles.boldLabel);
scroll = EditorGUILayout.BeginScrollView(scroll);
foreach (var info in EventAggregatorInspector.GetSubscriptions())
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(info.EventType.Name, GUILayout.Width(150));
EditorGUILayout.LabelField($"{info.SubscriberCount} subscriber(s)");
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndScrollView();
}
}

View File

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

View File

@ -0,0 +1,50 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
/// <summary>
/// EventAggregator의 내부 subscribers 딕셔너리를 리플렉션으로 읽어 구독 정보를 제공하는 헬퍼 클래스입니다.
/// </summary>
public static
class EventAggregatorInspector
{
public struct SubscriptionInfo
{
public Type EventType;
public int SubscriberCount;
}
/// <summary>
/// 현재 등록된 모든 이벤트 타입과 구독자 수를 반환합니다.
/// </summary>
public static IEnumerable<SubscriptionInfo> GetSubscriptions()
{
var eaType = typeof(EventAggregator);
var field = eaType.GetField("subscribers", BindingFlags.Static | BindingFlags.NonPublic);
if (field == null)
yield break;
var dict = field.GetValue(null) as Dictionary<Type, object>;
if (dict == null)
yield break;
foreach (var kvp in dict)
{
var listObj = kvp.Value;
var handlersField = listObj.GetType()
.GetField("handlers", BindingFlags.Instance | BindingFlags.NonPublic);
var list = handlersField?.GetValue(listObj) as ICollection;
int count = list != null ? list.Count : 0;
yield return new SubscriptionInfo
{
EventType = kvp.Key,
SubscriberCount = count
};
}
}
}

View File

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

View File

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

View File

@ -1,13 +1,24 @@
using UnityEngine;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameManager : DontDestroy<GameManager>
{
eScene nowScene;
Excel excel;
protected override void OnAwake()
{
Application.targetFrameRate = 120;
nowScene = eScene.Title;
//Datas.xlsx
// Dynamic
// Unit
excel = new Excel(new List<string>() {"Datas.xlsx"});
//사용법
//Debug.Log(excel.dynamic.selectVelue("maxSwingCount"));
}
public enum eScene

View File

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

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 46bd8f1c673ca8e40bf245d298edd854

View File

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

View File

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

View File

@ -1,16 +0,0 @@
using UnityEngine;
public class ItemManager : MonoBehaviour
{
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}

View File

@ -1,16 +0,0 @@
using UnityEngine;
public class UnitManager : MonoBehaviour
{
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}

View File

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

View File

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

View File

@ -0,0 +1,311 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_OcclusionBakeSettings:
smallestOccluder: 5
smallestHole: 0.25
backfaceThreshold: 100
m_SceneGUID: 00000000000000000000000000000000
m_OcclusionCullingData: {fileID: 0}
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 9
m_Fog: 0
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
m_FogMode: 3
m_FogDensity: 0.01
m_LinearFogStart: 0
m_LinearFogEnd: 300
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
m_AmbientIntensity: 1
m_AmbientMode: 3
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
m_SkyboxMaterial: {fileID: 0}
m_HaloStrength: 0.5
m_FlareStrength: 1
m_FlareFadeSpeed: 3
m_HaloTexture: {fileID: 0}
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
m_DefaultReflectionMode: 0
m_DefaultReflectionResolution: 128
m_ReflectionBounces: 1
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 12
m_GIWorkflowMode: 1
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
m_IndirectOutputScale: 1
m_AlbedoBoost: 1
m_EnvironmentLightingMode: 0
m_EnableBakedLightmaps: 0
m_EnableRealtimeLightmaps: 0
m_LightmapEditorSettings:
serializedVersion: 12
m_Resolution: 2
m_BakeResolution: 40
m_AtlasSize: 1024
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
m_ExtractAmbientOcclusion: 0
m_Padding: 2
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 1
m_PVRSampling: 1
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 512
m_PVRBounces: 2
m_PVREnvironmentSampleCount: 256
m_PVREnvironmentReferencePointCount: 2048
m_PVRFilteringMode: 1
m_PVRDenoiserTypeDirect: 1
m_PVRDenoiserTypeIndirect: 1
m_PVRDenoiserTypeAO: 1
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
m_PVREnvironmentMIS: 1
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 5
m_PVRFilteringGaussRadiusAO: 2
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
m_ExportTrainingData: 0
m_TrainingDataDestination: TrainingData
m_LightProbeSampleCountMultiplier: 4
m_LightingDataAsset: {fileID: 0}
m_LightingSettings: {fileID: 0}
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
serializedVersion: 3
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
agentSlope: 45
agentClimb: 0.4
ledgeDropHeight: 0
maxJumpAcrossDistance: 0
minRegionArea: 2
manualCellSize: 0
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
buildHeightMesh: 0
maxJobWorkers: 0
preserveTilesOutsideBounds: 0
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
--- !u!1 &100138859
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 100138862}
- component: {fileID: 100138861}
- component: {fileID: 100138860}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!81 &100138860
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100138859}
m_Enabled: 1
--- !u!20 &100138861
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100138859}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
m_projectionMatrixMode: 1
m_GateFitMode: 2
m_FOVAxisMode: 0
m_Iso: 200
m_ShutterSpeed: 0.005
m_Aperture: 16
m_FocusDistance: 10
m_FocalLength: 50
m_BladeCount: 5
m_Curvature: {x: 2, y: 11}
m_BarrelClipping: 0.25
m_Anamorphism: 0
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
near clip plane: 0.3
far clip plane: 1000
field of view: 60
orthographic: 1
orthographic size: 5
m_Depth: -1
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
m_TargetEye: 3
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 0
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
--- !u!4 &100138862
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100138859}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &276881589
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 276881591}
- component: {fileID: 276881590}
m_Layer: 0
m_Name: Subscriber
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &276881590
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 276881589}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 94e38aaf07012704180737c81b6ef3aa, type: 3}
m_Name:
m_EditorClassIdentifier:
isSubscribe: 1
--- !u!4 &276881591
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 276881589}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &2127602929
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2127602931}
- component: {fileID: 2127602930}
m_Layer: 0
m_Name: Publisher
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &2127602930
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2127602929}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a5ad87a4c1dd1d740adf7162b0067215, type: 3}
m_Name:
m_EditorClassIdentifier:
count: 0
--- !u!4 &2127602931
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2127602929}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
m_Roots:
- {fileID: 100138862}
- {fileID: 2127602931}
- {fileID: 276881591}

View File

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

View File

@ -0,0 +1,19 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EventTestPub : MonoBehaviour
{
[SerializeField]
private int count = 0;
// Update is called once per frame
void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
count++;
EventAggregator.Publish(count);
}
}
}

View File

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

View File

@ -0,0 +1,40 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EventTestSub : MonoBehaviour
{
[SerializeField] private bool isSubscribe = true;
private void OnEnable()
{
isSubscribe = true;
EventAggregator.Subscribe<int>(OnEvent);
}
private void OnEvent(int i)
{
Logger.LogInfo($"EventTestSub : {i}");
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
Unsubscribe();
}
}
private void OnDisable()
{
Unsubscribe();
}
private void Unsubscribe()
{
if(!isSubscribe) return;
isSubscribe = false;
EventAggregator.Unsubscribe<int>(OnEvent);
}
}

View File

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

8
Assets/1_Script/UI.meta Normal file
View File

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

8
Assets/3_Prefab/UI.meta Normal file
View File

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

View File

@ -0,0 +1,257 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &86175666393162572
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3402507250567265708}
- component: {fileID: 4584831872008053552}
m_Layer: 5
m_Name: BuffItem
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3402507250567265708
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 86175666393162572}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 3672454375619538113}
- {fileID: 7986320631835013854}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 100}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4584831872008053552
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 86175666393162572}
m_CullTransparentMesh: 1
--- !u!1 &2569719711926230642
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3672454375619538113}
- component: {fileID: 8661299944777536019}
- component: {fileID: 1009235263837955205}
m_Layer: 5
m_Name: Image
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3672454375619538113
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2569719711926230642}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 3402507250567265708}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 100, y: 0}
m_Pivot: {x: 0, y: 0.5}
--- !u!222 &8661299944777536019
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2569719711926230642}
m_CullTransparentMesh: 1
--- !u!114 &1009235263837955205
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2569719711926230642}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 590126888b2b8f84cae3cc870021e7d4, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &8321368714702731127
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7986320631835013854}
- component: {fileID: 771157028725780757}
- component: {fileID: 2112110482271504638}
m_Layer: 5
m_Name: Text (TMP)
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &7986320631835013854
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8321368714702731127}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 3402507250567265708}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 100, y: 0}
m_SizeDelta: {x: 200, y: 0}
m_Pivot: {x: 0, y: 0.5}
--- !u!222 &771157028725780757
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8321368714702731127}
m_CullTransparentMesh: 1
--- !u!114 &2112110482271504638
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8321368714702731127}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: 100%
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 36
m_fontSizeBase: 36
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 1
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 20, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}

View File

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

18
Assets/NuGet.config Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" enableCredentialProvider="false" />
</packageSources>
<disabledPackageSources />
<activePackageSource>
<add key="All" value="(Aggregate source)" />
</activePackageSource>
<config>
<add key="packageInstallLocation" value="CustomWithinAssets" />
<add key="repositoryPath" value="./Packages" />
<add key="PackagesConfigDirectoryPath" value="." />
<add key="slimRestore" value="true" />
<add key="PreferNetStandardOverNetFramework" value="true" />
</config>
</configuration>

23
Assets/NuGet.config.meta Normal file
View File

@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 356dafd8e98638044a64b00c1dfedcfe
labels:
- NuGetForUnity
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
userData:
assetBundleName:
assetBundleVariant:

8
Assets/Packages.meta Normal file
View File

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

View File

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

Binary file not shown.

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>ClosedXML</id>
<version>0.105.0</version>
<authors>Jan Havlíček, Francois Botha, Aleksei Pankratev, Manuel de Leon, Amir Ghezelbash</authors>
<license type="expression">MIT</license>
<licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
<icon>nuget-logo.png</icon>
<projectUrl>https://github.com/ClosedXML/ClosedXML</projectUrl>
<description>See release notes https://github.com/ClosedXML/ClosedXML/releases/tag/0.105.0 ClosedXML is a .NET library for reading, manipulating and writing Excel 2007+ (.xlsx, .xlsm) files. It aims to provide an intuitive and user-friendly interface to dealing with the underlying OpenXML API.</description>
<releaseNotes>See https://github.com/ClosedXML/ClosedXML/releases/tag/0.105.0</releaseNotes>
<tags>Excel OpenXml xlsx</tags>
<repository type="git" url="https://github.com/ClosedXML/ClosedXML" commit="d15f6690886801980c0d83b7eb1d1b5d2171ad31" />
<dependencies>
<group targetFramework=".NETStandard2.0">
<dependency id="ClosedXML.Parser" version="2.0.0" exclude="Build,Analyzers" />
<dependency id="DocumentFormat.OpenXml" version="[3.1.1, 4.0.0)" exclude="Build,Analyzers" />
<dependency id="ExcelNumberFormat" version="1.1.0" exclude="Build,Analyzers" />
<dependency id="Microsoft.Bcl.HashCode" version="1.1.1" exclude="Build,Analyzers" />
<dependency id="RBush.Signed" version="4.0.0" exclude="Build,Analyzers" />
<dependency id="SixLabors.Fonts" version="1.0.0" exclude="Build,Analyzers" />
<dependency id="System.Buffers" version="4.5.1" exclude="Build,Analyzers" />
<dependency id="System.Memory" version="4.5.5" exclude="Build,Analyzers" />
</group>
<group targetFramework=".NETStandard2.1">
<dependency id="ClosedXML.Parser" version="2.0.0" exclude="Build,Analyzers" />
<dependency id="DocumentFormat.OpenXml" version="[3.1.1, 4.0.0)" exclude="Build,Analyzers" />
<dependency id="ExcelNumberFormat" version="1.1.0" exclude="Build,Analyzers" />
<dependency id="RBush.Signed" version="4.0.0" exclude="Build,Analyzers" />
<dependency id="SixLabors.Fonts" version="1.0.0" exclude="Build,Analyzers" />
</group>
</dependencies>
</metadata>
</package>

View File

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

View File

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

View File

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

Binary file not shown.

View File

@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 42d57c3664c1ca04a9c903160812bfd4
labels:
- NuGetForUnity
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

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

Binary file not shown.

After

(image error) Size: 30 KiB

View File

@ -0,0 +1,137 @@
fileFormatVersion: 2
guid: bf51a5f6ee470a8419eaf4c1f06f3809
TextureImporter:
internalIDToNameTable:
- first:
213: -2711032071728996648
second: nuget-logo_0
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 2
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: nuget-logo_0
rect:
serializedVersion: 2
x: 47
y: 48
width: 1115
height: 1114
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 8ded6e5c1ba706ad0800000000000000
internalID: -2711032071728996648
vertices: []
indices:
edges: []
weights: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable:
nuget-logo_0: -2711032071728996648
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

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

Binary file not shown.

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>ClosedXML.Parser</id>
<version>2.0.0</version>
<authors>Jan Havlíček</authors>
<license type="expression">MIT</license>
<licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
<readme>README.md</readme>
<projectUrl>https://github.com/ClosedXML/ClosedXML.Parser</projectUrl>
<description>A lexer and parser of Excel formulas with focus on performance. Its goal is to create
an abstract syntax tree from a formula text to enable formula evaluation.</description>
<tags>ClosedXML parser formula xlsx</tags>
<repository type="git" url="https://github.com/ClosedXML/ClosedXML.Parser" commit="658973aeedc2fe289e0ccba2bb9959f67692ded5" />
<dependencies>
<group targetFramework=".NETStandard2.0">
<dependency id="System.Memory" version="4.5.4" exclude="Build,Analyzers" />
</group>
<group targetFramework=".NETStandard2.1" />
</dependencies>
</metadata>
</package>

View File

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

View File

@ -0,0 +1,110 @@
# ClosedParser
ClosedParser is a project to parse OOXML grammar to create an abstract syntax tree that can be later evaluated.
Official source for the grammar is [MS-XML](https://learn.microsoft.com/en-us/openspecs/office_standards/ms-xlsx/2c5dee00-eff2-4b22-92b6-0738acd4475e), chapter 2.2.2 Formulas. The provided grammar is not usable for parser generators, it's full of ambiguities and the rules don't take into account operator precedence.
# How to use
* Implement `IAstFactory` interface.
* Call parsing methods
* `FormulaParser<TScalarValue, TNode>.CellFormulaA1("Sum(A1, 2)", astFactory)`
* `FormulaParser<TScalarValue, TNode>.CellFormulaR1C1("Sum(R1C1, 2)", astFactory)`
## Visualizer
There is a visualizer to display AST in a browser at **[https://parser.closedxml.io](https://parser.closedxml.io)**
![image](https://github.com/ClosedXML/ClosedXML.Parser/assets/7634052/4beaab23-4599-44d4-be7b-705178b69f99)
# Goals
* __Performance__ - ClosedXML needs to parse formula really fast. Limit allocation and so on.
* __Evaluation oriented__ - Parser should concentrates on creation of abstract syntax trees, not concrete syntax tree. Goal is evaluation of formulas, not transformation.
* __Multi-use__ - Formulas are mostly used in cells, but there are other places with different grammar rules (e.g. sparklines, data validation)
* __Multi notation (A1 or R1C1)__ - Parser should be able to parse both A1 and R1C1 formulas. I.e. `SUM(R5)` can mean return sum of cell `R5` in _A1_ notation, but return sum of all cells on row 5 in _R1C1_ notation.
Project uses ANTLR4 grammar file as the source of truth and a lexer. There is also ANTLR parser is not used, but is used as a basis of recursive descent parser (ANTLR takes up 8 seconds vs RDS 700ms for parsing of enron dataset).
ANTLR4 one of few maintained parser generators with C# target.
The project has a low priority, XLParser mostly works, but in the long term, replacement is likely.
## Current performance
ENRON dataset parsed using recursive descent parser and DFA lexer in Release mode:
* Total: *946320*
* Elapsed: *1838 ms*
* Per formula: *1.942 μs*
2μs per formula should be something like 6000 instructions (under unrealistic assumption 1 instruction per 1 Hz), so basically fast enough.
## Limitations
The primary goal is to parse formulas stored in file, not user supplied formulas. The formulas displayed in the GUI is not the same as formula stored in the file. Several examples:
* The IFS function is a part of future functions. In the file, it is stored as `_xlfn.IFS`, but user sees `IFS`
* In the structured references, user sees @ as an indication that structured references this row, but in reality it is a specifier `[#This Row]`
Therefore:
* External references are accepted only in form of an index to an external file (e.g. `[5]`)
* There are several formula implementations out there with slighly different grammar incompatible with OOXML formulas (`[1]!'Some name in external wb'`). They are out of scope of the project.
# Why not use XLParser
ClosedXML is currently using [XLParser](https://github.com/spreadsheetlab/XLParser) and transforming the concrete syntax tree to abstract syntax tree.
* Speed:
* Grammar extensively uses regexps extensively. Regexs are slow, especially for NET4x target, allocates extra memory. XLParser takes up _47_ seconds for Enron dataset on .NET Framework. .NET teams had made massive improvements on regexs, so it takes only _16_ seconds on NET7.
* IronParser needs to determine all possible tokens after every token, that is problematic, even with the help of `prefix` hints.
* AST: XLParser creates concentrates on creation of concrete syntax tree, but for ClosedXML, we need abstract syntax tree for evaluation. IronParser is not very friendly in that regard
* ~~XLParser uses `IronParser`, an unmaintained project~~ (IronParser recently released version 1.2).
* Doesn't have support for lambdas and R1C1 style.
ANTLR lexer takes up about 3.2 seconds for Enron dataset. With ANTLR parsing, it takes up 11 seconds. I want that 7+ seconds in performance and no allocation, so RDS that takes up 700 ms.
## Debugging
Use [vscode-antlr4](https://github.com/mike-lischke/vscode-antlr4/blob/master/doc/grammar-debugging.md) plugin for debugging the grammar.
## Testing strategy
* Each token that contains some data that are extracted for a node (e.g. `A1_REFERENCE` `C5` to `row 5`, `column 3`) has a separate test class in `Lexers` directory with a `{TokenPascalName}TokenTests.cs`
* Each parser rule has a test class in `Rules` directory. It should contain all possible combinatins of a rule and comparing it with the AST nodes.
* Data set tests are in `DataSetTests.cs`. Each test tries to parse formula and ensures that **ANTLR** can parse it RDS can and can't parse a formula when **ANTLR** can't. There is no check of the output, just that formulas can be parsed. Data are contained in a `data` directory in CSV format with a one column.
## Rolex
Rolex is a DFA based lexer released under MIT license (see [Rolex: Unicode Enabled Lexer Generator in C#
](https://www.codeproject.com/Articles/5257489/Rolex-Unicode-Enabled-Lexer-Generator-in-Csharp)). ANTLR is still the source of truth, but it is used to generate Rolex grammar and then DFA for a lexer.
It is rather complicated, but two times faster than ANTLR lexer (1.9 us vs 3.676 us per formula).
## Generate lexer
Prepare rolex grammars
* Run Antlr2Rolex over FormulaLexer.g4 with A1 version to *ClosedXML.Parser\Rolex\LexerA1.rl*
* Add `/*` at the beginning of *Local A1 References* section. It comments out A1_REFERENCE and all its fragments
* Remove `/*` at the beinning of *Local R1C1 References* section. It contains a different tokens for A1_REFERENCE and its fragments
* Run Antlr2Rolex over FormulaLexer.g4 with R1C1 version to *ClosedXML.Parser\Rolex\LexerR1C1.rl*
Fix Rolex generator
* Fix bug in Rolex generator that doesn't recognize property \u1234 (just add `pc.Advance()` to FFA.cs `_ParseEscapePart` and `_ParseRangeEscapePart`]
Generate a DFA through Rolex
* `Rolex.exe ClosedXML.Parser\Rolex\LexerA1.rl /noshared /output ClosedXML.Parser\Rolex\RolexA1Dfa.cs /namespace ClosedXML.Parser.Rolex`
* `Rolex.exe ClosedXML.Parser\Rolex\LexerR1C1.rl /noshared /output ClosedXML.Parser\Rolex\RolexR1C1Dfa.cs /namespace ClosedXML.Parser.Rolex`
# TODO
* Lexer generation during build
* Proper CI pipeline.
* Azure Function
* Web
* Fuzzer
* PR to Rolex to fix unicode bug.
# Resources
* [MS-XML](https://learn.microsoft.com/en-us/openspecs/office_standards/ms-xlsx/2c5dee00-eff2-4b22-92b6-0738acd4475e)
* [Simplified XLParser grammar](https://github.com/spreadsheetlab/XLParser/blob/master/doc/ebnf.pdf) and [tokens](https://github.com/spreadsheetlab/XLParser/blob/master/doc/tokens.pdf).
* [Getting Started With ANTLR in C#](https://tomassetti.me/getting-started-with-antlr-in-csharp/)

View File

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

View File

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

View File

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

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