[App 개발] 맥마메 분석 - 4 HID 루틴
본문
네 번째 시간이 되었습니다. 솔직히 저는 조이스틱 지원에 대해서는 별로 관심은 없습니다. (집에 조이스틱이 없거든요. 하하…) 애플 II 에서는 조이스틱이 없으면 완전 바보였지만, 이제는 입력장치로 마우스가 있으니까요. 아마 피씨를 구입하여 사용하기 시작하면서 조이스틱하고는 인연을 끊은 것 같습니다.
그럼에도 제가 조이스틱 입력 부분을 살펴보려고 하는 이유는 단 한가지, HID 구동 루틴을 어떻게 작성하는지에 대한 기본적인 아이디어를 얻고 싶기 때문입니다. 마우스와 키보드 입력도 HID 장비로 취급할 수 있고, 애플에서 공식적으로 지원하는 게임 입력장치 방식이므로, 살펴보고 지나가는 것이 좋겠습니다.
저는 글을 쓰기 전에 분석을 이미 끝낸 상태가 아닙니다. ㅡㅡ; 소스 보면서 정리한 노트가 곧바로 글입니다. 따라서 앞뒤가 안 맞는 부분도 있을 수 있습니다만, 아마 소스를 같이 살펴보시는 분들에게는 좀 더 친밀감있는 글이 되지 않을까 싶습니다.
1) PollJoysticks()
함수 PollJoysticks() 에서 맨 먼저 검사하는 장비가 디지털 조이스틱입니다. 닌텐도 패미컴에서 사용하는 여덟 방향 버튼이 달린 Hatswitch 와 네 가지 색상의 A, B, C, D 버튼이 기본 장착되어 있죠. 요즘은 플스 때문에 햇 스위치, 버튼 네 개, 앞에 있는 집게손가락으로 누르는 버튼 네 개, 그리고 가운데 아날로그 조이스틱 두 개가 첨가되는 조이스틱이 있긴 합니다만… 일단은 닌텐도 게임 패드를 중심으로 생각해 보겠습니다.
보시면 sHIDDigital 이라는 배열에 조이패드 장비의 상태가 저장되고 있음을 알 수 있습니다. 햇 스위치의 8방향 스위치는 시계방향, 혹은 반시계방향으로 되어 있고 (드라이버에 따라 다른가봅니다) 8방향 스위치 중 특정 방향으로 버튼 세 개중 하나가 눌려 있으면 그 쪽 방향으로 눌려 있음을 알 수 있겠습니다. 이것은 아마도 오락실 조이스틱과 조이패드 스위치 눌림의 차이 때문에 발생하는 문제로 보입니다. 오락실 조이스틱에서 대각선 방향을 검사하는 방법은 예를 들어, 오른쪽 위 방향으로 조이스틱을 밀었다면 스위치 두 개가 눌리겠죠. (위쪽, 오른쪽) 조이패드에서는 버튼 하나가 눌립니다. 만약 조이스틱이 시계방향으로 값이 매겨져 있다면 위쪽=0, 오른쪽위=1, 오른쪽=2, 오른쪽아래=3,… 이런 식으로 값이 매겨지겠죠. 맥마메의 PollJoysticks() 함수에서는 먼저 HIDGetElementValue() 로 장비 값을 읽어들인 후 그 값을 valuesTable 배열과 대조합니다.
long v = HIDGetElementValue(sHIDDigital[i].pDevice, sHIDDigital[i].pElement);
int valuesTable[4][3] = { {0,1,7}, {4,3,5}, {6,5,7}, {2,1,3} }; // Up, Down, Left, Right
sHIDDigital[i].value = (v == valuesTable[i][0] || v == valuesTable[i][1] || v == valuesTable[i][2]);
각설하고… HID 장비의 상태를 점검하는 함수는 HIDGetElementValue() 입니다. 자매품으로HIDGetElementValueAsDigitalAxis() 라는 놈도 있는데, 이것은 HID External Interface 라고 해서, 유틸리티 소스에 포함되어 있군요. 이 놈은 잠시 건너뛰어도 좋을 것 같습니다.
아날로그 조이스틱의 경우에는 해당 축의 아날로그의 Raw 값을 돌려주게 됩니다. 따라서 아날로그 조이스틱을 사용할 때에는 그 값을 그대로 사용하기보다는 후처리가 좀 더 필요합니다. 맥마메 소스에서는 기본적인 처리 과정을 그대로 구현하고 있습니다. 먼저 입력된 Raw 값을 보정된 값으로 변환하는 HIDCalibrateValue() 함수를 호출하고, 그 값을 다시 정규화시키는 HIDScaleValue() 함수를 호출합니다. 최종값은 조이스틱 입력의 보정+정규화를 거친 값이 되는 것이죠.
sHIDAnalog[i].value = HIDGetElementValue (sHIDAnalog[i].pDevice, sHIDAnalog[i].pElement);
sHIDAnalog[i].value = HIDScaleValue (HIDCalibrateValue (sHIDAnalog[i].value, sHIDAnalog[i].pElement), sHIDAnalog[i].pElement);
만약 HIDGetElementValue() 함수를 마우스 장비에서 호출했다면 각 축의 delta 값이 입력됩니다. 지난 시간에 살펴보신 것과 같은 비슷한 처리를 거치면 게임에서 응용할 수 있게 되는 것이죠.
2) Device Enumeration
현재 컴퓨터에 몇 개의 입력 장치가 있는지 확인하고, 각각의 장비에 대하여 입력 컨텍스트를 설정하는 루틴이 초기화 과정에서 수행되어야 합니다. 위에서도 보시면 HIDGetElementValue() 함수에도 인수가 두 개 전달되어야 하는데, 첫 번째가 Device, 두 번째가 Element 라는 놈입니다. 이것이 어떻게 설정되는지를 찾아보려 합니다.
ParseHIDDevices() 라는 함수가 있기는 한데, 이것은 마메 디버그 상태에서 호출되는 함수이고, 실제 내용도 보시면 먼저 HIDGetFirstDevice() 함수로 첫 번째 HID 장비를 호출한 다음 그 안에 Element 가 어떤 것이 있는지를 단순히 나열하는 과정입니다. 간단하긴 합니다만, 꽤 유용한 루틴이고 HID 장비의 Enumeration 이 어떻게 수행되는지를 보여주는 좋은 예라고 할 수 있으므로 확인해 보시고… 그냥 배열로 쭈욱 만들어놓으면 얼마나 편하고 좋을까 생각도 됩니다만, 아무래도 입력 장비라는 것이 언제 설치되었다가 제거될지 모르는 상황에서, 그리고 그 안에 얼마나 많은 버튼이 구비되어 있는지, 키보드인지 마우스인지 조이패드인지 스틱인지 모르는 상황에서, 링크드 리스트로 구현하는 것이 가장 속편한 방법이긴 하겠죠?
InitializeInputDevices() 함수가 아마도 입력 장치 초기화 루틴으로 보입니다. 맨 처음 호출되는 함수는 HIDBuildDeviceList() 입니다. 장비 초기화에 앞서서 디바이스 리스트를 작성하는 과정이 되겠죠. 다른 소스들을 보면 OS X 10.2 이후의 변경 때문에 인수 두 개를 모두 NULL 로 채워넣은 것도 있는데 마메에서는 kHIDPage_GenericDesktop 상수를 전달하는군요.
그 다음은 LoadConfig_HID() 함수입니다. 본격적으로 HID 장비를 초기화하겠지요?
3) LoadConfig_HID(), SaveConfig_HID()
맥마메에서는HIDRestoreElementPref() 함수와 HIDSaveElementPref() 함수를 사용하는데 둘 다 HID Utilities 소스에 선언되어 있습니다. 그러니까 기본 라이브러리가 아니고 확장 소스로 구현된 루틴이죠.
실제 내용은 간단합니다. Joy 0 Button 0, Joy 0 Button 1, … 이런 식으로 문자열을 생성합니다. 조이스틱 안에 있는 버튼 갯수만큼이 되겠죠. 그 다음 저장되어 있던 해당하는 Device 와 Element 값을 받아옵니다.
그리고 조이스틱의 Configuration 은 ConfigJoystick_HID() 함수를 호출하도록 되어있는 것 같습니다. 다이얼로그 박스를 열어서 조이스틱의 상태에 대한 값이 출력되도록 구현하는 방법이 이 함수 안에 기록되어 있는 것 같군요. 참조하시고…
4) 정리
중구난방으로 여기저기를 마구 뒤져 보았습니다만, 그렇게 알찬 분석은 아니었던 것 같네요. ㅡㅡ;
그래서 제 나름대로 정리하자면 이렇습니다. ^^
Initialization:
HIDBuildDeviceList(NULL,NULL);
int num = HIDCountDevices();
if(num!=0) {
Device=HIDGetFirstDevice();
while(Device!=NULL) {
Element=HIDGetFirstDeviceElement(Device,kHIDElementTypeAll);
while(Element!=NULL&&!Element->pSibling) {
Element=Element->pChild;
}
while(Element!=NULL) {
Element=HIDGetNextDeviceElement(Element,kHIDElementTypeAll);
}
Device=HIDGetNextDevice(Device);
}
}
Poll:
for(int j=0;j for(int i=0;i value=HIDGetElementValue(Device[j],Element[i]);
}
}
Terminate:
HIDReleaseDeviceList();
최신글이 없습니다.
최신글이 없습니다.
댓글목록 0