본문 바로가기
개발/Unity

Unity) 오프스크린 인디케이터(off-screen indicator)

by 테샤르 2024. 10. 24.

오프스크린 인디케이터(off-screen indicator)

 

스크린의 화면 밖에 위치하게되면 해당 화살표(방향) 오브젝트를 보이게 하는 코드이다.

게임을 플레이하다보면 넓은 맵에서 위치를 알수 없는 경우가 종종 생긴다.

그런 상황에서 너무 상세한 정보를 주는것보다는 방향이나 거리의 정보를 노출해주는 것이다.

반응형

 

 

< 코드 >

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;

public class ScreenOffIndicator : MonoBehaviour
{
	public GameObject indicatorPrefab;      // 인디케이터 프리팹
	public GameObject indicatorCanvas;      // 인디케이터가 표시될 캔버스
	public GameObject player;               // 플레이어 객체
	public float detectionRadius = 10.0f;   // 추적할 반지름 범위
	public List<Enemy> enemyList;           // 추적할 적 리스트

	private List<GameObject> indicators;    // 각 적의 인디케이터 오브젝트 리스트

	private void Start()
	{
		// 각 적에 대한 인디케이터 오브젝트 생성
		indicators = new List<GameObject>();
		foreach (var enemy in enemyList)
		{
			GameObject indicatorInstance = Instantiate(indicatorPrefab);
			indicatorInstance.transform.SetParent(transform, false);
			indicatorInstance.transform.localScale = Vector3.one;
			indicatorInstance.SetActive(false);  // 처음엔 비활성화
			indicators.Add(indicatorInstance);
		}
	}

	void Update()
	{
		// LINQ를 사용해 detectionRadius 내에 있는 적들만 필터링
		var enemiesInRange = enemyList.Where(enemy => IsWithinRange(enemy.transform.position)).ToList();

		// 적 리스트를 순회하며 인디케이터 갱신
		for (int i = 0; i < enemyList.Count; i++)
		{
			Enemy enemy = enemyList[i];
			GameObject indicator = indicators[i];

			// 적이 탐지 범위 안에 있을 때만 처리
			if (enemiesInRange.Contains(enemy))
			{
				// 적이 화면 밖에 있는지 확인
				if (IsOffScreen(enemy.transform.position))
				{
					SetIndicator(enemy, indicator);  // 화면 밖에 있을 때 인디케이터 설정
				}
				else
				{
					indicator.SetActive(false);     // 화면 안에 있을 때 인디케이터 비활성화
				}
			}
			else
			{
				indicator.SetActive(false);          // 탐지 범위를 벗어나면 인디케이터 비활성화
			}
		}
	}

	// 특정 적의 인디케이터 위치 설정 및 회전 처리
	private void SetIndicator(Enemy enemy, GameObject indicator)
	{
		// 플레이어와 적 간의 방향 및 각도 계산 (Y축은 무시하고 XZ 평면에서만 계산)
		Vector3 direction = new Vector3(enemy.transform.position.x - player.transform.position.x, 0, enemy.transform.position.z - player.transform.position.z);
		float angle = Vector3.SignedAngle(Vector3.forward, direction, Vector3.up); // Z축을 기준으로 각도 계산

		// 월드 좌표를 뷰포트 좌표로 변환 (Y축은 무시하고 X와 Z만 사용)
		Vector3 viewportPosition = Camera.main.WorldToViewportPoint(enemy.transform.position);

		// 뷰포트가 화면을 벗어나 있는지 확인하고, 화면 경계에서 인디케이터 위치 조정
		RectTransform indicatorRect = indicator.GetComponent<RectTransform>();
		float clampedX = Mathf.Clamp(viewportPosition.x, 0.05f, 0.95f);
		float clampedY = Mathf.Clamp(viewportPosition.y, 0.05f, 0.95f);

		// 각도를 기준으로 인디케이터를 네 가지 방향으로 설정 (위, 아래, 왼쪽, 오른쪽)
		if (viewportPosition.x < 0 || viewportPosition.x > 1)  // 좌우에 벗어난 경우
		{
			if (direction.x > 0)
			{
				// 오른쪽에 위치
				indicatorRect.anchorMin = new Vector2(0.95f, clampedY);
				indicatorRect.anchorMax = indicatorRect.anchorMin;
			}
			else
			{
				// 왼쪽에 위치
				indicatorRect.anchorMin = new Vector2(0.05f, clampedY);
				indicatorRect.anchorMax = indicatorRect.anchorMin;
			}
		}
		else if (viewportPosition.y < 0 || viewportPosition.y > 1)  // 상하에 벗어난 경우
		{
			if (direction.z > 0)
			{
				// 위쪽에 위치
				indicatorRect.anchorMin = new Vector2(clampedX, 0.95f);
				indicatorRect.anchorMax = indicatorRect.anchorMin;
			}
			else
			{
				// 아래쪽에 위치
				indicatorRect.anchorMin = new Vector2(clampedX, 0.05f);
				indicatorRect.anchorMax = indicatorRect.anchorMin;
			}
		}

		// 인디케이터 회전 처리 (적 방향을 가리키도록)
		//indicatorRect.rotation = Quaternion.Euler(0, 0, -angle);
		indicatorRect.rotation = Quaternion.Euler(0, 0, -(angle + 180));

		// 인디케이터 중앙 위치 고정
		indicatorRect.anchoredPosition = Vector3.zero;
		indicator.SetActive(true);
	}

	// 적이 화면 밖에 있는지 확인
	private bool IsOffScreen(Vector3 position)
	{
		Vector3 viewportPosition = Camera.main.WorldToViewportPoint(position);
		return viewportPosition.x < 0 || viewportPosition.x > 1 || viewportPosition.y < 0 || viewportPosition.y > 1;
	}

	/// <summary>
	/// 노출하는 조건 추가(반지름)
	/// </summary>
	/// <param name="enemyPosition"></param>
	/// <returns></returns>
	private bool IsWithinRange(Vector3 enemyPosition)
	{
		// Y축은 고정하고 XZ 평면에서의 거리만 계산
		Vector3 adjustedEnemyPos = new Vector3(enemyPosition.x, player.transform.position.y, enemyPosition.z);
		float distance = Vector3.Distance(player.transform.position, adjustedEnemyPos);
		//return distance <= detectionRadius;  // 거리 범위 내에 있으면 true 반환
		return true;
	}
}

 

 

★☆☆☆☆

 

반응형

댓글