서론올해 6월인가 SQLD를 딴 경험이 있어서 어느정도 어깨뽕이 차오른 상태였다.물론 다시 생각해보면 SQLD는 간판 자격증이라 난이도가 낮아 쉽게 취득한거라 자만하면 안된다고 생각한다. 아무튼 그렇데 자만한 상태여서 시험 당일 전까지 기출을 단 한번도, 따로 책도 합격률을 제외한 어떤 정보도 수집하지 않았다.그런데 합격률을 봤을 때 자만해도 될것같은 난이도라는게 느껴지는 자격증이었다. 지난 3년간의 필기 합격률이다.응시자가 무려 5만정도이며 합격률이 50%를 가볍게 넘어간다. 분명 이중에 이런저런 핑계를 대며, 어떤 사정이 있어서 시험을 응시못한 사람들을 제외하면사실상 합격률은 80%정도일것이다. 그 점을 생각하면 시험에 응시했는데 떨어진다면 한심한 수준의 난이도라는걸 알 수 있다.그래서 벼락치기 조금..
반응형
서론
올해 6월인가 SQLD를 딴 경험이 있어서 어느정도 어깨뽕이 차오른 상태였다.
물론 다시 생각해보면 SQLD는 간판 자격증이라 난이도가 낮아 쉽게 취득한거라 자만하면 안된다고 생각한다.
아무튼 그렇데 자만한 상태여서 시험 당일 전까지 기출을 단 한번도, 따로 책도 합격률을 제외한 어떤 정보도 수집하지 않았다.
그런데 합격률을 봤을 때 자만해도 될것같은 난이도라는게 느껴지는 자격증이었다.
지난 3년간의 필기 합격률이다.
응시자가 무려 5만정도이며 합격률이 50%를 가볍게 넘어간다.
분명 이중에 이런저런 핑계를 대며, 어떤 사정이 있어서 시험을 응시못한 사람들을 제외하면
사실상 합격률은 80%정도일것이다.
그 점을 생각하면 시험에 응시했는데 떨어진다면 한심한 수준의 난이도라는걸 알 수 있다.
그래서 벼락치기 조금만 해도 붙을 수 있을거라 생각하고 시험 당일전날까지 하나도 공부를 안했었다.
이 문제는 Monotonic Queue 또는 우선순위 큐로 풀 수 있다. 하지만 성능적으로는 Monotonic Queue가 더 좋다.대용량일 때 우선순위 큐의 성능은 급격히 낮아지기 때문이다. 아래가 우선순위 큐, 위가 Monotonic Queue이다.시간을 보면 약 400ms가 차이나는것을 알 수 있다. 메모리 측면에서도 Monotonic Queue가 앞도적으로 더 좋은것을 알 수 있다. 일단 두 코드 모두 봐보자. 코드 [ Monotonic Queue ]#include #include #include #include using namespace std;struct Node { int value; int index;};int main() { ios::sync_with_stdio(false); cin.t..
반응형
이 문제는 Monotonic Queue 또는 우선순위 큐로 풀 수 있다.
하지만 성능적으로는 Monotonic Queue가 더 좋다.
대용량일 때 우선순위 큐의 성능은 급격히 낮아지기 때문이다.
아래가 우선순위 큐, 위가 Monotonic Queue이다.
시간을 보면 약 400ms가 차이나는것을 알 수 있다. 메모리 측면에서도 Monotonic Queue가 앞도적으로 더 좋은것을 알 수 있다.
일단 두 코드 모두 봐보자.
코드 [ Monotonic Queue ]
#include <iostream>
#include <vector>
#include <deque>
#include <algorithm>
using namespace std;
struct Node {
int value;
int index;
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int N, L;
cin >> N >> L;
vector<int> A(N);
vector<int> B(N);
deque<Node> dq;
for (int i = 0; i < N; ++i) {
cin >> A[i];
if (dq.empty())
dq.push_back({A[i], i});
else
{
Node current = { A[i], i };
while (!dq.empty() && dq.back().value >= current.value)
dq.pop_back();
dq.push_back(current);
}
while (!dq.empty() && i - dq.front().index >= L)
dq.pop_front();
B[i] = dq.front().value;
}
for (auto& b : B) {
cout << b << " ";
}
return 0;
}
현재 인덱스는 index ~ index + L만큼의 유효범위를 갖는다.
따라서 덱에 값을 넣을 때 뒤부터 넣되 맨 뒤값이 현재보다 크다면 당연히 빼도 된다.
어차피 그 노드의 index + L만큼은 현재 인덱스의 값으로 대체해야하기 때문이다.
그리고 푸시과정이 끝났다면 가장 앞에있는 값이 최소값임을 보장할것이다.
이제 그 최소값이 유효 범위가 지났는지를 체크하면되기에 범위가 지났으면 삭제시키는 과정을 반복해주면 된다.
코드 [우선순위 큐 ]
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
struct Node {
int value;
int index;
bool operator<(const Node& other) const {
return value > other.value; // Min-heap based on value
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int N, L;
cin >> N >> L;
vector<int> A(N);
vector<int> B(N);
priority_queue<Node> minHeap;
for (int i = 0; i < N; ++i) {
cin >> A[i];
minHeap.push({A[i], i});
while (i - minHeap.top().index >= L) {
minHeap.pop();
}
B[i] = minHeap.top().value;
}
for (auto& b : B) {
cout << b << " ";
}
return 0;
}
게임에서 캐릭터가 땅에 서 있는지 판정하는 건 아주 기본적이지만, 생각보다 정확하게 구현하기가 까다롭다.특히 박스 콜라이더(BoxCollider)를 쓸 때, 한 지점만 검사하면 경사로나 돌출부 때문에 의도치 않은 결과가 나올 수 있기 때문에 문제가 생각보다 빈번하게 발생한다.이번 글에서는 박스 콜라이더 바닥 여러 위치에서 Raycast를 쏴서 안정적으로 바닥을 감지하는 방법을 소개한다.왜 여러 위치에서 체크해야 할까?박스 콜라이더는 사각형 모양이라, 바닥에 완벽하게 평평하게 닿지 않는 경우가 많다.한 지점만 검사하면 캐릭터가 바닥에 붙어있어도 감지 못할 수 있고, 경사진 지형에서 문제를 일으킨다.그래서 콜라이더 바닥 주변의 8개 위치(네 구석과 네 중간 지점)에서 짧은 Raycast를 쏴서 하나라도 닿으..
반응형
게임에서 캐릭터가 땅에 서 있는지 판정하는 건 아주 기본적이지만, 생각보다 정확하게 구현하기가 까다롭다. 특히 박스 콜라이더(BoxCollider)를 쓸 때, 한 지점만 검사하면 경사로나 돌출부 때문에 의도치 않은 결과가 나올 수 있기 때문에 문제가 생각보다 빈번하게 발생한다.
이번 글에서는 박스 콜라이더 바닥 여러 위치에서 Raycast를 쏴서 안정적으로 바닥을 감지하는 방법을 소개한다.
왜 여러 위치에서 체크해야 할까?
박스 콜라이더는 사각형 모양이라, 바닥에 완벽하게 평평하게 닿지 않는 경우가 많다. 한 지점만 검사하면 캐릭터가 바닥에 붙어있어도 감지 못할 수 있고, 경사진 지형에서 문제를 일으킨다.
그래서 콜라이더 바닥 주변의 8개 위치(네 구석과 네 중간 지점)에서 짧은 Raycast를 쏴서 하나라도 닿으면 바닥에 닿았다고 판단한다.
코드 예제
internal bool CanJump()
{
var bounds = _boxCollider.bounds;
var min = bounds.min;
var max = bounds.max;
var center = bounds.center;
int groundLayerMask = LayerMask.GetMask("Ground");
List<Vector3> checkPoints = new List<Vector3>
{
new Vector3(min.x, min.y + 0.05f, min.z),
new Vector3(center.x, min.y + 0.05f, min.z),
new Vector3(max.x, min.y + 0.05f, min.z),
new Vector3(min.x, min.y + 0.05f, center.z),
new Vector3(max.x, min.y + 0.05f, center.z),
new Vector3(min.x, min.y + 0.05f, max.z),
new Vector3(center.x, min.y + 0.05f, max.z),
new Vector3(max.x, min.y + 0.05f, max.z)
};
foreach (var point in checkPoints)
{
if (Physics.Raycast(point, Vector3.down, 0.05f, groundLayerMask))
return true;
}
return false;
}
여기서 볼 수 있는 사람은 반드시 그 사람과 나 사이에 둘 중 하나보다 더 큰 사람이 있어선 안된다.예를 들어 [4, 4, 2, 2] 가 있다 쳤을 때 0번째와 2, 3번째는 볼 수 없다. 왜냐면 1번째 4가 2, 3번째보다 크기 때문이다. 이 문제에서 사람의 수는 int값을 가진다.하지만 결과값은 최대인 경우 int를 초과해 버린다.입력될 수 있는 수의 양은 50만 이기에 만약 50만의 수가 동일하다면 50만^2 으로 가뿐하게 int 범위를 벗어나기에 결과값은 long으로 해야 한다. 또한 반드시 O(N) 보다 작거나 같게 성능 최적화를 해야한다.N^2으로 짜면 최악의 경우 일반 컴퓨터에서 하루종일 돌려도 연산을 못끝낼 수도 있다. 이렇게 세 가지 주의점을 파악한 뒤 로직을 한번 짜보자로직 O(N)으로..
반응형
여기서 볼 수 있는 사람은 반드시 그 사람과 나 사이에 둘 중 하나보다 더 큰 사람이 있어선 안된다.
예를 들어 [4, 4, 2, 2] 가 있다 쳤을 때 0번째와 2, 3번째는 볼 수 없다. 왜냐면 1번째 4가 2, 3번째보다 크기 때문이다.
이 문제에서 사람의 수는 int값을 가진다.
하지만 결과값은 최대인 경우 int를 초과해 버린다.
입력될 수 있는 수의 양은 50만 이기에 만약 50만의 수가 동일하다면 50만^2 으로 가뿐하게 int 범위를 벗어나기에 결과값은 long으로 해야 한다.
1번에서 pair<int, int>로 선언하는 이유는 first의 값이 arr[i]이고 second값이 키 높이가 같은 사람의 수를 의미한다.
아까 위쪽에서 말한 최적화 부분을 적용하기 위해 pair<int, int>로 한다.
스택은 내림차순으로 쌓이게 했다. 이 경우 현재 내 키와 stack.top을 비교하여 만약 top이 더 작다면 count후 pop을 하면 된다.
왜 pop을 할까? 라고 생각이 든다면 [1, 2, 1] 이 입력인 경우를 가정했을 때 0번째와 2번째는 1번째에 의해 서로 볼 수 없다.
즉 현재 나보다 이전 사람들 중 나보다 키가 작은 사람들은 앞에 사람들과 볼 수 없다는걸 적용시키는 것이다.
이어서 설명하자면 3번을 통해 최신화를 적용한 뒤 스택이 빈 경우 push만 해주고
5번과 6번으로 if문이 갈릴텐데 5번의 경우 top이 같다는거니까 즉 [5, 1, 2, 2, 2]가 있을 때 3번째와 4번째 2는 3번째 2와 0번째 5를 볼 수 있다.
5번을 적용한다면 3, 4번째를 순회할 때 스택에는 [(5, 1), (2, 1)] 형태일 것이고
3번째를 순회하면 [(5, 1), (2, 2)]
4번째를 순회하면 [(5, 1), (2, 3)]가 된다.
두 번을 순회했는데 3, 4번째는 동일한 키인 top을 보고 한 쌍을 이룰 수 있고 top이 동일한 키 이기에 이전에 있던 사람을 볼 수 있다. 따라서 키가 5인 사람과도 추가로 한 쌍을 이룰 수 있는 것이다.
6번의 경우 스택의 top에 있는 사람과 반드시 볼 수 있는 상황이기에 count해주고 push후 넘어간다.
이 로직을 전체 코드로 짜면 아래와 같다.
전체코드
#include <iostream>
#include <stack>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> arr(n);
stack<pair<int, int>> s;
for (int i = 0; i < n; ++i)
cin >> arr[i];
int result = 0;
for (int& t : arr) {
if (s.empty())
{
s.push({ t, 1 });
continue;
}
// 현재 내 키를 보고 이전 사람들의 키를 비교하며 쌍을 이룬다.
while (!s.empty() && s.top().first < t)
{
result += s.top().second;
s.pop();
}
if (s.empty())
{
s.push({ t, 1 });
continue;
}
auto& top = s.top();
// 현재 내 키가 이전 사람들의 키와 같으면 쌍을 이룰 수 있다.
if (top.first == t)
{
result += top.second;
top.second++;
// 만약 이전 사람들 전에 키가 더 큰사람이 있었다면 그것도 쌍을 이루기에 카운트
if (s.size() > 1)
result++;
}
else
{
// 현재 내 키가 만약 이전 사람들보다 작다면 이전사람들과 딱 한쌍만을 이룰 수 있다.
s.push({ t, 1 });
result++;
}
}
cout << result << '\n';
return 0;
}
로직에서 나온 그대로 while 반복문을 짜서 구현했다.
맨 처음 생각 안하고 count를 int로 했다가 50몇퍼쯤인가에서 빠꾸먹고 머리가 띵해져 있었는데 아! 하고 수정하니 잘 되었다.
프로젝트 아키텍처 구조 중에 룸들이 있는데 클라이언트가 요청할 때마다 이 룸들의 정보를 반환해주는게 있다. 그런데 요청할 때마다 새롭게 정보를 최신화 해주는것은 성능적으로 문제가 있다고 판단했다.그래서 아래와 같이 성능을 최적화 시키고자 했다.SizedByteArray를 통해 룸들의 정보를 바이트 배열화 시킨다.룸들이 추가되거나 삭제될 때마다 bool dirty 값을 true로 바꿔준다.클라이언트가 요청할 때마다 dirty값을 보고 바이트 배열을 최신화 한뒤 false로 바꿔주고 바이트 배열을 반환한다. 이렇게 한다면 매번 클라이언트가 룸 정보를 요청할 때마다 룸 정보를 만들어서 할 필요가 없이룸정보가 바뀌더라도 바로 최신화 하지 않고 룸 정보가 바뀌었으며 클라이언트가 요청할 때 최신화가 안되었으면 최신..
반응형
프로젝트 아키텍처 구조 중에 룸들이 있는데 클라이언트가 요청할 때마다 이 룸들의 정보를 반환해주는게 있다.
그런데 요청할 때마다 새롭게 정보를 최신화 해주는것은 성능적으로 문제가 있다고 판단했다.
그래서 아래와 같이 성능을 최적화 시키고자 했다.
SizedByteArray를 통해 룸들의 정보를 바이트 배열화 시킨다.
룸들이 추가되거나 삭제될 때마다 bool dirty 값을 true로 바꿔준다.
클라이언트가 요청할 때마다 dirty값을 보고 바이트 배열을 최신화 한뒤 false로 바꿔주고 바이트 배열을 반환한다.
이렇게 한다면 매번 클라이언트가 룸 정보를 요청할 때마다 룸 정보를 만들어서 할 필요가 없이
룸정보가 바뀌더라도 바로 최신화 하지 않고 룸 정보가 바뀌었으며 클라이언트가 요청할 때 최신화가 안되었으면 최신화를 하는 방식이기에 성능을 비약적으로 최적화시킬 수가 있다.
그런데 주의점이라하면 SizedByteArray는 과연 안전한 배열일까?
Byte[] 를 반환하는데 이 배열값을 FlatBufferBuilder 내부에서 관리하는 것이라면 FlatBufferBuilder가 GC에 의해 삭제되면 나중에 메모리 오염 문제가 생기지 않을까? 하는 의문점이 생긴다.
그래서 직접 FlatBufferBuilder 클래스의 SizedByteArray를 타고타고 들어가 보았다.
총 3번에 걸쳐서 함수가 완성되는 구조였다.
마지막 ToArray<T>를 호출할 때 T를 byte로 호출한다.
그리고 ToArray함수 내부에는 new로 배열을 새로 할당한 뒤 BlockCopy로 딥카피를 해준다.
따라서 반환되는 바이트 배열은 FlatBufferBuilder가 가지고 있는 배열과는 완전히 다른 배열인것이다!
NPC 말풍선 구현OnMouseDown & OverlapPoint 미작동플래피버드 캐릭터 회전 기능UI Toolkit ListView/ScrollView 기능 구현 NPC 말풍선 구현https://www.youtube.com/watch?v=WOmJ4ZPSsCk&ab_channel=DevGomDol처음엔 이 영상을 보고 구현하려고 했다. 영상에서 말풍선 구현하는 방법은 다음과 같다.3d 사각형 오브젝트를 생성하고 이를 말풍선 배경으로 사용한다.TextMePro를 사용한다. 이때 TMP는 GUI가 아닌 일반 오브젝트이다.TMP를 말풍선 오브젝트에 넣고 자식으로 만든다.말풍선이 나올 위치를 NPC의 ChatPoint위치로 초기화 한다.텍스트 길이에 따라 말풍선의 가로 세로 길이를 초기화 한다. 3d 오브젝트는..
3d 오브젝트는 sprite renderer가 아닌 mesh renderer를 사용하는게 정법이다.
문제점은 이 프로젝트는 2d 프로젝트여서 대부분 sprite renderer를 사용하고 있었다.
그런데 mesh renderer는 항상 sprite renderer 뒤에 렌더링 되는 문제가 있어서 해결 방법을 구글링 해보았다.
하지만 아무리 글을 찾아봐도 Mesh Renderer를 Sprite Renderer앞에 렌더링 하는법을 모르겠다.
How to rendering mesh renderer in front of sprite renderer in unity 라고 검색을 했을 때 유니티 포럼에서의 글들은 별로 도움이 되질 않았고 다른 글들 또한 마찬가지였다.
포기하던 중 문득 든 생각이 mesh renderer에도 sprite renderer처럼 sorting layer를 설정하는게 있지 않을까? 라는 생각이 들었고 그것은 정답이었다.
사진을 확인해보면 MeshRenderer는 Renderer를 상속받고 있고 Renderer는 sortingorder를 멤버 변수로 들고있다.
SpriteRenderer또한 Renderer를 상속받고 있다.
이것을 보고 말풍선 배경 부분은 sprite rederer로 해도 상관이 없을것 같아 변경하였고 매번 말풍선을 생성할 때마다 TMP의 sortingorder값을 코드에서 수정하게 두어서 해결했다.(meshrenderer는 inspector 창에서 sortingorder를 수정할 수 없다.)
OnMouseDown & OverlapPoint 미작동
OnMouseDown은 마우스 클릭했을 때 레이를 쏴서 콜라이더를 장착한 오브젝트 클릭 감지하는 함수이다.
이 함수를 이용해서 다른 씬을 로드하는 로직을 짜고 싶었다.
문제는 에디터 상에서 문제없이 작동되는것을 보고 바로 빌드를 해보았더니 빌드한 게임에서는 클릭 감지가 되지 않았다.
이유는 정확히 모르겠으나 게임 해상도 문제이지 않을까 싶다.
그래서 OverlapPoint를 써보았다.
Physics2D.OverlapPoint는 특정 Vector3 위치의 콜라이더를 가지고 오는 함수이다.
이놈 또한 에디터상에서는 문제가 없었지만 빌드했을 때는 감지를 못했다.
더 큰 문제는 이 함수의 파라미터로 넣는 Vector3위치가 잘못됐나 싶어서 점을 생성해보았다.
하지만 육안으로도 확인될 만큼 정확한 위치를 넘겨주어도 이 함수의 반환값은 항상 null이었다.
OverlapPointAll 또한 마찬가지였다.
그래서 다른 방법을 찾다가 Collider.bounds에 Contains라는 메서드가 있는것을 발견했다.
Collider.bounds.Contains는 Vector3를 파라미터로 받아 그 위치가 콜라이더와 겹치는지에 따라 bool을 반환하는 함수이다.
이 함수를 통해 클릭 위치를 감지하게 했더니 그제서야 정상적으로 클릭 감지를 할 수 있었다.
이번 포스트에서는 2D 애니메이션에 대해서 알아보겠다. Project창에서 우클릭 → Create 하면 Animation관련 데이터를 생성할 수 있다. 먼저 Animation을 생성한다는건 Animation Clip 을 생성한다는것이다.Animation Clip우선 Animation Clip을 자세히 보기 위해선 Animation 창을 열어야 한다.Window → Animation → Animation에서 열 수 있다. 그 이후 Animator Controller 컴포넌트가 장착된 게임 오브젝트를 클릭하면Animation창에서 해당 Anim Controller에 들어가있는애니메이션을 미리보기할 수 있는 기능들이 모여있다.보고 싶은 애니메이션을 설정할 수 있다. 오른쪽에 있는 것들은 특정 프레임에 이벤트를..
반응형
이번 포스트에서는 2D 애니메이션에 대해서 알아보겠다.
Project창에서 우클릭 → Create 하면 Animation관련 데이터를 생성할 수 있다.
먼저 Animation을 생성한다는건 Animation Clip 을 생성한다는것이다.
Animation Clip
우선 Animation Clip을 자세히 보기 위해선 Animation 창을 열어야 한다.
Window → Animation → Animation에서 열 수 있다.
그 이후 Animator Controller 컴포넌트가 장착된 게임 오브젝트를 클릭하면
Animation창에서 해당 Anim Controller에 들어가있는
애니메이션을 미리보기할 수 있는 기능들이 모여있다.
보고 싶은 애니메이션을 설정할 수 있다. 오른쪽에 있는 것들은 특정 프레임에 이벤트를 삽입하는 기능이다. (이벤트는 스크립트의 특정 함수를 호출하는 등 다양한 이벤트가 있다.)
현재 Clip에서 사용되는 게임 오브젝트의 속성들이다. 이곳에서 게임 오브젝트에 달려있는 웬만한 컴포넌트들을 조작할 수 있다.
실제 애니메이션이 실행될 때 프레임별로 기능들을 보여주는 곳이다. 사진처럼 Sprite들이 보통 있고 Position이나 Scale 등을 수정하는 경우도 많다.
DopeSheet는 현재 사진처럼 각 프레임별로 어떤 동작을 하는지 볼 수 있고 Curves는 프레임별로 이전, 다음 프레임과의 동작 연결시키는 시간을 조절할 수 있다. (만약 Scale이나 Position을 Clip에서 사용하며 프레임에 2개이상 있을 때 나올것이다. 첫 프레임에서 Scale.X를 3으로 했다가 두 번째 프레임에서 5로 설정한다 쳤을 때 다음 프레임으로 전환될 때 3에서 5로 즉시 전환될 지 3... 4... 5... 이런식으로 전환될지를 설정할 수 있다.)
Animator
Animator Controller를 두번 클릭하면 해당 Animator에 관련해서 창이 열린다.
Layer같은 경우 여러 애니메이션을 동시에 실행시키고 싶을 때 사용한다.
예를 들어 하체는 달리기, 상체는 활 쏘기, 휘두르기 등을 분리시켜 동시에 실행시킬 수 있다.
왼쪽에는 Animation을 전환할 때 사용될 조건문에 들어갈 변수들이다.
흔히 아는 int, float, bool을 주로 사용한다.
오른쪽 창에 뭔가 많은데 주황색, 회색 박스들이 Animation Clip 또는 Clip 전환용 State이다.
주황색 박스는 기본 State로 Animator가 실행될 때 가장 처음 연결될 State이다.
각 박스마다 화살표가 연결되어있는데 이는 State 전환 방향을 의미하며 현재 State에서 연결된 State로 전환될 수 있음을 뜻한다.
해당 State에 Parameter를 넣어서 조건문을 달아줄 수도 있다.
화살표를 클릭하면 나오는 Inspector이다.
디버깅 용으로만 사용되는 Solo와 Mute이다.
Solo: 해당 트랜지션이나 상태만 단독으로 재생하도록 설정한다.
Mute: 해당 트랜지션이나 상태를 무시하고 재생되지 않게 함
애니메이션끼리 전환될 때 설정하는 부분이다.
Has Exit Time: 전환되기 전 실행되고 있는 애니메이션을 다 끝낸 다음에 넘어갈지를 결정한다.
Exit Time: 애니메이션 전환 시간(0초면 애니메이션 종료, Has Exit time이 설정된 경우에만 켜짐)