본문 바로가기

Unity 개발 공부

[내배캠] 본캠 34일차. 3d 작업시 트러블슈팅


skybox 세팅

메터리얼폴더를만들고
메터리얼을 만든뒤
인스펙터 - 쉐이더 - skybox/procedural 로세팅,

window- rendering - lighting 에서 envirnment 탭에
skyboxmaterial 에 해당 메터리얼 드롭다운.

그후 해당 메터리얼을 가지고 인스펙터창에서 컬러, 틴트태양크기,노출등 다양한 옵션 변경가능
그 아래에 sun source에 directional light를 드롭다운해놓으면 해가 로테이션 가능한 상태가된다.
물론 자동으로 낮밤을 반복하게하려면 스크립트로 코드를 짜줘야한다.



    void Move()
    {
        Vector3 dir = transform.forward * curMovementInput.y + transform.right * curMovementInput.x;
        dir *= moveSpeed;
        dir.y = _rigidbody.velocity.y;

        _rigidbody.velocity = dir;
    }

    public void OnMoveInput(InputAction.CallbackContext context)
    {
        if(context.phase == InputActionPhase.Performed )
        {
            curMovementInput = context.ReadValue<Vector2>();
        }
        else if(context.phase == InputActionPhase.Canceled)
        {
            curMovementInput = Vector2.zero;
        }
    }

여기서 보면 inputsystem에 의해 값을 반환받는 
curMovementInput의 경우 w : 0,1 s: 0,-1, a: -1,0 , d: 1,0 의 값들을 계속 반환할거고
curMovementInput.y의 경우도 버튼을 누를때마다 -1,0,1 중하나를 반환할것이다.
curMovementInput.x의 경우도 마찬가지다.

dir의 경우 transform.forward 는 0,0,1 즉 정면을 바라보는 방향벡터다
transform,right은 1,0,0 즉 오른쪽을 바라보는 방향벡터다
각각에 curMovementInput.y 와 curMovementInput.x를 곱해줘도 크기는(거리는) 달라지지않으니까
방향만 가진 단위벡터라고 할수있다. 방향만 -1,0,1에 의해 바뀌는것이다.
그러나
dir의 경우는 두개의 축입력의 합성이다. 전후진, 좌우이동을 더해주기때문에
예를들어 w만 누르거나  a만 누를경우는 rigidbody.veolocity의 이동은 곱해주는 moveSpeed에 의해 일정한 속도로
나가겠지만 대각선의 이동의 경우 크기가 1이상이 되어버린다. (두개의 단위벡터이동을 더했을때 크기 √2 (또는 √(x²+y²)) 가나오니까)
대략적으로 1.414정도가 될터이다
따라서 대각선 방향으로 w와 d를 함께 누를경우에는 기존 방향만 가지는 단위벡터상의  속도계수 1이아니라 1.414만큼빨라질테고
거기에 moveSpeed를 곱하면 대각선 이동이 더 빠르다...
이를 보정해주고싶으면
dir = dir.normalized; 를 해주는게좋다.
단, 정지상태일때 즉 dir.magnitude가 0일때는 normalized하면 즉 해당 벡터에 분모에 벡터의크기가 0인상태로 들어간다면
0/0은 수학적으로 정의되지않아서 컴퓨터에서 NaN(Not a Number)을 반환한다.
따라서 조건문으로 안전장치를 줘서 체크를 해주는게좋은데, 

void Move()
{
    // 1) 입력 기반 방향 계산
    Vector3 dir = transform.forward * curMovementInput.y
                + transform.right   * curMovementInput.x;

    // 2) 수평 방향만 정규화 (크기를 1로)
    //    dir.magnitude가 0일 때는 Normalize 하면 NaN 되니까 체크
    if (dir.sqrMagnitude > 0.0001f)
        dir = dir.normalized;

    // 3) 속도 적용
    dir *= moveSpeed;

    // 4) 수직(중력) 성분 유지
    dir.y = _rigidbody.velocity.y;

    // 5) 최종 속도 설정
    _rigidbody.velocity = dir;
}

이렇게 dir.sqrMagnitude(루트안벗긴) > 0.0001f
인상황에서만 dir를 정규화시키는 조건을 걸어준다.
이렇게하면 대각선이동시에도 같은 속도로 이동가능해진다.

물론 이렇게 코드 추가할것없이, inputsystem에서 move 액션쪽에서
2d Vector compiste 바인딩 선택에서 digital이아닌 digital normalize를 선택한다면 알아서 정규화 해서 값을 반환해준다.

궁금한 부분은
Debug.Log( _rigidbody.velocity);를 작성해서 알아보자.



UI 슬라이드바를 만들기위해 scale말고 다른방법써보기
window - package manager - unity registry 모드 - 2dsprite 설치

에셋의 텍스쳐폴더나 적당한곳에 create - 2d - sprite -square 로 square 생성후
hierarchy창에서 필요한 image(바처럼생긴이미지) source에 연결
inspector에서 image 타입 filled로 옵션 변경
필요한 옵션 선택후 fill amount 조절해서 슬라이더처럼 사용가능

ui 컨테이너만들기위해서
canvas 아래에 빈오브젝트 conditions 생성, 해당 오브젝트에 컴포넌트 vertical layer group 부착
위치잡기(rect로)
해당 컨테이너 안에 상태에 관한 ui image를 집어넣으면 vertical layout group 구조때문에 알아서 정렬된다.
 그외에도 horizontal layout group등도 있다.
grid layout group도 있는데 이건 상하 좌우 정렬도해준다. 먼저 가로로 정렬, 줄이넘어갈때 한행 추가 이런식이다.
설정에따라 반대로도 가능
해당 layoutgroup은 경계를 잡아주는개념이라서 빈 empty object를 만들어서 넣어준다.
보통 image UI로 보여지는 경계를 만들고, 그안에 버튼이나, 아이템슬롯, 텍스트등을 행과 열로 정렬하기위해서는
그 백그라운드 이미지 hierarchy 아래에 빈오브젝트 만들고 해당 layoutgroup을 취사선택해서 넣은뒤
그안의 hierarchy에 필요한 버튼,아이템슬롯이미지 등등을 넣어주는식으로 트리를 정리가능하다.



AnimationCurve 에 대해서 알아보자. 해당 타입으로선언하면
유니티에서 연출작업시에 선형적이지 않은 움직임을 작업해줄수있다.

https://taeyeokim.tistory.com/84

animCurve.Evaluate(float time) 에 대해서 알아보자. animCurve는 담아둔 변수다.
Evaluate은 time을 매개로 받아서 해당 time에 해당하는 값을 반환한다.

환경의 빛을 조절할때, 엠비언트와, 반사 강도도 조절가능한데 
RenderSettings에 접근해서, 찾아오면된다.
이런식으로 시간에 맞춰 조절이 가능하다.

        RenderSettings.ambientIntensity = lightingIntensityMultiplier.Evaluate(time);
        RenderSettings.reflectionIntensity = reflectionIntensityMultiplier.Evaluate(time);

Random.value에 대해서 알아보자,
    Instantiate(data.dropPrefab, dropPosition.position, Quaternion.Euler(Vector3.one * Random.value * 360));
Random.value는 0.0이상 1.0이하 이다. 즉 해당 구문은 0부터 360도까지 랜덤으로 뽑아서 1,1,1에 랜덤하게 나온
각도를 대응하게 넣어준다. x,y,z 축 각각에 동일하게 회전값을 랜덤한 값으로 준다는 뜻이다.

무기장착을위해선 무기만을 위한 카메라를 하나 더만들어주는게좋다.
플레이어 오브젝트안에 카메라컨테이너안에 회전을 담당하는 카메라가 들어있을것이다.
그밑에 하나더 카메라를 만들어주되, skybox모드가아닌 depth only 모드로 만들어준다.
그리고 culling mask에서 해당하는것만 보이도록 설정해줄 것이다.
레이어를 만들고 culling mask에 집어넣어준뒤에,
hierarchy 하단에 해당 레이어인 오브젝트를 넣어주면 그 오브젝트만 보인다.


GetComponent<T>(); 란 T 타입의 인스턴스를 반환하는것이다. 이 스크립트가 붙어있는
같은 GameObject에서 T 타입 컴포넌트를 찾아서 반환해주는 메서드다.
캐싱할때
audioSource = GetComponent<AudioSource>(); 이뜻은
이 MonoBehaviour 스크립트가 붙어있는 GameObject 에 AudioSource 컴포넌트가 붙어있는지 확인하고
있으면 그 컴포넌트의 인스턴스(참조)를 반환한다.
그리고 Start() 같은데서 한번만 호출해서 audioSource 변수에 해당 참조를 저장(caching)함으로서 
해당 참조로 접근하여 컴포넌트의 그능들을 코드로 꺼내서 구현할수있다.

CharacterManager.Instance.Player.condition.GetComponent<IDamageable>.TakePhysicalDamage(damage);
이 구문의경우엔 IDamageable 인터페이스의 구현체인 PlayerCondition 컴포넌트를 반환했다.
그래서 PlayerCondition에 작성했던 TakePhysicalDamage(damage) 메서드를 읽어온것이다.
번외로 해당 컴포넌트가 달려있는 Player 객체의 경우 CharacterManager라는 싱글톤 클래스를 통해
접근가능하도록되어있어 GetComponent 값이 비싸니까 제외하고 
CharacterManager.Instance.Player.condition.TakePhysicalDamage(damage); 이런식으로 바로도 쓸수있다.

Rigidbody.velocity는 Vector3 타입의 프로퍼티고 단위시간당 위치 변화 (속도)를 나타낸다.
즉 물리적으로 선형 속도벡터이다.
벡터이기때문에 방향과 크기를 모두 가진다.
단위는 유닛 epr 초이다.
velocity.magnitude = 벡터 길이 = 실제 속도(스피드) 이다.
float speed = rigidbody.velocity.magnitude;
rigidbody.velocity는 월드 공간(World Space) 기준의 속도이다.
velocity = new Vector3(1,0,0)이면 월드의 x방향으로 초당 1유닛 이동을 의미한다.

Vector3 dir = rb.velocity.normalized;이렇게하면 정규화하면,
rb.veolicity가 가리키는 방향과 동일한 길이가 1인 단위벡터(방향벡터)를 얻을수있다.

OnTriggerEnter(collsion other) 기능을 이용하면서 아주 간단하지만 생소한 실수를했다.
Tag와 Layer를 헷갈렸다.
        if (other.CompareTag("Player"))
        {
            targetVolume = maxVolume;
            Debug.Log("Player entered the music zone. Volume set to: " + targetVolume);
        }
이것은 Tag를 감지하는데 Player Tag는 설정안해놓고 layer만 Player니까 작동하겠지라고 착각해서
음악 볼륨이 안들렸었다.

Tag와 Layer의 차이는 이러하다.

Tag는 스크립트 레벨에서 이게 플레이어야, 이건 적이야 처럼 의믜 단위로 오브젝트를 구분한다.
문자열 한개만 지정가능
주로 CompareTag(), FindGameObjectsWithTag() 등으로 사용한다.
런타임 스크립트에서 빠르게 특정역할의 오브젝트만 골라내고 싶을때 유용하다.

Layer의 경우 물리충돌 처리, 카메라 렌더링, 광선 충돌필터링등 시스템 수준에서 오브젝트를 분류한다.
0~31번까지의 총 32개의 숫자레이어 사용하며
비트마스크 형태로 여러 레이어를 한번에 조합가능하다.
Physics Collision Matrix에서 “어떤 레이어끼리 충돌할지”를 세밀하게 설정
Camera Culling Mask로 “어떤 레이어를 카메라가 그릴지” 결정
Raycast 시 특정 레이어만 걸러낼 때도 사용

[DisallowMultipleComponent] 어트리뷰트에 대해서알아보자.
말그대로 하나의 오브젝트에 이 스크립트를 붙인 컴포넌트가 여러개 붙는걸 막아준다.
로직이 중복실행되는 버그같은걸 예방하기위해 한번만 붙도록 강제한다.
 

의문점해결
메서드에 파라메터에 특정 타입으로 인자를 받는경우가있다.
그리고 메서드의 스코프내에서 해당 인자를 통해 접근해서 해당 인자의(인자가 클래스타입인경우) 메서드들을 쓰는경우를
자주 본다.
그런데 어떨때는 해당 메서드에 파라메터에서 받은 인자의 타입에 대한 변수가
클래스의 맴버로 정의되어있지않거나, 캐싱되지않는경우가 많다. 
어떨때는 맴버로 정의하고 어떨때는 안하니까 그 사용성 구분에대해서 궁금했다.
위와 같은 상황에선
1. 메서드 내부에서 한번만 쓰인다.
파라미터가 그 메서드안에서만 사용되고 호출이 끝나면 더 이상 참조할 일이없다면
로컬 파라미터로 두는게 맞다.
메서드의 시그니처와 구현만으로 충분히 의도가 드러나고 불필요한 상태 관리가없어지기때문이다.

그러나 
2. 여러 메서드에서 반복적으로사용해야하거나, 여러프레임걸쳐 상태로 남겨야할때는
예를들어 PlayerController controller 처럼 다른 메서드에서도 자주 GetComponent<T>()로 꺼내쓰거나
Awake() 시 한번 받아와서 여러군데서 참조한다면 필드나 프로퍼티로 가지고 있는 편이 성능과
가독성 면에서 유리하다.

카메라 클리핑해결을위해선( 땅을 바라볼때 땅 매쉬를 파고들어가버렸다)
각도에대한부분도 건들수있지만
main camera 인스펙터창에 들어가서 clipping planes 의 near 수치를 줄여주면된다. 0.3에서 0.05까지 줄여줬다.
Near Clip Plane은 카메라가 렌더링을 시작하는 가장 가까운 거리를 정의한다.
이 값보다 가까이있는 모든 지형, 오브젝트는 카메라보다 더 앞에 있다고 판단하고 화면에서 잘려버린다.
기존에 0.3일때는 카메라보다 0.3 앞에있는 지형부터 렌더링하니 시선을 기울여서 땅을 바라볼때
0.3m안에 들어가지는 지형들은 렌더링이 안되었고 뚫려버렸던것이다.
0.05로 줄여주니 0.05 지점 앞부터 렌더링을 시작해서 지형을 파고들지않았다.


딕셔너리의 경우 
Add메서드는 키와 값이 모두 필요하고 Add(Key,Value)
Remove메서드는 키만 파라메터에 넣어주면 해당 키 뿐아니라 키에 대응하는 값을 찾아 지워준다. Remove(Key)
둘이 필요로하는 인자가 다르다는걸 알자.

화면 UI에 여러가지 아이콘이나 슬라이더를 띄워주기위해
빈오브젝트에 Vertical LayoutGroup이나 Grid Layout Group등을 컴포넌트로 달아주는것을 배웟었다.
그런데, 그안에 동적으로 버프아이콘들이 들어갈때는 간격조절이 처음에 쉽지않았다.
두가지 설정을 통해 간격을 원하는대로 자연스럽게 설정가능한데,
우선 Horizontal LayoutGroup을 통해 가로로 길게 버프들을 표시한다고 생각해보자.
해당 컴포넌트의 옵션중
Child Force Expend가 켜져있어서 예를들어 자식으로 들어간 버프프리팹(아이콘, 정보등 표시)의 간격이 완전히
해당 layout 길이만큼 채워지기전까지는 멀리떨어져있는 상태일것이다.
Width, Height 모두 꺼주면 프리팹간의 간격이 바짝 붙게된다. 여기서 spacing이나 padding 값을 만져서 자식간의
간격을 조절해줄 수 있다.
두번째는 자식프리팹의 갯수가 계속 늘어날텐데, layoutGroup의 크기가 정해져있으면 삐져나와버린다.
이걸 해결해주기위해서, 해당 LayoutGroup이 붙어있는 빈오브젝트에 Content Size Fitter 컴포넌트를 추가해준다.
현재 Horizontal Layout Group이니까 그안에 옵션중에 Horizontal FIt만 Preferred Size로 바꿔주면
해당 빈오브젝트의 자식인 버프프리팹들이 늘어날때마다 동적으로 그 Layout의 사이즈도 바뀌게 된다.

헷갈려서 한번더 정리,
Action<T> 변수의 경우 해당 Action 델리케이트가 T타입의 인자를 매개변수로 받겠다는뜻이다.
즉 해당 Action에 구독하는 메서드들도 반드시 같은 시그니처로서 T타입의 인자를 매개변수로 받아야한다.
Action이 대신해서 Invoke를 통해 그 구독한 메서드를 실행시킬것이기때문에 같은 매개변수를 받는것이다.

 

physics기반 캐릭터이동에 있어서는
charactermovement의 경우 낑김현상등 예외사항이 많이발생한다
단순히 rigidbody를 쓴다고 플레이어가 기대하는 게임스러운 조작감, 자연스러운 반응등이 자동으로안나옴
혼자 r&d 하는건 비효율적이다.
예외처리와 physics시스템에 대한 이해도 역시 높아야한다. 그리고 각종 수학적인 함수들을 사용할 수 잇어야한다.
따라서 이미 잘 구현된 코드 참고하고 사용하면 좋다.

지형에 따라 자연스럽게 잘 움직이게하려면 예외처리할게 매우많다.
이미구현된 코드들을 많이 보자.
https://github.com/Jan-Ott/CharacterMovementFundamentals?tab=readme-ov-file
참고링크

실무가서도..길찾기 알고리즘 a* 이런것도 잘만들어진것 그냥 쓴다..