[1. TurnInPlace 기능]

지난번 AimOffset의 연장선으로 Yaw 값이 일정값을 넘어서면 해당하는 방향으로 캐릭터가 회전하도록 하였습니다.

 

 

회전할때 하반신 모션이 좀 어색하긴한데 Mixamo에서 이것보다 괜찮은게 안보여서 해당 모션을 사용했습니다.

아마 추후에 더 괜찮은 모션을 찾게 된다면 수정될 것 같습니다.

 

[2. 사격 기능#1]

 

사격시 총알은 총구에서 플레이어의 화면의 중앙으로 발사되도록 하였습니다.

또한 사격 시 탄피가 생성되고 해당 탄피에서 충돌 이벤트 발생 시 탄피 떨어지는 소리를 한 번 내고 4초 뒤에 사라지도록 하였습니다.

 

처음 사격 방향 계산하는 거에서 참고한 자료에선 트레이스 채널을 ECC_Visibility로 해서 플레이어 캐릭터에 막혀서 반대로 쏘거나 하는 상황이 생겨 ECC_Camera로 변경하였으나 오른쪽으로 이동하면서 사격 시 이전과 같은 문제가 발생해서 이에 대해 수정할 예정입니다.

 

트레이스 채널을 손보거나 사격하는 방향을 찾는 과정 자체를 수정하거나 할 것 같습니다.

[1. 모션 수정]

 

앉기 키 입력 시 버벅대던 것을 TriggerEvent::Completed로 수정하여 해결하였고

SpringArm에 보간을 줘서 카메라가 부드럽게 움직이도록 하였습니다.

 

 

 

또한 각 모션간의 전환을 추가 및 개선하고 모션 별로 MaxWalkSpeed 또는 MaxWalkSpeedCrouched가 변하도록 하였습니다.

 

[2. Aim Offset 추가]

 

Aim Offset을 추가하고 이동 시에는 조준점의 Pitch 값, 정지 시 Pitch 값과 Yaw 값을 이용해 상반신이 조준점을 향하도록 하였습니다.

 

 

현재 멀티플레이 테스트 결과 호스트는 클라이언트의 Pitch와 Yaw를 둘 다 볼 수 있으나 클라이언트는 호스트 또는 타 클라이언트의 Pitch만 볼 수 있는 문제가 있어 다음 포스팅까지 해결하도록 하겠습니다.

[3. FABRIK]

 

Aim Offset에서 왼손이 총기를 제대로 따라가지 못하는 문제가 생겨 총기의 스켈레탈 메시에 왼손을 위치시킬 소켓을 만든 후 Transform을 구해 FABRIK 노드에 전달하는 식으로 해결하였습니다.

 

 

 

[달리기 모션]

 

Enhanced Input을 이용해 왼쪽 Shift 홀드로 달리기와 걷기를 전환하도록 하였습니다.

키를 눌렸을 때와 놓았을 때 간단한 가속/감속 기능 또한 추가해보았습니다.

[Weapon 클래스]

Weapon 클래스를 만들고 해당 클래스와의 상호작용을 추가하였습니다.

또한 무기를 들고 있을 때의 모션을 추가하였습니다.

관련 에셋으로는 언리얼 마켓플레이스의 Military Weapons Silver와 애니메이션 스타터 팩을 사용했습니다.

 

[무기 관련 상호작용]

 

 

 [무기 장착 시 애니메이션]

 

 

애니메이션 몇 개가 좀 어색하기도 하고 앉기 기능에도 조금 문제가 있어 추후 수정할 예정입니다.

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

 

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

using namespace std;

/*
[x, y, a, b]
x, y는 기둥 또는 보를 설치할 교차점의 좌표
a = 0은 기둥, 1은 보
b = 0은 삭제, 1은 설치
구조물은 좌표 기준 보는 오른쪽, 기둥은 위쪽으로 설치

설치 조건
기둥은 바닥 위, 보의 한쪽 끝 부분, 기둥 위에 있어야함
보는 한쪽 끝 부분이 기둥 위에 있거나 양쪽 끝 부분이 다른 보와 연결되어 있어야함
 + 구조물이 겹치도록 설치되는 경우는 주어지지 않음

삭제 조건
삭제한 후에도 남은 기둥과 보들이 설치 조건을 만족한 상태여야 삭제 가능
 + 없는 기둥을 삭제하는 경우는 주어지지 않음
 
정렬 조건
x 좌표 기준 오름차순 정렬, x 좌표가 같으면 y 좌표 기준 오름차순 정렬
x, y 동일 시 기둥이 보보다 앞으로
*/

int N;
bool Pillar[101][101];
bool Beam[101][101];

struct STRUCTURE {
    int x;
    int y;
    int type;
    bool isDeleted;
};

vector<STRUCTURE> list;

// 기둥 설치 가능 여부
bool checkDeployPillar(int x, int y) {
    // 바닥이면
    if(x == N) return true;
    // 보의 오른쪽에 설치할 때
    if(Beam[x][y - 1] && y >= 1) return true;
    // 보의 왼쪽에 설치할 때
    if(Beam[x][y]) return true;
    // 기둥 위에 설치할 때
    if(Pillar[x + 1][y]) return true;
    
    return false;
}

// 보 설치 가능 여부
bool checkDeployBeam(int x, int y) {
    // 왼쪽 끝에 기둥이 있을 때
    if(Pillar[x + 1][y]) return true;
    // 오른쪽 끝에 기둥이 있을 때
    if(Pillar[x + 1][y + 1] && y + 1 <= N) return true;
    // 양쪽 끝에 보가 있을 때
    if(Beam[x][y - 1] && Beam[x][y + 1]) return true;
    
    return false;
}

// 기둥 삭제 가능 여부
void DeletePillar(int x, int y) {
    int idx = 0;
    // 해당 기둥이 설치되지 않음을 가정
    Pillar[x][y] = false;
    
    for(int i = 0; i < list.size(); i++) {
        int curX = list[i].x;
        int curY = list[i].y;
        int Type = list[i].type;
        
        // 현재 구조물이 삭제하려는 기둥과 같다면
        if(x == curX && y == curY && Type == 0) {
            idx = i;
            continue;
        }
        
        // 이미 삭제된 구조물이면
        if(list[i].isDeleted) 
            continue;
        
        // 리스트에 있는 기둥 중 설치가 불가능한 기둥이 나온다면
        if(Type == 0 && checkDeployPillar(curX, curY) == false) {
            Pillar[x][y] = true;
            return;
        }
        
        // 리스트에 있는 보 중 설치가 불가능한 보가 나온다면
        if(Type == 1 && checkDeployBeam(curX, curY) == false) {
            Pillar[x][y] = true;
            return;
        }
    }
    
    list[idx].isDeleted = true;
}

// 보 삭제 가능 여부
void DeleteBeam(int x, int y) {
    int idx = 0;
    // 해당 보가 설치되지 않았음을 가정
    Beam[x][y] = false;
    
    for(int i = 0; i < list.size(); i++) {
        int curX = list[i].x;
        int curY = list[i].y;
        int Type = list[i].type;
        
        // 현재 구조물이 삭제하려는 보과 같다면
        if(x == curX && y == curY && Type == 1) {
            idx = i;
            continue;
        }
        
        // 이미 삭제된 구조물이면
        if(list[i].isDeleted) 
            continue;
        
        // 리스트에 있는 기둥 중 설치가 불가능한 보가 나온다면
        if(Type == 0 && !checkDeployPillar(curX, curY)) {
            Beam[x][y] = true;
            return;
        }
        
        // 리스트에 있는 보 중 설치가 불가능한 보가 나온다면
        if(Type == 1 && !checkDeployBeam(curX, curY)) {
            Beam[x][y] = true;
            return;
        }
    }
    
    list[idx].isDeleted = true;
}

bool cmp(STRUCTURE a, STRUCTURE b) {
    if(a.y <= b.y) {
        if(a.y == b.y) {
            if(a.x >= b.x) {
                if(a.x == b.x) {
                    if(a.type < b.type) {
                        return true;
                    }
                    return false;
                }
                return true;
            }
            return false;
        }
        return true;
    }
    return false;
}

vector<vector<int>> solution(int n, vector<vector<int>> build_frame) {
    N = n;
    vector<vector<int>> answer;
    
    for(int i = 0; i < build_frame.size(); i++) {
        int x = n - build_frame[i][1];
        int y = build_frame[i][0];
        int type = build_frame[i][2];
        int deploy = build_frame[i][3];
        
        // 설치일 때
        if(deploy == 1) {
            // 기둥
            if(type == 0 && checkDeployPillar(x, y)) {
                list.push_back({x, y, type, false});
                Pillar[x][y] = true;
            }
            // 보
            if(type == 1 && checkDeployBeam(x, y)) {
                list.push_back({x, y, type, false});
                Beam[x][y] = true;
            }
        }
        // 제거일 때
        else {
            // 기둥
            if(type == 0) {
                DeletePillar(x, y);
            }
            // 보
            if(type == 1) {
                DeleteBeam(x, y);
            }
        }
    }
    
    sort(list.begin(), list.end(), cmp);
    
    for(int i = 0; i < list.size(); i++) {
        if(list[i].isDeleted)
            continue;
        
        answer.push_back({list[i].y, N - list[i].x, list[i].type});
    }
    
    return answer;
}
 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

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

using namespace std;

/*
순열을 통해 조합을 만들고 해당 조합에서 banned_id의 크기만큼 아이디를 선택하여 조건을 만족하는지 체크
이후 조건을 만족하는 아이디들의 크기가 banned_id와 같으면 set에 저장(중복 제거)
*/

set<string> s;

// 조건 체크
// a가 banned_id, b가 조건 체크할 id
bool isBanned(string a, string b) {
    if(a.length() == b.length()) {
        for(int i = 0; i < a.length(); i++) {
            if(a[i] == b[i] || a[i] == '*')
                continue;
            else 
                return false;
        }
    }
    else
        return false;
    
    return true;
}

int solution(vector<string> user_id, vector<string> banned_id) {
    // 순열 사용을 위해 정렬
    sort(user_id.begin(), user_id.end());
    
    do {
        vector<string> v;
        string str = "";
        
        // 조건 체크
        for(int i = 0; i < banned_id.size(); i++)
            if(isBanned(banned_id[i], user_id[i]))
                v.push_back(user_id[i]);
        
        // banned_id.size()만큼의 id가 조건을 충족했다면
        if(banned_id.size() == v.size()) {
            // 순서가 관계 없어야하기 때문에 정렬
            sort(v.begin(), v.end());
            
            for(auto& e : v)
                str += e;
            
            s.insert(str);
        }
        
    } while(next_permutation(user_id.begin(), user_id.end()));
    
    return s.size();
}
 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

  • 이분탐색 문제
  • 데이터 타입에 주의하자

 

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

using namespace std;

/*
이분 탐색
최소 시간을 1명을 1분으로 끝낸다고 가정하면 1
최대 시간은 가장 오래 걸리는 심사원에게 모든 인원이 심사를 받으러 갔을 때
위 둘을 각각 left, right로 하여 이분탐색 직행
mid를 각 심사위원이 심사하는데 걸리는 시간으로 나눈 값을 더하면 mid분일 때 심사할 수 있는 인원이 나옴
mid분에서 처리할 수 있는 인원이 n보다 크면 right를 mid - 1로 하고 작으면 left를 mid + 1로 해서 계속 탐색
left == right가 되면 탐색을 중단
*/

long long solution(int n, vector<int> times) {
    long long answer = 0;
    
    // times 배열 정렬
    sort(times.begin(), times.end());
    
    // 최소 시간
    long long left = 1;
    // 최대 시간, 데이터 타입에 주의
    long long right = n * (long long)times.back();
    
    while(left <= right) {
        long long mid = (left + right) / 2;
        
        // mid분에서 심사를 끝낼 수 있는 인원 수
        long long cnt = 0;
        
        for(int i = 0; i < times.size(); i++)
            cnt += mid / (long long)times[i];
        
        // n보다 많거나 같은 수의 인원의 심사를 끝낼 수 있다면
        if(cnt >= n) {
            right = mid - 1;
            answer = mid;
        }
        // n 보다 적은 수의 인원의 심사를 끝낼 수 있다면
        else {
            left = mid + 1;
        }
    }
    
    
    return answer;
}

 

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

  • dp 문제
  • 전체 수열에 펄스 수열을 곱해서 나올 수 있는 연속 펄스 수열의 종류는 두 가지 뿐
  • 두 종류의 수열을 만든 후 두 수열을 돌면서 연속된 구간의 합이 최대가 되는 곳을 찾아야함
  • 점화식이 2번째 요소부터 시작인데 sequence의 사이즈는 최소 1일 수 있어서 sequence의 사이즈가 1인 경우를 고려해야함

 

#include <string>
#include <vector>

using namespace std;

// 펄스 수열은 [1, -1, 1...] 또는 [-1, 1, -1...], 즉 1 또는 -1로 시작
// 전체 수열에 펄스를 곱해서 나올 수 있는 수열은 두 가지 뿐
// 두 종류의 수열을 만든 후 두 수열을 돌면서 연속된 구간의 합이 최대가 되는 곳을 찾아야함
// 점화식으로 나타내면 dp[i] = max(dp[i - 1] + sequence[i], sequence[i])

// dp가 2번째 요소부터 시작이다보니 주어진 수열의 사이즈가 1인 경우도 따로 고려해야함
// 이거 안해서 2번 테스트 케이스 틀림

// 연속 펄스 수열 만들기
vector<int> multiplyPurse(vector<int> sequence, int pulse) {
    for(int i = 0; i < sequence.size(); i++) {
        sequence[i] = sequence[i] * pulse;
        pulse *= -1;
    }
    
    return sequence;
}

long long solution(vector<int> sequence) {
    long long answer = INT32_MIN;
    int size = sequence.size();
    
    // 각각 1과 -1로 시작하는 펄스 수열을 곱한 연속 펄스 수열 만들기
    vector<int> sequence1 = multiplyPurse(sequence, 1);
    vector<int> sequence2 = multiplyPurse(sequence, -1);
    
    long long dp1[size];
    long long dp2[size];
    
    dp1[0] = sequence1[0];
    dp2[0] = sequence2[0];
    
    // 이거 안해줘서 틀림
    if(size == 1) {
        answer = max(dp1[0], dp2[0]);
        return answer;
    }
    
    // dp 수행
    for(int i = 1; i < size; i++) {
        dp1[i] = max(dp1[i-1] + (long long)sequence1[i], (long long)sequence1[i]);
        answer = max(dp1[i], answer);
    }
    
    for(int i = 1; i < size; i++) {
        dp2[i] = max(dp2[i-1] + (long long)sequence2[i], (long long)sequence2[i]);
        answer = max(dp2[i], answer);
    }
    
    return answer;
}

 

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

  • 콘이 최대한 늦게 버스 정류장에 도착하여 사무실로 가려면 마지막 버스를 마지막으로 타면 됨

 

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

using namespace std;

// 셔틀은 9시부터 t분 간격으로 n번 도착
// 셔틀이 도착한 순간에 자리가 남고 그 순간에 도착한 크루가 있으면 탑승 가능
// 콘은 최대한 늦게, 그러니까 마지막 버스를 타야함
// 일단 시:분 형태로 주어지는 문자열을 시 * 60 + 분 형태의 int형으로 변환

string solution(int n, int t, int m, vector<string> timetable) {
    string answer = "";
    int time = 0;
    
    vector<int> timeTable;
    
    for(auto& time : timetable)
        timeTable.push_back(stoi(time.substr(0, 2)) * 60 + stoi(time.substr(3, 2)));
    
    sort(timeTable.begin(), timeTable.end());
    
    int cnt = 0;
    int arrivalTime = 540;
    
    for(int i = 1; i <= n; i++) {
        int cntInBus = 0;
        
        while(cntInBus < m && cnt < timeTable.size()) {
            if(timeTable[cnt] <= arrivalTime) {
                cntInBus++;
                cnt++;
            }
            else 
                break;
        }
        
        // 마지막 버스일 때
        if(i == n) {
            // 자리가 남으면 버스 도착시간에 맞춰 오면 됨
            if(cntInBus < m)
                time = arrivalTime;
            // 자리가 안남으면 마지막 사람보다 1분 일찍 오면 됨
            else
                time = timeTable[cnt - 1] - 1;
        }
        
        arrivalTime += t;
    }
    
    int hour = time / 60;
    int minute = time % 60;
    
    if(hour < 10)
        answer = "0" + to_string(hour) + ":";
    else 
        answer = to_string(hour) + ":";
    
    if(minute < 10)
        answer += "0" + to_string(minute);
    else
        answer += to_string(minute);
    
    return answer;
}

 

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

  • 해당 요청시점에서 들어온 작업 중 소요 시간이 짧은 작업들부터 처리하는 것이 관건
  • 작업 배열을 요청 시점 기준으로 오름차순 정렬하고 이후 우선순위 큐에 소요 시간 기준으로 오름차순 정렬
  • 이후 모든 작업을 완료할 때까지 작업 수행

 

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

using namespace std;

// 소요 시간이 짧은 것부터 처리하는 것이 평균을 줄이는 방법
// 일단 jobs 배열을 오름차순으로 정렬하고 우선순위 큐에 소요 시간을 기준으로 오름차순 정렬하면 될듯

struct cmp {
    bool operator()(vector<int> a, vector<int> b) {
        return a[1] > b[1];
    }
};

int solution(vector<vector<int>> jobs) {
    // 각각 총 소요시간, 현재까지의 소요시간, 작업 카운트
    int totalCost = 0, curCost = 0, cnt = 0;
    
    priority_queue<vector<int>, vector<vector<int>>, cmp> pq;
    
    // 정렬
    sort(jobs.begin(), jobs.end()); 
    
    // 작업이 끝나지 않은 동안
    while(cnt < jobs.size() || !pq.empty()) {
        
        // 요청 시간이 현재까지의 소요 시간보다 작으면
        if(cnt < jobs.size() && jobs[cnt][0] <= curCost) {
            pq.push(jobs[cnt]); 
            cnt++;
            continue;
        }
        
        // 우선순위 큐에 수행할 작업이 들어있다면
        if(!pq.empty()) {
            curCost += pq.top()[1];
            totalCost += curCost - pq.top()[0];
            pq.pop();
        }
        // 작업은 남았는데 현재 시간에 우선순위 큐에 들어있는 작업이 없다면
        else {
            curCost = jobs[cnt][0];
        }
    }
    
    return totalCost / jobs.size();
}

 

 

[온라인 서브 시스템을 이용한 세션 생성]

 

학습이나 코드 작성은 오래 걸리지 않았으나 생각지도 못한 곳에서 문제가 생겨 시간이 오래 걸렸습니다.

 

일단, 온라인 서브 시스템 스팀이 5.3에서 문제가 생길 수 있다하여 5.2로 다운그레이드 하였습니다.

 

해당 과정에서 언리얼 엔진이 설치 도중 일정 진행도에서 멈추는 문제가 계속 발생하여 이틀 정도 설치 눌러놓고 방치해보기도 하고 에픽 게임즈 재설치도 해보고 포맷도 해보았으나 해결되진 않았고 그러던 중 VPN을 사용하자 정상적으로 다운로드가 진행되었습니다.

추측으로는 집에 있는 컴퓨터는 문제가 없었으나 연구실의 컴퓨터가 전부 그런 이상이 있었던걸 보면 학교 측에서 뭔가 막아둔 것이 아닌가.. 싶기도 합니다.

 

이후 에셋들을 전부 옮기고 다시 패키징 후 테스트하였습니다.

 

 

[노트북을 이용한 테스트]

노트북 성능이 좀? 안좋습니다

 

 

[친구들 불러다가 시킨 테스트]

 

+ Recent posts