문제(크기가 작은 부분 문자열)
https://school.programmers.co.kr/learn/courses/30/lessons/147355
제출한 답안
#include <string>
#include <vector>
#include <iostream>
using namespace std;
int solution(string t, string p) {
int answer = 0;
for (int i = 0; i <= t.size() - p.size(); i++)
{
string three = t.substr(i, p.size());
if (stol(three) <= stol(p))
{
answer++;
}
}
return answer;
}
항상 조건을 잘 읽어보자. 조건에서 p의 길이가 18자리까지 나온다는데 이건 int로는 담을 수 없는 큰 수이기 때문에 stol을 사용해 long으로 비교해야 한다.
문제(최소직사각형)
https://school.programmers.co.kr/learn/courses/30/lessons/86491
제출한 답안
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int solution(vector<vector<int>> sizes) {
int answer = 0;
int count = sizes.size();
vector<int> max{};
vector<int> min{};
for (int i = 0; i < count; i++)
{
if (sizes[i][0] > sizes[i][1])
{
max.push_back(sizes[i][0]);
min.push_back(sizes[i][1]);
}
else
{
max.push_back(sizes[i][1]);
min.push_back(sizes[i][0]);
}
}
sort(max.begin(), max.end());
sort(min.begin(), min.end());
answer = max.back() * min.back();
return answer;
}
명함을 회전시킨다는 생각에 얽매이지 말고 모든 숫자 중에서 가장 큰 수와 가장 작은 수부터 오름차순으로 명함 개수만큼의 수들 중에 가장 큰 수를 곱하면 답이라고 생각 했다. 그래서 가로 세로를 신경 쓰지 않고 그저 명함 하나에 있는 두 수의 크기를 비교해 큰 값은 큰 값끼리, 작은 값은 작은 값끼리 묶어놓고 그 중에서 가장 큰 수를 찾아 곱했다.
문제(시저 암호)
https://school.programmers.co.kr/learn/courses/30/lessons/12926
제출한 답안
#include <string>
#include <vector>
#include <iostream>
using namespace std;
string solution(string s, int n) {
for (int i = 0; i < s.size(); i++)
{
for (int j = 1; j <= n; j++)
{
if (s[i] != ' ')
{
if (s[i] == 'z')
{
char c = 96;
s[i] = c;
}
else if (s[i] == 'Z')
{
char c = 64;
s[i] = c;
}
s[i]++;
}
}
}
return s;
}
문제(숫자 문자열과 영단어)
https://school.programmers.co.kr/learn/courses/30/lessons/81301
제출한 답안
#include <string>
#include <vector>
#include <map>
#include <iostream>
using namespace std;
map<string, char> init()
{
map<string, char> table{ {"zero", '0'}, {"one", '1'}, {"two", '2'}, {"three", '3'}, {"four", '4'},
{"five", '5'}, {"six", '6'}, {"seven", '7'}, {"eight", '8'}, {"nine", '9'}};
return table;
}
int solution(string s) {
string result = "";
string word = "";
map<string, char> table = init();
for (char c : s)
{
if (isdigit(c))
{
result += c;
}
else
{
word += c;
}
auto iter = table.find(word);
if (iter != table.end())
{
result += iter->second;
word = "";
}
}
return stoi(result);
}
처음에 생각한 방식으로 하니 core dumped오류가 떠서 검색하면서 고쳐봤다. 아마 기존 코드는 find를 많이 해서 안되는 것 같다. 그래서 찾아보니 s의 문자 하나씩 숫자인지 아닌지 체크하는게 핵심인 것 같다.
문자가 숫자면 그대로 넣고 숫자가 아니면 다른곳에 넣어서 단어가 되는지 체크하고 단어가 되면 그 단어에 맞는 값을 넣어주고 단어는 초기화 한다.
애셋 매니저
Asset : 블루프린트, 사운드 등 진짜 하나의 완성된 객체를 저장하는 단위
TSubclassof : 특정 클래스의 서브클래스의 타입 정보
CDO : 클래스의 기본 인스턴스
Staticclass : 메타 데이터(리플렉션) -> UClass
리플렉션 : 런타임 단계에서 클래스의 정보를 알 수 있는 것(C++에서는 불가능해서 언리얼에서 강제로 만들었다. 엔진에서 프로퍼티를 붙히고 디테일 패널에서 수정할 수 있는게 이 기능이다.)
TSubclassof : 이것도 UClass처럼 정보를 저장하지만 아무거나 다 들어오지 않고 적어준 클래스를 상속받는 클래스만 들어올 수 있게 해준다.(player의 정보를 가져오고 싶은데 갑자기 bullet같은 걸 넣어버리면 안되니 이걸 사용 한다.)
엔진의 프로젝트 설정에서 AssetManager를 만든 애셋 매니저로 바꿔줘야 엔진이 이 매니저를 사용한다고 알 수 있다.
아래 사진의 내용이 원래 중단점을 찍고 디버그를 해도 안되는데 이유는 디버깅 옵션이 Development로 되어 있는 것과 프로젝트 설정에서 만든 애셋 매니저를 사용한다고 알려주지 않아서 그렇다. 그래서 설정도 바꿔주고 이렇게 매크로를 넣어주면 디버그 중에 중단점한 부분에 맞게 멈춘다.(이 매크로 사이의 부분은 꼭 디버깅이 되어야 한다는 뜻인 것 같다.)
기본적으로 언리얼 엔진은 멀티쓰레드를 지원하고 에디터에서는 안전한 환경인 것 같다. 하지만 아직 에디터가 켜지기 전인 상태에서는 안전하지 않을 수 있다. 그런데 애셋 매니저가 생기고 작업하는 때가 에디터가 켜지기 전이기 때문에 한번에 애셋들이 들어오지 않도록 Lock을 해준 것이다.
Lock을 일종의 열쇠라고 생각해서 이 열쇠를 가지고 있는 사람만 들어올 수 있게 하고 들어오면 열쇠를 다시 반납해서 다음 사람이 열쇠를 가지고 들어온다.(한번에 들어오지 않고 하나하나씩 들어오게 만든다.)
Experience
원래 예를 들어 FPS에서 AOS로 바꾸려면 게임모드를 바꿨다. 그런데 이게 굉장히 무겁고 부담되서 라이라에서는 딱 하나의 게임모드만 사용 한다. 즉, 게임의 모드를 바꾸려고 할 때 라이라는 게임모드가 전혀 바뀌지 않고 Experience라는 새로운 개념을 이용 한다.
익스피리언스는 게임모드를 대체할 수 있는 작은 게임모드라고 생각하면 된다.
cpp와 헤더 한번에 만들기(GameModeBase 상속 1개, PrimaryDataAsset 상속 3개)
여기서 중요한 건 UserFacingExperience다. 이게 FPS나 AOS같은 모드들이 각각 하나씩은 가지고 있어야하는 것이다.
MapID는 말 그대로 맵을 저장하는 곳이다. 그런데 뒤에 AllowedTypes가 중요한데 이건 뒤에 ""에 적은 것만 가질 수 있는 것이다. 그렇다면 밑은 CloneLyraExperienceDefinition만 가지겠다는 뜻이다.
그리고 ExperienceDefinition은 폰 데이터를 가지고 있다.
그리고 지금은 없지만 폰은 스킬이나 어빌리티, 인풋 같은 것을 들고 있을 것이다.
즉, 구조가 이렇게 된다.
최종적으로 게임모드가 이 익스피리언스를 읽어서 관련된 데이터를 전부 소환하는게 전체적인 흐름이다.
언리얼엔진의 프로젝트 세팅에서 게임모드를 만든 게임모드로 바꿔주면 된다.
(앞으로 월드세팅의 게임모드 오버라이드는 none에서 절대 바뀌지 않는다.)
GameFeature는 일단 게임의 룰을 담당하는 부분이라고 생각하면 된다.(점령전이나 데스매치 등)
컨텐츠 폴더에 새로운 폴더를 만들고 empty맵을 연다.(ctrl + n)
Windows -> Env.LightMixer창에서 Create Sky Light부터 전부 누르면 기본적인 환경이 만들어진다.
큐브로 바닥 만들고 라이라에서 바닥 머티리얼을 이주해와서 적용
만든 UserFacingExperience로 데이터 애셋을 만들고 들어가서 map을 만든 맵으로 고르려해도 나오질 않는다. 이 때 스캔을 해야 한다.(액터 블루프린트도 만든다.)
스캔 : 우리는 애셋매니저가 맵을 주시하고 있어야 한다는 걸 알려줘야 한다. 이 때 애셋매니저가 맵을 주시하는걸 스캔이라고 한다. 이 스캔은 로딩과 아예 다른 것이다. 스캔은 어디까지나 있다는 것만 알 뿐 로딩은 하지 않은 것이다.(메모리를 다 들고 있기 힘드니 경로만 알고 있는 것도 하나의 스캔이다.)
아래 사진에서 Specific Assets는 아예 그거 하나를 스캔하라고 찍어주는 것이고 Directories는 폴더를 지정하면 그 폴더에 있는 것들 중에 Map만 전부 스캔 한다.
(사진의 기준이 Map인 것일 뿐 당연히 바꿀 수 있다.)
이제 데이터 애셋을 들어가서 보면 만든 맵이 바로 나온다.
만든 ExperienceDefinition을 받는 블루프린트 클래스를 만든다.
(이건 블루프린트로만 만든다. 나중에 이유가 나온다.)
만든 클래스도 데이터 애셋의 익스피리언스 ID에 넣어야하는데 안나오니 스캔이 안됐다고 바로 알아서 스캔하고 넣어준다.(새로 추가해서 넣어줄 수 있고 추가해서 넣을 때 당연하지만 Primary Asset Type과 Asset Base Class를 정확하게 적어줘야 한다.)
여기서 주의할 점은 블루프린트로 만든 클래스는 밑의 Has Blueprint Classes를 체크해줘야 한다.(체크하고 언리얼 엔진을 다시 켜야 한다.)
이번에는 폴더 경로를 지정해서 스캔을 하려고 하는데 추가하면 갑자기 비주얼스튜디오 디버깅이 멈추고 뭐라 나오는데 그냥 f5 눌러주면 된다. 그리고 경로 추가하고 데이터 애셋에서 넣어주면 된다.
이제 맵에 있는 모든 익스피리언스를 액터가 저장하도록 만든다.
만든 액터 블루프린트에서 변수 생성하고 코드 작성(UserFacingExperienceList, PortalSpacing = 500.0)
GetPrimaryAssetIdList는 리스트를 보면 알지만 현재 애셋 매니저가 스캔중인 타입의 리스트를 전부 알려주는 편리한 노드이다.
우리는 UserFacingExperience를 알고 싶은데 리스트에 안나오는 이유는 당연히 프로젝트 세팅에서 스캔 리스트에 안 넣어줬기 때문이다.(넣고 노드에서 바꿔준다.)
우리가 코드에 만든 로딩은 동기식 로딩이지만 이 노드는 비동기식 로딩이라 Completed를 따로 알려주고 작업들도 따로 해줘야 한다.
중단점을 찍고 실행해서 노드들의 반환값을 보면 잘 들어오는 걸 확인할 수 있다.
이렇게 처음에 만들어두는게 귀찮을 수 있지만 한번 만들면 굉장히 편하면서 계속 사용할 수 있다.
나중에 프로젝트가 커져서 익스피리언스가 100개정도 되면 매번 이걸 로딩하기 불편하면 그냥 스캔에서 빼면 된다고 한다. 그래서 한번 만들어두면 사용하기 굉장히 편하다고 한다.