Eli의 여백

바쁜 나날들 사이에서 생각났던 이런저런 것들을 적어봅니다.

IT/새로운 시도

C# WPF MVVM 미니맵 프로젝트 중간기록

Eli♪ 2023. 12. 12. 15:44

들어가며

예전부터 길찾기 하는 알고리즘은 수도 없이 많이 들어봤지만 실제로 구현을 안해봐서 약간 로망같은 게 있었다. 마침 내가 하는 테일즈위버라는 게임에서 컨텐 클리어타임 단축을 위한 최적 동선 예측 목적으로 동선 검증용 길찾기 프로그램을 만들어보는 시도를 진행 중이었고, 연말에 시간이 많지 않아 지금쯤 중간 정리를 하고 넘어가지 않으면 그간 고민했던 내용들에 대해 다시 고민해야 할 것 같아 현 상태로 기록으로 남긴다.

 

기본적인 목표는 컨텐용 맵 몇 개의 미니맵을 확보하여 중요 동선을 찍어보고 최적인지 아닌지 확인하는 것이며, 이용약관에 위배되지 않는 선에서 육안으로 확인이 가능한 정보만으로 클라에 대한 리버스 엔지니어링 없이 동선 검증을 하고자 했다.

 

프로젝트 개괄

구현 세부목표

1. 맵 구별: 인게임에서 좌표 옆 맵 코드로 확인 또는 수동으로 맵 구별

2. 미니맵 확보: 미니맵 캡쳐 또는 동영상 녹화 후 이어붙이기로 완성

3. 미니맵 좌표 변환: 사람이 보기 힘든 isometric 좌표계를 직교 좌표계로 변환

4. 길찾기 알고리즘 적용: A*에 휴리스틱 적용하여 길찾기 구현

 

완료된 부분

먼저 4번을 검증하기 위하여 python으로 개념검증을 시도하려고 하였으나 , 막상 길찾기를 구현하려니 임의의 맵에서 되는지부터 확인을 해야 해서 맵 생성 알고리즘도 만들어야 했음. 많은 알고리즘 중에 randomized Prim's algorithm을 적용하여 맵 생성에 성공했고, reduced A* (A star) 알고리즘을 적용하여 임의의 맵에서 길찾기가 성공적으로 잘 되는 것을 확인했다.

 

다음 절차로 바이너리 배포가 어려운 python보다는 native app으로 만드는 것이 맞을 것 같아 C#으로 포팅하는 작업을 시작했다. 인게임에서 맵 수집이 편할 수 있도록 창 모양 변경이 자유로운 WPF로 개발을 시작했고, 1차적으로 기본 기능은 완성했으나 기능별 변수 공유 문제로 인해 MVVM으로 아키텍쳐를 옮겨보기로 결정했다. 그리고 작업결과 MVVM 자체의 한계로 인해 순수 MVVM만으로는 안된다고 판단하였고 개선해야 할 필요성까지 확인된 상황이다.

 

이 상태까지 완료된 결과물 스샷이 다음과 같다.

Python 길찾기 (코드 200~300줄 사이)

 

C# WPF 앱(구현 진행 중)

 

구현 과정에서 느낀 점

1. Python은 개념검증에 좋은 수단이다.

다른 언어를 사용하면 이런저런 한계점들로 인해 구현이 어려운 부분이 있는데, python은 인터프리터라서 즉각적인 결과물 확인도 잘 되고 잦은 수정에도 incremental하게 개발할 수 있었다. 다만 UI부분이 약한 측면이 있는데, 그나마 나한테 필요했던 기능은 간단한 canvas 정도만 있으면 되고 주로 정적인 이미지를 다루기 때문에 pygame과 python image library (PIL), 그리고 matplotlib로 해결했다. 이렇게 금방 완성한 알고리즘을 다른 곳으로 포팅하기에도 가독성이 괜찮아서 나쁘지 않았다.

 

2. C# WPF MVVM은 배우기가 어렵다.

프로그램 속도나 향후 배포를 고려하면 native로 개발하는 것이 맞겠다 싶어서 C#으로 개발을 진행했고, 이 결정에 대해서는 지금도 후회는 없다. 그리고 UI 커스텀이 어려운 Windows forms보다는 WPF로 개발을 진행하는 것이 앞으로 전망을 봤을 때 더 낫다고 판단하여 WPF로 진행했고, 이것도 아직까지는 문제가 없었다. 그러나 VS에서 WPF로 개발을 진행하다 보니 여러 창 사이에 공유해야 하는 variable이 많아졌기도 하고 이것 때문에 특정 클래스에서 static이 남발되는 사태가 벌어졌다. 기존에 학술 목적으로 프로그래밍할 때는 전역변수가 그다지 문제가 되지 않았지만 이렇게 범용 프로그램으로 개발하는 경우 static을 최소화하는 것이 좋은 것 같아서 알아보니 dependency injection과 sigleton을 사용하면 되는 것으로 파악해서 그렇게 진행했고 하다 보니 MVVM으로 개발하게끔 유도하게 되어있어서 이참에 MVVM을 적용했다. MVVM 개념 자체는 어렵진 않은데 실제로 구현할 때 의존성을 제거하기 위해 같은 변수를 이곳저곳에 써야 하는 불편함이 있었고, 이를 해결하기 위해 나온 Community toolkit.MVVM 은 또 이미 MVVM에 익숙한 사람들을 위해 이런저런 convention 및 변수 naming 규칙을 적용하다보니 코드 양은 줄었지만 inner circle들의 convention을 익히지 못하면 아예 개발이 불가능한 수준이었다. 코드상으로 드러나지 않는 변수들과 binding 때문에 익히는 데 거의 3~4일 가량이 걸렸고, 그나마 기존 플밍 지식이 있었기 때문에 이 정도 시간에 가능했었지 만약 초짜였다면 더 오래 걸렸을 것으로 생각한다. 그만큼 진입장벽이 높다는 것이다. 요약하면 C#이나 WPF 자체는 어렵진 않은데 MVVM을 적용하여 개발하려면 초짜에게는 매우 어려울 수 있으므로 입문자는 MVVM을 적용하지 않는 것을 추천하는 바이다.

 

3. 마우스 좌표 이벤트 처리에 MVVM은 적합하지 않다.

Visual studio에서 제공하는 WPF 기능은 기본적으로 xaml이 있고 xaml에 붙어 있는 code behind CS 파일이 있다. 기본 기능만을 사용하는 경우 마우스 이벤트 처리의 구현은 cs code behind에서 sener와 mouse event를 수신할 수 있게 되어 있다. 그러나 이를 MVVM으로 개발하려면 interface 개념의 하위 집합(subset)인 command라는 것을 써야 하고, 이 때 view와의 의존성을 줄이기 위해 view의 parameter를 전달하기가 매우 어렵게 되어 있다. 특히 마우스 이벤트를 저리할 때 단순히 클릭하거나 mouse over가 된 상황인 경우에는 Microsoft.Xaml.Behaviors.Wpf 를 이용하면 쉽게 구현할 수 있지만 마우스 좌표가 중요하거나 화면에 있는 요소를 움직여야 하는 경우에 MVVM으로 구현하면 라이브러리의 한계로 인해 구현이 간단하게 되지 않는 부분이 있었다. 즉 xaml의 code behind인 xaml.cs에 init을 제외한 아무 코드도 안적고 구현하려니 매우 어려웠단 뜻이다. 여러 모로 고민을 많이 했지만 결국 좌표 처리부분은 어쩔 수 없이 code behind에서 처리하는 것으로 방향성을 정한 상황이다.

 

4. Canvas 및 동적 UIElement 생성에 MVVM은 적합하지 않다.

내가 하려고 했던 것은 평소에 자주 사용하고 있는 WebPlotDigitizer처럼 처음에 좌표축 calibration을 진행하는 과정이었다. 그런데 이렇게 하려면 C#에서는 canvas에 점 3개를 추가하고, 자유롭게 마우스 또는 키보드로 움직일 수 있어야 했다. 이 때 MVVM을 사용하게 되면 ViewModel에서 View의 요소에 직접접근이 불가능하기 때문에 매우 불편하게 구현해야 한다. 이미 완성한 사람(외부 글)도 있지만, 구조를 보면 mouse의 behavior를 완전히 재정의해서 쓰는 상황으로 모든 부분을 handle해야 하는 불편함이 있다. 아직 itemsControl의 container를 canvas로 했을 때 내부에 있는 element들의 좌표가 외부 singleton class에 binding하는 것에 대해 완전하게 체득하지 못한 상황이며 (icommand 및 relaycommand까지는 해 봤으나 MouseEvent.Getposition을 xaml에서 하는 방법에 대해서 모름), 이렇게까지 어려울 것이었으면 애초에 MVVM을 건드리지 않는 것이 맞았겠다는 생각이 들 정도로 동적 Element의 드래그한 좌표를 바인딩하는 것은 불가능은 아닐지 몰라도 적합하진 않다고 생각한다.

 

향후 계획

어쩌다 보니 알고리즘 다 완성해 놓고 구현에서 UI 때문에 시간이 계속 끌리고 있는 것 같다. UI는 향후에 개선이 가능할 것으로 보고 일단은 한 개에 대해서 돌아가는 시스템부터 우선 구축하고 확장성은 그 다음에 고려하는 것이 좀 더 효율적인 방향일 것으로 생각한다.

 

구현에 참고한 링크들

1. 닷넷 dependency injection: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection

2. MVVM community toolkit RelayCommand: https://learn.microsoft.com/ko-kr/dotnet/communitytoolkit/mvvm/generators/relaycommand

3.WPF itemsControl: https://learn.microsoft.com/ko-kr/dotnet/api/system.windows.controls.itemscontrol?view=windowsdesktop-8.0

4. WPF contentpresenter: https://learn.microsoft.com/ko-kr/dotnet/api/system.windows.controls.contentpresenter?view=windowsdesktop-8.0

5.Canvas.Left의 binding이 element가 움직이면 풀린다는 글: https://stackoverflow.com/questions/20609974/binding-to-canvas-left-breaks-when-element-is-moving

6. Microsoft.Xaml.Behaviors.Wpf를 사용한 드래그 가능한 object 구현 (itemscontrol 내부요소에는 잘 적용 안됨): https://github.com/Microsoft/XamlBehaviorsWpf/wiki/MouseDragElementBehavior

7. MVVM으로 내부 element drag 및 확대축소 가능한 canvas 구현: https://crystalcube.co.kr/153