02/01 본캠프 #28

guno park·2024년 2월 1일
0

본캠프

목록 보기
28/77
post-thumbnail

알고리즘 풀어보기

멀리 뛰기

풀이

1과 2로 n을 만드는 모든 조합의 개수를 구하라는 의미가 담긴 문제다.
n이라는 수가 있을 때, 그 이전에 있는 모든 숫자들을 1,2로 조합할 수 있는 개수를 구해 더한다.

public class Solution {
    public long solution(int n) {
        //1칸 or 2칸만 뛸 수 있음. 1이랑 2로 n을 만드는 경우의 수를 구하라
        long answer = 0;
        int[] dp = new int[n + 1];
        
        dp[0] = 1;
        
        for (int i = 1; i <= n; i++)
        {            
            if (i - 1 >= 0)
                dp[i] += dp[i - 1]%1234567;
            if (i - 2 >= 0)
                dp[i] += dp[i - 2]%1234567;
        }
        return dp[n]%1234567;
    }
}

3D 입문 강의 정리

프로젝트 준비하기

2D -> 3D 차이점

2D에서는 첫 생성 시 메인 카메라만 존재하지만 3D에는 Directional Light가 존재한다.
이 라이트는 적당히 조절하는 편이 좋다. 빛의 연산이 리소스를 많이 사용하기 때문에 성능에 미치는 영향이 커서 부하가 걸릴 수 있다.
때문에 적절한 장소에만 사용하는게 좋다.

메테리얼 활용해 스카이박스 만들기

메테리얼 : 보여지는 재질(빛을 받아서 반사하는 색)
스카이박스는 보통 구체나 큐브모양으로 되어있음.

메테리얼의 쉐이더를 변경해 스카이박스로 만들 수 있다.
컴포넌트를 적절히 조절한 후, Ctrl+9를 누르거나

를 눌러 빛과 관련된 탭을 켤 수 있다.

Input System : Invoke Unity Event

InputAction : Mouse delta :마우스가 움직인 만큼 값을 받아온다

이전 시간에는 샌드메시지를 사용 상태로 사용해보았고, 이번 시간에는 인보크 유니티 이벤트를 사용한다.

샌드메시지는 스크립트로 Input을 받아서 연결된 동작을 처리하는 방식이고,
인보크 유니티 이벤트는 밑에 Events를 눌러 입력과 동작을 직접 연결하는 방식이다.

OnDrawGizmos & Shading Mode

OndrawGizmos를 사용하면 원래는 눈에 보이지 않는 것(Raycast)들을 눈으로 볼 수 있도록 그려준다.

오브젝트들이 복잡하게 있는 상태에서 봐야한다면 OnDrawGizmosSelected를 사용하면 된다. 선택할 때만 볼 수 있으며, 없으면 항상 보인다.

 private void OnDrawGizmos()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawRay(transform(발사 위치) , Vector3(발사 방향));        
    }

Shding Mode를 사용하면 내가 원하는 것만 볼 수 있다.
오브젝트 때문에 콜라이더가 보이지 않는 상황 등에 활용하면 좋다.

UI 만들기

HP바 만들기

지난 시간에는 HP바를 슬라이더로 만들었다.
이번에는 스프라이트만 사용한다.
※ 3d에서 2d 스프라이트 쓰려면 패키지 매니저에서 설치해야됨.

스프라이트 이미지 타입따라 사용목적이 다르다. 여기서는 filled를 사용한다.
채워지는 방향을 설정할 수 있고, 그 총량은 0~1사이로 퍼센트 값으로 결정한다.

레이아웃 그룹

레이아웃 그룹을 추가하고, 하위에 내가 생성한 UI들을 넣으면 설정한 영역에 UI들을 알아서 정렬해준다.
사이 간격이나 어느 곳을 기준으로 정렬할 것인지 사이즈는 어떻게 할 것인지 알아서 다 처리해주기 때문에 유용하게 사용할 수 있을 듯 하다.
종류는 Grid, Horizontal, Vertical 3종류가 있다.

데미지 처리하기

Clipping Planes

Camera에 달려있는 Camera컴포넌트의 요소로 현재 보고있는 시야를 조절할 수 있다.
Near는 가까운 곳, Far은 먼 곳까지의 거리를 얘기하며, Near를 0으로 설정하면 카메라의 위치부터 Far까지 보인다.

낮과 밤 구별하기

낮과밤 구별하기 위해서는 Light가 두 개 있어야한다.
해와달 정도로 구분한다고 생각하면 편하다.

스크립트 분석

[Range(0.0f, 1.0f)] 
    public float time; //현재 시간
    public float fullDayLength; //하루의 전체 시간
    public float startTime = 0.4f;
    private float timeRate; //1 / 전체 시간 => 시간 비율 맞추기
    public Vector3 noon; //정오의 각도를 얘기함 (90도)

    [Header("Sun")] 
    public Light sun;
    public Gradient sunColor; //그라데이션
    public AnimationCurve sunIntensity; //그래프에 맞춰 원하는 값들을 타임값을 꺼내올수있음

    [Header("Moon")] 
    public Light moon;
    public Gradient moonColor;
    public AnimationCurve moonIntensity;

    [Header("Other Lighting")] 
    public AnimationCurve lightingIntensityMultiplier; //자연광 세기 배율
    public AnimationCurve reflectionIntensityMultiplier; //반사광 세기 배율
 private void Update() //해가 떳다지는 한 순환 안에서 빛을 조절함.
    {
        time = (time + timeRate * Time.deltaTime) % 1.0f; //퍼센트로 쓰기위해서
        UpdateLighting(sun,sunColor,sunIntensity);
        UpdateLighting(moon,moonColor,moonIntensity);

        //환경광, 환경에서 받아오는 광들, 빛을 최소화시키려고함.
        RenderSettings.ambientIntensity = lightingIntensityMultiplier.Evaluate(time); 
        RenderSettings.reflectionIntensity = reflectionIntensityMultiplier.Evaluate(time);
    }
void UpdateLighting(Light lightSource, Gradient colorGradiant, AnimationCurve intensityCurve)
    {
        float intensity = intensityCurve.Evaluate(time); 

        lightSource.transform.eulerAngles = (time - (lightSource == sun ?
        0.25f : 0.75f)) * noon * 4.0f; //noon은 90도
        //낮과 밤이 원으로 순환한다고 생각했을 때, noon은 90도로 4번 있고, 그 중 sun일 때 첫번째 정오로 1/4 한 곳, 밤일 때는 3번째 3/4 한 곳을 지점으로 잡는다.
        lightSource.color = colorGradiant.Evaluate(time);
        lightSource.intensity = intensity;

        GameObject go = lightSource.gameObject;
        if(lightSource.intensity ==0 && go.activeInHierarchy)
            go.SetActive(false);
        else if (lightSource.intensity >0 && !go.activeInHierarchy)
            go.SetActive(true);
    }

이 중 Evaluate(time)을 사용하면

같은 그래프에서 time에 맞게 값을 가져올 수 있다.

아이템과 상호작용

통나무랑 돌 안움직이는 문제 : 인스펙터에 이름 옆에 보면 Static이라고 있는데 그게 걸려있었다.

트러블 슈팅

상호작용하는 코드에 MaxDistance가 빠져있으면 오류가 발생한다.

 if (Physics.Raycast(ray, out hit, layerMask)) //ref는 채울수도 안채울수도, out은 무조건 채워서 돌려줌.
            {
                if (hit.collider.gameObject != curInteractGameObject)
                {
                    curInteractGameObject = hit.collider.gameObject;
                    curInteractable = hit.collider.GetComponent<IInteractable>();
                    SetPromptText();
                }
            }
            else
            {
                curInteractGameObject = null;
                curInteractable = null;
                promptText.gameObject.SetActive(false);
            }

layerMask가 다르면 curInteractGameObject가 null인데 없는 정보를 보려고 해서 그렇다.
이걸 해결하려면?

 if (curInteractable != null)
        {
            promptText.gameObject.SetActive(true);
            promptText.text = string.Format("<b>[E]</b> {0}",curInteractable.GetInteractPrompt());             
        }

이런 느낌으로 조건을 걸어주면 된다.
근데 최대 거리가 없다면 무조건 찍히다 보니 프롬포트가 안닫힌다.
상호작용은 보통 거리가 있으니까 닫히는거까지 고칠생각은 안해봐도 될듯하다.

폰트설정 뜯어보기

32-126,44032-55203,12593-12643,8200-9900

영어 범위 : 32-126

한글 범위 : 44032-55203

한글 자모 : 12593-12643

특수 문자 : 8200-9900

글꼴에 따라 지원하지 않는 특수 문자는 missing

인벤토리 만들기

인벤토리용 캔버스 만들기

인벤토리처럼 캔버스를 클릭해야 한다면 캔버스레이캐스트 컴포넌트가 있어야한다.
처음 만들때부터 추가되어 있는 것 같긴하다.
그리고 캔버스끼리도 LayerOrder를 정할 수 있다.

OutLine이란 컴포넌트를 이용해서 쉽게 테두리를 칠해줄 수 있다.

AddItem 메서드

 public void AddItem(ItemData item)
    {
        if (item.canStack)
        {
            ItemSlot slotToStackTo = GetItemStack(item); //쌓을수 있는거면 기존에 아이템이 있는지 비교
            if (slotToStackTo != null)
            {
                slotToStackTo.quantity++;
                UpdateUI();
                return;
            }
        }

        ItemSlot emptySlot = GetEmptySlot();

        if (emptySlot != null)
        {
            emptySlot.item = item;
            emptySlot.quantity = 1;
            UpdateUI();
            return;
        }
        
        ThrowItem(item); //return을 계속 걸어주는건 예외처리를 다 통과했을 떄 아이템을 못먹는 거라는 느낌
    }

AddItem 하는데 return을 계속 사용해준다. 더 이상 넘어가지 말라는 느낌으로 작성하는 것 같다. 예외처리의 일환인 듯
저기까지 다 넘어왔다면 못먹으니까 버려라.

아이템 장착과 모션

무기용 카메라 분리하기

캐릭터가 무기를 들고있을 때 벽같은데 붙으면 무기가 사라지는 현상이 발생한다.
그런 현상을 방지하기 위해 카메라를 분리한다.
카메라가 두 개가 되면 오디오 리스너도 두 개가 되어서 문제가 발생한다. 하나는 삭제해주자.
카메라 컴포넌트에 컬링마스크를 통해서 무엇을 찍어줄지 결정할 수 있다.

메인 카메라는 그대로 두고 장착용 카메라의 경우 클리어플래그를 변경해주어야한다.
스카이박스로하면 덮어씌워지니까 Depth Only로 바꿔준다.

자원 채취와 공격처리

애니메이션 커브에 애드이벤트로 애니메이션 특정 구간에 이벤트를 넣어줄 수 있다.

메모리 특강

메모리 기본

메모리는 4가지 영역이 있음
Code : 프로그래머가 작성한 코드를 보관
Data : static, const, 전역변수 어플리케이션 전반에 필요한 데이터 저장
Heap : 참조 데이터(객체) 저장
new를 통해 생성, GC(가비지 컬렉터)로 인해 삭제
Stack : 로컬 변수, 매개변수 저장, 어플리케이션 실행 순서에 필요한 데이터 보관
변수 할당시 시작시 생성, { }끝날때 삭제

메모리의 주소는 2진수로 되어있다.
만약 int x =10; 이라고 하면 메모리에 10을 저장하고 주소를 x로 바꾼다고 생각하면됨.
클래스는 힙에 저장하도록 되어있다.

그래서 클래스를 변수로 받아온다는 것은 변수는 스택 주소가 되고 그 스택의 값은 힙의 주소가 된다.

메모리 오버헤드

메모리의 데이터가 삭제되는 것도 생각해야된다.
중괄호 안에서 만들어진 애들은 중괄호가 끝나는 순간 삭제가 된다.
함수가 끝나더라도 게속 저장하고 싶을때가 있다. 그때 쓰는곳이 힙. 힙은 블레이스가 끝나도 사라지지 않는다.
참조된 스택이 없으면 힙의 데이터도 사라진다. ->가비지콜렉터가 수거해감. 근데 그게 언제가 될지 모름.
그래서 업데이트같은 곳에 계속 새로운 클래스를 참조하면 가비지가 많이 생성되서 프로그래밍에 무리가 간다.

이 경우
C#할때 중요한 것은 가비지가 생기는 것을 최대한 방지해야한다.

코루틴에서도 무한루프 굴리면 yield return new WaitForSeconds(1) 계속 돌리는 것도 똑같다.

IEnumerator update()
        {
            while (true)
            {
                yield return new WaitForSeconds(1);
            }
        }

이런 경우 데이터를 한번 준비하고 재활용하는 편이 좋다.

private WaitForSeconds wait = new WaitForSeconds(1);
IEnumerator update()
        {
            while (true)
            {
                yield return wait;
            }
        }

몇몇 속성들은 업데이트 안에서 써도 문제가 없다. Vector3나 Quaternion은 구조체로함수 끝날때 바로바로 삭제되서 남는게 없다.

string 더하기

문자열끼리 더하는 게 문제가 되는 것은 우리가 생각하는 것처럼 연산이 돌아가는게 아니라는 점이다.
문자열끼리 더하면 더할때마다 새롭게 메모리를 할당한다. => 똑같은 내용을 몇개씩 가진다는 얘기
이를 두고 문자의 불변성이라 한다. (한번 할당되면 변하지 않음)
그리고 이렇게 똑같은 문자가 생기는 것을 파편화라고 한다.

해결법
1. : 스트링을 한번에 제작[보간 문자열,스트링 포맷,+연산자;
보간문자열을 추천 - 요즘에 많이쓰기도하고 보기도 편함.

2. StringBuilder의 사용
스트링빌더를 만들어놓고 하나씩 추가하다가 마지막에 ToString() 하는 방법이 있다.
1의 방법이 안되면 2를 사용하자.
스트링빌더의 경우 그냥 자리를 많이 잡아놓음. 스트링빌더도 크기를 미리 제한해둘수있다.
게임 개발을 할때는 예상치 못한 텍스트를 넣는 경우는 없음.

Data영역에 저장되는 변수

static, Const, 전역변수 - Data 영역에 저장되는 변수/ 프로그램 시작 시 생성, 프로그램 종료 시 삭제
그래서 필요할 때 바로 쓸수 있는 것, 따로 할당할 필요가 없음.

숫자를 비교한다고 쳤을 때 숫자만 써놓으면 뭔지 알기가 힘들다. 그런 면에서 Const데이터로 선언해서 비교해준다고 하면 알기쉽다. 그리고 여러곳에서 쓸 경우 변수를 이용하면 한번에 값을 바꾸기 쉽다.

private float time;

void Update(){
	time += Time.deltaTime;
    if(time >0.0f)
}
//이것 보다는
private float time;
private const float starttime =0.0f;

void Update(){
	time += Time.deltaTime;
    if(time >starttime) //이렇게 작성하는 게 이해하기도 쉽고, 값을 변경하기도 용이하다.
}

정리

스택에 할당 : 값 형식 (Call By Value) - C# 기본 타입 (int, float, bool, char 등), 구조체, enum
힙에 할당 : 참조 형식 (Call By Reference) - string class 배열 interface delegate

스택이 힙보다 빠르지만 용량이 작다.

값형은 재활용하는 것보다 쓰고 없에는게 좋다.

구조체와 클래스의 차이 | 값형과 참조형 + 구조체는 상속이 안됨.

면접에서 이런 질문이 자주 나온다고 한다. 출제자의 의도를 파악하자면 클래스와 구조체의 차이를 물어보는 것은 메모리 할당에 대해 인지하고 있는 지 확인하는 것이다.
대답을 서술적으로 할 필요가 없다. 오히려 복잡해진다.

코딩 팁

  • 특정 공통 부분을 바꿔야 할 때 알트 드래그하면 원하는 부분만큼 선택된다.
  • 생각보다 폰트 용량이 크니까 너무 많이 가지고있지말자

  • enum에 한글도 들어간다.

0개의 댓글