제목으로는 어떤 포스팅인지 궁금할 것이다.

 

우리는 유니티에서 회전값을 받아오거나 셋팅할때 Quaternion 자료형을 쓴다.

 

평소때는 부모의 회전값을 고려할 필요가 없고, 있더라도 localRotation을 씀으로써 별 어려움 없이 쓸 수 있다.

하지만, 근래 모바일의 자이로 센서 회전값을 카메라에 적용해야 할 필요가 있어, Input.gyro.attitude 값을 그대로 카메라에 넣었더니, 당연하게도 정상동작을 하지 않았다.

 

첫번째 난관으로는 오른손 좌표로 넘어오는 자이로 회전값을 왼손좌표를 쓰는 유니티 좌표로 변환하는 곳

두번째 난관으로는 변환 후 카메라에 해당 회전값을 적용하는 곳이었다.

여기까지는 단순히 자이로 공간 -> 유니티 카메라 공간 일 뿐이므로, 약간의 삽질은 있었지만, 샘플 프로젝트에서 정상동작 확인 후 실 프로젝트에 적용하게 되었다.

 

하지만 역시나 되지 않았다.

몇번의 구글링을 해보니 카메라의 부모 오브젝트를 하나 만들고 그녀석의 회전값을 (90f, 180f, 0f) 해놓고 자이로 값을 카메라의 localRotation에 넣으라신다.

 

하지만 나는 부모의 부모의 회전값도 실시간으로 건드려야 하기 때문에 로컬회전값 따윈 필요하지 않아서, '좋아 얘를 월드회전으로 바꿔야지' 하면서 호기롭게 부모의 월드 Quaternion * 자이로 회전값 을 카메라의 월드회전값에 적용했다.

결과는 비루했고, 원인을 찾아 해결은 햇지만 추후 다시 쿼터니언을 다룰 일이 있을것 같아 알게된 몇가지 명제에 대해 정리하고자 한다.

 

  • transform.rotation : Quaternion.Inverse(transform.parent.rotation) * transform.localRotation
  • 쿼터니언 A, B 간의 차이 : A * Quaternion.Inverse(B)  쿼터니언의 곱은 교환법칙이 성립하지 않는다. 순서 주의
    • A에서 B로 가기 위한 회전값으로도 간주할 수 있다.
  • A 쿼터니언에 회전값 B를 추가 : B * A
    • 주의하자. 회전값을 추가할때는 항상 추가하고자 하는 값을 먼저 곱한다.

 

이를 이용해 자이로 센서값을 카메라의 월드회전에 적용하기 위한 코드는 다음과 같다.

camera.rotation = Quaternion.Inverse(Quaternion.Euler(90f, 180f, 0f)) 
					* Input.gyro.attitude 
					* new Quaternion(0f, 0f, 1f, 0f);

 

 

포지션은 부모의 위치를 쭉 순서대로 더하면 나의 월드 위치를 알 수 있는 것관 다르게 회전은 부모의 회전 역행렬을 먼저 곱해야 나온다는 사실을 잊지 말아야겠다.

 

 

'Unity' 카테고리의 다른 글

Batch 및 SetPass Calls  (0) 2020.12.03
rigidbody, collider 개념 및 정리  (0) 2020.10.08
IL2CPP  (0) 2020.09.29
Addressables  (0) 2020.07.28
Mobile 최적화  (0) 2020.06.12
  • tex2D(sampler2D,TEXCOORD)
    • 버텍스 셰이더에서 전달받은 정점의 UV 좌표 (해당 정점이 텍스쳐의 어느 점을 그릴 것인지) 와 실제 텍스쳐를 이용해 해당 픽셀에 그려질 텍스쳐 색상값을 구하는 함수
    • 첫번째 인자는 텍스쳐 (=텍스쳐를 셰이더에서 쓰기 위해선 sampler 과정을 거친 sampler2D 자료형을 써야함), 두번째 인자는 정점의 UV 좌표이다.
    • 리턴 값으로는 float4 형태의 색상값을 받는다.
  • float
    • shader가 받아들일 자료형 중 가장 범위가 큼 (GPU별로 다르긴 하지만 32bit ~ 16bit로 할당됨)
  • half
    • float 값 범위의 1/2 
    • float 보다 덜 정밀한 값을 다룰 때 사용
  • fixed
    • 일반적으로 11bit 이며 범위는 -2.0 ~ 2.0
    • 간단한 컬러 작업 수행에 용이

주어진 정점에서 최단경로로 이동시 가장 먼 정점들의 갯수를 구하는 문제로서, 노드와 엣지 & 가중치가 있기 때문에 다익스트라 알고리즘을 적용해야 하는 문제이다.

 

입력값은 정점들의 갯수와 [[3, 6], [4, 3], [3, 2], [1, 3], [1, 2], [2, 4], [5, 2]] 과 같이 두 정점간 연결 상태를 제시한다.

 

해결단계는 크게 3단계로 나누었다.

  1. 데이터 구조 마련
  2. 알고리즘 적용하여 각 정점별 최단거리 추출
  3. 최대값의 갯수 추출 (해)

각 단계에 대한 설명

  • 데이터 구조 마련
    • arrange 함수에서 입력값을 알고리즘 적용에 용이하도록 구조를 짬
    • newEdge
      • 알고리즘이 알아들을 수 있는 데이터 구조 (dictionary 선언)
    • for _from, _to in edge
      • 입력값 내에서 쌍으로 되어있는 엣지 두개를 추출한다
    • {시작정점 : [끝정점 : 가중치]} 형태가 되도록 데이터를 채워간다
      • 양방향 노드이므로 정점의 순서를 바꿔서 채우기도 포함
def arrange(edge):
    newEdge = {}
    for _from, _to in edge:
        empty = [_to, 1]
        if not _from in newEdge:
            newEdge[_from] = []
        
        newEdge[_from].append(empty)

        empty = [_from, 1]
        if not _to in newEdge:
            newEdge[_to] = []
        
        newEdge[_to].append(empty)

    return newEdge

 

  • 다익스트라 알고리즘
import heapq

def dijkstra(cnt, edge):
    # 최단거리 검색 시간을 O(N * log N) 수준으로 줄이기 위해 우선순위 큐를 이용할 것이고, 이를 위한 컨테이너
    arc = [] 

    # 시작정점에서 목표정점까지의 거리(가중치)를 담을 리스트 선언
    # 리스트의 인덱스 번호를 정점 번호(이름)로 간주할 것이므로 엣지 갯수 + 1 의 크기로 선언
    # distances[0] 은 0번 정점까지의 거리를 의미하는데, 문제에서 0번 정점은 존재하지 않는다.
    # 그러므로 유효한 값은 distances[1] ~ distances[정점갯수] 가 된다.
    # 우리는 이 distances 값을 구하는 게 목표이다.
    # 초기에는 각 정점까지의 거리를 알 수 없으므로 무한대 거리로 선언 - float('inf')
    # distances[1] = 0 은 시작점(1번 정점) 에서 1번 정점간 거리가 0이라는 의미이고, 
    # distances[2] = 1 는 시작점에서 2번 정점으로 가기까지의 거리가 1 이라는 의미이다. 
    distances = [float('inf') for i in range(cnt+1)]

    # 문제에서 시작점은 1로 고정이므로 distances[1] = 0 이 된다 (자기 자신과의 거리는 0 으로 설정)
    distances[1] = 0

    # 첫번째 인자는 넣을 데이터, 두번째 인자는 넣을 아이템인데, 만약 이값이 튜플이라면 튜플의 첫번째 요소를 우선순위 값으로 간주한다.
    # 이를 응용해 최소힙만 지원하는 heapq를 최대힙으로 바꿀수도 있다. (첫번째 인자에 -를 붙이면 됨)
    # 튜플의 첫번째 요소는 두번쨰 요소인 정점까지의 거리, 두번째 요소는 정점으로 설정하면
    # 우선순위인 거리에 의해 자동정렬되어 최단거리 검색 시간을 선형리스트를 쓰는 것에 비해 줄일 수 있다
    # 선형 리스트 O = N^2, 우선순위 큐 O = N * logN
    heapq.heappush(arc, (0, 1))

    # 큐에 더이상 값이 없을때까지 반복
    while arc:

        # 힙에서 뽑아낸 값은 튜플로서 (정점, 거리)
        current, distance = heapq.heappop(arc)
        
        # 만약 이미 저장된 정점까지의 거리보다 새로 뽑은 값이 크면 이미 최단 경로가 저장되어 있다는 의미이므로 넘긴다.
        if distances[current] < distance:
            continue

        # 위 arrange 함수에서 각 정점이 어떤 정점들과 연결되어 있는지 정리했으므로
        # 현재 정점과 연결되어있는 모든 정점의 거리를 추출한다. 
        for _next, _nextDist in edge[current]:
            # _next : 인접노드
            # _nextDist : 인접노드까지의 거리

            # 선택된 노드를 인접 노드로 거쳐서 가는 거리
            _nextDist += distance

            # 거쳐서 가는 거리가, 기존 거리보다 작으면 (짧으면) 최적 경로로 간주하고 비용 교체
            if _nextDist < distances[_next]:
                distances[_next] = _nextDist
                heapq.heappush(arc, (_nextDist, _next))

    # 유효한 값은 distances[1] 부터 이므로 짜른다
    return distances[1:]

 

  • 최대값의 갯수 추출
def solution(n, edge):
    answer = 0
    distances = dijkstra(n, arrange(edge))
    maxDist = max(distances)
    for dist in distances:
        if dist == maxDist:
            answer += 1

    return answer

 

'Python > 프로그래머스 코딩테스트 연습' 카테고리의 다른 글

완전탐색 / 모의고사  (0) 2020.09.15
정렬 / H-Index  (0) 2020.09.10
힙 / 더 맵게  (0) 2020.09.10
스택&큐 / 기능개발  (0) 2020.09.10
스택&큐 / 주식가격  (0) 2020.09.10

알파가 포함된 이미지의 실제 보이는 부분만 터치영역으로 설정하고 싶을때가 있다.

가령 아래와 같은 이미지

참고 이미지

UI 는 기본적으로 사각형 형태로 영역이 잡히기 때문에 몇가지 설정을 해주어야 하는데,

  1. Texture Import Inspector
    • Sprite MeshType 을 FullRect로 설정
    • Advanced -> Read/Write Enable 체크
  2. Image 의 AlphaHitTestMinimumThreshold 설정

GetComponent<Image>().alphaHitTestMinimumThreshold = 0~1값

터치의 raycast hit 테스트의 알파값을 설정할 수 있는 값으로, 설정 값 이상의 알파값을 갖는 영역만 터치로 간주하겠다는 의미

유니티에서 충돌을 처리하기 위해선 rigidbody와 collider를 Object에 붙여야 한다.

 

하지만 프로젝트마다 충돌의 목적 및 기대결과가 다르고 두 컴포넌트간의 옵션값 설정에 따라 충돌감지 및 물리현상 발현 유무가 다르기 때문에 충돌에 대해 정확히 정리해야 할 필요가 있다.

 

  • 충돌감지
    • 양쪽 오브젝트에 Collider추가
      • Collider와 Collider2D는 서로 감지가 안되므로 Collider는 Collider끼리 Collider2D는 Collider2D 끼리 추가
    • 충돌을 감지할 오브젝트에 rigidbody 추가
      • 위에서 2D를 달앗으면 rigidbody도 2D를 달아야함
      • 꼭 두 오브젝트 모두에 rigidbody를 달 필요는 없음
    • rigidbody가 달린 오브젝트에 monobehaviour 스크립트 추가하고 다음 세가지 함수 추가
      • OnCollisionEnter - 다른 어떤 Collider가 내 Collider 영역으로 들어왔을때 호출됨
      • OnCollisionStay - 다른 어떤 Collider가 내 Collider 영역에 있을때 호출됨
      • OnCollisionExit - 다른 어떤 Collider가 내 Collider 영역에 있다가 나갔을때 호출됨

기본은 이렇게만 하면 위 세가지 함수가 정상적으로 호출되며 충돌감지시 처리를 하면 되는데, rigidbody 옵션과 collider 의 isTrigger 옵션에 따라 함수 호출이 안되는 경우가 있다. 이럴때 아래 옵션값을 확인하며 체크를 해보자

 

  • rigidbody2D의 Simulated 가 체크되어 있는가? (다른 충돌체와 상호작용을 원하지 않을때는 체크해제한다)
  • rigidbody의 Kinematic 속성이 켜져 있을 경우 순전히 운동성(속도, 가속도, 위치)만 가지므로 위의 OnCollisionEnter, Stay, Exit 함수도 호출되지 않는다 (충돌이 일어나지 않으므로 서로 물리법칙에 의해 튕겨 나가는 등의 인터렉션이 없다 !!)
    • Kinematic 을 유지한채 충돌감지를 하고 싶을땐
      1. Collider의 isTrigger 옵션을 켠다.
      2. OnTriggerEnter, OnTriggerStay, OnTriggerExit 를 사용한다.
        • OnTrigger, OnCollision 두 종류 함수는 결코 같이 호출되는 일이 없다.

결론

만약 두 오브젝트를 중력이나 충돌상호작용 등 물리법칙에 영향받지 않고 충돌감지만 하고 싶을때는 Rigidbody의 Kinematic을 켜고, Collider의 isTrigger 옵션을 체크한 후 OnTriggerOOO 함수를 Rigidbody가 달린 스크립트에 추가하자

 

 

 

'Unity' 카테고리의 다른 글

Batch 및 SetPass Calls  (0) 2020.12.03
Quaternion 공간변환  (0) 2020.11.23
IL2CPP  (0) 2020.09.29
Addressables  (0) 2020.07.28
Mobile 최적화  (0) 2020.06.12

Mac 에서 vscode로 Unity 프로젝트를 열었는데,  어느날 갑자기 다음과 같은 창이 뜨며 프로젝트를 불러오지 못했다.

그래서 Show Output을 클릭했더니 Omnisharp 콘솔창에 아래와 같은 메세지를 발견

 

The reference assemblies for .NETFramework,Version=v4.7.1 were not found. To resolve this, install the Developer Pack (SDK/Targeting Pack) for this framework version or retarget your application. You can download .NET Framework Developer Packs at https://aka.ms/msbuild/developerpacks

 

확인결과 vscode 의 최신 C# Extension 이 해당 버전의 프레임웍을 불러오지 못해 발생한 이슈였다.

 

해결법은 2가지라고 하는데 첫번째는 최신 mono를 깔기, 두번째는 C# Extension 다운그레이드

하지만 본인은 이미 최신 모노였으므로, Extension 다운그레이드를 진행했다.

 

포스팅 날짜 기준으로 .NETFramework 4.7.1은 1.22.1로 다운그레이드 했더니 정상적으로 프로젝트가 로딩된다.

 

extension 다운그레이드 방법은,

 

  1. vscode 실행
  2. ⇧ + ⌘ + P  를 누른다
  3. Extensions: Install Specific Version of Extensions... 항목을 선택
  4. C# 선택
  5. 1.22.1 선택

 

 

 

'IDE > Visual Studio Code' 카테고리의 다른 글

개발환경 셋팅  (0) 2020.06.10
단축키  (0) 2020.06.01

터미널에서 mongo 명령을 실행한 뒤 터미널 명령어 정리

 

$ db

현재 존재하는 DB 검색

 

$ use [DB-NAME]

해당 DB로 이동

 

$ show collections

존재하는 모든 collections 나열

 

CRUD 명령

CREATE와 UPDATE는 주로 코드상에서 실행하므로 READ, DELETE Shell만 소개

 

READ

  • $ db.[COLLECTION-NAME].find()
    • 컬렉션에 존재하는 모든 도큐먼트 나열
  • $ db.[COLLECTION-NAME].find().pretty()
    • 위 명령어로 나오는 도큐먼트들을 형식에 맞게 formating 해서 나열

DELETE

  • $ db.[COLLECTION-NAME].remove({})
    • 컬렉션 내에 존재하는 모든 도큐먼트 삭제

 

'웹 개발 > MongoDB' 카테고리의 다른 글

MongoDB 설치  (2) 2020.08.14

Unity가 제공하는 scripting backend 로서 빌드시 mono 대신 사용함.

 

C#은 msc.exe 라는 프로그램에 의해 IL 코드로 변환되며 유니티에서는 C# 코드를 빌드하면 Assembly-CSharp.dll 이라는 결과파일이 나오는데 이것을 IL 이라고 지칭한다.

이를 기계에 넣어서 실질적으로 실행시키기 위해선 Assembly코드, 즉 Binary로 변환해야 하는데 이 역할을 기존의 Mono가 함.

이 Mono가 코드를 한줄씩 읽으면서 실시간으로 Assembly 언어로 변환하는데, 이러한 일련의 과정을 JIT컴파일링 이라고 함 (Just In Time : IL을 해석하고 기계어로 변환하는 과정만을 JIT컴파일링 이라고 함)

 

IL2CPP도 msc.exe를 이용해 IL코드를 생성하는건 동일하지만, il2cpp.exe라는 프로그램을 통해 IL을 C++로 변환한후 이를 기계어까지 생성한다 (현재 대부분의 컴파일러들은 C++을 이용해 기계어를 생성할 수 있기 때문에 C++로만 바꿔도 따로 기계어로 번역하는 시스템을 안써도 됨 = Mono 로부터의 자유)

이렇게 되면 JIT컴파일링과는 다르게 이미 기계어로 변환되어 있기 때문에 실행중에 변환하는 Interprete 과정이 생략되어 성능이 향상됨

(외국인에게 얘기할때 중간에 통역자가 있으면 의사소통이 느리지만, 영어로 바로 얘기하면 서로 의사소통이 빠른것과 같음. 여기서 통역자는 Mono 되시겠다)

이런식의 실행도중 컴파일 과정이 없는 형태를 AOT컴파일링 이라고 하며 JIT와 반대개념이다.

물론 Mono에서도 AOT컴파일링을 제공한다. C++을 거치지않고..

 

여기서 한가지 의문점 발생

아니 그럼 Mono써서 그냥 AOT컴파일링 하면 되잖아. 왜 굳이 C++로 변환을 하지? 라고 생각이 들것이다.

몇가지 이유를 찾아봤는데,

  • 성능, 보안 및 플랫폼 호환성을 개선하는 용도
    • 성능 - 사용하지 않는 유니티 모듈들을 C++변환과정애서 제거할 수 있음 (link.xml 에 해당정보가 있음)
    • 보안 - Mono로 빌드시 디컴파일러 툴을 이용해 쉽게 소스코드로 변환이 가능한데 IL2CPP로 빌드된건 디컴파일해도 쉽게 해킹 불가능
    • 호환성 - iOS 64bit 호환 가능 (Mono는 지원하지 않음)
    • 구버전의 Mono로 최신 플랫폼등을 대응하기가 어려움

정도이다.

 

장점만 나열하니 우와 좋은거네 라고 할 수도 있지만, 단점도 분명 존재한다.

  • 컴파일 시간이 길다.
    • iOS의 경우 Xcode 프로젝트로 뽑아내는 시간 + Xcode 에서 앱빌드하는 시간
    • 중간에 C++로 변환하는 단계를 거치기 때문에 다소 시간이 더걸림
  • AOT 컴파일만 지원

'Unity' 카테고리의 다른 글

Quaternion 공간변환  (0) 2020.11.23
rigidbody, collider 개념 및 정리  (0) 2020.10.08
Addressables  (0) 2020.07.28
Mobile 최적화  (0) 2020.06.12
좌표계  (0) 2020.06.09

+ Recent posts