diff --git a/BlueWater/Assets/02.Scritps/Ai.meta b/BlueWater/Assets/02.Scritps/Ai.meta
new file mode 100644
index 000000000..3388639e9
--- /dev/null
+++ b/BlueWater/Assets/02.Scritps/Ai.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b9d5d35f7fe78034eae26b45ec497cd8
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/BlueWater/Assets/02.Scritps/Ai/AiWeight.cs b/BlueWater/Assets/02.Scritps/Ai/AiWeight.cs
new file mode 100644
index 000000000..a04d59fae
--- /dev/null
+++ b/BlueWater/Assets/02.Scritps/Ai/AiWeight.cs
@@ -0,0 +1,324 @@
+using System;
+using UnityEngine;
+using UnityEngine.AI;
+
+// ReSharper disable once CheckNamespace
+namespace BlueWater
+{
+ public class AiWeight : MonoBehaviour
+ {
+ [Serializable]
+ private struct DirectionInfo
+ {
+ public bool hit; // 장애물 충돌 여부
+ public Vector3 direction; // 방향 노말값
+ public float interest; // 호기심 수치
+ public float danger; // 위험 수치
+
+ public DirectionInfo(bool hit, Vector3 direction, float interest, float danger)
+ {
+ this.hit = hit;
+ this.direction = direction;
+ this.interest = interest;
+ this.danger = danger;
+ }
+ }
+
+ [field: Tooltip("Scene뷰에서의 가중치 시스템 가시화 여부")]
+ [field: SerializeField] private bool drawGizmo = true;
+
+ [field: Tooltip("target과의 거리가 설정한 값보다 큰 경우 일직선으로 이동")]
+ [field: SerializeField] private float maxDistance = 10f;
+
+ [field: Tooltip("target과의 거리가 설정한 값보다 큰 경우 가중치 시스템에 의해 이동")]
+ [field: SerializeField] private float minDistance = 3f;
+
+ [field: Tooltip("target과의 거리가 설정한 값보다 큰 경우 target을 중심으로 공전")]
+ [field: SerializeField] private float orbitDistance = 2f;
+
+ [field: Tooltip("공전 허용 범위\norbitDistance < target < orbitDistance + 설정 값")]
+ [field: SerializeField] private float orbitAllowableValue = 2f;
+
+ [field: Tooltip("가중치 시스템 장애물에 해당되는 레이어 설정")]
+ [field: SerializeField] private LayerMask obstacleWeightLayer;
+
+ [field: Tooltip("장애물 최대 인지 거리")]
+ [field: SerializeField] private float obstacleDist = 5f;
+
+ [field: Tooltip("방향이 가지는 구조체의 배열")]
+ [field: SerializeField] private DirectionInfo[] directionInfo = new DirectionInfo[24];
+
+ [field: Tooltip("현재 공전 중일 때, 해당 방향의 위험 수치가 설정한 값 이상일 경우, 반대편 회전")]
+ [field: SerializeField] private float dangerValue = 0.8f;
+
+ [field: Header("Target Raycast")]
+ [field: Tooltip("목표 레이어 설정")]
+ [field: SerializeField] private LayerMask targetLayer;
+
+ [field: Tooltip("목표와의 장애물에 해당되는 레이어 설정")]
+ [field: SerializeField] private LayerMask obstacleTargetLayer;
+
+ [field: Tooltip("현재 공전 중일 때, 시계 방향으로 회전 중인지 여부")]
+ [field: SerializeField] public bool isClockwise;
+
+ [field: Tooltip("현재 공전 중인지 여부")]
+ [field: SerializeField] public bool beInOrbit;
+
+ [field: Tooltip("현재 회피 중인지 여부")]
+ [field: SerializeField] public bool isAvoiding;
+
+ private bool usedWeighted;
+
+ private void Reset()
+ {
+ drawGizmo = true;
+ maxDistance = 10f;
+ minDistance = 3f;
+ orbitDistance = 2f;
+ orbitAllowableValue = 2f;
+ obstacleDist = 5f;
+ directionInfo = new DirectionInfo[24];
+ dangerValue = 0.8f;
+ }
+
+#if UNITY_EDITOR
+ private void OnDrawGizmos()
+ {
+ if (!drawGizmo || !usedWeighted) return;
+
+ for (var i = 0; i < directionInfo.Length; i++)
+ {
+ var myPos = transform.position;
+
+ Gizmos.color = GetFinalDirectionValue().direction == directionInfo[i].direction
+ ? Color.green
+ : Color.white;
+
+ var resultWeighted = Mathf.Clamp(directionInfo[i].interest - directionInfo[i].danger, 0f, 1f);
+ Gizmos.DrawLine(myPos, myPos + directionInfo[i].direction * resultWeighted);
+ }
+ }
+#endif
+
+ private void Awake()
+ {
+ var angle = 360f / directionInfo.Length;
+ for (var i = 0; i < directionInfo.Length; i++)
+ {
+ var direction = Utils.AngleToDir(angle * i + 5);
+ directionInfo[i] = new DirectionInfo(false, direction, 0, 0);
+ }
+ }
+
+ ///
+ /// Update()에서 사용하는 Enemy의 가중치 시스템
+ ///
+ public void UpdateWeighedEnemy(NavMeshAgent agent, Transform targetTransform,
+ Transform rotationTransform, bool canLookAtTarget, float distanceToTarget, Vector3 directionToTarget)
+ {
+ var resultDirection = SetResultDirection(directionToTarget);
+ CheckObstacle();
+ resultDirection = CheckTurnAround(resultDirection, directionToTarget);
+
+ // raycast가 장애물에 막혔을 경우, agent 사용
+ if (Physics.Raycast(transform.position, directionToTarget, distanceToTarget, obstacleTargetLayer))
+ {
+ usedWeighted = false;
+ MoveTowardDirection(agent, targetTransform.position, targetTransform, rotationTransform,
+ canLookAtTarget);
+ return;
+ }
+
+ usedWeighted = true;
+ SetWeights(distanceToTarget, directionToTarget, resultDirection);
+ MoveTowardDirection(agent, transform.position + CalcDirectionInfo().direction, targetTransform,
+ rotationTransform, canLookAtTarget);
+ }
+
+ ///
+ /// 방향별로 장애물 체크 및 위험 수치 계산 함수
+ ///
+ private void CheckObstacle()
+ {
+ for (var i = 0; i < directionInfo.Length; i++)
+ {
+ if (Physics.Raycast(transform.position, directionInfo[i].direction, out var hit, obstacleDist,
+ obstacleWeightLayer))
+ {
+ var obstacleFactor = (obstacleDist - hit.distance) / obstacleDist;
+ directionInfo[i].danger = obstacleFactor;
+ directionInfo[i].hit = true;
+ }
+ else
+ {
+ directionInfo[i].danger = 0f;
+ directionInfo[i].hit = false;
+ }
+ }
+ }
+
+ ///
+ /// 방향별로 호기심 수치 계산 함수
+ ///
+ private void SetWeights(float distanceToTarget, Vector3 directionToTarget, Vector3 resultDir)
+ {
+ // target이 raycast로 닿았다면, 가중치 시스템 + 공전 + 회피 이동
+ for (var i = 0; i < directionInfo.Length; i++)
+ {
+ if (distanceToTarget > maxDistance)
+ {
+ usedWeighted = false;
+ SetBeInOrBitAndIsAvoiding(false, false);
+ if (Vector3.Dot(directionInfo[i].direction, directionToTarget) > 0.7f)
+ {
+ directionInfo[i].interest = 1f;
+ }
+ }
+ else if ((!beInOrbit && distanceToTarget > minDistance) ||
+ (beInOrbit && distanceToTarget > orbitDistance + orbitAllowableValue))
+ {
+ usedWeighted = true;
+ SetBeInOrBitAndIsAvoiding(false, false);
+ directionInfo[i].interest =
+ Mathf.Clamp(Vector3.Dot(directionInfo[i].direction, directionToTarget), 0, 1)
+ - directionInfo[i].danger;
+ }
+ else if (distanceToTarget > orbitDistance)
+ {
+ usedWeighted = true;
+ SetBeInOrBitAndIsAvoiding(true, false);
+ directionInfo[i].interest = Mathf.Clamp(Vector3.Dot(directionInfo[i].direction, resultDir), 0, 1)
+ - directionInfo[i].danger;
+ }
+ else if (distanceToTarget <= orbitDistance)
+ {
+ usedWeighted = true;
+ SetBeInOrBitAndIsAvoiding(false, true);
+ directionInfo[i].interest =
+ Mathf.Clamp(Vector3.Dot(directionInfo[i].direction, -directionToTarget), 0, 1);
+ }
+ }
+ }
+
+ ///
+ /// 공전 중이며, 공전 하는 방향의 위험 수치가 일정 수치 이상일 때, 반대 방향으로 회전하는 함수
+ ///
+ private Vector3 CheckTurnAround(Vector3 resultDir, Vector3 directionToTarget)
+ {
+ if (beInOrbit || directionInfo[GetDirectionIndex(resultDir)].danger < dangerValue) return resultDir;
+
+ isClockwise = !isClockwise;
+ return SetResultDirection(directionToTarget);
+ }
+
+ ///
+ /// target을 쳐다보는지의 여부
+ ///
+ ///
+ ///
+ ///
+ private void LookAtTarget(Transform targetTransform, Transform rotationTransform, bool canLookAtTarget)
+ {
+ var targetPosition = targetTransform.position;
+
+ targetPosition.y = transform.position.y;
+ if (canLookAtTarget)
+ {
+ rotationTransform.LookAt(targetPosition);
+ }
+ }
+
+ ///
+ /// 가중치가 가장 높은 방향으로 이동시키는 함수
+ ///
+ /// NavMeshAgent Component
+ /// 최종 이동할 위치
+ /// 목표 객체 : TargetTransform
+ /// 회전 객체 : RotationTransform
+ /// 일정 거리 안에 Target이 있다면 true를 반환
+ private void MoveTowardDirection(NavMeshAgent agent, Vector3 resultDirection, Transform targetTransform,
+ Transform rotationTransform, bool canLookAtTarget)
+ {
+ LookAtTarget(targetTransform, rotationTransform, canLookAtTarget);
+ agent.SetDestination(resultDirection);
+ }
+
+ ///
+ /// 호기심 수치가 가장 높은 방향의 정보를 받는 함수
+ ///
+ private DirectionInfo CalcDirectionInfo()
+ {
+ var bestDirectionIndex = 0;
+ var bestWeight = 0f;
+
+ for (var i = 0; i < directionInfo.Length; i++)
+ {
+ if (directionInfo[i].interest <= bestWeight) continue;
+
+ bestWeight = directionInfo[i].interest;
+ bestDirectionIndex = i;
+ }
+
+ return directionInfo[bestDirectionIndex];
+ }
+
+ ///
+ /// 호기심 수치가 가장 높은 방향의 정보를 받는 함수
+ ///
+ private DirectionInfo GetFinalDirectionValue()
+ {
+ var bestDirectionIndex = 0;
+ var bestWeight = 0f;
+
+ for (var i = 0; i < directionInfo.Length; i++)
+ {
+ var finalInterestValue = Mathf.Clamp(directionInfo[i].interest - directionInfo[i].danger, 0f, 1f);
+ if (finalInterestValue <= bestWeight) continue;
+
+ bestWeight = finalInterestValue;
+ bestDirectionIndex = i;
+ }
+
+ return directionInfo[bestDirectionIndex];
+ }
+
+ ///
+ /// 매개변수의 방향값과 가장 인접한 방향의 Index를 반환하는 함수
+ ///
+ private int GetDirectionIndex(Vector3 direction)
+ {
+ var maxDotValue = 0f;
+ var index = 0;
+
+ for (var i = 0; i < directionInfo.Length; i++)
+ {
+ var dotValue = Vector3.Dot(directionInfo[i].direction, direction);
+
+ if (maxDotValue >= dotValue) continue;
+
+ maxDotValue = dotValue;
+ index = i;
+ }
+
+ return index;
+ }
+
+ ///
+ /// 공전하는 방향을 설정하는 함수
+ /// target과 수직인 시계방향 : new Vector3(-directionToTarget.z, 0f, directionToTarget.x)
+ /// target과 수직인 반시계방향 : new Vector3(directionToTarget.z, 0f, -directionToTarget.x)
+ ///
+ private Vector3 SetResultDirection(Vector3 directionToTarget)
+ {
+ return isClockwise
+ ? new Vector3(-directionToTarget.z, 0f, directionToTarget.x)
+ : new Vector3(directionToTarget.z, 0f, -directionToTarget.x);
+ }
+
+ private void SetBeInOrBitAndIsAvoiding(bool beInOrbitValue, bool isAvoidingValue)
+ {
+ beInOrbit = beInOrbitValue;
+ isAvoiding = isAvoidingValue;
+ }
+ }
+}
\ No newline at end of file
diff --git a/BlueWater/Assets/02.Scritps/Ai/AiWeight.cs.meta b/BlueWater/Assets/02.Scritps/Ai/AiWeight.cs.meta
new file mode 100644
index 000000000..7c6ebb908
--- /dev/null
+++ b/BlueWater/Assets/02.Scritps/Ai/AiWeight.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2dc50c9a12874b64abe96c8fd8c34f85
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/BlueWater/Assets/02.Scritps/Utility.meta b/BlueWater/Assets/02.Scritps/Utility.meta
new file mode 100644
index 000000000..3cb751c0b
--- /dev/null
+++ b/BlueWater/Assets/02.Scritps/Utility.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 76cdfb05d24c5ae4fb75fa114838a35b
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/BlueWater/Assets/02.Scritps/Utility/Utils.cs b/BlueWater/Assets/02.Scritps/Utility/Utils.cs
new file mode 100644
index 000000000..81bf26a36
--- /dev/null
+++ b/BlueWater/Assets/02.Scritps/Utility/Utils.cs
@@ -0,0 +1,53 @@
+using System.Diagnostics;
+using UnityEngine;
+using UnityEngine.AI;
+using UnityEngine.Assertions;
+
+// ReSharper disable once CheckNamespace
+namespace BlueWater
+{
+ public class Utils
+ {
+ ///
+ /// 각도를 통해 방향값을 반환하는 함수
+ ///
+ public static Vector3 AngleToDir(float angle)
+ {
+ var radian = angle * Mathf.Deg2Rad;
+ return new Vector3(Mathf.Sin(radian), 0f, Mathf.Cos(radian)).normalized;
+ }
+
+ // public static T FindTaskAndAssert(BehaviorTree behaviorTree) where T : Task
+ // {
+ // var newBehaviorTask = behaviorTree.FindTask();
+ // Assert.IsNotNull(newBehaviorTask, typeof(T) + "타입의 Task가 존재하지 않습니다.");
+ //
+ // return newBehaviorTask;
+ // }
+
+ public static T GetComponentAndAssert(Transform componentLocation) where T : class
+ {
+ var newComponent = componentLocation.GetComponent();
+ Assert.IsNotNull(newComponent, typeof(T) + "타입의 Component가 존재하지 않습니다.");
+
+ return newComponent;
+ }
+
+ public static bool ArrivedAgentDestination(NavMeshAgent agent) =>
+ !agent.pathPending && agent.remainingDistance <= agent.stoppingDistance;
+
+ ///
+ /// 메서드를 누가 호출하는지 알려주는 메서드입니다
+ /// 메서드 마지막에 이 함수를 호출하면 됩니다
+ ///
+ public static void WhereCallThisFunction()
+ {
+ var stackTrace = new StackTrace();
+ var stackFrame = stackTrace.GetFrame(1);
+ var method = stackFrame.GetMethod();
+ var className = method.DeclaringType?.Name;
+ var methodName = method.Name;
+ UnityEngine.Debug.Log($"Call {className}.{methodName}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/BlueWater/Assets/02.Scritps/Utility/Utils.cs.meta b/BlueWater/Assets/02.Scritps/Utility/Utils.cs.meta
new file mode 100644
index 000000000..9e0d909eb
--- /dev/null
+++ b/BlueWater/Assets/02.Scritps/Utility/Utils.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e33166dd6fcd1904ba01f20e5e733bde
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/BlueWater/Assets/ToonyTinyPeople/TT_RTS/TT_RTS_Standard/animation/animation_cavalry/cavalry_archer/cav_archer_01_idle.FBX.meta b/BlueWater/Assets/ToonyTinyPeople/TT_RTS/TT_RTS_Standard/animation/animation_cavalry/cavalry_archer/cav_archer_01_idle.FBX.meta
index 2d9943df7..2442b702a 100644
--- a/BlueWater/Assets/ToonyTinyPeople/TT_RTS/TT_RTS_Standard/animation/animation_cavalry/cavalry_archer/cav_archer_01_idle.FBX.meta
+++ b/BlueWater/Assets/ToonyTinyPeople/TT_RTS/TT_RTS_Standard/animation/animation_cavalry/cavalry_archer/cav_archer_01_idle.FBX.meta
@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: 91c0b7cee7717f44e8592a8ae0b83446
ModelImporter:
- serializedVersion: 26
+ serializedVersion: 22200
internalIDToNameTable:
- first:
1: 100000
@@ -329,7 +329,7 @@ ModelImporter:
second: //RootNode
externalObjects: {}
materials:
- importMaterials: 0
+ materialImportMode: 0
materialName: 0
materialSearch: 1
materialLocation: 0
@@ -338,6 +338,7 @@ ModelImporter:
bakeSimulation: 0
resampleCurves: 1
optimizeGameObjects: 0
+ removeConstantScaleCurves: 0
motionNodeName:
rigImportErrors:
rigImportWarnings:
@@ -524,26 +525,35 @@ ModelImporter:
addColliders: 0
useSRGBMaterialColor: 1
sortHierarchyByName: 1
+ importPhysicalCameras: 1
importVisibility: 0
importBlendShapes: 0
importCameras: 0
importLights: 0
+ nodeNameCollisionStrategy: 0
+ fileIdsGeneration: 1
swapUVChannels: 0
generateSecondaryUV: 0
useFileUnits: 1
keepQuads: 0
weldVertices: 1
+ bakeAxisConversion: 0
preserveHierarchy: 0
skinWeightsMode: 0
maxBonesPerVertex: 4
minBoneWeight: 0.001
+ optimizeBones: 1
meshOptimizationFlags: -1
indexFormat: 1
secondaryUVAngleDistortion: 8
secondaryUVAreaDistortion: 15.000001
secondaryUVHardAngle: 88
+ secondaryUVMarginMethod: 0
+ secondaryUVMinLightmapResolution: 40
+ secondaryUVMinObjectScale: 1
secondaryUVPackMargin: 4
useFileScale: 1
+ strictVertexDataChecks: 0
tangentSpace:
normalSmoothAngle: 60
normalImportMode: 0
@@ -554,7 +564,6 @@ ModelImporter:
normalSmoothingSource: 0
referencedClips: []
importAnimation: 1
- copyAvatar: 1
humanDescription:
serializedVersion: 3
human: []
@@ -573,8 +582,13 @@ ModelImporter:
skeletonHasParents: 0
lastHumanDescriptionAvatarSource: {fileID: 9000000, guid: 7d2ee18e23ef99c4dbe6c40764865159,
type: 3}
+ autoGenerateAvatarMappingIfUnspecified: 1
animationType: 2
humanoidOversampling: 1
+ avatarSetup: 2
+ addHumanoidExtraRootOnlyWhenUsingAvatar: 0
+ importBlendShapeDeformPercent: 0
+ remapMaterialsIfMaterialImportModeIsNone: 1
additionalBone: 0
userData:
assetBundleName:
diff --git a/BlueWater/Packages/packages-lock.json b/BlueWater/Packages/packages-lock.json
index e9f36e96e..f2ce94270 100644
--- a/BlueWater/Packages/packages-lock.json
+++ b/BlueWater/Packages/packages-lock.json
@@ -1,5 +1,99 @@
{
"dependencies": {
+ "com.unity.2d.animation": {
+ "version": "9.0.3",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {
+ "com.unity.2d.common": "8.0.1",
+ "com.unity.2d.sprite": "1.0.0",
+ "com.unity.collections": "1.1.0",
+ "com.unity.modules.animation": "1.0.0",
+ "com.unity.modules.uielements": "1.0.0"
+ },
+ "url": "https://packages.unity.com"
+ },
+ "com.unity.2d.aseprite": {
+ "version": "1.0.0",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {
+ "com.unity.2d.sprite": "1.0.0",
+ "com.unity.2d.common": "6.0.6",
+ "com.unity.mathematics": "1.2.6",
+ "com.unity.modules.animation": "1.0.0"
+ },
+ "url": "https://packages.unity.com"
+ },
+ "com.unity.2d.common": {
+ "version": "8.0.1",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "com.unity.2d.sprite": "1.0.0",
+ "com.unity.mathematics": "1.1.0",
+ "com.unity.modules.uielements": "1.0.0",
+ "com.unity.modules.animation": "1.0.0",
+ "com.unity.burst": "1.7.3"
+ },
+ "url": "https://packages.unity.com"
+ },
+ "com.unity.2d.pixel-perfect": {
+ "version": "5.0.3",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {},
+ "url": "https://packages.unity.com"
+ },
+ "com.unity.2d.psdimporter": {
+ "version": "8.0.2",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {
+ "com.unity.2d.animation": "9.0.1",
+ "com.unity.2d.common": "8.0.1",
+ "com.unity.2d.sprite": "1.0.0"
+ },
+ "url": "https://packages.unity.com"
+ },
+ "com.unity.2d.sprite": {
+ "version": "1.0.0",
+ "depth": 1,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.2d.spriteshape": {
+ "version": "9.0.2",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {
+ "com.unity.mathematics": "1.1.0",
+ "com.unity.2d.common": "8.0.1",
+ "com.unity.modules.physics2d": "1.0.0"
+ },
+ "url": "https://packages.unity.com"
+ },
+ "com.unity.2d.tilemap": {
+ "version": "1.0.0",
+ "depth": 1,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.tilemap": "1.0.0",
+ "com.unity.modules.uielements": "1.0.0"
+ }
+ },
+ "com.unity.2d.tilemap.extras": {
+ "version": "3.1.1",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {
+ "com.unity.modules.tilemap": "1.0.0",
+ "com.unity.2d.tilemap": "1.0.0",
+ "com.unity.ugui": "1.0.0",
+ "com.unity.modules.jsonserialize": "1.0.0"
+ },
+ "url": "https://packages.unity.com"
+ },
"com.unity.addressables": {
"version": "1.21.14",
"depth": 0,
@@ -24,7 +118,7 @@
"url": "https://packages.unity.com"
},
"com.unity.burst": {
- "version": "1.8.4",
+ "version": "1.8.7",
"depth": 1,
"source": "registry",
"dependencies": {
@@ -48,6 +142,16 @@
"dependencies": {},
"url": "https://packages.unity.com"
},
+ "com.unity.collections": {
+ "version": "1.2.4",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "com.unity.burst": "1.6.6",
+ "com.unity.test-framework": "1.1.31"
+ },
+ "url": "https://packages.unity.com"
+ },
"com.unity.ext.nunit": {
"version": "1.0.6",
"depth": 1,