HUNIL PARK

Dino Go

Sui 블록체인 위치 기반 NFT 수집 게임

Next.jsTypeScriptThree.jsTailwind CSSMoveSui SDK
Dino Go

프로젝트 개요

Dino Go는 3일간의 해커톤에서 진행한 Sui 블록체인 기반 위치 기반 NFT 수집 게임 프로젝트입니다. 사용자가 실제 위치를 이동하며 가상의 공룡 NFT를 수집하고, 수집한 NFT를 마켓플레이스에서 거래할 수 있는 게임입니다. Google Maps API와 Three.js를 결합하여 3D 맵 인터페이스를 구현했고, Walrus 분산 스토리지에 NFT 메타데이터를 저장하며, Seal 임계값 암호화로 데이터를 보호했습니다. 프론트엔드는 Next.js, TypeScript, Three.js, Tailwind CSS로 구성되었고, 백엔드는 Move 스마트 컨트랙트 4개 모듈과 Sui SDK 기반 Web3 통신 레이어로 이루어졌습니다.

4인 팀에서 프론트엔드 전체를 담당했으며, NFT Studio(민팅 인터페이스), Marketplace UI, 3D 맵 인터페이스를 포함한 10개 이상의 페이지를 구현했습니다. Google Maps API를 Three.js와 통합하여 지도 위에 3D 공룡 캐릭터와 인터랙티브 요소를 오버레이했고, 사용자가 지도를 드래그하거나 줌할 때도 3D 오브젝트가 정확한 위치에 렌더링되도록 좌표 변환 로직을 구현했습니다. NFT Studio에서는 사용자가 NFT를 민팅하고 Walrus에 메타데이터를 업로드할 수 있는 UI를 제공했으며, Marketplace에서는 NFT 목록 조회, 검색, 필터링, 거래 기능을 구현했습니다. Sui SDK, Walrus, Seal, Kiosk SDK를 통합하는 3개의 커스텀 Web3 클라이언트 라이브러리를 개발하여, 프론트엔드에서 블록체인 및 분산 스토리지와의 통신을 추상화했습니다.

Dino Go architecture

기술 구현

Google Maps + Three.js 3D 맵 통합

Google Maps API는 2D 지도 렌더링에 최적화되어 있고, Three.js는 WebGL 기반 3D 그래픽 라이브러리입니다. 두 라이브러리를 결합하여 Google Maps 위에 3D 공룡 캐릭터와 인터랙티브 요소를 오버레이하는 것은 기술적으로 도전적이었습니다. Google Maps의 지도 타일 위에 Three.js의 WebGL 캔버스를 정확히 정렬해야 했고, 사용자가 지도를 드래그하거나 줌할 때 3D 오브젝트도 동기화되어 움직여야 했습니다. 또한 위도/경도 좌표를 Three.js의 3D 공간 좌표로 변환하는 수학적 계산이 필요했고, 모바일 환경에서도 부드럽게 렌더링되도록 성능 최적화가 필수였습니다.

Google Maps의 OverlayView API를 활용하여 커스텀 오버레이를 생성하고, 그 안에 Three.js의 WebGLRenderer 캔버스를 삽입했습니다. OverlayView의 draw() 메서드를 오버라이드하여 지도가 움직일 때마다 Three.js 카메라의 위치와 회전을 업데이트하도록 구현했습니다. 위도/경도 좌표는 Google Maps의 Projection 객체를 사용해 픽셀 좌표로 변환한 뒤, Three.js의 월드 좌표 시스템에 매핑했습니다. 3D 공룡 모델은 glTF 형식으로 로드하고, 각 공룡의 위치 정보(위도/경도)를 기반으로 지도 위 적절한 위치에 렌더링했습니다. 성능 최적화를 위해 뷰포트 밖의 오브젝트는 컬링(culling)하여 렌더링하지 않도록 했고, LOD(Level of Detail) 기법으로 거리에 따라 모델의 디테일을 조절했습니다.

Google Maps 위에 3D 공룡 캐릭터가 자연스럽게 오버레이되어, 사용자는 실제 지도를 탐색하며 가상의 공룡을 발견하는 경험을 할 수 있었습니다. 지도를 드래그하거나 줌할 때도 3D 오브젝트가 정확한 위치에 동기화되어 렌더링되었고, 모바일 환경에서도 30fps 이상의 부드러운 프레임률을 유지할 수 있었습니다. 이 독특한 UX는 해커톤 심사에서 '혁신적인 사용자 경험'으로 높은 평가를 받았습니다.

NFT Studio 및 Marketplace UI

사용자가 NFT를 민팅하고 거래할 수 있는 인터페이스가 필요했습니다. NFT 민팅 과정은 복잡한데, 사용자가 이미지를 업로드하면 Walrus 분산 스토리지에 저장하고, 메타데이터를 생성하여 Sui 블록체인에 기록해야 했습니다. Marketplace에서는 민팅된 NFT 목록을 조회하고, 검색 및 필터링 기능을 제공하며, 사용자가 NFT를 구매하거나 판매할 수 있는 거래 UI를 구현해야 했습니다. 블록체인 트랜잭션은 비동기적이고 시간이 걸리기 때문에, 사용자에게 명확한 진행 상태 피드백을 제공하는 것도 중요했습니다.

NFT Studio에서는 React Hook Form을 활용한 다단계 폼을 구현하여, 사용자가 NFT 이름, 설명, 이미지를 입력하도록 했습니다. 이미지는 클라이언트 사이드에서 리사이징한 뒤 Walrus API를 통해 업로드하고, 반환된 해시를 메타데이터에 포함시켰습니다. Sui SDK를 사용해 Move 스마트 컨트랙트의 mint 함수를 호출하여 NFT를 민팅했고, 트랜잭션 진행 상태를 실시간으로 UI에 표시했습니다(Pending → Confirming → Success). Marketplace UI는 TanStack Table을 활용해 NFT 목록을 그리드로 표시하고, 검색 바와 필터(가격 범위, 희귀도 등)를 제공했습니다. NFT 카드를 클릭하면 상세 정보 모달이 열리고, '구매하기' 버튼을 누르면 Kiosk SDK를 통해 거래 트랜잭션을 생성하여 블록체인에 전송했습니다. 거래 완료 후에는 사용자의 NFT 소유권이 업데이트되도록 UI를 자동 새로고침했습니다.

사용자는 직관적인 UI로 NFT를 쉽게 민팅하고 거래할 수 있었습니다. Walrus 분산 스토리지 덕분에 NFT 이미지가 안전하게 저장되었고, 블록체인에 기록된 메타데이터로 소유권이 투명하게 관리되었습니다. Marketplace의 검색 및 필터 기능 덕분에 사용자는 원하는 NFT를 빠르게 찾을 수 있었고, 실시간 트랜잭션 상태 피드백으로 사용자 경험이 크게 향상되었습니다. 해커톤 데모에서 심사위원들이 실제로 NFT를 민팅하고 거래해보며 '완성도 높은 UI'라는 평가를 받았습니다.

Web3 클라이언트 라이브러리 개발

프론트엔드에서 Sui SDK, Walrus, Seal, Kiosk SDK를 직접 사용하면 코드가 복잡해지고, 블록체인 통신 로직이 UI 컴포넌트와 뒤섞여 유지보수가 어려워집니다. 각 SDK마다 사용법이 다르고, 에러 처리와 재시도 로직도 일관되지 않아, 프론트엔드 개발자가 블록체인 세부 사항을 모두 이해해야 하는 부담이 있었습니다. 또한 여러 페이지에서 동일한 블록체인 호출 로직이 중복되어, 변경 사항이 생기면 여러 곳을 수정해야 했습니다. 블록체인과 분산 스토리지 통신을 추상화한 클라이언트 라이브러리가 필요했습니다.

Sui SDK, Walrus, Seal을 각각 래핑한 3개의 커스텀 클라이언트 라이브러리를 개발했습니다. SuiClient는 Sui 블록체인과의 통신을 추상화하여, mintNFT(), transferNFT(), getNFTsByOwner() 같은 고수준 메서드를 제공했습니다. WalrusClient는 파일 업로드와 다운로드를 간단한 인터페이스로 래핑하여, uploadImage(file)만 호출하면 자동으로 리사이징, 업로드, 해시 반환을 처리했습니다. SealClient는 임계값 암호화를 활용해 민감한 메타데이터를 암호화하고 복호화하는 기능을 제공했습니다. 각 클라이언트는 에러 핸들링과 재시도 로직을 내장하여, 네트워크 실패 시 자동으로 재시도하도록 구성했습니다. TypeScript로 작성하여 타입 안정성을 확보했고, 모든 메서드는 Promise 기반 비동기 API로 통일했습니다.

프론트엔드 컴포넌트에서 블록체인 로직을 직접 다루지 않고, 간단한 클라이언트 메서드만 호출하면 되어 코드가 훨씬 간결하고 읽기 쉬워졌습니다. 블록체인 통신 로직이 중앙화되어, SDK 버전 업데이트나 에러 처리 개선이 필요할 때 클라이언트 라이브러리만 수정하면 모든 페이지에 자동 반영되었습니다. 타입 안정성 덕분에 컴파일 타임에 에러를 잡을 수 있었고, 팀원들도 블록체인 세부 사항을 몰라도 클라이언트 인터페이스만 보고 쉽게 개발할 수 있었습니다. 이 추상화 덕분에 3일이라는 짧은 해커톤 기간에도 안정적인 Web3 기능을 구현할 수 있었습니다.

트러블슈팅

Three.js와 Google Maps 통합 시 렌더링 충돌 이슈

Google Maps의 지도 타일 렌더링과 Three.js의 WebGL 렌더링이 동시에 실행되면서, 두 렌더러가 서로 충돌하여 지도가 깜빡이거나 3D 오브젝트가 사라지는 현상이 발생했습니다. 특히 사용자가 지도를 빠르게 드래그하거나 줌할 때 렌더링 프레임이 뒤섞여 시각적 버그가 자주 관찰되었습니다. Google Maps의 이벤트 루프와 Three.js의 렌더링 루프가 독립적으로 동작하다 보니, 동기화가 어려웠고, 어느 시점에 Three.js를 업데이트해야 하는지 명확하지 않았습니다. 또한 모바일 환경에서는 성능 저하로 인해 프레임률이 급격히 떨어지는 문제도 있었습니다.

Google Maps의 idle 이벤트를 활용하여 지도 움직임이 완전히 멈춘 후에만 Three.js를 업데이트하도록 변경했습니다. 이를 통해 사용자가 지도를 드래그하는 동안에는 3D 렌더링을 일시 중지하고, 드래그가 끝나면 최종 위치에서 3D 오브젝트를 다시 렌더링하는 방식으로 충돌을 방지했습니다. 또한 requestAnimationFrame을 활용해 Three.js 렌더링 루프를 브라우저의 리페인트 주기와 동기화하여 부드러운 애니메이션을 유지했습니다. 모바일 성능 최적화를 위해 3D 모델의 폴리곤 수를 줄이고, 텍스처 해상도를 낮추며, 뷰포트 밖의 오브젝트는 렌더링하지 않도록 프러스텀 컬링(Frustum Culling)을 적용했습니다. 디바이스 성능에 따라 렌더링 품질을 자동 조절하는 적응형 품질 설정도 추가했습니다.

렌더링 충돌 문제가 해결되어 지도와 3D 오브젝트가 안정적으로 함께 렌더링되었습니다. 사용자가 지도를 조작할 때도 시각적 버그 없이 부드러운 경험을 제공할 수 있었고, 모바일 환경에서도 허용 가능한 프레임률을 유지하여 사용성이 크게 개선되었습니다. 해커톤 데모에서 다양한 디바이스로 테스트했을 때 모두 안정적으로 동작하여 심사위원들에게 긍정적인 평가를 받았습니다.

회고

3일간의 해커톤이라는 극한의 시간 제약 속에서 프론트엔드 전체를 개발하며, 빠른 의사결정과 우선순위 설정의 중요성을 체감했습니다. Three.js를 활용한 3D 그래픽스 구현과 Google Maps API 통합 경험을 통해 WebGL 렌더링과 지도 서비스를 결합하는 독특한 기술 스택을 다뤄볼 수 있었고, Web3 기술 스택(Sui SDK, Move, Walrus, Seal)을 실전에서 활용하며 블록체인 프론트엔드 개발 역량을 쌓았습니다. 특히 커스텀 Web3 클라이언트 라이브러리를 설계하고 구현하면서, 복잡한 외부 SDK를 추상화하여 프론트엔드 개발자 친화적인 인터페이스로 만드는 아키텍처 설계 능력을 키울 수 있었습니다. Google Maps와 Three.js 통합 과정에서 두 렌더링 시스템을 동기화하는 문제를 해결하며, 저수준 렌더링 최적화와 성능 튜닝 경험도 얻었습니다. 해커톤 특유의 빠른 개발 속도 속에서도 기능을 완성하고 데모할 수 있었던 것은, 명확한 우선순위 설정과 팀원 간의 원활한 협업 덕분이었습니다.

3일의 제한된 시간으로 인해 코드 품질을 타협할 수밖에 없었습니다. 테스트 코드는 전혀 작성하지 못했고, 에러 핸들링도 최소한만 구현하여 엣지 케이스에서 버그가 발생할 가능성이 높습니다. 만약 프로덕션 환경으로 발전시킨다면, 철저한 리팩토링과 테스트 커버리지 확보가 필수적입니다. 모바일 최적화도 미흡했습니다. Three.js 렌더링이 모바일에서 배터리를 많이 소모하고 발열이 발생하는 문제가 있었는데, 시간 부족으로 근본적인 최적화를 하지 못했습니다. 향후에는 WebGL 렌더링 빈도를 줄이거나, 모바일에서는 3D 효과를 축소하는 등의 최적화가 필요합니다. Three.js 성능 최적화 여지도 많습니다. 모델 압축, 텍스처 아틀라스, 인스턴싱 등 고급 최적화 기법을 적용하면 성능을 더 향상시킬 수 있을 것입니다. 해커톤 경험을 통해 '빠르게 프로토타입을 만드는 것'과 '안정적인 프로덕션 코드를 작성하는 것'의 차이를 명확히 이해하게 되었고, 향후 프로젝트에서는 초기 설계에 더 많은 시간을 투자하여 견고한 기반을 다지고 싶습니다.