Sparta Unreal 부트캠프 112일차

정찬호·2026년 5월 12일

코딩 테스트 연습


프로그래머스 - 숫자 카드 나누기

이전에 풀어본 적 있는 문제네요.
최대한 코드를 보지 않고 풀어야겠습니다.

#include <string>
#include <vector>
#include <algorithm>

using namespace std;

int GCD(int a,int b){return a%b==0?b:GCD(b,a%b);}
int GetGCD(vector<int> &a,vector<int> &b)
{
    int gcd=a[0];
    for(int A:a)
        gcd=GCD(A,gcd);
    for(int B:b)
        if(B%gcd==0)
            return 0;
    return gcd;
}
int solution(vector<int> arrayA, vector<int> arrayB) {
    int answer = 0;
    answer=max(GetGCD(arrayA,arrayB),GetGCD(arrayB,arrayA));
    return answer;
}

철수와 영희는 숫자가 하나씩 적힌 카드들을 절반씩 나누어 가졌다.
다음 두 조건 중 하나를 만족하는 가장 큰 양의 정수 a의 값을 구해야 한다.

철수의 카드들의 공약수이자, 영희의 카드들의 약수가 되지 않는 a
영희의 카드들의 공약수이자, 철수의 카드들의 약수가 되지 않는 a

두 수열에 대해 최대 공약수를 구하고 그 공약수의 약수들 중 다른 수열에 수를 나눌 수 없는 가장 큰 수를 구해야 겠네요.
양쪽에서 찾을 수 없으면 0으로 두고요.

오늘의 내 코드

#include <string>
#include <vector>
#include <algorithm>

using namespace std;

int GetGCD(int a, int b)
{
    if(a % b != 0)
    {
        int temp = a % b;
        a = b;
        b = temp;
        return GetGCD(a, b);
    }

    return b;
}

int GetArrayGCD(const vector<int>& array)
{
    int gcd = array[0];
    
    for(int i = 1; i < array.size(); i++)
    {
        gcd = GetGCD(gcd, array[i]);
    }
    
    return gcd;
}

int solution(vector<int> arrayA, vector<int> arrayB) {
    int answer = 0;
    
    int gcdA = GetArrayGCD(arrayA);
    int gcdB = GetArrayGCD(arrayB);
    
    vector<int> measureA, measureB;
    
    for(int i = gcdA; i >= 1; --i)
    {
        if(gcdA % i == 0)
        {
            measureA.push_back(i);
        }
    }
    
    for(int i = gcdB; i >= 1; --i)
    {
        if(gcdB % i == 0)
        {
            measureB.push_back(i);
        }
    }
    
    for(int measure : measureA)
    {
        bool isA = true;
        for(int arr : arrayB)
        {
            if(arr % measure == 0)
            {
                isA = false;
                break;
            }
        }
        
        if(isA)
        {
            answer = measure > answer ? measure : answer;
            break;
        }
    }
    
    for(int measure : measureB)
    {
        bool isA = true;
        for(int arr : arrayA)
        {
            if(arr % measure == 0)
            {
                isA = false;
                break;
            }
        }
        
        if(isA)
        {
            answer = measure > answer ? measure : answer;
            break;
        }
    }
    
    return answer;
}

이야.. 이전 코드와 비교했을 때 코드량 차이가 어마무시하네요.
아마 다른 분의 코드를 보고 저렇게 줄일 수 있구나? 라는 생각을 하면 따라 해봤던 기억이 나는 것 같기도 하고?

GetGCD를 삼항 연산자를 통해 한줄로 줄이고
각 배열의 GCD를 구하고 다른 배열에서 나누어지는 지 체크하는 것도 하나의 함수로 처리하고 있네요.
max로 양쪽의 결과값 중 더 큰 것을 찾아내기도 하고요.

그런데 gcd보다 더 작은 약수가 정답일 경우는 어떻게 되는 거죠?

gemini에게 물어보고 답변 듣기

결론: GCD보다 작은 약수는 고려할 필요가 없습니다.결론부터 말씀드리면, 어떤 약수 dd가 조건을 만족한다면, 그 dd를 포함하는 최대공약수 GG도 반드시 조건을 만족하거나 혹은 더 나은 후보가 됩니다. 따라서 가장 큰 값을 찾는 이 문제에서는 최대공약수만 확인하면 충분합니다.그 이유는 다음과 같습니다:
1. 공약수의 성질:
철수 카드들의 어떤 공약수 dd가 있다면, dd는 항상 철수 카드들의 최대공약수인 GAG_A의 약수입니다. (dGAd \mid G_A)

  1. 나누어떨어지지 않는 조건:
    우리의 목표는 "영희의 카드들 중 dd로 나누어떨어지는 것이 하나도 없는" 상태를 찾는 것입니다.만약 최대공약수 GAG_A로 영희의 카드들을 나누었을 때, 나누어떨어지는 카드가 하나도 없다면, GAG_A 자체가 우리가 찾는 가장 큰 aa의 후보가 됩니다.만약 최대공약수 GAG_A로 영희의 카드 중 어떤 카드 BiB_i가 나누어떨어진다면, GAG_A의 약수인 dd 역시 BiB_i를 나누어떨어지게 할 가능성이 생깁니다. (물론 안 그럴 수도 있지만, 우리는 가장 큰 수를 찾아야 합니다.)

수학적인 핵심 포인트
이 문제의 핵심은 "철수의 모든 카드를 나누어 떨어뜨리는 가장 큰 수"가 바로 GCD(arrayA)GCD(arrayA)라는 점입니다.만약 GCD(arrayA)GCD(arrayA)가 영희의 카드 중 하나라도 나누어 떨어뜨린다면, 그보다 작은 GCD(arrayA)GCD(arrayA)의 약수들을 검사해 볼 필요가 있을까요?설령 그 약수 중 하나가 영희의 카드들을 하나도 나누지 못한다고 하더라도, 이미 GCD(arrayB)GCD(arrayB) 쪽에서 더 큰 숫자가 나올 가능성이 훨씬 높으며, 결정적으로 최대공약수가 안 되면 그 약수도 안 될 확률이 높거나, 되더라도 최대값이 될 수 없습니다.

모의 면접 피드백


기술적인 질문에 대해 생각 후 답변한 것
고민하고 답변하는 모습
답변할 때는 답변자의 시간이기에 차분히 시간을 가져가서 답변하는 것은 좋았다


정확하지 않으면 답변하지 말자
"정확하지 않지만 Tick 간격을 조정할 수 있는 것으로 합니다." -> 꼬리질문이 들어온다.
꼬리질문에 답변을 못하면 멘탈이 흔들린다.

기술적인 질문들은 모범적으로 답변, 이미 답변으로도 좋은데 더 나아가서 Delegate 등으로 특정 이벤트에만 동작하게 한다 같은 식으로 대답하면 더 좋았을 것.
Stack의 선입 후출, Heap 프로그래머가 원할 때 할당/해제, Stack은 자동 할당/해제, Heap은 new/delete, stack은 그냥 선언
-> 좀 부족한 답변

스마트 포인터 - 메모리 누수를 위해 왜 필요하냐?를 물어봤을 때
로우 포인터 사용하면 왜 안되냐를 마지막에 물어봤는데 메모리 릭, 더블 프리, 댕글링 포인터

언리얼 스마트 포인터
TObjectPtr은 애매한 답변
원했던 답변은 TSharedPtr, TWeakPtr 등이었습니다.
뭐 나쁘지는 않았습니다.

일반 스마트 포인터와 언리얼의 스마트 포인터의 차이 까지 질문이 나올 수 있기 때문에 잘 알아둘 것

오브젝트 풀링 설명은 잘 되었다.


욕심을 부리자면
Rendering Pipeline은 제대로 답변하지 못했습니다.
IA -> VS -> Resterize -> PS -> OM
GS는 어디에?

이런 것 말고도 행렬 변환 과정의 Rendering Pipeline이 존재. 클라이언트에게 가까운 쪽은 이쪽이다.
world space --> --> Screen Space로의 변환 과정을 알아두자.

코드 리뷰가 비판적일 때 고운 워딩이 안 나올 것 같다는 답변은 안하는 것이 좋다.
결과물에 대한 평가지, 나 자신에 대한 평가는 아니다. 이를 알고 수용하고 받아들 수 있다는 식으로 말할 것.

자기소개했을 때부터 답변이 나아가면서 개발자로서의 주관이나, 어떤 사람인지에 대한 스토리가 사라지는 경향이 존재합니다.
개발자로서 나는 어떤 신념을 가지고 있고, 나는 어떤 사람이고, 팀원들과의 화합을 위해 나는 어떤 일을 했고, 같은 것이 머리속에 펼쳐질 수 있게 이야기하자.

스토리가 앞뒤 맥락이 맞지 않는 내용이 있는 것 같아 자기소개부터 마지막 답변까지 스토리를 이을 수 있게 답변할 수 있도록 만들기.

면접 경험이 부족하다는 것이 느껴진다.
동기들과 모의 면접을 진행해보는 것도 좋다.
ChatGPT등의 AI에게 음성채팅으로 모의 면접 상황을 설정하고 면접을 진행해보는 것도 좋다.
카메라도 포함시키면 더 좋다.
제일 좋은 것은 사람을 만나서 직접 대면해서 하는 것
지금은 집이라는 심리적 편안한 공간 + 캠으로 인해 직접 대면 안함으로 부담이 적음에도 상당히 긴장한 것이 느껴집니다.

소프트 스킬은 중요.



AnimLayerInterface - 김조은 튜터님 오후 3시


ALI(Animation Layer Interface)란?

애니메이션 블루프린트의 로직을 모듈화하기 위한 인터페이스입니다. 인터페이스처럼 레이어의 "틀"만 정의해두고, 실제 구현은 각 ABP에서 담당합니다. 이를 통해 캐릭터 본체 ABP와 무기/상태별 ABP를 분리해서 관리할 수 있습니다.

문서들:

https://dev.epicgames.com/documentation/unreal-engine/using-animation-blueprint-linking-in-unreal-engine
https://dev.epicgames.com/documentation/unreal-engine/animation-blueprint-linking-in-unreal-engine


클래스 디폴트에서 애님레이어 상속 및 구현을 지정? 확인할 수 있습니다.

클래스 디폴트에서 확인하는 것

클래스 디폴트(Class Defaults)에서 어떤 ALI를 상속(Implement)하고 있는지, 그리고 현재 링크된 레이어 클래스가 무엇인지 확인할 수 있습니다. Linked Anim Layer 항목에서 각 레이어에 어떤 ABP가 연결되어 있는지 기본값을 설정할 수 있습니다.


애니메이션 블루 프린트를 만들 때 탬플릿으로 만들면 미리 로직을 구현해 둘 수 있고,
이걸 상속해서 ABP를 만들 때 스켈레탈 메쉬를 지정하면 된다고 합니다.
이런게 있다는 것을 처음 알았습니다.

템플릿 ABP

애니메이션 블루프린트를 생성할 때 Template Animation Blueprint으로 만들면, 특정 스켈레탈 메쉬에 종속되지 않고 로직만 미리 구현해둘 수 있습니다. 이후 이 템플릿을 상속(Inherit) 해서 새 ABP를 만들 때 스켈레탈 메쉬를 지정해주면, 로직은 그대로 가져오면서 다른 메쉬에 적용할 수 있습니다. 리타기팅 없이 여러 캐릭터에 같은 애니메이션 로직을 재사용할 때 유용합니다.


ALI를 사용하려면 드래그해서 AnimGraph에 놓으면 배치가 됩니다.
구현이 안된 경우 Input을 그대로 Output에 연결합니다.
이는 몽타주와 같이, 몽타주의 Defalut Slot 같은 경우 몽타주 재생이 없으면 입력값을 그대로 전달하는 것처럼 구현이 안 되어 있다면 그냥 전달하기 위함입니다.

ALI 배치 및 미구현 시 처리

ALI를 AnimGraph에 드래그해서 놓으면 해당 레이어의 노드 블록이 배치됩니다.
구현이 안 된 레이어는 Input 포즈를 그대로 Output에 연결하는데, 이는 패스스루(Pass-through) 처리로, 해당 레이어가 활성화되지 않았을 때 원본 포즈를 손상 없이 흘려보내기 위함입니다. 몽타주의 Default Slot과 같은 개념입니다.


Link Anim Class Layer 노드로 덮어쓸 ALI와 가져올 ALI를 잘 지정해야 합니다.

이 노드는 런타임에 특정 ALI 레이어를 다른 ABP로 교체하는 노드입니다.

In Layer : 교체할 대상 레이어 (어떤 ALI 레이어인지)
In Class : 실제로 연결할 ABP 클래스

이 두 가지를 정확히 맞춰야 합니다. 잘못 지정하면 아무 반응이 없거나 크래시가 발생할 수 있습니다.


1. 별도의 C++ AnimInstance 사용

무기 전용 AnimInstance를 C++로 만들어서 사용할 경우, ABP의 Event Graph에서 같은 로직을 또 돌리면 NativeUpdateAnimation과 BlueprintUpdateAnimation이 중복 실행되어 변수가 두 번 갱신되거나 의도치 않은 동작이 발생할 수 있습니다. C++에서 처리한 내용은 ABP에서 중복 구현하지 않도록 주의해야 합니다.

2. 미리 로딩 (하드 레퍼런스)

Link Anim Class Layer로 런타임에 연결할 ABP 클래스를 본체 ABP의 변수로 미리 선언해두면, 하드 레퍼런스이기 때문에 본체 AnimInstance가 생성될 때 해당 ABP도 함께 메모리에 로드됩니다. 소프트 레퍼런스로 두면 런타임 로딩 타이밍 문제로 연결 실패가 날 수 있으니 주의가 필요합니다.

3. 블렌딩 미지원

ALI 레이어 간 전환 시 자동 블렌딩이 없습니다. Link Anim Class Layer는 즉각적으로 교체됩니다.

자동으로 Blend 되는 방법을 튜터님이 Claude로 알아봐 주셨는데 그 링크는 아래와 같습니다.
https://claude.ai/share/cb2e0fd7-b2f7-482b-82f4-940a917e2567

Lyra를 열어봐도 Layer blend per bone으로 일일히 Blend해주고 있습니다.
아 이거 아니라고 하네요.
보원님 : layered blend per bone은
스켈레톤 마스크/본 계층별로 블렌딩하는거라
저렇게 애님 간 블랜드에는
해당사항이 없긴해요

Lyra에서 Layered Blend Per Bone을 사용하는 것은 상체/하체처럼 본 계층을 나눠서 다른 애니메이션을 섞는 것이고, 보원님 말씀대로 이것은 ALI 전환 시의 블렌딩과는 별개입니다. ALI 전환 간 부드러운 블렌딩을 원한다면 별도의 블렌드 로직(예: Custom Blend Space, 수동 Alpha 처리)을 직접 구현해야 합니다.



에셋 탐색

https://www.fab.com/listings/81d0200f-825e-45f2-9fe8-ae4ee18708be
톤앤 매너 합격

https://www.fab.com/listings/fe3fd371-1808-4854-9b3e-fe7508b5a8e7
비쌈

https://www.fab.com/listings/c4e83f22-369a-4f5c-8f86-d53ccd716ae6
https://www.fab.com/listings/838bc247-437d-4429-bfe0-6ff0aa31842c
https://www.fab.com/listings/552df954-0bf2-4c2d-bb7d-c624805262ba
레퍼런스

https://www.fab.com/listings/52529a12-e88e-41a0-8834-b87306f20c24
라이팅 뽑아오기?

profile
게임 개발 지망생입니다.

0개의 댓글