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,