[TIL] Unity - 3D 프로젝트 복습

MINO·2024년 6월 10일
0
post-thumbnail

2024-06-10


마우스 움직임에 따라 회전하는 화면

void CameraLook()
{
	camCurXRot += ( 1 ) * lookSensitivity;
    camCurXRot = Mathf.Clamp(camCurXRot, minXLook, maxXLook)
    
    float YRot = CameraContainer.localEulerAngles.y + ( 2 ) * lookSensitivity;
    
    CameraContainer.localEulerAngles = new Vector3(-camCurXRot, YRot, 0);
}

1) mouseDelta.y , 2) mouseDelta.x

X 방향으로 화면을 회전하기 위해, 오일러각의 Y 값을 변경,
Y 방향으로 화면을 회전하기 위해, 오일러각의 X 값을 변경한다.


왜 localEulerAngles 값을 변경했을까

CameraContainer 를 회전시킬 때, eulerAngles 가 아닌 localEulerAngles 값을 변경했다.

그 이유는, CameraContainer 가 Player 의 자식이기 때문에,
부모인 Player의 회전 값을 그대로 적용받으면서 추가로 x 축 회전을 적용하기 위해
localEulerAngles 를 사용한다.

Transform 에서 관리하는 데이터인 게임 오브젝트의 position , rotation , scale 은 부모 오브젝트의 변경 사항이 자식 오브젝트에도 함께 반영된다.
[World 좌표계] - 유니티 씬 전체를 기준으로 오브젝트의 절대적인 위치, 회전, 크기를 나타냄
[Local 좌표계] - 부모 오브젝트를 기준으로 상대적인 위치, 회전, 크기를 나타냄


인터페이스 활용

public interface IDamagable
{
	public void TakePhysicalDamage(int amount);
}

public class Ally : MonoBehaivor , IDamagable
{
	public void TakePhysicalDamage(int amount)
	{
    	Debug.Log($"아군 유닛이 {amount} 의 피해를 입었습니다."};
	}
}

public class Enemy : MonoBehavior , IDamagable
{
	public void TakePhysicalDamage(int amount)
    {
    	Debug.Log($"적 유닛이 {amount} 의 피해를 입었습니다."};
	}
}

public class CampFire : MonoBehaviour
{
    private int damage = 5;
    private float damageRate = 1;

    private List< 1 > things = new List< 2 >();

    private void Start()
    {
        InvokeRepeating("DealDamage", 0, damageRate);
    }

    void DealDamage()
    {
        for (int i = 0; i < things.Count; i++)
        {
            things[i].( 3 )( ( 4 ) );
        }
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.TryGetComponent(out ( 5 ) target))
        {
            things.Add(target);
        }
    }

    private void OnTriggerExit(Collider other)
    {
        if (other.TryGetComponent(out ( 6 ) target))
        {
            things.Remove(target);
        }
    }
}   

1, 2) IDamagable , 3) TakePhysicalDamage , 4) damage , 5, 6) IDamagable

어떤 클래스를 상속받거나 인터페이스가 구현된 자식 클래스는 부모 클래스(또는 인터페이스) 자료형의 변수에 할당이 가능 (업캐스팅) 하다.


GetComponent 가 아닌 TryGetComponent 를 쓴 이유

OnTriggerEnter 나 OnTriggerExit 로 감지한 오브젝트에 IDamagable 을 구현하는 컴포넌트가 없을 경우, GetComponent<IDamagable> 는 null 을 반환하므로,

TryGetComponent 를 통해 예외처리를 해주었다.


1인칭, 3인칭 시점 변경이 가능한 게임

public class Interaction : MonoBehaviour
{
    private float checkRate = 0.05f;
    private float lastCheckTime;
    private float maxCheckDistance = 2;
    private LayerMask layerMask;

    private GameObject curInteractGameObject;

    private Camera camera;
    private bool nowFirstPerson = true;

    private Transform interactionRayPointTransform;
    
    private void Start()
    {
        layerMask = 1 << 6;
        camera = Camera.main;
        lastCheckTime = Time.time;

        //구성을 단순화하기 위해 이렇게 초기화했습니다. GetChild를 활용해서 초기화하는 방법은 권장되지 않습니다.
        interactionRayPointTransform = transform.GetChild(0).GetChild(1);
    }

    private void Update()
    {
        if (Time.time - lastCheckTime > checkRate)
        {
            lastCheckTime = Time.time;
            
            Ray ray = returnInteractionRay();
            RaycastHit hit;

            if (Physics.Raycast(ray, out hit, maxCheckDistance, layerMask))
            {
                if (hit.collider.gameObject != curInteractGameObject)
                {
                    curInteractGameObject = hit.collider.gameObject;
                    Debug.Log($"{curInteractGameObject.name}과 상호작용할 수 있습니다.");
                }
            }
            else
            {
                curInteractGameObject = null;
            }
        }
        
        // 스페이스 바를 눌렀을 때 시점을 전환합니다.
        if (Input.GetKeyDown(KeyCode.Space)) SwitchView();
    }
    
    public void SwitchView()
    {
        if (nowFirstPerson)
        {
            nowFirstPerson = false;
            camera.transform.localPosition = new Vector3(0, 0.5f, -5);
        }
        else
        {
            nowFirstPerson = true;
            camera.transform.localPosition = Vector3.zero;
        }
    }

    private Ray returnInteractionRay()
    {
        Ray ray;
        if (nowFirstPerson)
        {
            //TODO
            //camera를 활용할 것
        }
        else
        {
            //TODO
            //interactionRayPointTransform를 활용할 것
        }
        return ray;
    }
}

정답 코드

private Ray returnInteractionRay()
{
    Ray ray;
    if (nowFirstPerson)
    {
        ray = camera.ScreenPointToRay(new Vector2(Screen.width / 2, Screen.height / 2));
    }
    else
    {
        ray = new Ray(interactionRayPointTransform.position, interactionRayPointTransform.forward);
    }
    return ray;
}

  • Ray : Ray 를 쏠 시작점과 방향에 대한 데이터가 저장된 객체
  • Raycast : 쏠 Ray, 검사하는 거리, 검사하는 레이어를 정해 Ray를 쏘고 어떤 오브젝트가 검출되었는지 확인하는 메서드

코루틴 동작 방식

public class CoroutineTest : MonoBehaviour
{
    private Coroutine myCoroutine;
    private void Start()
    {
        StartTestCoroutine();
        Invoke("StartTestCoroutine", 1);
    }

    void StartTestCoroutine()
    {
        if (myCoroutine != null) StopCoroutine(myCoroutine);
        myCoroutine = StartCoroutine(TestCoroutine());
    }
    IEnumerator TestCoroutine()
    {
        Debug.Log("a");
        yield return null;
        Debug.Log("b");
        yield return new WaitForSeconds(3);
        Debug.Log("c");
    }
}

출력 내용 : a - b - a - b - c

StartTestCoroutine 을 통해 TestCoroutine 이 코루틴으로 시작되어 myCoroutine 에 할당되고, a 를 출력, 한 프레임 쉬고 b 를 출력한다.
3초를 대기하는 동안 Invoke 로 지연된 StartTestCoroutine 이 실행되면서 myCoroutine 에 할당되어 있던 코루틴을 종료시키고 새로운 코루틴이 실행되어 a, b, c 를 출력한다.


TIL 마무리

퀴즈를 통해, 그동안 학습해온 내용을 정리해보며 헷갈렸던 내용과 잘 몰랐던 내용을 복습할 수 있어 좋은 시간이었다.

profile
안녕하세요 게임 개발하는 MINO 입니다.

0개의 댓글