본문 바로가기
DIY

SimTools 없이 만드는 모션 시뮬레이터 제작기 1탄

by 에일라거 2018. 7. 1.


전부터 만든다 만든다 해 놓고 감히 엄두도 못 내고 있었던 모션 시뮬레이터. Racing Sim이라고도 하는 이 게이밍의 끝판왕격 물건을 만들기 위해 드디어 남들에겐 작지만 나에겐 큰 발걸음을 한발짝 떼었다.


난 이거저거 사브작사브작 하면서 뭐 만들고 그러는 걸 좋아해서, 이번에 아두이노를 얼결에 배웠는데 이게 또 이렇게 연결돼서 써먹을 데가 있네.... 하여튼 배우면 배울수록 뭔가 프로젝트를 하기 위한 연결고리들이 많아져서 좀 더 쉽게쉽게 접근할 수 있게 되는 거 같다.


서론은 여기까지 하고, 일단 결론부터 보자. 붕붕붕~


Pre-Simulator

이번 글은, 실제 레이싱 시뮬레이터를 만들기 전 단계로 서보모터로 먼저 구현해 본 "미니" 시뮬레이터에 관한 내용이다. 아래 동영상에서 보듯이 꼼지락꼼지락거리면서 귀요미가 잘 움직이는데, 이게 움직이게 하기 위한 많은 절차 중에서 모터를 제어하는 부분 (이 부분이 사실은 제일 간단한 부분이다!) 만 크게 키우면 그게 레이싱 시뮬레이터가 되는 셈. 


그러니 이 "미니" 레이싱 시뮬레이터를 어떻게 만들었는지를 이 글에서 먼저 살펴보고, 2탄에서 이걸 어떻게 큼지막하게 만들었는지를 살펴보자.


부아아아앙~부아아아아아아앙~ 꼼지락꼼지락 슈각슈각


SimTools 없이 만들어보자!!!

일단 세부 튜닝은 아직 덜 되어 있는데, 그건 머 차차 해 나가면 되고 필요한 플로우들을 각각 연결해서 기능을 구현하는데 우선적으로 집중했다. 사실 이 모든 절차의 핵심은 SimTools 없이 했다는 것! 모션 시뮬레이터를 만들고자 하는 사람이라면 가장 먼저 접하는 단어가 'SimTools를 다운받아라...' 부터 시작한다. 근데 그 사이트 들어가니까 이건 뭐 코인을 어느 세월에 모아서 그걸로 결제를 해서 플러그인을 받아서... 때려쳐


어떻게 하는 건지 플로우를 하나씩 따라가 보자.



Data Flow




겁나 뭐가 많은 거 같지만 결국 다음과 같다.


1. 게임 데이터를 받아오기 (Shared Memory Interface)

2. 받아온 데이터를 변환해서 아두이노로 보내기 (Python 이용, 텔레메트리 → 모터 각도 변환) 

3. 받아온 모터 각도로 실제 서보모터를 제어하는 부분 (아두이노)


Step #1 - 게임 데이터 받아오기

이 부분을 위해 SimTools를 활용하는 것이 일반적이나, 코인을 모으고 어쩌구 하기가 귀찮았다. (SimTools는 https://www.xsimulator.net/ 에서 받을 수 있다. 게임별로 필요한 플러그인이 다르다.)


내 목표는 아세토 코르사 (Assetto Corsa) 게임을 하기 위한 모션 시뮬레이터를 만드는 것! 그렇다면 아세토 코르사에서 데이터 추출을 위해 자체적으로 제공하는 API가 없는가?


이러한 고민 중에 신묘한 링크를 하나 발견!


[DOC] Shared Memory Reference


오오 성님... 쇤네 그저 성님께서 뿜어내는 빛이 너무 밝아 고개를 들지 못하겠습니다요... 굽신굽신


이 링크가 무엇인가! 아세토 코르사 텔레메트리를 뽑아내도록 해주는 프로그램의 무려 소스코드다.... 역시 세상엔 훌륭한 사람들이 많다. 프로그램 구조는 다음과 같다.



소스코드를 보면 단순히 main(){} 함수로 되어 있어서 무한루프를 돌도록 되어 있는데, 헤더파일 등에 게임과 통신하는 함수가 다 정리되어 있어서 나는 그저 받아온 데이터를 파일로 저장하도록 코드를 살짝 변경하기만 하면 되었다. 그리고 프로그램 실행하자마자 저장하기 시작하면 의미 없으므로 특정한 키보드를 입력받으면 그때서야 저장하도록 변경하는 정도? 그런 인터페이스 부분만 살짝살짝 고치고 틀은 그대로 뒀다. 내가 수정한 코드는 아래에 공유.


AC_Telemetry.zip


Visual Studio 솔루션을 통째로 공유하려고 했는데 파일 크기가 너무 커서 안올라간다네-_- 그냥 cpp 파일이랑 헤더파일만 압축해서 올린다. 어차피 그냥 console application이니까 이거만 있으면 된다. 위의 링크의 본문이, 아세토 코르사 버전업에 따라 코드를 이러이러하게 고쳐라... 라는 내용인데, 그걸 반영한 헤더파일이 위 Zip 파일에 들어있다.


SimTools가 결국 텔레메트리 받아와서 모터를 제어해주는 프로그램인 거 같은데 (써보질 않아서 정확히는....) 결국 나는 데이터만 받아오면 그 뒤는 어떻게든 할 수 있으니까 머 걍 데이터만 받고 생각하자는 마음으로... 자 이제 다음 스텝



Step #2 - 텔레메트리 정보를 레이싱 시뮬레이터 움직임으로 변환


텔레메트리 정보는 종류가 넘나 많은데, 결국 내가 하고자 하는 건 2 DOF 모션 시뮬레이터. 어차피 움직임에 많은 제한이 있다. 그러면 가장 '리얼'하다고 느끼는 움직임은 Roll이랑 Pitch 정도가 있겠지? 


차량의 회전 각도


차량이 회전할 때 각 회전 방향에 따라 위와 같이 부른다.  나는 종/횡가속도를 느끼기 위해 Roll/Pitch 정보를 쓴다고 했다.


그런데 말입니다


텔레메트리에서 건네받을 수 있는 정보는 저것 외에 X/Y/Z 축의 가속도 정보도 함께 나온다. 여기서 고민이 생기는 것이, 실제 Roll/Pitch 정보를 이용할 거냐 아니면 가속도 정보를 이용할 거냐....


결론적으로 가속도 정보를 이용하기로 했다. 왜냐면, 내가 모션 시뮬레이터를 쓰는 것은 실차에서 느낄 수 있는 종/횡 가속도를 조금이나마 느껴보고 싶기 때문이지, 차량이 실제로 얼마나 기울어지는가를 보고자 하는 게 아니기 때문. 차량의 기울어짐은 사실 서스펜션 세팅에 따라서도 완전 달라질 수 있고, 그래서 오히려 구린 차가 슈퍼카보다 Roll/Pitch가 더 많이 생길 수 있어서 가속도 정보로 하는 게 맞다고 판단했다. 


아세토 코르사 차량 좌표계


우선 아세토 코르사에서 나오는 데이터의 차량 좌표계는 그림과 같다. Longitudinal 방향은 가속도와 Pitch 방향이 같고, Lateral 방향은 가속도와 Roll방향이 반대다. 아래 그래프를 보자.


가속→좌회전우회전감속 시 텔레메트리 정보


좌회전하는 경우를 살펴보자. 그러면 구심력이 왼쪽을 향하므로 가속도 방향은 왼쪽이다. (헷갈리지 말자. 앞으로 가속할 경우 몸은 뒤로 젖혀진다. 왼쪽으로 가속할 경우 몸은 오른쪽으로 젖혀진다.) 종가속도 정보는 좌회전할 때 (+)이고, 이때 차체는 오른쪽으로 기울어지므로 Roll 방향은 오른쪽이 되는데 이걸 (-)로 정의했다. (내가 아니라 아세토 코르사가 이렇게 정의함) 아니 방향을 똑같이 정해놓으면 될 것을 왜 반대로 정해놨는지 모르겠다. 


일단 게임에서 나오는 데이터는 그렇다 치고, 나는 그래서 결국 Roll/Pitch 정보는 버리고 X/Z축 가속도 정보만 이용하기로 했다. 


이 시점에서 헷갈릴 수 있는데, 잘 보자. 나는 차량의 Roll/Pitch 정보가 아닌, 종/횡가속도를 내가 앉는 의자 기울기로 반영하기로 정한 것. 즉, 종/횡가속도 정보 = 의자의 기울기 각도 로 봐도 무방하다. 그럼 이렇게 놓고 모션 시뮬레이터 시점에서 좌표계를 다시 정의하자.





가속도 정보로 제어할 경우 의자의 회전정보는 위와 같다. Roll 이 반대로 갔다고 해서 헷갈리지 말자. 여기서의 Roll은 의자의 Roll이다. 즉 횡가속도와 방향이 일치해야 해서 아까와는 반대로 된 거다. 예를 들어 좌회전해서 왼쪽으로 2G의 가속도 = 의자로 볼 때 오른쪽으로 2도의 각도 라는 식. 그러면 각도벡터의 방향 상 위와 같이 보는 게 맞다. 좌표계는 그런 식으로 방향을 정의하고, 여기서 계산해야 할 것은 특정 회전각도에서 Left/Right의 높이는 어떻게 움직여야 하는가, 이다.


일단 저 네모는 레이싱 시뮬레이터에서 내가 앉을 의자를 올려둘 판때기다. 원점에다가 유니버셜 조인트를 놓고, 뒤쪽에 모터 두개가 밀고 당기면서 의자를 움직이는 구조. 판때기의 길이 L은 대략 40cm 정도가 나온다.


그러면 이제 나는 의자를 움직일 것이므로.... 아까도 말했지만 가속도 정보를 의자의 각도로 변경해줘야 한다. 가속도 정보는 게임을 이차 저차 이트랙 저트랙 여러번 해 보니까 대충 -2G ~ +2G... 근데 이걸 위에서처럼 그대로 각도로 변환하면 의자의 각도가 1~2도로 너무 작기 때문에 대략 7.5 정도를 곱해준다. 한마디로 2G정도 되면 의자를 15도 기울여라... 이런 뜻. 이차저차 이트랙저트랙 다 플레이해서 데이터를 전부 때려넣고 돌리면 대략 아래와 같은 히스토그램으로 나온다.


종/횡가속도에 따른 의자의 기울기


굳... 이거 데이터 뽑는다고 게임을 세시간을 했네-_- 세로축은 퍼센트다. 머 이정도면 모터 추종성도 그럭저럭 나오고 의자도 자연스럽게 기울지 않을까 하는 일단 생각. 나중에 실제 만들고 튜닝해봐야 알겠지만...


이제는 특정 의자 각도에서 과연 얼마나 오르락내리락 해줘야 하는가를 계산할 차례. 각도 θ와 φ가 있을 때 오른쪽으로 내려가는 길이는 다음과 같다.



응..? 갑자기?


이거 계산을 어케 했냐면....



Roll이 먼저 일어났다고 치자. 그러면 의자는 위 그림의 빨간색 선처럼 휙 돌게 된다. 이때 오른쪽 밑의 길이는 그림에서 표기한 것과 같다. 이 상태에서 Pitch가 일어났다고 치자. 그러면 빨간색 네모를 xz-plane에 투영한 네모가 축을 따라 돌 것이다. 즉, 다음 그림과 같다.



파란색 굵은 점선의 길이를 저 위에서 구했고, 각각 길이가 그림과 같이 된다. 그래서 두개를 더하면... 아래 식이 나온다. 



...엥? 위에랑 다른데? 라고 생각이 당연히 들겠지만... 이게 지금은 Roll이 먼저 일어났다고 치는데 Pitch가 먼저 일어났다고 치면 계산결과가 또 반대로 나올거잖아? 그러니까 대충 둘 다 반영하면 저 위에처럼 되지 않을까...?? 해서 만들었다. 까다롭게 굴지 말자... 얼추 맞다.... 


여기서 각도로 적어서 더 헷갈릴 수 있는데, 그냥 θ = X축 가속도 , φ = Z축 가속도 라고 생각하면 쉬울 거 같다. 


판때기 오른쪽 왼쪽이 얼마나 오르락내리락 하는지는 구했으니까 이제는 실제로 모터가 얼마나 왔다갔다 해야 이 판때기를 오르락내리락할 수 있는지 구할 차례. 근데 실제 시뮬레이터에서는 또 이 부분을 좀 더 고민해봐야겠고, 이번에 하는 미니 시뮬레이터에서는 대충대충 계산했다. 아래와 같다.



y만큼 올라갔을 때 각도 α는 어떻게 되는가? 하는 문제인데, 사실은 링키지 길이랑 뭐 꿍짝꿍짝 각도 계산을 다 해야겠지만 귀찮으니까 그냥 y = r*sin(α) 로 근사한다! 어차피 진짜 시뮬레이터 만들 때나 필요하지 뭐 미니미니한 애한테 이런 게 다 무슨 소용이야


그러니까,



이렇게 된다. 저 위에서 X/Z축 가속도를 가지고 계산한 y 값에다가 최종적으로 요 위의 식을 먹여서 각도를 구해내고, 이걸 아두이노로 보내준다. 즉, 최종적으로 두 개 모터의 목표각도까지 몽땅 PC에서 계산한 다음 아두이노로는 목표값만 딱 보내주면 아두이노는 모터제어만 담당하는 식.


일단은 이걸 파이썬 코드에 넣어뒀는데, 나중에 C++코드로 옮겨심기로. 아두이노랑 통신하는 부분도 다 옮겨심어서 파일 저장하고 파이썬 구동하는 부분은 없앨 예정이다. 파이썬 코드는 아래와 같이 공유.


racingSimRealtime.zip



덧. New Step #2 - C++코드와 Python 코드 합쳐서 C++ 하나로 구현


위의 과정에서, C++코드를 통해 게임 데이터를 파일로 저장하고, 그 저장된 파일을 파이썬에서 읽어와서 아두이노로 보낸다고 했다. 물론 파이썬으로 코드를 짜는 게 C++에 비해 간편하고 좋긴 하나, 결국 C++로 게임 데이터를 읽어와야 한다면 아두이노로 송신하는 것까지 C++로 하는 것이 인지상정... 그 소스코드는 아래에 공유한다.


AC_Telemetry_new.zip


이 소스코드에서는 사용자가 고칠 게 거의 없다. 파일 경로같은 게 들어가는 것도 아니고... 그냥 아두이노랑 통신할 때 시리얼포트만 자기 설정에 맞게 변경해 주면 된다.



이런 식으로... 포트ㅡ 번호만 바꿔주면 동작할 듯. 그리고 당연한 얘기지만 자기가 만든 모션 시뮬레이터의 기구학에 맞게 좌표계 변환을 해 줘야 함. SimTools가 아마 그런 부분에서 몇개의 튜닝만으로 좌표계 변환 등등을 계산할 필요없이 알아서 모터 이동하게 해 주는 프로그램일 거 같은 느낌? 안써봐서 모르겠다만...;;



Step #3 - 아두이노 서보모터 제어 코드 짜기


이건..... 더이상은 글쓰기가 귀찮아져서 자세한 설명은 생략한다. 그냥 구글링하면 여기저기 널려 있으니까 참조하자... 내 소스코드도 올려줄께....

절차는, 시리얼통신으로 데이터를 받고, 받은 데이터를 각각 모터에 뿌린다. 끗.


시리얼 통신을 주기적으로 받고 이런 걸 처리하는 부분이 짱나지 서보모터 제어 자체는 개껌임


RACING_SIM.zip


이렇게 일단 리얼타임으로 데이터 보내서 미니미니를 움직이는 것까지는 1차적으로 끝났다. 제작기 2탄은


* 실제로 기구물 만들기

* 기구학 해석해서 모터의 목표값 계산

* 아두이노로 DC모터 제어하기


이정도가 되지 않을까? DC모터 제어하는 게 좀 겁나는 게 모터 하나에 대충 90W 정도 소모한단다.... 와나 깜빡하면 탈 거 같은데 방열이나 냉각을 어떻게 해야 할지 고민이다.


아 몰라 어쨌든 일단 하나 했으니 다음 탄에 다시! 한... 한달 후 쯤 되지 않을까 싶네

뭐 쨌든 거의 다 했다 오예~



* 레이싱 시뮬레이션 제작하기 시리즈

1. SimTools 없이 만드는 레이싱 시뮬레이터 제작기 1탄

1. SimTools 없이 만드는 레이싱 시뮬레이터 제작기 2탄

1. SimTools 없이 만드는 레이싱 시뮬레이터 제작기 마지막

댓글