Sparta Unreal 부트캠프 111일차

정찬호·2026년 5월 11일

코딩 테스트 연습


프로그래머스 - 마법의 엘리베이터

이전 코드

#include <string>
#include <vector>

using namespace std;

int solution(int storey) {
    int answer = 0;
    int maxDividor = 1;
    int dividor = 1;
    
    while(maxDividor < storey)
    {
        maxDividor *= 10;
    }
    
    while(dividor <= maxDividor)
    {
        int nowNum = storey / dividor;
        nowNum %= 10;
        int nextNum = storey / (dividor * 10);
        nextNum %= 10;
        
        if(nowNum > 5 
           || (nowNum == 5 && nextNum >= 5))
        {
            answer += (10 - nowNum);
            storey += (10 - nowNum) * dividor;
        }
        else
        {
            answer += nowNum;
            storey -= nowNum * dividor;
        }
        dividor *= 10;
    }
    
    if(storey != 0)
    {
        answer++;
    }
    return answer;
}

주말이 지나 오늘 코드를 보니 문제점을 알겠습니다.
maxDividor을 구할 때 storey보다 커질 때 까지 maxDividor * 10을 하고 있는데,
maxDividor은 storey 보다 작거나 같은 값이니 10을 마지막에 나누어 주어야 하네요.

수정된 코드 - 통과됨

#include <string>
#include <vector>

using namespace std;

int solution(int storey) {
    int answer = 0;
    int maxDividor = 1;
    int dividor = 1;
    
    while(maxDividor <= storey)
    {
        maxDividor *= 10;
    }
    
    maxDividor /= 10;
    
    while(dividor <= maxDividor)
    {
        int nowNum = storey / dividor;
        nowNum %= 10;
        int nextNum = storey / (dividor * 10);
        nextNum %= 10;
        
        if(nowNum > 5 
           || (nowNum == 5 && nextNum >= 5))
        {
            answer += (10 - nowNum);
            storey += (10 - nowNum) * dividor;
        }
        else
        {
            answer += nowNum;
            storey -= nowNum * dividor;
        }
        dividor *= 10;
    }
    
    if(storey != 0)
    {
        answer++;
    }
    return answer;
}

아 이거 정답 처리 받았던 문제였네요.
잘못 풀었다.


프로그래머스 - 거리두기 확인하기

이 문제가 이전에 풀다 말았던 문제네요.

이전 코드

#include <string>
#include <vector>
#include <queue>

using namespace std;

struct Node
{
    pair<int, int> prevPos;
    pair<int, int> curPos;
};

bool CheckPInRange(const vector<string>& map, pair<int, int> pos)
{
    vector<pair<int,int>> dir = {{0,1}, {0, -1}, {-1, 0}, {1, 0}};
    
    queue<Node> nodes;
    Node node;
    node.prevPos = pos;
    node.curPos = pos;
    
    while(!nodes.empty())
    {
        Node curNode = nodes.front();
        nodes.pop();
        pair<int, int> prev = curNode.prevPos;
        pair<int, int> cur = curNode.curPos;
        
        for(int i = 0; i < 4; i++)
        {
            pair<int, int> newPos = cur;
            newPos.first += cur.first + dir[i].first;
            newPos.second += cur.second + dir[i].second;
            
            if(newPos.first < 0 || newPos.first >= 5
              || newPos.second < 0 || newPos.second >= 5)
            {
                continue;
            }
            else if(map[newPos.first][newPos.second] == 'X')
            {
                continue;
            }
            else if(newPos == prev)
            {
                continue;
            }
            else if(map[newPos.first][newPos.second] == 'P')
            {
                return true;
            }
            
            Node newNode;
            newNode.prevPos = cur;
            newNode.curPos = newPos;
            nodes.push(newNode);
        }
    }
    return false;
}

vector<int> solution(vector<vector<string>> places) {
    vector<int> answer;
    
    for(const vector<string>& place : places)
    {
        vector<pair<int, int>> points;
        
        bool bIsSafe = true;
        for(int i = 0; i < place.size(); i++)
        {
            for(int j = 0; j < place[i].size(); j++)
            {
                if(place[i][j] == 'P')
                {
                    if(CheckPInRange(place, make_pair(i, j)))
                    {
                        bIsSafe = false;
                        break;
                    }
                }
            }
            
            if(!bIsSafe)
            {
                break;
            }
        }
        
        answer.push_back(bIsSafe ? 1 : 0);
    }
    return answer;
}

음 코드를 다시 살펴보니 CheckPInRange에서 queue에 집어넣는 코드가 빠져있고, 거리를 체크하는 것도 빠져있네요.

수정 코드 - 실패 정답률 하락

#include <string>
#include <vector>
#include <queue>

using namespace std;

struct Node
{
    pair<int, int> prevPos;
    pair<int, int> curPos;
};

bool CheckPInRange(const vector<string>& map, pair<int, int> pos)
{
    vector<pair<int,int>> dir = {{0,1}, {0, -1}, {-1, 0}, {1, 0}};
    
    queue<Node> nodes;
    Node node;
    node.prevPos = pos;
    node.curPos = pos;
    nodes.push(node);
    
    while(!nodes.empty())
    {
        Node curNode = nodes.front();
        nodes.pop();
        pair<int, int> prev = curNode.prevPos;
        pair<int, int> cur = curNode.curPos;
        
        for(int i = 0; i < 4; i++)
        {
            pair<int, int> newPos = cur;
            newPos.first += cur.first + dir[i].first;
            newPos.second += cur.second + dir[i].second;
            
            int dist = abs(pos.first - newPos.first) +
                abs(pos.second - newPos.second);
            if(dist > 2)
            {
                continue;
            }
            
            if(newPos.first < 0 || newPos.first >= 5
              || newPos.second < 0 || newPos.second >= 5)
            {
                continue;
            }
            else if(map[newPos.first][newPos.second] == 'X')
            {
                continue;
            }
            else if(newPos == prev)
            {
                continue;
            }
            else if(map[newPos.first][newPos.second] == 'P')
            {
                return true;
            }
            
            Node newNode;
            newNode.prevPos = cur;
            newNode.curPos = newPos;
            nodes.push(newNode);
        }
    }
    return false;
}

vector<int> solution(vector<vector<string>> places) {
    vector<int> answer;
    
    for(const vector<string>& place : places)
    {
        vector<pair<int, int>> points;
        
        bool bIsSafe = true;
        for(int i = 0; i < place.size(); i++)
        {
            for(int j = 0; j < place[i].size(); j++)
            {
                if(place[i][j] == 'P')
                {
                    if(CheckPInRange(place, make_pair(i, j)))
                    {
                        bIsSafe = false;
                        break;
                    }
                }
            }
            
            if(!bIsSafe)
            {
                break;
            }
        }
        
        answer.push_back(bIsSafe ? 1 : 0);
    }
    return answer;
}

정확성 30.2%를 받았습니다.

코드를 다시 살펴보니 또 잘못된 부분을 확인했습니다.

        for(int i = 0; i < 4; i++)
        {
            pair<int, int> newPos = cur;
            newPos.first += cur.first + dir[i].first;
            newPos.second += cur.second + dir[i].second;

newPos.first = cur.first + dir[i].first ; 나
newPos.first += dir[i].first; 로 작성했어야 했네요.

수정하니 예시 테스트 케이스에서 실패하네요.

어디에서 또 잘못되어 있을까..

거리가 2여도 한바퀴 돌아서 제자리로 돌아올 수 있었네요.
일단 원위치로 돌아오는 것을 막도록 수정했습니다.

#include <string>
#include <vector>
#include <queue>

using namespace std;

struct Node
{
    pair<int, int> prevPos;
    pair<int, int> curPos;
};

bool CheckPInRange(const vector<string>& map, pair<int, int> pos)
{
    vector<pair<int,int>> dir = {{0,1}, {0, -1}, {-1, 0}, {1, 0}};
    
    queue<Node> nodes;
    Node node;
    node.prevPos = pos;
    node.curPos = pos;
    nodes.push(node);
    
    while(!nodes.empty())
    {
        Node curNode = nodes.front();
        nodes.pop();
        pair<int, int> prev = curNode.prevPos;
        pair<int, int> cur = curNode.curPos;
        
        for(int i = 0; i < 4; i++)
        {
            pair<int, int> newPos = cur;
            newPos.first += dir[i].first;
            newPos.second += dir[i].second;
            
            int dist = abs(pos.first - newPos.first) +
                abs(pos.second - newPos.second);
            if(dist > 2 || newPos == prev || newPos == pos)
            {
                continue;
            }
            
            if(newPos.first < 0 || newPos.first >= 5
              || newPos.second < 0 || newPos.second >= 5)
            {
                continue;
            }
            else if(map[newPos.first][newPos.second] == 'X')
            {
                continue;
            }
            else if(map[newPos.first][newPos.second] == 'P')
            {
                return true;
            }
            
            Node newNode;
            newNode.prevPos = cur;
            newNode.curPos = newPos;
            nodes.push(newNode);
        }
    }
    return false;
}

vector<int> solution(vector<vector<string>> places) {
    vector<int> answer;
    
    for(const vector<string>& place : places)
    {
        vector<pair<int, int>> points;
        
        bool bIsSafe = true;
        for(int i = 0; i < place.size(); i++)
        {
            for(int j = 0; j < place[i].size(); j++)
            {
                if(place[i][j] == 'P')
                {
                    if(CheckPInRange(place, make_pair(i, j)))
                    {
                        bIsSafe = false;
                        break;
                    }
                }
            }
            
            if(!bIsSafe)
            {
                break;
            }
        }
        
        answer.push_back(bIsSafe ? 1 : 0);
    }
    return answer;
}

이동 횟수 제한도 추가했습니다. 거리 2니까 4 정도로 제한했습니다.

#include <string>
#include <vector>
#include <queue>

using namespace std;

struct Node
{
    pair<int, int> prevPos;
    pair<int, int> curPos;
    int moveCnt = 0;
};

bool CheckPInRange(const vector<string>& map, pair<int, int> pos)
{
    vector<pair<int,int>> dir = {{0,1}, {0, -1}, {-1, 0}, {1, 0}};
    
    queue<Node> nodes;
    Node node;
    node.prevPos = pos;
    node.curPos = pos;
    node.moveCnt = 0;
    nodes.push(node);
    
    while(!nodes.empty())
    {
        Node curNode = nodes.front();
        nodes.pop();
        pair<int, int> prev = curNode.prevPos;
        pair<int, int> cur = curNode.curPos;
        
        for(int i = 0; i < 4; i++)
        {
            pair<int, int> newPos = cur;
            newPos.first += dir[i].first;
            newPos.second += dir[i].second;
            
            int dist = abs(pos.first - newPos.first) +
                abs(pos.second - newPos.second);
            if(dist > 2 || newPos == prev || newPos == pos
              || curNode.moveCnt >=4)
            {
                continue;
            }
            
            if(newPos.first < 0 || newPos.first >= 5
              || newPos.second < 0 || newPos.second >= 5)
            {
                continue;
            }
            else if(map[newPos.first][newPos.second] == 'X')
            {
                continue;
            }
            else if(map[newPos.first][newPos.second] == 'P')
            {
                return true;
            }
            
            Node newNode;
            newNode.prevPos = cur;
            newNode.curPos = newPos;
            newNode.moveCnt = curNode.moveCnt + 1;
            nodes.push(newNode);
        }
    }
    return false;
}

vector<int> solution(vector<vector<string>> places) {
    vector<int> answer;
    
    for(const vector<string>& place : places)
    {
        vector<pair<int, int>> points;
        
        bool bIsSafe = true;
        for(int i = 0; i < place.size(); i++)
        {
            for(int j = 0; j < place[i].size(); j++)
            {
                if(place[i][j] == 'P')
                {
                    if(CheckPInRange(place, make_pair(i, j)))
                    {
                        bIsSafe = false;
                        break;
                    }
                }
            }
            
            if(!bIsSafe)
            {
                break;
            }
        }
        
        answer.push_back(bIsSafe ? 1 : 0);
    }
    return answer;
}


ssd 구매 조언 정리하기


메인보드 prime b365m-a, GPU GTX-2060
메인보드 호환 여부, DRAM 지원, 발열 등등 알아봐야 할 것이 너무 많습니다.
몇 년전에는 9만원이던 것이 지금은 34만원이고 가격대가 장난 아니네요.
이게 내려가긴 할 지...

동생과 같이 찾아본 결과

https://www.yopc.co.kr/shop/item.php?it_id=5565683998
발열이 적고 가성비가 좋다고 합니다. 이때 DRAM이 중요하다는 것을 알게 되었습니다.
SATA, M.2라는 SSD 종류가 존재한다는 것도 알게 되었네요.

김조은 튜터님과 상담한 결과

1TB 살돈이면 돈 더 보태서 2TB를 사는 게 나은 상황이라 아예 중고를 싸게 살 것인지
추후 업그레이드를 생각해서 4.0이나 5.0으로 살 것인지 -> 현재 보드는 3.0 지원 더 높은 것 호환 여부 확인 필요, 호환되도 3.0 수준으로 하향될 것

https://prod.danawa.com/info/?pcode=17788451&cate=112760
4.0, 2TB 52만

https://www.daangn.com/kr/buy-sell/xpg-sx8200-pro-m-2-ssd-512gb-1tb-sd9g499mqhhm/
중고, 보증기간 끝남, 1TB 15만

김봉재 튜터님과 상담하기 - 오후 4시


괜찮은 중고 전문 판매 업체를 소개받아 그곳에서 동생과 찾아던 ssd를 절반보다 높은 수준의 가격으로 구매하게 되었습니다.

2TB도 좋지만 지금 메인보드 상태상 4.0은 제대로 받아들이지 못하고 최대한 언리얼에만 사용한다면 용량이 괜찮을 것이라는 이야기를 해주셨습니다.



면접 팁 - 김조은 튜터님

https://mystisle.notion.site/35d25e4e7f808021a6aee9fb57b510dd?pvs=73

모의면접 준비

  • 면접이 잡힌 이유는 서류만으로 충분히 일할 수 있는 인재라고 생각되기 때문
  • 면접을 하는 이유
    • 실제 포트폴리오가 거짓이 없는지 검증.
    • 기초가 얼마나 탄탄한지 → 포텐셜 → 육성 코스트가 적게 드는 인재 확인
    • 우리팀이랑 잘 맞는지 인상을 의식적으로든 무의식적으로든 확인
    • 얼굴보는게 가장 큼 : 얼굴보고, 이야기 나누고 잘맞나 안맞나. 본능
      • 여유있게, 자신감 넘치게 : 안되면 우황청심환
  • 자기소개 : 지원 이유 , 살아온 과정을 게임 개발쪽으로 풀어서
  • 포텐셜 기초 질문
    • C++
    • 자료구조 알고리즘, CS
    • 언리얼
  • 포트폴리오 질문
    • 어떻게
    • 이슈
    • 성과
  • 인성
    • 본인의 장단점
      • 장점 :
      • 단점 : 성향이 쫌 이렇긴한데, 나는 이렇게 노력하고 있다…
        • 실제로 이런부분들이 개선되었다.
        • 단점이라기보다 내 노력, 보완
    • 갈등 해결 방식 : 팀문화
      • 이슈가 있었고, 어떻게 해결했다.
      • 문서, 중재, 방향성 확립
    • 어려운 버그를 만나면…
  • 개인사 : 팀문화
    • 취미
    • 독서
    • 기술 RnD
  • 회사 홈페이지
    • 회사 가치관
    • 대표나 지원 부서 관련 유명인 구글링 뉴스
    • 이 회사에 나는 어떤 인재로 기여하고싶다.
  • 면접 전날
    • 예상 질문 목록 및 답변 준비 : 자신감에 상당히 도움이 많이 된다.
  • 자세
    • 패기 있게
    • 해본건 자신있게, 못한건 열심히 하겟다. 관심있다.
  • TIP
    • 질문이 많은건 여러분들에 대한 관심이 많아진 것 : 좋은 징조
    • 여러분들도 질문을 준비해 가셔야 함.
      • 팀 , 프로젝트에 대한 질문 : 회사/팀에 대한 관심을 어필하는것
    • 여러분들은 성장도 중요하지만… 내가 발전하기 위해서 간다는 (줄여주고)
      내가 이 회사에 어떤 것을 기여할 수 있는지
    • 면접 끝나면 질문하라고하면서, 시간이 남거나 기회를 주는 경우
      → 파이널 PR : 컨셉, 패기, 나의 능력 - 정리 - 어필 - 인상을 남기는 도장찍기


Lyra 애니메이션 구조 분석


ABP_Mannequin_Base

프로퍼티 엑세스 노드로 값을 가져와서 사용하고, Blueprint Thread Safe Update Animation에서 값을 갱신합니다.
Blueprint Thread Safe Update Animation는 그래프 업데이트 직전, Work Thread에서 애니메이션 블루프린트가 업데이트될 때 실행된다고 합니다.
즉 값 갱신 -> 그래프 업데이트 보장
스레드 세이프 기능은 다른 스레드로 인한 데이터 덮어쓰기를 방지하기 위해 객체의 데이터 직접 엑세스하는 대신, Property Access 시스템을 사용하여 데이터에 접근합니다. 안전할 때에 값을 복제해두는 방식.

애니메이션 그래프를 살펴보니 애니메이션을 직접 참조하지 않고 몽타주와 링크드 애니메이션 레이어가 그래프의 특정 지점에서 포즈를 재생할 수 있는 진입점을 제고한다고 합니다.

ABPMannequin_Base의 주요 목점은 이러한 진입점을 함께 블렌딩하는 것이라고 하는데, 이 방식 덕분에 애니메이션이 필요할 때만 로드할 수 있게 된다고 합니다.
예를 들어, 무기에는 필요한 몽타주 및 링크드 애니메이션 레이어에 대한 참조가 저장되므로 무기가 로드될 때만 데이터가 로드됩니다.
더 자세히 예를 들면, B_WeaponInstance_Shotgun은 몽타주 및 링크드 애니메이션 레이어에 대한 참조를 보유하고 있습니다. 해당 데이터는 B_WeaponInstance_Shotgun이 로드될 때만 로드됩니다.
B
무기 인스턴스_베이스는 무기용 애니메이션 레이어를 연결하는 역할을 합니다.

내부 함수를 확인하던 중 정리를 해보았습니다.

Velocity를 LocalVelocity2D로 변환할 때 Unroatate Vector를 사용하는 이유
GetVelocity()는 월드 공간(World Space) 벡터를 반환합니다. 예를 들어 캐릭터가 북동쪽으로 달리면 (1, 1, 0) 같은 값이 나옵니다. 문제는 "캐릭터가 앞으로 달리는가, 옆으로 달리는가" 는 캐릭터가 어느 방향을 바라보느냐에 따라 달라진다는 점입니다.

  • 캐릭터가 북동쪽을 바라보면 → 정면(Forward)
  • 캐릭터가 남쪽을 바라보면 → 오른쪽 뒤(Back-Right)

UnrotateVector(WorldVelocity2D, WorldRotation) 은 월드 속도를 캐릭터 회전의 역방향으로 돌려서 캐릭터 로컬 공간 벡터(LocalVelocity2D) 로 바꿉니다. 이 결과의 X성분 = 전후, Y성분 = 좌우가 됩니다.
이후 블렌드스페이스, 발 IK 등에서 이 성분을 직접 사용한다고 합니다.

CalculateDirection이 WorldVelocity2D와 WorldRotation 둘 다 필요한 이유
CalculateDirection()은 "이동 방향 벡터"와 "캐릭터 전방 벡터" 사이의 부호 있는 각도를 계산합니다:
둘 다 있어야 atan2(cross, dot) 형태로 −180°~+180° 반환이 가능합니다.
무슨 함수인지 의아했는데 각도의 형태로 이동방향을 나타내기 위해 사용한다고 합니다.

SelectCardinalDirectionFromAngle의 역할
연속 각도(−180~+180°)를 4방향 열거형(AnimEnum_CardinalDirection)으로 변환합니다.

단순 스냅이 아니라 세 가지 스마트 기능이 추가됩니다:
|기능|파라미터|효과|
|-|-|-|
|히스테리시스|CardinalDirectionDeadZone|경계선 근처에서 방향이 빠르게 왔다갔다 깜박이는 현상 방지|
|이전 방향 참조|CurrentDirection (이전 프레임 값)|히스테리시스 기준으로 사용|
|정지 시 방향 고정|UseCurrentDirection = WasMovingLastUpdate|캐릭터가 멈추면 마지막 이동 방향을 유지해서 재출발 시 스냅 방지|

GCNL_Death에 대해
GCNL 접두사 의미
GCNL = GameplayCueNotify_BurstLatent

접두사클래스특성
GCN_UGameplayCueNotify_Burst단발성, 같은 프레임 완료
GCNL_UGameplayCueNotify_BurstLatent단발성이지만 지연 가능 (Timeline, AsyncNode 사용 OK)
(별도)UGameplayCueNotify_LoopingActive/WhileActive/Remove 이벤트 처리

봉재 튜터님과 ssd 상담 받을 때 보원님을 불러서 같이 이야기
라이라 애니메이션은 Advanced locomotion system v4이라는 플러그인에서 발전된 것?
Advanced locomotion system v4이 언리얼에 팔려가서 통합이 되었고 그것을 Lyra에서
애니메이션들을 인터페이스화 해서 서로 연관성을 줄이기 위해 AnimLayerInterface가 도입됨.

스테이트 내부에 사이클이 존재

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

0개의 댓글