안녕하세요! 오늘부터 아두이노에 대한 글을 하나씩 쓸 예정이고, 오늘이 그 시작점입니다.

아두이노는 정말 말도 안되게 손쉽게 하드웨어와 소프트웨어를 같이 만질 수 있는 오픈 소스 프로토타이핑 플랫폼입니다. 아두이노의 장점에 대해서 간단히 살펴보면,

  1. 저렴하다.
  2. 윈도우즈, 맥, 리눅스 등 다양한 플랫폼을 지원한다.
  3. 간단하고 명확한 프로그래밍 환경을 제공한다.
  4. 오픈 소스이면서 확장 가능한 소프트웨어이다.
  5. 오픈 소스이면서 확장 가능한 하드웨어이다.

이러한 장점들 덕분에 교육용이나 취미용으로 정말 많은 인기를 끌고 있죠.

핵심 기능을 하는 하드웨어가 아두이노 보드인데, 이 보드도 용도에 따라 여러 종류를 제공하고 있습니다. 참고로 제가 사용하는 보드는 Arduino Uno 보드이며, 보드의 종류는 여기에서 확인하실 수 있습니다.

아두이노는 개별 제품으로도 판매를 하지만, Kit 형태로도 많이 판매를 하고 있습니다. 조금 익숙해지고, 특정 필요한 부품이 있는 경우에는 개별 제품을 구매하셔도 되지만, 처음 시작하는 입장에서는 여러 종류의 Kit이 편합니다. 특히 입문자도 고민하지 않고 살 수 있는 스타터 킷도 많이 판매하고 있으므로 각종 쇼핑몰에서 아두이노 스타터 킷이라고 검색해보시면 많은 결과를 보실 수 있을 것 입니다.

저는 Arduino Upgraded Learning Kit를 구매했습니다.

그림 1 - Arduino Upgraded Learning Kit
그림 1 - Arduino Upgraded Learning Kit

그림 2 - 킷 구성품
그림 2 - 킷 구성품

킷의 구성품을 보시면 아시겠지만, 정말 많은 부품들이 포함되어 있고, 그래서 아두이노를 이용해 여러가지를 배우기에 꽤나 괜찮은 것 같습니다.

그림 3은 아두이노 하드웨어의 핵심이라 할 수 있는 아두이노 보드의 모습입니다. 보드에는 입출력, 전원, 그라운드 등 여러가지 핀들이 존재합니다. 이런 것들에 대해서는 차차 알아나가도록 하죠.

그림 3 - 아두이노 우노 보드
그림 3 - 아두이노 우노 보드

오늘은 아두이노에 대해 정말! 간단한 소개와 제가 구매한 킷을 정말! 간단하게 살펴보았습니다.
다음 시간에는 아두이노 개발 환경 설정에 대한 내용으로 이어가겠습니다.

이 글은 유니티 자습서의 글을 번역한 글입니다. 원문은 여기에서 볼 수 있습니다.




이 주제는 그렇게 새로운 내용은 아니지만, 매우 중요한 주제입니다. 코딩 습관에 대한 이야기이고, 이는 여러분의 삶을 훨씬 더 쉽게 만들 수 있습니다.

코딩 습관에 깔려있는 생각은 여러분이 코드를 작성할 때 어떤 가이드 라인을 떠올린다는 것 입니다. 이러한 가이드라인은 여러분의 코드가 유지보수 가능하고, 확장 가능하고, 읽기 쉬워서 리팩토링을 할 때의 고통을 덜어주도록 설계되어 있습니다.

특정 코드를 참조하는 클래스가 많아서 생각했던 것보다 훨씬 어려워진 코드 때문에, 여러분의 프로젝트의 해당 코드를 고치고 싶었던 적이 얼마나 되나요? - 이는 키보드 선을 뽑기 위해 컴퓨터 뒤를 봤는데, 수많은 선들이 서로 엉망진창으로 꼬여 있어서 키보드 선을 찾을 수 있다는 희망을 버리게 되는 상황과 비슷합니다.

그래서, 여기에 여러분의 코드에 엉킨 부분을 풀고, 단정된 형태를 유지할 수 있는 일반적인 가이드 라인을 제공합니다.

단일 원칙 Single Responsibility

주어진 클래스는 이상적으로 오직 하나의 일에만 책임을 져야합니다.

단일 원칙 뒤에 있는 생각은 다음과 같습니다.

각 클래스는 작은 일(task)을 하고 이러한 작은 일을 조합함으로써 보다 큰 목표를 해결할 수 있습니다.


유니티는 기능을 분리해서 재사용 가능한 부분들로 설계했고, 단일 원칙은 여기에서 가장 중요한 원칙 중 하나 일지도 모릅니다.

이 원칙에 대한 예를 들기 위해서, Player 클래스가 있다고 생각해 봅시다. Player는 입력, 물리, 무기, 체력, 인벤토리를 다룹니다.

Player가 책임을 지고 있는 부분이 꽤 많아 보입니다. 우리는 이를 여러 개의 다른 컴포넌트로 쪼개서 각 컴포넌트가 오직 하나 만을 수행하도록 해야합니다.

  • PlayerInput - 입력을 관리하는 책임
  • PlayerPhysics - 물리 시뮬레이션을 관리하는 책임
  • WeaponManager - 플레이어 무기를 관리하는 책임
  • Health - 플레이어의 체력을 관리하는 책임
  • PlayerInventory - 플레이어 인벤토리를 관리하는 책임

아, 훨씬 낫네요. 이제 이 클래스들은 모두 서로에게 큰 의존성을 가지고 있고, 여전히 약간의 문제가 있습니다. 이를 해결하기 위한 방법을 보죠.

의존 관계 역전 Dependency Inversion

만약 어떤 클래스가 다른 클래스의 의존한다면, 그 관계를 추상화 시키십시오.


의존 관계 역전의 뒤에 있는 생각은 다음과 같습니다.

어떤 클래스가 다른 클래스를 호출할 때마다, 우리는 그러한 호출을 어떤 종류의 추상화로 바꿔야하며, 이로 인해 각 클래스가 서로 다른 클래스로부터 독립적일 수 있습니다.


가장 바람직한 방법은 클래스 의존 관계를 인터페이스로 대체하는 것 입니다. 그래서 클래스 A가 클래스 B의 메서드를 호출하는 것보다는, 클래스 B가 구현하고 있는 ISomeInterface의 메서드를 호출하도록 합니다.

다른 방법은 클래스 B가 상속받는 부모 클래스를 도입하는 것 입니다. 그래서 클래스 A가 클래스 B를 직접적으로 의존하는게 아닌, 부모 클래스를 다루도록 하는 것 입니다.

물론, 가장 바람직하지 않는 방법은 클래스 A가 직접적으로 클래스 B에 의존하는 것 입니다. 이는 우리가 피하고 싶은 방향이죠.

우리의 이전 예에서, 컴포넌트들은 서로 상당히 강한 커플링을 가지고 있었습니다. PlayerInput은 PlayerPhysics에 대해 의존하고 있었을테고, WeaponManager는 Inventory에 의존하는 등 말이죠.

이 들을 추상화시켜 보죠.

  • IActorPhysics - 입력을 받아들일 수 있고, 아마 물리 시뮬레이션에 영향을 미칠 수 있는 클래스를 위한 인터페이스
  • IDamageable - 대미지를 입을 수 있는 클래스를 위한 인터페이스
  • IInventory - 아이템을 저장하고 가져올 수 있는 클래스를 위한 인터페이스

이제 우리의 PlayerPhysics는 IActorPhysics를 구현하고, Health는 IDamageable을 구현하고, PlayerInventory는 IInventory를 구현합니다.

그러면 이제 PlayerInput은 어떤 IActorPhysics의 존재에 의존하게 되고, WeaponManager는 어떤 IInventory의 존재에 의존하게 되고, 플레이어의 대미지를 다루게 되는 어떤 것은 어떤 IDamageable의 존재에 의존하게 됩니다.

의존 관계 역전은 단일 책임을 어느정도 보강하는데에 도움이 됩니다. PlayerInput은 플레이어가 어떤게 움직이는지 신경쓰지 않아도 되고, 무엇이 플레이어를 움직이는지도 신경쓰지 않아도 됩니다. 그냥 그런 기능을 약속하는 어떤 것이 있다는 사실만 알면됩니다.

모듈화

저는 유니티에서 개인적으로 이런 형태의 코드를 많이 보았습니다.

switch( behavior )
{
    case 1: // some behavior
    case 2: // another behavior
    case 3: // yet another behavior
    case 4: // the last behavior
}

물론 이 코드는 동작하지만, 더 나아질 수 있습니다. 이 코드는 실제로 모듈화해달라고 소리지르고 있습니다. 각각의 행동들은 모듈화될 수 있으며, 그렇게 하면 "행동"은 enum에서 모듈의 인스턴스로 바꿀 수 있습니다. ( 이 경우에 우리의 모듈은 delegate라고 하죠.) 그러면 코드는 이렇게 될 것 입니다.

behavior();

그리고 어딘가 다른 곳에 이 모듈을 할당할 수 있습니다 :

MyClass.behavior = someBehavior;

여기에서 가정상 someBehvior는 함수입니다. ( 만약 우리의 모듈이 클래스라면, 클래스의 인스턴스가 될 수도 있겠죠.)

우리가 상태 머신이나 메뉴 시스템을 만들고 있다면 이 개념을 특히나 유용하게 사용할 수 있죠. 모든 가능한 상태나 메뉴를 enum으로 정의하는 것보다, 쉽게 변경할 수 있는 모듈로 상태와 메뉴를 정의하는 것이 더 이치에 맞습니다. 이렇게 하면 우리는 enum에 새로운 항목을 추가할 필요도 없고, 무엇인가 추가할 때마다 switch case 문을 확장해야할 필요도 없습니다.

결론

이 글은 유일한 코딩 습관에 관한 내용은 아닙니다. 하지만 따라야할 굉장히 중요한 것이죠.

여러분이 항상 이 내용을 따르지는 않아도 됩니다 - 가끔은, 이렇게 하는게 과할 수도 있고, 이치에 맞지 않을 수도 있습니다. 하지만, 비판적인 생각을 연습하고 여러분의 코드가 이로 인해 이득을 얻을 수 있을지 결정해보세요. 명심하십시오. 이는 여러분의 코드 몇 줄을 줄여주는 그런게 아니라, 나중에 올 수 있는 두통으로부터 여러분을 지켜주는 것 입니다.

이 글은 유니티 자습서의 글을 번역한 글 입니다. 원문은 여기에서 확인할 수 있습니다.



이 글에서는 게임에서 물리를 사용할 때의 모범 사례를 약간 살펴볼 것이고, 왜 이렇게 써야하는지에 대한 증명도 다룰 것 입니다.

Layer와 Collision Matrix

따로 설정하지 않은 모든 게임 오브젝트는 기본적으로 Default 레이어로 생성이 되고 이는 모든 것과 충돌됨을 의미합니다. 이는 매우 비효율적입니다. 어떤 것끼리 서로 충돌을 할 지 설정을 하세요. 이렇게 하기 위해서는 오브젝트의 타입에 맞게 다른 레이어를 설정해야 합니다. 새로운 레이어가 생길 때마다 Collision Matrix에 새로운 행과 열이 생기게 됩니다. 이 매트릭스는 레이어들 간의 상호작용을 정의하게 됩니다. 기본적으로, 새로운 레이어를 추가하면, Collision Matrix는 새 레이어를 다른 모든 레이어와 충돌하게 끔 설정합니다. Collision Matrix를 제대로 설정함으로써 불필요한 충돌 처리를 피할 수 있습니다.

시연 목적으로 간단한 데모를 만들었는데, 여기에서는 하나의 박스 컨테이너 안에 2000 개의 오브젝트(빨간색 1000개와 초록색 1000개)를 생성합니다. 녹색 오브젝트는 같은 녹색 오브젝트와 컨테이너 벽과만 상호 작용을 해야하고, 빨간색의 경우도 마찬가지 입니다. 테스트 중 하나에서, 모든 인스턴스가 Default 레이어에 속하고, 충돌 리스너에 있는 게임 오브젝트 태그를 문자열 비교함으로써 상호 작용이 이루어지게 됩니다. 다른 테스트에서는, 각 오브젝트 타입은 각각의 레이어로 설정되고, Collision Matrix를 통해서 각 레이어의 상호작용 설정을 하게 됩니다. 이 경우에는 필요한 충돌만 발생하기 때문에 문자열 테스트가 필요없습니다.

그림 1 : Collision Matrix 설정
그림 1 : Collision Matrix 설정

아래 이미지는 시연에서 가져온 결과입니다. 이 시연에는 간단한 매니저 클래스가 있는데, 이 클래스는 충돌의 수를 세고, 5초 후에 자동으로 멈추게 합니다. 공통의 레이어를 사용하는 경우에 불필요한 여분의 충돌이 발생하는 부분이 매우 인상깊습니다.

그림 2 : 5초 동안의 충돌
그림 2 : 5초 동안의 충돌

더 자세한 데이터를 위해 물리 엔진의 프로파일러 데이터도 캡쳐했습니다.

그림 3 : (공통 레이어 vs 분리한 레이어) 물리 프로파일러 데이터
그림 3 : (공통 레이어 vs 분리한 레이어) 물리 프로파일러 데이터

프로파일러 데이터에서 볼 수 있듯이, 물리에서 CPU 사용량의 차이가 큰 것을 확인할 수 있습니다. 단일 레이어를 사용하게 되면 평균 27.7 ms 정도이고, 레이어를 분리해서 사용하게 되면 평균 17.6 ms 정도 입니다.

RAYCASTS

레이캐스팅은 물리 엔진에서 굉장히 유용하고 강력한 도구입니다. 이는 우리가 특정 방향으로 특정 길이의 광선(ray)을 쏠 수 있도록 해주고, 이를 통해서 어떤 것과 부딪혔는지를 알 수 있습니다. 하지만, 이는 비싼 동작입니다 ; 이 성능은 광선의 길이와 씬에서의 콜라이더의 타입에 크게 영향을 받습니다.

이를 사용하는 데에 도움이 될 수 있는 몇가지 힌트를 드리겠습니다.

  • 이는 명확합니다. 하지만, 작업을 진행하기 위한 광선은 최소한으로 사용하십시오.
  • 필요 이상의 광선 길이를 확장하지 마십시오. 광선이 길어질 수록, 더 많은 오브젝트가 테스트 되어야 합니다.
  • FixedUpdate() 함수 안에서 Raycast를 사용하지 마십시오. 가끔은 Update() 에서의 사용도 과잉 사용이 될 수 있습니다.
  • 여러분이 사용하고 있는 콜라이더의 타입을 알고 계십시오. 메쉬 콜라이더에 대한 레이캐스팅은 정말 비쌉니다.
    • 좋은 해결책은 자식에 기본 콜라이더(primitive colliders)를 사용하고, 대략적으로 매쉬와 비슷한 모양이 되도록 노력하는 것 입니다. 부모 Rigidbody 밑에 있는 모든 자식 콜라이더는 합성 콜라이더처럼 동작할 것 입니다.
    • 메쉬 콜라이더의 사용이 절실한 경우에는 최소한 그들을 볼록하게 되도록 사용하십시오.
  • 광선이 무엇을 맞춰야하는지를 확실히 하고, 항상 raycast 함수에 레이어 마스크를 지정하도록 하십시오.
    • 이는 공식 문서에 잘 설명되어 있지만 raycast 함수에 사용해야할 값은 layer id가 아닌 bitmask 값 입니다.
    • 따라서 만약 레이어 아이디가 10인 오브젝트를 맞추고 싶다면, 10이 아닌 1<<10 (1을 왼쪽으로 10만큼 비트 이동) 을 사용하세요.
    • 레이어 10이 아닌 나머지 모든 것들을 맞추고 싶은 경우에는, 비트 보수 연산자인 ~을 사용하세요. 그러면 비트 마스크의 각 비트의 역을 구할 수 있습니다.


어떤 오브젝트가 녹색 박스에서 충돌하는 광선을 쏘는 그런 간단한 시연을 하나 만들었습니다.

그림 4 : 간단한 Raycast 시연 씬
그림 4 : 간단한 Raycast 시연 씬

이 시연에서 저는 앞에서 말한 내용을 지지할 수 있는 프로파일러 데이터를 뽑아내기 위해 광선의 수와 길이를 조절해 보았습니다. 아래의 그래프를 보면 광선의 수와 길이가 성능에 얼마나 영향을 미치는지 알 수 있습니다.

그림 5 : 성능에 영향을 미치는 광선의 수
그림 5 : 성능에 영향을 미치는 광선의 수

그림 6 : 성능에 영향을 미치는 광선의 길이
그림 6 : 성능에 영향을 미치는 광선의 길이

시연의 목적으로, 기본 콜라이더를 메쉬 콜라이더로 변경하기로 결정했습니다.

그림 7 : 메쉬 콜라이더 씬 (콜라이더 당 110 개의 버텍스)
그림 7 : 메쉬 콜라이더 씬 (콜라이더 당 110 개의 버텍스)

그림 8 : 기본 콜라이더 vs 메쉬 콜라이더 물리 프로파일러 데이터
그림 8 : 기본 콜라이더 vs 메쉬 콜라이더 물리 프로파일러 데이터

프로파일 그래프에서 볼 수 있듯이, 메쉬 콜라이더에 대한 레이캐스팅은 물리 엔진이 프레임당 더 많은 작업을 하도록 만듭니다.

Physics 2D vs 3D

어떤 물리 엔진이 여러분의 프로젝트에 최고 적절한지 선택하십시오. 만약 2D 나 2.5D 게임(2D 평면에서 만드는 3D 게임)을 개발 중이라면, 3D 물리 엔진을 사용하는 것은 과한 선택입니다. 이 여분의 차원이 여러분의 프로젝트에서 불필요한 CPU 비용을 차지하게 됩니다. 두 물리 엔진의 성능 차이를 확인하고 싶다면, 저자가 이전에 작성한 글을 참고해 보세요.

http://x-team.com/2013/11/unity3d-v4-3-2d-vs-3d-physics/

RigidBody

RigidBody 컴포넌트는 오브젝트 간의 물리적 상호작용을 추가하기 위한 필수 컴포넌트 입니다. 콜라이더를 트리거로써만 사용하는 경우에도, OnTrigger 이벤트를 적절히 사용하기 위해서 게임 오브젝트에 RigidBody를 추가해야 합니다. RigidBody를 가지고 있지 않은 게임 오브젝트는 정적 콜라이더로 간주됩니다. 정적 콜라이더를 움직이려는 시도를 하게 되면, 물리 엔진이 물리 세계 전체를 다시 계산하도록 강제하기 때문에 극도로 비효율적으로 동작하게 됩니다. 다행히도, 프로파일러는 여러분이 이러한 시도를 할 때 CPU 프로파일러 탭에서 경고를 보여주기 때문에 이러한 문제를 알아차릴 수 있습니다. 정적 콜라이더를 움직일 때의 영향을 좀 더 확실히 시연해 보기 위해, 첫번째 데모의 움직이는 모든 오브젝트에서 RigidBody를 제거했고, 이에 대한 프로파일러 데이터를 캡쳐했습니다.

그림 9 : 움직이는 정적 콜라이더 경고
그림 9 : 움직이는 정적 콜라이더 경고

그림에서 볼 수 있듯이, 총 2000개의 경고가 생성되었고, 이는 하나하나가 움직이는 게임 오브젝트에 대한 것입니다. 또한 물리에 사용된 평균 CPU 량도 대략 17.6ms에서 35.85ms로 증가했습니다. 게임 오브젝트를 움직일 때 RigidBody를 붙이는 것은 필수적입니다. 만약 여러분이 게임 오브젝트를 직접적으로 조정하고 싶다면, rigidbody 속성 중에 kinematic을 마트하시면 됩니다.

Fixed TimeStep

TimeManager의 Fixed Timestep 값을 변경하는 것은, FixedUpdate()와 물리 업데이트 비율에 직접적인 영향을 미칩니다. 이 값을 변경함으로써 물리에서 정확성과 CPU 사용 시간 사이의 조절을 할 수 있습니다.

요약

이번에 다룬 주제는 설정과 구현이 쉽고, 여러분이 개발하는 대부분의 프로젝트는 물리 엔진을 사용할 것이기 때문에 성능의 차이를 가져올 수 있을 것 입니다.

+ Recent posts