본문 바로가기
스파르타 내배캠

스파르타 내배캠 Unity 3기 - 48

by LemongO 2024. 3. 19.

다크소울의 그것...

 

 

 


Lockon Indicator

 

3D 3인칭 게임 + 정신없는 화면속 락온 기능없이 플레이를 하는것은 많이 어지럽고 하기 싫을 것이다...

 

그래서 우리 게임에는 락온 기능을 넣어 해당 적을 계속 주시하도록 해주었다.

 

 

여기서 락온 기능을 어디서 담당할지 조금 고민이 되었는데 현재 플레이어의 전반적인 로직을 담당하는 PlayerStateMachine에서 이것또한 담당하기엔 코드가 많이 길어지기도 하고 그다지 좋은 선택은 아니라고 생각되어

LockonSystem Class를 만들어 여기서 담당하도록 만들었다.

 

먼저 Lockon을 할 키 입력으로 마우스 휠 클릭을 선택하였고

[Serializable]
public class LockOnSystem
{
    [SerializeField] Transform _followOnTargetMode;
    [SerializeField] LayerMask _targetLayer;
    [SerializeField] private float _scanRange;
}

LockonSystem 클래스는 PlayerStateMachine에서 생성되지만

인스펙터에서 할당할 정보들이 있기 때문에 직렬화를 해줬다.

 

private PlayerStateMachine _stateMachine;

public CinemachineFreeLook FollowCam { get; private set; }    
public CinemachineVirtualCamera LockOnCam { get; private set; }
public CinemachineTargetGroup TargetGroup { get; private set; }

public Transform TargetEnemy { get; private set; }        

public void Setup(PlayerStateMachine stateMachine)
{
    _stateMachine = stateMachine;

    // 시네머신 카메라 초기화
    FollowCam = GameObject.Find("@FollowCam").GetComponent<CinemachineFreeLook>();
    LockOnCam = GameObject.Find("@LockOnCam").GetComponent<CinemachineVirtualCamera>();
    TargetGroup = GameObject.Find("@TargetGroup").GetComponent<CinemachineTargetGroup>();       

    FollowCam.Follow = _stateMachine.transform;
    FollowCam.LookAt = _stateMachine.transform;

    LockOnCam.Follow = _followOnTargetMode;
    LockOnCam.LookAt = TargetGroup.transform;
    LockOnCam.gameObject.SetActive(false);

    TargetGroup.AddMember(_stateMachine.transform, 1, 0);
    TargetGroup.AddMember(_followOnTargetMode, 1, 0);
}

클래스가 생성되고 Setup 시 카메라에 대한 초기화가 이루어지고 해당 세팅에 맞춰 시네머신이 작동된다.

 

// ### PlayerStateMachine.cs

public bool IsLockOn { get; private set; }

private void OnLockOn(InputAction.CallbackContext context)
{
    if (IsLockOn)
    {
        IsLockOn = false;
        LockOnSystem.ReleaseTarget();
    }
    else
    {
        if (LockOnSystem.IsThereEnemyScanned())
        {
            IsLockOn = true;
            LockOnSystem.LockOnTarget();
        }
    }
}

PlayerStateMachine의 OnLockOn 콜백함수를 마우스 휠 클릭 시 호출하게 되면

현재 락온 여부에 따라 행동이 달라진다.

락온을 풀거나 [ReleaseTarget()]
락온 대상이 있으면 락온을 하거나 [LockOnTarget()]

 

// ### LockOnSystem.cs

public void LockOnTarget()
{
    Managers.ActionManager.CallLockOn(TargetEnemy);
    LockOnCam.gameObject.SetActive(true);
    TargetGroup.AddMember(TargetEnemy, 1, 0);        
}

public void ReleaseTarget()
{
    FollowCam.m_XAxis.Value = LockOnCam.transform.rotation.eulerAngles.y;    

    Managers.ActionManager.CallRelease();
    LockOnCam.gameObject.SetActive(false);
    TargetGroup.RemoveMember(TargetEnemy);
    TargetEnemy = null;
}

 

대상을 락온시 락온대상을 HUD상 UI가 계속 추적해야 하기 때문에 해당 Action에 구독된 함수를 호출 후
Cinemachine 캠 역시 교체하여 준다.

 

이미 락온 중이라면 해제해야 하므로 Action에 구독된 함수를 호출 후

LockonCam은 끄고 락온중인 대상을 없앤다.

 

// ### LockOnSystem.cs

public bool IsThereEnemyScanned()
{
    Vector3 origin = Camera.main.transform.position;
    RaycastHit[] hits = Physics.SphereCastAll(origin, _scanRange, Camera.main.transform.forward, 50f, _targetLayer);
    if (hits.Length == 0)
    {
        Debug.Log("현재 조준시스템에 포착된 적이 없습니다.");
        return false;
    }

    int closestIndex = GetClosestTargetIndex(hits);
    TargetEnemy = hits[closestIndex].transform.GetComponent<Test_Enemy>().transform;
    return true;
}

private int GetClosestTargetIndex(RaycastHit[] hits)
{
    float closestDist = float.MaxValue;
    int closestIndex = -1;
    for (int i = 0; i < hits.Length; i++)
    {
        if (hits[i].distance < closestDist)
        {
            closestIndex = i;
            closestDist = hits[i].distance;
        }
    }

    return closestIndex;
}

 

락온 로직은 SphereCastAll을 하여 카메라 기준 전방을 향해 구 형태 레이를 쏴 진행방향에 검출된 모든 적들을 찾아

가장 가까운 적을 찾아 할당하는 방법을 사용했다.

 

해당 방법은 CastAll 방식도 그렇고 Ray를 사용했다는 것도 그렇고 어느정도 성능을 포기한 부분이 있기 때문에

리팩토링 대상이며 나중에 좀 더 나은 방법을 찾아보도록 해야겠다.

 

// ### UI_HUD.cs

private void Update()
{
    if (!_lockOnIndicator.activeSelf || _target == null)
        return;

    _lockOnIndicator.transform.position = Camera.main.WorldToScreenPoint(_target.position);        
}

 

그리고 HUD에서 락온 된 적을 계속 추적해주는 표시를 해줘야 하기 때문에 UI_HUD Class에서 Update문으로
해당 UI를 적 포지션으로 맞춰주도록 한다.