2023-08-01 07:56:29 +00:00
|
|
|
using System;
|
|
|
|
using UnityEngine;
|
|
|
|
using UnityEngine.AI;
|
|
|
|
|
|
|
|
// ReSharper disable once CheckNamespace
|
2023-08-03 05:49:05 +00:00
|
|
|
namespace BlueWaterProject
|
2023-08-01 07:56:29 +00:00
|
|
|
{
|
|
|
|
public class AiWeight : MonoBehaviour
|
|
|
|
{
|
2024-02-12 20:50:24 +00:00
|
|
|
/***********************************************************************
|
|
|
|
* Definitions
|
|
|
|
***********************************************************************/
|
|
|
|
#region Definitions
|
|
|
|
|
2023-08-01 07:56:29 +00:00
|
|
|
[Serializable]
|
|
|
|
private struct DirectionInfo
|
|
|
|
{
|
2024-02-12 20:50:24 +00:00
|
|
|
public RaycastHit RaycastHit { get; set; }
|
|
|
|
[field: SerializeField] public Vector3 Direction { get; set; }
|
|
|
|
[field: SerializeField] public float Interest { get; set; }
|
|
|
|
[field: SerializeField] public float Danger { get; set; }
|
2023-08-01 07:56:29 +00:00
|
|
|
|
2024-02-12 20:50:24 +00:00
|
|
|
public DirectionInfo(RaycastHit raycastHit, Vector3 direction, float interest, float danger)
|
2023-08-01 07:56:29 +00:00
|
|
|
{
|
2024-02-12 20:50:24 +00:00
|
|
|
RaycastHit = raycastHit;
|
|
|
|
Direction = direction;
|
|
|
|
Interest = interest;
|
|
|
|
Danger = danger;
|
2023-08-01 07:56:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-12 20:50:24 +00:00
|
|
|
#endregion
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
* Variables
|
|
|
|
***********************************************************************/
|
|
|
|
#region Variables
|
|
|
|
|
2023-08-01 07:56:29 +00:00
|
|
|
[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;
|
|
|
|
|
2024-02-12 20:50:24 +00:00
|
|
|
[Range(1, 36), Tooltip("방향의 갯수")]
|
|
|
|
[SerializeField] private int directionIndex = 24;
|
|
|
|
public int DirectionIndex
|
|
|
|
{
|
|
|
|
get => directionIndex;
|
|
|
|
set
|
|
|
|
{
|
|
|
|
directionIndex = value;
|
|
|
|
directionInfo = new DirectionInfo[DirectionIndex];
|
|
|
|
|
|
|
|
var angle = 360f / directionInfo.Length;
|
|
|
|
for (var i = 0; i < directionInfo.Length; i++)
|
|
|
|
{
|
|
|
|
var direction = Utils.AngleToDir(angle * i);
|
|
|
|
directionInfo[i] = new DirectionInfo(default, direction, 0, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-01 07:56:29 +00:00
|
|
|
[field: Tooltip("방향이 가지는 구조체의 배열")]
|
2024-02-12 20:50:24 +00:00
|
|
|
[field: SerializeField] private DirectionInfo[] directionInfo;
|
2023-08-01 07:56:29 +00:00
|
|
|
|
|
|
|
[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;
|
2024-02-12 20:50:24 +00:00
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
* Unity Methods
|
|
|
|
***********************************************************************/
|
|
|
|
#region Unity Methods
|
2023-08-01 07:56:29 +00:00
|
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
private void OnDrawGizmos()
|
|
|
|
{
|
|
|
|
if (!drawGizmo || !usedWeighted) return;
|
|
|
|
|
|
|
|
for (var i = 0; i < directionInfo.Length; i++)
|
|
|
|
{
|
|
|
|
var myPos = transform.position;
|
|
|
|
|
2024-02-12 20:50:24 +00:00
|
|
|
Gizmos.color = GetFinalDirectionValue().Direction == directionInfo[i].Direction
|
2023-08-01 07:56:29 +00:00
|
|
|
? Color.green
|
|
|
|
: Color.white;
|
|
|
|
|
2024-02-12 20:50:24 +00:00
|
|
|
var resultWeighted = Mathf.Clamp(directionInfo[i].Interest - directionInfo[i].Danger, 0f, 1f);
|
|
|
|
Gizmos.DrawLine(myPos, myPos + directionInfo[i].Direction * resultWeighted);
|
2023-08-01 07:56:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2024-02-12 20:50:24 +00:00
|
|
|
private void Update()
|
2023-08-01 07:56:29 +00:00
|
|
|
{
|
2024-02-12 20:50:24 +00:00
|
|
|
GetDirectionRaycastHit(transform.position);
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private void GetDirectionRaycastHit(Vector3 startPosition)
|
|
|
|
{
|
|
|
|
// 각 방향에 대한 raycastHit 정보 받아두기
|
2023-08-01 07:56:29 +00:00
|
|
|
for (var i = 0; i < directionInfo.Length; i++)
|
|
|
|
{
|
2024-02-12 20:50:24 +00:00
|
|
|
Physics.Raycast(startPosition, directionInfo[i].Direction, out var hit, obstacleDist,obstacleWeightLayer, QueryTriggerInteraction.Ignore);
|
|
|
|
|
|
|
|
directionInfo[i].RaycastHit = hit;
|
2023-08-01 07:56:29 +00:00
|
|
|
}
|
|
|
|
}
|
2024-02-12 20:50:24 +00:00
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
* Methods
|
|
|
|
***********************************************************************/
|
|
|
|
#region Methods
|
|
|
|
|
2023-08-01 07:56:29 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Update()에서 사용하는 Enemy의 가중치 시스템
|
|
|
|
/// </summary>
|
|
|
|
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);
|
2024-02-12 20:50:24 +00:00
|
|
|
MoveTowardDirection(agent, transform.position + CalcDirectionInfo().Direction, targetTransform,
|
2023-08-01 07:56:29 +00:00
|
|
|
rotationTransform, canLookAtTarget);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 방향별로 장애물 체크 및 위험 수치 계산 함수
|
|
|
|
/// </summary>
|
|
|
|
private void CheckObstacle()
|
|
|
|
{
|
2024-02-12 20:50:24 +00:00
|
|
|
// 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;
|
|
|
|
// }
|
|
|
|
// }
|
2023-08-01 07:56:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 방향별로 호기심 수치 계산 함수
|
|
|
|
/// </summary>
|
|
|
|
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);
|
2024-02-12 20:50:24 +00:00
|
|
|
if (Vector3.Dot(directionInfo[i].Direction, directionToTarget) > 0.7f)
|
2023-08-01 07:56:29 +00:00
|
|
|
{
|
2024-02-12 20:50:24 +00:00
|
|
|
directionInfo[i].Interest = 1f;
|
2023-08-01 07:56:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ((!beInOrbit && distanceToTarget > minDistance) ||
|
|
|
|
(beInOrbit && distanceToTarget > orbitDistance + orbitAllowableValue))
|
|
|
|
{
|
|
|
|
usedWeighted = true;
|
|
|
|
SetBeInOrBitAndIsAvoiding(false, false);
|
2024-02-12 20:50:24 +00:00
|
|
|
directionInfo[i].Interest =
|
|
|
|
Mathf.Clamp(Vector3.Dot(directionInfo[i].Direction, directionToTarget), 0, 1)
|
|
|
|
- directionInfo[i].Danger;
|
2023-08-01 07:56:29 +00:00
|
|
|
}
|
|
|
|
else if (distanceToTarget > orbitDistance)
|
|
|
|
{
|
|
|
|
usedWeighted = true;
|
|
|
|
SetBeInOrBitAndIsAvoiding(true, false);
|
2024-02-12 20:50:24 +00:00
|
|
|
directionInfo[i].Interest = Mathf.Clamp(Vector3.Dot(directionInfo[i].Direction, resultDir), 0, 1)
|
|
|
|
- directionInfo[i].Danger;
|
2023-08-01 07:56:29 +00:00
|
|
|
}
|
|
|
|
else if (distanceToTarget <= orbitDistance)
|
|
|
|
{
|
|
|
|
usedWeighted = true;
|
|
|
|
SetBeInOrBitAndIsAvoiding(false, true);
|
2024-02-12 20:50:24 +00:00
|
|
|
directionInfo[i].Interest =
|
|
|
|
Mathf.Clamp(Vector3.Dot(directionInfo[i].Direction, -directionToTarget), 0, 1);
|
2023-08-01 07:56:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 공전 중이며, 공전 하는 방향의 위험 수치가 일정 수치 이상일 때, 반대 방향으로 회전하는 함수
|
|
|
|
/// </summary>
|
|
|
|
private Vector3 CheckTurnAround(Vector3 resultDir, Vector3 directionToTarget)
|
|
|
|
{
|
2024-02-12 20:50:24 +00:00
|
|
|
if (beInOrbit || directionInfo[GetDirectionIndex(resultDir)].Danger < dangerValue) return resultDir;
|
2023-08-01 07:56:29 +00:00
|
|
|
|
|
|
|
isClockwise = !isClockwise;
|
|
|
|
return SetResultDirection(directionToTarget);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// target을 쳐다보는지의 여부
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="targetTransform"></param>
|
|
|
|
/// <param name="rotationTransform"></param>
|
|
|
|
/// <param name="canLookAtTarget"></param>
|
|
|
|
private void LookAtTarget(Transform targetTransform, Transform rotationTransform, bool canLookAtTarget)
|
|
|
|
{
|
|
|
|
var targetPosition = targetTransform.position;
|
|
|
|
|
|
|
|
targetPosition.y = transform.position.y;
|
|
|
|
if (canLookAtTarget)
|
|
|
|
{
|
|
|
|
rotationTransform.LookAt(targetPosition);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 가중치가 가장 높은 방향으로 이동시키는 함수
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="agent">NavMeshAgent Component</param>
|
|
|
|
/// <param name="resultDirection">최종 이동할 위치</param>
|
|
|
|
/// <param name="targetTransform">목표 객체 : TargetTransform</param>
|
|
|
|
/// <param name="rotationTransform">회전 객체 : RotationTransform</param>
|
|
|
|
/// <param name="canLookAtTarget">일정 거리 안에 Target이 있다면 true를 반환</param>
|
|
|
|
private void MoveTowardDirection(NavMeshAgent agent, Vector3 resultDirection, Transform targetTransform,
|
|
|
|
Transform rotationTransform, bool canLookAtTarget)
|
|
|
|
{
|
|
|
|
LookAtTarget(targetTransform, rotationTransform, canLookAtTarget);
|
|
|
|
agent.SetDestination(resultDirection);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 호기심 수치가 가장 높은 방향의 정보를 받는 함수
|
|
|
|
/// </summary>
|
|
|
|
private DirectionInfo CalcDirectionInfo()
|
|
|
|
{
|
|
|
|
var bestDirectionIndex = 0;
|
|
|
|
var bestWeight = 0f;
|
|
|
|
|
|
|
|
for (var i = 0; i < directionInfo.Length; i++)
|
|
|
|
{
|
2024-02-12 20:50:24 +00:00
|
|
|
if (directionInfo[i].Interest <= bestWeight) continue;
|
2023-08-01 07:56:29 +00:00
|
|
|
|
2024-02-12 20:50:24 +00:00
|
|
|
bestWeight = directionInfo[i].Interest;
|
2023-08-01 07:56:29 +00:00
|
|
|
bestDirectionIndex = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
return directionInfo[bestDirectionIndex];
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 호기심 수치가 가장 높은 방향의 정보를 받는 함수
|
|
|
|
/// </summary>
|
|
|
|
private DirectionInfo GetFinalDirectionValue()
|
|
|
|
{
|
|
|
|
var bestDirectionIndex = 0;
|
|
|
|
var bestWeight = 0f;
|
|
|
|
|
|
|
|
for (var i = 0; i < directionInfo.Length; i++)
|
|
|
|
{
|
2024-02-12 20:50:24 +00:00
|
|
|
var finalInterestValue = Mathf.Clamp(directionInfo[i].Interest - directionInfo[i].Danger, 0f, 1f);
|
2023-08-01 07:56:29 +00:00
|
|
|
if (finalInterestValue <= bestWeight) continue;
|
|
|
|
|
|
|
|
bestWeight = finalInterestValue;
|
|
|
|
bestDirectionIndex = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
return directionInfo[bestDirectionIndex];
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 매개변수의 방향값과 가장 인접한 방향의 Index를 반환하는 함수
|
|
|
|
/// </summary>
|
|
|
|
private int GetDirectionIndex(Vector3 direction)
|
|
|
|
{
|
|
|
|
var maxDotValue = 0f;
|
|
|
|
var index = 0;
|
|
|
|
|
|
|
|
for (var i = 0; i < directionInfo.Length; i++)
|
|
|
|
{
|
2024-02-12 20:50:24 +00:00
|
|
|
var dotValue = Vector3.Dot(directionInfo[i].Direction, direction);
|
2023-08-01 07:56:29 +00:00
|
|
|
|
|
|
|
if (maxDotValue >= dotValue) continue;
|
|
|
|
|
|
|
|
maxDotValue = dotValue;
|
|
|
|
index = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 공전하는 방향을 설정하는 함수<br/><br/>
|
|
|
|
/// target과 수직인 시계방향 : new Vector3(-directionToTarget.z, 0f, directionToTarget.x)<br/>
|
|
|
|
/// target과 수직인 반시계방향 : new Vector3(directionToTarget.z, 0f, -directionToTarget.x)
|
|
|
|
/// </summary>
|
|
|
|
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;
|
|
|
|
}
|
2024-02-12 20:50:24 +00:00
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 각도를 통해 방향값을 반환하는 함수
|
|
|
|
/// </summary>
|
|
|
|
private Vector3 AngleToDir(float angle)
|
|
|
|
{
|
|
|
|
var radian = angle * Mathf.Deg2Rad;
|
|
|
|
return new Vector3(Mathf.Sin(radian), 0f, Mathf.Cos(radian)).normalized;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
2023-08-01 07:56:29 +00:00
|
|
|
}
|
|
|
|
}
|