[유니티 기초] 기분 좋은 터치 감각 (physic material , gravity)


오늘은 유니티로 간단한 게임을 만들어 보면서

액션 게임의 기본이 되는 기분 좋은 터치 감각을

어떻게 줄 수 있을까에 대해 알아보도록 하겠다.


매우 심오한 내용은 아니지만

하나의 '아이디어 출발점'은 될 수 있을 것이다.

대작 게임도 이러한 작은 아이디어에서 출발한다.




오늘 만들어볼 게임은 날아오는 공을

받아쳐서 '기분 좋은 터치감'으로 넘겨 버리는 게임이다.

왼쪽에서 초록색 공이 포물선을 그리면서 날아오고

빨간색 플레이어가 그 공을 받아쳐서 넘기면 된다.

간단한다.


01. 공이 포물선을 그리며 날아온다




02. 플레이어와 접촉




03. 기분 좋게 날려 버리기~




소스 코드는

player, ball, launcher 이렇게 세가지로 나뉜다.


01. Ball

생성과 동시에 Rigidbody의 velocity 메소드에 의해

포물선을 그리면서 날아가게 된다.


새로 배운 내용

void OnBecameInvisible()

오브젝트가 화면 밖으로 사라졌을 때 호출되는 함수이다.


02. Launcher

A키를 눌렀을 때 ballPrifab이 생성된다.


음.. 하지만 이 코드는 A키를 누를 때마다

Ball을 동적 생성하고 있으며, Ball스크립트를 보면

화면 밖으로 Ball이 사라졌을 때 해제를 해주고 있다.




동적 생성/해제는 추가적인 메모리를 필요로 하기 때문에

되도록이면 동적 생성/해제가 빈번하게 일어나는 오브젝트는

오브젝트 풀로 관리를 하는 것이 좋다. (후에 더 자세히 살펴보자.)



03. Player

is_landing --> 이단, 삼단 ... 점프를 방지하기 위한 플래그

A키를 누르면 플레이어가 점프를 하게 되는데

공중에 떠있어도 계속 점프를 하게되는 상황이 발생한다.


Floor에 접지되었을 때만 점프가 가능하게 해야한다.

플레이어의 점프 또한 Rigidbody의 velocity 메소드 사용.




새로 배운 내용

1. Debug.Break() -> 일시정지 시킨다.

간단한 디버깅을 할 때 Debug.Log를 자주 사용했다.

이와 비슷하게 Debug.Break()도 디버깅에 유용하게 쓰일 듯 하다.


2. Rigidbody --> mass

Ball이 플레이어와 접촉 했을 때,

두 오브젝트 모두 축축 쳐지는 느낌이 있었다.

이 미니게임의 목표는 '기분 좋은 터치감'이다.

경쾌하게 타격하여 공을 넘기는 것이 목표이다.


mass는 오브젝트의 무게를 설정하는 항목이다.

공의 무게가 너무 무거워서 접촉과 동시에

플레이어가 급하강 하는 모습을 볼 수 있다.

때문에 공의 mass를 0.01로 설정, 즉 공의 질량을

작게 하였으므로 플레이어는 공과 충돌해도 그대로 상승한다~


3. physic material

mass를 통해 질량을 달리하였다해도

'기분 좋게' 튕겨나가는 모습은 보이지 않는다.


physic materal을 공에 추가해 보도록 하자.

이렇게 생긴 놈이다.





Friction은 마찰에 대한 항목

Bounciness는 수치가 클수록 오브젝트가

쉽게 튀어오를 수 있도록 한다.




이로써 포물선을 그리며 날아오는 공을

플레이어가 경쾌하게 받아 쳐 넘길 수 있는

'기분 좋은 터치감'을 가진 게임을 만들어보았다.


간단한 구현이라도 큰 게임의 핵심 요소가 될 수도 있기 때문에

차근차근 하나씩 쌓아나가야 할 것이다.




다음에는 Rigidbody의 Velociy와 AddForce의

차이점을 알아보고 활용 예에 대해서 알아보겠다.





[알고리즘 기초][구현] X보다 작은 수, 윷 놀이 (백준)


백준 사이트에서 단계별로 풀어보기를 하다가

구현이라는 단계의 몇 문제를 풀어 보았다.

알고리즘 기초 중의 기초 문제인 듯 하다.




01. X보다 작은 수






02. 윷놀이





간단 알고리즘 기초 문제를 풀어 보았다.

오랜만에 알고리즘 문제를 풀어서 쉬운 문제를 골랐는데

음.. 너무 쉬운 문제를 골랐나..?




[자료구조] 이진 트리의 이해


연결 리스트는 대표적인 선형 구조의 예이다.

오늘 포스팅할 이진 트리는 비선형 구조의

대표적인 예라 할 수 있겠다.




일단 이진 트리는 

1. 배열 기반

2. 연결 리스트 기반

으로 나눌 수 있는데 연결 리스트 기반으로 알아보자.


이진 트리에 들어가기 앞서 알아둘 내용은

1. 2개의 자식 노드를 가질 수 있다는 점과

2. 하나의 노드도 이진 트리라는 점이다.

자식 노드가 없는 노드는 공집합 노드를 가지게 되는데

자식이 하나 혹은 하나도 없는 노드 또한 이진 트리라고

기대할 수 있게 하기 위함이라고 한다.




이진 트리를 표현한 구조체



헤더 파일에 선언된 함수들



구현



예시




짚고 넘어가야할 부분

구현 부분의 MakeLeftSubTree() 함수를 보면

추가하려는 자리에 기존 데이터가 있으면

삭제하고 대체하게 되는데 이 과정에서


삭제하려는 노드의 서브트리가 존재하게 되면

메모리 누수로 이어지게 된다.


이 해결책으로 순회라는 방법이 있는데

다음 포스팅에 계속 이어 가보도록 하겠다.


이진 트리를 이해하기 위해 간단한 구현을 해보았다.

앞으로 이진 트리에 대한 어마어마한 내용이 남아 있기 때문에

꼭 마스터 하고 넘어가도록 하자.







[자료구조] 연결 리스트의 이해(3)


미 노드 기반의 연결 리스트에 대해 알아보자.

그 전 포스팅에서 보았듯이, 삽입, 삭제, 참조 시에

첫 노드와 그 이외의 노드의 처리과정이 차이가

나는 것을 볼 수 있었다. 모든 노드들을 일관되게

처리를 해주는 것이 좋은 코드라고 말했는데,

이는 더미 노드를 이용하면 쉽게 해결할 수 있다.




더미 노드란?

유요한 데이터를 저장하지 않는 노드이다.

즉, 헤드가 데이터를 가지지 않는 껍데기(?) 뿐인

노드를 물고있는 것이다. 아래 그림을 보자


기존의 연결 리스트




더미 노드 기반 연결 리스트




왜 이렇게까지 해야할까 의문이 들수도 있지만

연결 리스트의 구현이 더욱 복잡해 졌을 때, 코드의

일관성은 무시할 수 없기 때문이다. 때문에 현재

더미 노드 기반의 연결 리스트가 표준에 더욱 가깝다.




더미 노드를 통해 구현한 삽입, 삭제, 참조를 보도록 하자.

첫 노드를 할당했지만, 아무런 데이터를 물고 있지 않다.

데이터는 이 더미노드 뒤부터 추가되게 된다.




01. 삽입

더 이상 head를 다루지 않아도 된다.

head는 더미 노드이고 실제 데이터는 그 후로 저장되어 있기 때문




02. 참조



03. 삭제




기존의 연결 리스트와 다른 차이점이 보이지 않는다.

다만 처음에 head가 데이터를 저장하지 않는 노드를 물고 있다.


즉, 첫 노드는 아무런 데이터가 없다.

첫 노드를 제외한 모든 코드를 다루는 것이기 때문에

기존의 코드에서 첫 노드를 다루는 부분만 삭제해주면 된다.








DFS 알고리즘 백준의 미로 탐색 문제


백준 알고리즘 사이트의 미로 탐색 문제를 풀어보았다.

DFS 알고리즘, BFS 알고리즘 모두 가능 하지만

DFS 알고리즘을 이용하여 풀어 보았다.


결과는...





내가 생각하기에도 너무 쓸데없는 탐색이 많이 들어가있다.

한 지점에 들렀을 때 이 지점을 경유하면 최소값이

나올 수 없다는 것을 판단하는 부분을 넣어서 

아예 그 지점에 대한 DFS 알고리즘을 수행하지 않는

방법으로 가보는 것이 좋을 것 같다.




01. 초기화 , 체크 함수



02. DFS 알고리즘



03. 메인 함수


DFS 알고리즘은 항상 풀기는 쉬운데

시간 초과가 많이 뜨는 것 같다.

이 문제는 정답을 맞춘 후에

위의 포스팅과 비교하여 어떤 부분에서

시간초과가 났는지 분석해서 올리도록 하겠다.




BFS 알고리즘 백준의 토마토 문제


정말 오랜만에 BFS 알고리즘 문제를 풀어 보았다.

그저께 DFS 알고리즘 문제를 풀다가 2시간 멘붕이 와서

사실 자심감이 많이 떨어진 상태..


BFS, DFS만은 자신 있었는데.. 꾸준히 해야겠다는 생각이 든다.

이 문제도 40분 걸렸지만, 한 번에 성공했다는 것에 만족~








01. 구조체 , 큐, 박스 등등




02. 초기화



03. 체크 함수




04. 모두 익었니? 아니면 덜 익은게 있니?




05. BFS 알고리즘




06. 메인 함수



BFS 알고리즘을 이용하여 백준의 토마토 문제를 풀어보았다.

한동안 알고리즘에 손 떼고 있다가 풀어보니 낯설다..

무엇이든 꾸준함이 가장 중요한 것 같다.


[C++ STL] C++의 선형 검색


전 포스팅에서 보았던 C로 작선된 find1은 char 형식의

배열만을 검색할 수 있었다. C++의 경우에는 템플릿을

이용해서 함수 인수의 형식을 매개변수화할 수 있으므로,

find1을 char 이외의 형식에 대해 일반화하는 것이 가능하다.




template <typename T>

T* find2 (T* first , T* last, T value);


상당히 명백하고도 직관적인 일반화이다.

하지만 STL은 약간은 덜 명백한 일반화를 사용한다.




template <typename Iterator. typename T>

Iterator find (Iterator first, Iterator last, const T& value){

while (first != last && *first != value)

++first;


return first;

}


이런 형태의 find가 find2보다 나은 점은 무엇일까?


find가 더욱 일반적이라는 데 있다. find2에는 

first, last가 포인터이어야 한다는 테한이 있지만,

find에는 그런 제한이 없다. 물론 포인터 연산의

구문을 사용하기는 하지만, Iterator가 반드시

포인터이어야 하는 것은 아니다. 

Iterator가 포인터 같은 인터페이스를 지원하기만 하면 된다.




find는 1차원적인 요소들의 순차열이라는 개념을 지원하는

임의의 자료구조에 대해 검색을 수행하는 하나의 일반적 알고리즘이다.

1. 선형 검색을 위해 필요한 것은, 하나의 요소를 조사하고,

2. 다음 요소로 넘어가고, 3. 모든 요소들을 처리했는지 판정하는 것이다.


[C++ STL] C의 선형 검색


선형적인 구간에 대해 작동하는 알고리즘의

중요한 아이디어에 대해 이야기해보자.


선형적인 구간이란, 하나의 첫 번째 요소와 하나의 마지막

요소가 존재하며, 마지막 요소를 제외한 모든 요소들에는 '그 다음 요소'가 있고,

첫 번째 요소에서 시작해서 다음 요소들을 계속 따라가다 보면 마지막 요소에

도달하게 되는 1차원적인 요소들의 모음을 말한다. 이러한 선형적인 구간들에

대해 작동하는 알고리즘들이 STL의 핵심부를 이루고 있다.




C의 선형 검색

C의 라이브러리 함수 strchr을 보도록 하자.

char* strchr(char* s, int c);


char* strchr( char* s, int c){

while ( *s != '\0' && *s != c)

++s;

return *s == c ? s : (char*) 0;

}


선형 검색의 일반적인 아이디어는

1. 순차열의 시작에서부터 요소 하나씩 거쳐가면서 주어진 값다 비교한다

2. 현재 요소가 주어진 값과 같으면, 알고리즘은 순차열 안에서의 현재 요소를 리턴

3. 같지 않다면 다음 요소로 넘어간다.

4. 다음 요소가 없다면 실패했음을 알리는 뭔가를 리턴




이러한 선형 검색을 구현할 때에는 몇 가지 이슈가 있다.

1. 검색하고자 하는 순차열을 어떻게 지정할 것인가?

2. 순차열 안의 한 위치를 어떻게 표현할 것인가?

3. 다음 요소로 넘어가려면 어떻게 할 것인가?

4. 순차열의 끝에 도달했음을 어떻게 알아낼 것인가?

5. 실패했음을 알리는 값으로 무엇을 사용할 것인가?




1. ctrchr 함수를 호출할 때 검색할 문자열의 첫 번째 요소를

가리키는 포인터를 넘겨준다.

2. 문자열 안의 한 위치 또한 포인터로 나타낸다.

3. 포인터 연산을 통해 접근한다.

4. 검색할 순차열은 하나의 C문자열, 즉 널로 끝나는 문자열이다.

때문에 현재 포인터가 \0을 가르킨다면 끝에 도달한 것

5. 선형 검색을 하는 동안 해당 문자를 찾지 못했다면

포인터는 문자열의 끝에 가 있을 것이므로 널 포인터를 리턴.




하지만 strchr은 일반적이지 않다. 이 함수는 char들의 배열 안에서

하나의 char를 검색하는 데에만 사용할 수 있다.

그리고, strchr의 첫 번째 인수는 널 종료 문자 배열을 가리키는

하나의 포인터이어야 하는데, 이는 보기보다 일반적이지 않다.

C에서는 널 종료 문자열이 대부분이긴하지만 부분문자열을

표현하려한다면 문제각 생기는 것도 사실이다.




부분 문자열을 지원하기 위해 strchr의 인터페이스를 수정해보자.

검색할 배열의 끝을 가리키는 포인터를 또 다른 인수로 지정해 보자.


char* find1 (char* first, char* last, int c){

while (first != last && *first != c)

++first;

return first;

}


이 find1 구현은 배열의 끝을 루프 반복의 종료 조건으로 해서

포인터로 배열을 훑는, 흔히 볼 수 있는 C관용구를 이용한다.

이 루프는 first가 last와 같으면 즉시 끝나며, 따라서 last는

역참조 되지 않는다. 즉, last 바로 전까지 모든 요소를 검색한다.




위의 함수에서는 3가지 포인터들이 사용된다.

1. 역참조할 수 있는 보통의 포인터

2. NULL 같은 유효하지 않은 포인터

3. 역참조할 수 없지만 포인터 산술에 사용할 수 있는 끝을 넘어선 포인터




char A[N];

C의 모든 배열에는 끝을 넘어선 포인터가 하나 존재한다. (A+N)

아무것도 가리키지 않기 때문에 역참조해서는 안되며,

딱 하나만 있으므로 포인터를 증가시키려 해서도 안된다.

오직 포인터 산술 연산에만 사용된다. (시작 이전 포인터 같은 것은 없다. A-1과 같은 ... )




[C#] 가비지 컬렉터 (Garbage Collector) 의 원리, 동작 메커니즘


CLR은 어떻게 메모리에 객체를 할당하는가?

C#으로 작성한 소스코드를 컴파일하고 이 파일을 실행하면,

CLR은 이 프로그램을 위한 일정 크기의 메모리를 확보한다.

C-런타임처럼 메모리를 쪼개는 일은 하지 않는다.


넓은 메모리 공간을 통째로 확보해서 하나의 관리되는 힙 (Managed Heap)을

마련한다. 그리고 CLR은 이렇게 확보한 관리되는 힙의 첫 번째 주소에

"다음 객체를 할당할 메모리의 포인터"를 위치 시킨다.




여기에 첫 번째 객체를 할당해보자.

object A = new object();


CLR이 코드를 실행하면 "다음 객체를 할당할 메모리의 포인터"가

가리키고 있는 주소에 A객체를 할당하고 포인터를 A 객체가

차지하고 있는 공간 바로 뒤로 이동시킨다.



또 다른 객체를 하나 만들어보자.

object B = new object();




보시다시피 CLR은 객체가 위치할 메모리를 할당하기

위해 메모리 공간을 쪼개 만든 연결 리스트를 탐색하는 시간과

재조정하는 작업도 필요하지 않다. 그저 메모리만 할당할 뿐


그렇다면 언제, 어떻게 메모리에서 해제될까?

일단 쓰레기인지 아닌지를 판별해야 하지 않을까?


if (true){

object a = new object();

}

객체의 내용물은 힙에, A가 위치한 힙 메모리 주소는 a에 있다.



if 의 블록이 끝나면 a는 없어지게 된다.

그렇다면 A는..?




a를 잃은 채 힙에 남아 있는 객체 A는 이제 코드의 어디에서도

접근할 수 없기 때문에 더 이상 사용할 수 없게 되었다.

즉 쓰레기가 되고, 가비지 컬렉터가 집어가게 된다.



가비지 컬렉터의 원리, 동작 메커니즘

사라져버린 a처럼 할당된 메모리의 위치를 참조하는 객채를 일컬어

루트(Root)라고 부른다. 루트는 a의 경우처럼 스택에 생성될 수도 있고

정적 필드처럼 힙에 생성될 수도 있다. .NET 에플리케이션이 실행되면

jit 컴파일러가 이 루트들을 목록으로 만들고, CLR은 이 루트 목록을

관리하며 상태를 갱신한다. 이 루트가 중요한 이유는 가비지 컬렉터가

CLR이 관리하고 있던 루트 목록을 참조해서 쓰레기를 수집하기 때문.



1. 작업을 시작하기 전에, 가비지 컬렉터는 모든 객체가 쓰레기라고 가정.

루트 목록 내의 어떤 루트도 메모리를 가리키지 않는다고 가정.


2. 루트 목록을 순회하면서 각 루트가 참조하고 있는 힙 객체와의

관계 여부를 조사. 루트가 참조하고 있는 힙의 객체가 또 다른 힙 객체를

참조하고 있다면 이 역시도 해당 루트와 관계가 있는 것으로 판단.

어떤 루트와도 관계가 없다면 쓰레기로 간주.


3. 쓰레기 객체가 차지하고 있던 메모리는 이제 '비어있는 공간'


4. 루트 목록에 대한 조사가 끝나면, 가비지 컬렉터는 이제 힙을

순회하면서 쓰레기가 차지하고 있던 '비어있는 공간'에 쓰레기의

인접 객체들을 이동시켜 차곡차곡 채워 넣는다.




가비지 컬렉터의 원리, 동작 메커니즘에 대해 살펴보았다.

다음 포스팅에서는 가비지 컬렉션의 성능을 높이기 위한

'세대별 가비지 컬렉션' 알고리즘에 대해 알아보겠다.

[C#] 가비지 컬렉터


C++에서 클래스를 만드는 과정에 있어

가장 신경썻던 부분이 new/delete를 통한

메모리 할당/해제가 아니었나 생각이 든다.


할당은 그렇다 쳐도 해제하는 것을 까먹는다거나

해제한 줄도 모르고 그 포인터에 접근하는 경우가 발생한다.

때문에 스마트 포인터를 사용하여 이 문제를 극복하곤 하는데

c#에서는 가비지 컬렉터라는 멋진 놈이 이러한 문제를 해결해준다.




C/C++의 메모리 할당

C/C++는 힙에 객체를 할당하기 위해 C-런타임은 객체를

담기 위한 메모리를 여러 개의 블록으로 나눈 뒤, 이 블록을

연결 리스트로 묶어서 관리하게 된다.


어떤 객체를 힙에 할당하는 코드가 실행되면, C-런타임은

메모리 연결 리스트를 순차적으로 탐색하면서 해당 객체를

담을 수 있을 만한 여유가 있는 메모리 블럭을 찾는다.




적절한 크기의 메모리 블록을 만나면, 프로그램은 이 메모리

블록을 쪼개서 객체를 할당하고 메모리 블록의 연결리스트를

재조정한다. 따라서 메모리 공간에 데이터를 집어넣는다는 것은

1. 탐색 2. 분할 3. 재조정의 오버헤드가 필요하다는 것이다.




C#의 메모리 할당

C#은 CLR이 자동 메모리 관리 기능을 제공한다.

이 기능의 중심에는 가비지 컬렉션 (Garbage Collection) 이 있다.

가비지 컬렉션은 프로그래머로 하여금 컴퓨터가 무한한 메모리를

가지고 있는 것처럼 간주하고 코드를 작성할 수 있게 한다.

(여기서 가비지란 더 이상 사용하지 않는 객체)




CLR 안에는 이러한 가비지 컬렉션을 담당하는

가비지 컬렉터 (Garbage Collector)라는 놈이 있다.

가비지 컬렉터는 객체 중에 쓰레기인 것과 쓰레기가

아닌 것을 완벽하게 분리해서 쓰레기들만 수거한다.




하지만 가비지 컬렉터 역시 CPU와 메모리 자원을 소모한다.

때문에 가비지 컬렉터가 최소환으로 이 자원을 사용할 수 있게

만들 수 있다면 성능을 아낀 자원의 양만큼 끌어올릴 수 있게 된다.




그렇다면 가비지 컬렉터가 최소환으로 자원을 사용하게 만들기 위해

우선 가비지 컬렉터가 어떻게 동작하는지에 대한 메커니즘을 이해해야 한다.

포스팅이 길어질테니 여기서 끊고, 다음 포스팅에서 가비지 컬렉터의

동작 메커니즘에 대해 알아보도록 하겠다.





+ Recent posts