w99hyun
프로필프로젝트포스트연락처
w99hyun

본 사이트는 개인 포트폴리오 게시용으로 제작되었습니다.

ⓒ 2026. w99hyun(Woohyun Cho) All rights reserved.

포스트

블로그에서 더 보기
보기 방식

2026.07.23.

XREAL 10기 해커톤 ㅡ 🏆대상 수상..!

7월 18-19일 양일간 무박 2일로 진행된 XREAL 10기 해커톤에서 대상을 수상했다..! 이번 해커톤은 XR-AI for Public Good 이라는 대주제로 진행되었으며, 우리팀은 Healthcare & Wellbeing 의 소트랙을 배정받았다. 팀 빌딩 해커톤 시작 전, 팀 빌딩을 위해 참가자들이 서로 돌아다니면서 자기소개를 하는 아이스브레이킹 시간이 있었다. 이 때 외부 참가자이신 리서치 두 분을 만났고, 그 중 한분이 심리·정신건강쪽에 관심이 있으셨고 나는 XREAL에서 악몽 치료 관련된 Spoof 프로젝트를 진행하고 있었어서 먼저 팀을 제안해 주셨다. 이후 디자인과 개발 직군의 팀원들을 추가로 모집하면서 리서치 2명, 디자이너 2명, 개발자 2명​으로 팀이 완성되었다. 아이디어 도출 해커톤은 짧은 시간 내에 결과물을 만들어야되기 때문에 빠르게 아이디어를 도출하고 우선순위를 정하는 것이 중요했다. 팀원들끼리 Figjam을 활용해 브레인스토밍을 진행했고, 그 중 실현가능하고 의미 있는 아이디어를 선택했다. 나를 돌아보고, 나를 찾는 서비스 아이디어의 출발점은 현대인의 뇌는 SNS와 같은 즉각적 피드백에 길들여져 있고, 즉각적 피드백이 없는 '내면 성찰'을 두려워한다는 문제의식에서 시작했다. 그래서 VR 환경 속에서 온전히 '나'에게 집중하고, 나를 돌아볼 수 있는 서비스를 게이미피케이션 형태로 만들고자 했다. 이러한 목표를 갖고 Innerverse 라는 서비스를 제작했고, 자세한 개발내용은 https://w99hyun.com/works/innerverse 에서 확인할 수 있다. 결과 정말 감사하게도 🏆 대상 을 수상했다. 처음 참가하는 해커톤이었고, 걱정도 많았는데 기대 이상의 좋은 결과를 얻을 수 있어 뜻깊은 경험이었다. 무엇보다 좋은 결과를 만들 수 있던건 정말 좋은 팀원들을 만났기 때문이라고 생각한다. 누구 한 명 빠지지 않고 6명 모두가 최선을 다해 열심히 했고, 좋은 아이디어를 많이 내주었다. 다음에도 이런 기회가 있다면 또 같은 팀으로 하고싶다는 생각이 들 정도로.. 짧은 시간이었지만 너무 고마운 팀원들이었다. 그리고! 운영진분들도 온전히 해커톤에만 집중할 수 있도록 감사하게도 많이 도와주셨다. 귀한 XR 해커톤, 그리고 첫 해커톤에서 좋은 결과를 얻어 감사했고, 앞으로도 이런 기회가 있다면 계속 도전해보고 싶다. (평소에 커피 마시지도 않는데 두 통을 내리 꽂으니 두통이 오더라구요😥)

2026.07.23.

Unity UNITE Seoul 2026 후기

7월 21일 코엑스에서 열린 Unity UNITE Seoul 2026에 참석했다. 지금까지의 유니티와 앞으로의 유니티, 그리고 산업 전반의 유니티가 어떻게 활용되고 있는지를 확인할 수 있는 좋은 기회였기 때문에 참석했다. 정말 많은 세션과 토크가 준비되어 있었고, 너무 주제가 다양했고 많았기 때문에 동시간대에 여러 장소에 진행되어 자신이 관심있는 분야를 골라들을 수 있었다. 굉장히 많은 세션들 중에서 몇몇 세션을 골라들었는데, Track 1과 Track 2에 관심있는 분야가 많이 배치되어 두 트랙을 중점적으로 들었다. 또한 이런 세션말고도 Indie Relay Talk 이라고 인디게임 업계에 종사하는 사람들의 여러 이야기도 들을 수 있었다. 굉장히 재밌고 유익한 스토리들이 많았고, 인디 릴레이 톡은 별도로 공개되지는 않는 것으로 알고있다. 굉장히 많고, 다양한 사람들이 방문했었다. 이번이 첫 UNITE 참석이었는데, 앞으로도 기회가 있다면 지속적으로 참석할 것 같다.

2026.03.07.

Tuanjie Engine - Unity China

최근에 유니티 글로벌 에셋스토어에서 중화권 퍼블리셔가 배포하는 에셋이 모두 내려간다는 안내가 메일로 발송되었다. 무슨 일인가 싶어 알아보니, 유니티 차이나의 매각소식 이 들려오는데, 이를 위한 선제적 조치라고 보여진다. 관련해서 정보를 찾아보니 글로벌에서는 유니티 2022버전 다음으로 유니티6를 배포하고 있는데, 유니티 차이나만 독자적으로 유니티 2022를 fork해서 독자적으로 Tuanjie Engine으로 개발해 배포하고 있었다고 한다. 아예 처음부터 유니티6를 배포하지 않은게 아니라, 어느 순간부터 유니티6 관련 정보를 모두 삭제하고 독자적으로 배포했다고 한다. 이름의 유래는 Unity → 단결 → 团结(Tuanjie) 라고... 이 Tuanjie 엔진의 특이한 점은 Nanite 와 Lumen 을 지원하는 점이다. 언리얼엔진에서나 지원하던 것들을 집어넣었다는 건데... 누군가 이미 2년 전에 올렸던 정보이다. 글로벌 유니티와 어떤식으로 분기했고, 어떤점이 다른지 궁금해서 한 번 설치해보았다. 계정은 글로벌 유니티 계정 연동이 가능했다. 아마 3월 말이 지난 이후로는 독자 계정을 사용하지 않을까 싶다. 우선 Tuanjie Hub인데 이것도 글로벌 Unity Hub를 fork해서 만들었는지 크게 다르지도 않고 심지어 한국어까지 지원한다. 그 외에 중국 독자적인 여러 기능들이 나열되어 있다. 정보를 찾아봤을 땐 에디터를 설치할 때 VPN을 사용해야한다고 했는데, 그냥 아무런 오류없이 빠르게 설치되었다. ↑ Tuanjie Engine ↑ Unity 2022 엔진 splash 화면 역시 2022기반답게 기존 글로벌 유니티 2022버전의 splash와 유사하다. You San Di Technology가 유니티 차이나의 중국 합작법인인 모양이다. (U3D 인듯) 엔진 내부인데 크게 외관상으로 달라보이는 점은 없다. 엔진 내부또한 한국어를 지원한다. 일부 독자 기능이 있는듯한데, 이런 독자 기능도 영어를 지원해주는걸 보니, 사용상에 크게 문제는 없을 것 같다. (한자만 있었으면 어쩌나 싶었다.) 이건 Lumen에 대응되는 것으로 보이는 Tuanjie GI 설정이다. 이건 Volume Profile에 추가되어 있다. Virtual Geometry로 불리는 Nanite와 비슷한 기능으로 보인다. 해당 기능을 활성화하면 에디터 재시작 안내문이 나온다. Tuanjie에서는 기존 렌더 파이프라인에서 업데이트한 것을 GDRP라고 부르는 듯하다. GDRP를 통해서 Unreal에 대응되는 기능들을 많이 만들어 둔 것으로 보아 대규모 씬에서는 오히려 글로벌 유니티 엔진을 사용하는 것보다 성능과 품질면에서 유리할 수도 있을 것 같다. 이렇게 중국에서 사용되는 Tuanjie Engine을 알아보았다. 아직까지는 패키지 매니저에서 글로벌 에셋 스토어의 에셋도 가져와지고, 글로벌 유니티와의 분리 작업이 진행되진 않았다. 일반적으로 다른 버전의 유니티 프로젝트를 여는 것처럼 글로벌 유니티로 만든 프로젝트도 버전 업데이트를 하는 방식으로 Tuanjie Editor에서 열 수 있으니, WeChat 등 중국 현지 플랫폼을 타겟팅한다면 Tuanjie의 사용이 필요할 수도 있을 것 같다.

2026.03.05.

C++ Summary 2

C++에서 주로 사용되는 문법을 정리한다. 1. 기본 (입출력 최적화) int main() { ios::sync_with_stdio(false); cin.tie(NULL); } ios::sync_with_stdio(false) : C / C++ 입출력 동기화 해제 cin.tie(NULL) : cout flush 방지 대량 입출력 문제에서 시간초과 방지 2. STL 컨테이너 2.1 vector vector<int> v; v.push_back(10); v.push_back(20); cout << v[0]; v.pop_back(); sort 정렬 sort(v.begin(), v.end()); // 오름차순 sort(v.begin(), v.end(), greater<int>()); // 내림차순 커스텀 정렬 sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); erase-remove 특정 값 삭제 vector<int> v = {1,3,2,3,4}; v.erase(remove(v.begin(), v.end(), 3), v.end()); <blockquote> <blockquote> 결과 1 2 4 unique 중복 제거 sort(v.begin(), v.end()); v.erase(unique(v.begin(), v.end()), v.end()); lower_bound / upper_bound vector<int> v = {1,2,2,2,3}; int idx = lower_bound(v.begin(), v.end(), 2) - v.begin(); <blockquote> <blockquote> 결과 1 lower_bound : x 이상 첫 위치 upper_bound : x 초과 첫 위치 binary_search vector<int> v = {1,2,3,4,5}; bool exist = binary_search(v.begin(), v.end(), 3); 2.2 string find 문자열 검색 string s = "hello world"; int pos = s.find("world"); if(pos == string::npos) { cout << "not found"; } substr 문자열 자르기 string s = "abcdef"; cout << s.substr(2,3); <blockquote> <blockquote> 결과 cde replace 문자열 치환 string s = "abcdef"; s.replace(2,3,"ZZ"); <blockquote> <blockquote> 결과 abZZf 여러 번 치환 string s = "ayaaya"; int pos; while((pos = s.find("aya")) != string::npos) { s.replace(pos,3,"!"); } erase 문자 삭제 string s = "abcdef"; s.erase(2,3); <blockquote> <blockquote> 결과 abf getline 공백 포함 입력 string line; while(getline(cin,line)) { cout << line; } 2.3 pair 두 값을 묶어서 저장 pair<int,int> p = {3,5}; cout << p.first; cout << p.second; BFS에서 많이 사용 queue<pair<int,int>> q; 2.4 map 정렬된 key-value 저장 map<string,int> m; m["apple"] = 3; cout << m["apple"]; 자동 정렬 시간복잡도 O(logN) 2.5 unordered_map 해시맵 unordered_map<string,int> m; m["apple"] = 3; 평균 O(1) 정렬 없음 2.6 set 중복 없는 집합 set<int> s; s.insert(3); s.insert(3); s.insert(5); for(int x : s) { cout << x; } 출력 3 5 2.7 stack stack<int> s; s.push(10); s.push(20); cout << s.top(); s.pop(); 2.8 queue queue<int> q; q.push(10); q.push(20); cout << q.front(); q.pop(); 2.9 priority_queue (힙) priority_queue<int> pq; pq.push(5); pq.push(10); pq.push(1); cout << pq.top(); 결과 10 최소 힙 priority_queue<int, vector<int>, greater<int>> pq; 3. 그래프 탐색 BFS queue<int> q; q.push(1); while(!q.empty()) { int cur = q.front(); q.pop(); for(int next : graph[cur]) { if(!visited[next]) { visited[next] = true; q.push(next); } } } DFS void dfs(int node) { visited[node] = true; for(int next : graph[node]) { if(!visited[next]) { dfs(next); } } } 격자 탐색 int dx[4] = {1,-1,0,0}; int dy[4] = {0,0,1,-1}; for(int i=0;i<4;i++) { int nx = x + dx[i]; int ny = y + dy[i]; } 4. 알고리즘 패턴 Prefix Sum (누적합) vector<int> psum(n+1); for(int i=1;i<=n;i++) { psum[i] = psum[i-1] + arr[i]; } 구간합 sum = psum[r] - psum[l-1]; Two Pointer int l = 0; int r = 0; while(r < n) { sum += arr[r]; while(sum > target) { sum -= arr[l]; l++; } r++; } Binary Search int l = 0; int r = n-1; while(l <= r) { int mid = (l+r)/2; if(arr[mid] == target) break; if(arr[mid] < target) l = mid + 1; else r = mid - 1; } 5. math #include <cmath> 함수 설명 abs(x) 절댓값 sqrt(x) 루트 pow(a,b) 거듭제곱 ceil(x) 올림 floor(x) 내림

2026.02.28.

Unity 렌더 파이프라인 관련 소식 (HDRP 업데이트 중단)

2월 23일, 유니티에서는 렌더링 파이프라인 관련 여러 소식을 발표했다. 가장 큰 이슈는 두 가지 정도인데, Built-in 파이프라인에 대한 지원을 종료한다는 소식과 HDRP에 대한 업데이트가 중단된다는 소식이다. Built-in Pipeline 중단 우선 Built-in 파이프라인에 대해서는 Unity 6.5에서부터 서서히 퇴출 절차를 밟아 6.7까지만 지원하겠다는 것이다. 물론 커뮤니티 의견에 따라 달라질 수 있다는 전제조건을 달았지만, built-in 파이프라인의 지원 종료 의지를 보인만큼 종료를 하겠다는 결과에는 달라지지 않을 것으로 보인다. HDRP 업데이트 중단 두 번째로 HDRP에 대한 업데이트 중단 소식인데, 이에 앞서 URP 기능 강화에 대한 소식도 전했다. 기존에 HDRP에서 지원했던 기능들을 URP에서도 업데이트 하겠다는 것이다. 해당 기능의 목록은 다음과 같다. physical light units pre & auto exposure physical sky and dynamic sky manager real-time GI(Global Illumination) screen space reflections on-tile post processing(for mobile device) HDRP에 대한 업데이트를 중단하고, 유지보수만 하기로 하면서 URP에 해당 기능들을 업데이트 하겠다는 것인데 지속적으로 URP의 퀄리티를 HDRP 수준으로 끌어올리겠다는 것인지에 대해서는 아직 명확한 답변이 없다. HDRP는 닌텐도 스위치2를 지원하는 것까지 업데이트하고 유지보수로만 넘어갈 것으로 보인다. (어디선가 HDRP로 AAA게임을 개발중이라는 합리적 의심) 원본 글 : Unity Discussions https://discussions.unity.com/t/render-pipelines-strategy-for-2026/1710004 개인적으로 프로젝트에 HDRP를 사용해보기도 했고, HDRP로 만들어진 프로젝트들을 보면서 엄청난 비주얼 퀄리티에 감탄하기도 했었는데 이렇게 지원을 종료해버린다니 너무 아쉽게 느껴진다. 언리얼엔진에 대응하기 위해 고퀄리티 렌더 파이프라인을 만든것일텐데 이러면 AAA게임단에서는 더 이상 선택지가 사라지는게 아닌가 싶다. URP를 HDRP정도의 퀄리티로 끌어올려준다면 다행이겠지만 너무 오랜시간이 걸릴것이고 유니티가 그럴 의지가 있는지도 모르겠다.

2025.12.19.

QT - QML ListView (with. C++ Items)

QT QML의 ListView에서는 QML 내에서 자체적으로 생성한 모델을 출력할 수도 있지만, C++에서 생성한 List Model을 불러와 동적으로 생성하는 방법도 존재한다. 프로그램을 제작하다보면 보통 전자보다는 후자를 많이 사용하게 될 것이다. Create List Item C++에서 데이터 모델을 생성하고 QML에 표시하는 방법을 알아보겠다. 우선, QML의 ListView에서 출력할 C++ 데이터 모델이 필요하다. 이 모델은 ListView에 연동할 모델이므로, QAbstractListModel을 상속받아야 한다. 여기서는 예를들어 QML에서 timestamp, message 두 가지를 표시해보려고 한다. struct ItemEntry { QString timestamp; QString message; }; class EntryModel : public QAbstractListModel { private: QList<ItemEntry> m_entries; public: enum Roles { TimestampRole = Qt::UserRole + 1, MessageRole, }; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash<int, QByteArray> roleNames() const override; } QAbstractListModel을 상속받은 class를 생성하고, m_entries에 데이터들이 저장된다. rowCount() , data() 은 QAbstractListModel의 가상함수이므로 반드시 override해야하며, QML에서 사용하려면 enum 과 함께 roleNames() 도 override해주는 것이 좋다. 편의성의 이유이며, 자세한 내용은 아래에서 확인할 수 있다. rowCount() int EntryModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent);// 함수 인자를 쓰지 않을 때 경고 무시를 위한 구문 return m_entries.count(); } m_entries의 개수를 반환해서 ListView에서 구현해야 할 Item 개수를 인식시켜주도록 한다. data() QVariant EntryModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_entries.size()) return QVariant(); const ItemEntry& entry = m_entries.at(index.row()); switch (role) { case TimestampRole: return entry.timestamp; case MessageRole: return entry.message; default: return QVariant(); } } m_entries의 행에 맞는 데이터를 가져와서 role에 맞는 데이터를 반환한다. roleNames() QHash<int, QByteArray> EntryModel::roleNames() const { QHash<int, QByteArray> roles; roles[TimestampRole] = "timestamp"; roles[MessageRole] = "message"; return roles; } QHash : 키-값 쌍을 저장하는 해시 테이블이다. enum Roles { TimestampRole = Qt::UserRole + 1, MessageRole, }; Roles.TimestampRole → 256 Roles.MessageRole → 257 를 의미하므로, 역할 번호를 문자열 이름에 매핑하는 과정이다. QQmlEngine* engine = m_qmlView->engine(); if (engine) { engine->rootContext()->setContextProperty("itemModel", m_logModel); } QML에서 itemModel 키워드로 C++ 객체에 접근할 수 있도록 등록한다. ListView { id: ListView anchors.fill: parent model: typeof itemModel clip: true spacing: 0 delegate: Rectangle { width: parent.width height: 25 Row { anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter spacing: 8 Text { text: "[" + (model.timestamp || "") + "]" color: "#D9D9D9" font.pixelSize: 11 } Text { text: model.message || "" color: "#D9D9D9" font.pixelSize: 11 } } } } QML에서 각 아이템에 대한 Rectangle이다. ListView 내부에서 rowCount() 를 호출해서 표시할 항목 수를 확인한 뒤, rowCount() 의 반환값만큼 delegate 항목이 생성된다. m_entries 개수에 따라 delegate 항목의 개수가 결정되는 것이다. QML 내부에서는 C++ 로직에서 등록을 해줬기 때문에, model.timestamp , model.message 와 같이 해당 m_entries index에 맞는 값을 가져올 수 있다. 예를 들어 model.timestamp 는 내부적으로 data(index, TimestampRole) 를 호출한다. 그 후, roleNames() 에서 매핑값을 확인해서 해당 role의 값을 반환한다. ⚠️ 만약, C++에서 roleNames()를 구현하지 않았다면 QML에서 model.timestamp 로는 접근이 불가능하고 model[256] 과 같은 방식으로만 접근이 가능하다. model을 QML에 등록할 때 QT가 내부적으로 roleNames()를 호출해서 매핑 테이블을 생성하는 것이다. Update List Item 위에서는 C++ 모델을 QML에서 표시하는 방법을 확인했고, 만약 C++ 모델에서 새로운 아이템이 추가된다면 QML에서도 업데이트 될 수 있도록 업데이트 로직이 필요하다. void EntryModel::addItem(const QString& message, const QString& level) { //1. ListView에 "데이터 추가 시작" 알림 beginInsertRows(QModelIndex(), m_entries.size(), m_entries.size()); //2. 실제 데이터 추가 ItemEntry entry; entry.timestamp = getCurrentTimestamp(); entry.message = message; m_entries.append(entry); //3. ListView에 "데이터 추가 완료" 알림 endInsertRows(); //4. countChanged 시그널 발생 (QML에서 count 속성 업데이트) emit countChanged(); } 외부에서 아이템을 추가할 때 위의 함수를 호출하는 것이다. 여기서 핵심은 beginInsertRows() 와 endInsertRows() 이다. 아래에서 동작 순서를 확인할 수 있다. beginInsertRows() 호출 ↓ ListView가 새 항목이 추가되려고 한다는 것을 감지 ↓ endInsertRows() 호출 ↓ ListView가 rowCount() 다시 호출 → 아이템 증가 확인 ↓ 새로운 delegate 자동 생성 (새로운 아이템 index) ↓ 새 delegate에서 model.timestamp, model.message 접근 ↓ 화면에 새 아이템 항목 표시 beginInsertRows() bool beginInsertRows(const QModelIndex &parent, int first, int last) 이 함수의 인자에 대한 설명이다. (QModelIndex) parent 부모 인덱스를 의미한다. 계층 구조가 없으면 QModelIndex() 를 사용한다. (int) first 삽입할 첫 번째 행의 index이다. 0부터 시작한다. (int) last 삽입할 마지막 행의 index이다. first 예시의 코드에서는 리스트 끝에 하나를 추가하는 방식이므로 first == last이다. 지금까지 다뤘던 프레임워크에 비해 ListView를 만드는 과정이 꽤나 복잡하고 어려움을 느낄 수 있었다. 다만 기본적으로 솔루션을 제공하는 것이니 익숙해진다면 어렵지 않게 만들 수 있을 것이다.

2025.12.15.

QT - C++에서 선언한 Enum을 QML에서 불러오기

QT에서 C++ 로직에 enum을 활용할 경우, QML에서 해당 enum을 불러오기 위해선 별도의 등록이 필요하다. 예를 들어 아래의 enum을 사용하고 싶다면, <CPP> class MainWindow : public QMainWindow { Q_OBJECT public: enum class Example { //enum과 enum class 모두 가능하다. A, B, C, D, E, }; Q_ENUM(Example) //선언 <CPP> qmlRegisterType<클래스명>("enum클래스명", 1, 0, "enum클래스명"); ex) qmlRegisterType<MainWindow>("Example", 1, 0, "Example"); 이와 같이 Q_ENUM(Example)과 qmlRegisterType을 등록해줘야한다. 선언 위치는 상관없지만 보통 main 에서 선언하는 경우가 일반적으로 보인다. 이렇게 C++에서 선언해줬으면 QML에서는 다음과 같이 사용할 수 있다. <QML> import Example 1.0; //qmlRegisterType으로 등록한 것을 import ... Example.A Example.B 이런식으로 불러와 사용할 수 있다.

2025.12.14.

Oracle Cloud - Database

이번에는 Oracle Cloud에서 Database를 구동하려고 한다. 여기서는 오픈 소스로 공개되는 MariaDB를 사용하기로 한다. Oracle Cloud에서 Database를 서비스로도 제공하지만, VM Instance에서 직접 MariaDB 서버를 돌리는 방법을 설명하겠다. MariaDB Installation & Settings powershell로 VM Instance에 ssh 접속하여 아래 명령어를 입력한다. 자동으로 일련의 과정이 진행되면서 MariaDB servr와 client가 설치된다. sudo apt install mariadb-server mariadb-client -y 정상적으로 설치됐는지 확인한다. mariadb --version 서비스 상태 확인 sudo systemctl status mariadb 자동 실행 설정 sudo systemctl enable mariadb 설치 확인과 자동 실행 설정을 완료했으면 기본 보안 설정을 진행해야 한다. sudo mysql_secure_installation Enter current password for root (enter for none) → 아직 설정된 root 비밀번호가 없으므로 바로 엔터를 눌러 넘어간다. Switch to unix_socket authentication → Y Change root password? → N : root는 localhost만 접속 가능하므로 root 비밀번호를 설정하지 않고 넘어간다. Remove anonymous users? → Y Disallow root login remotely? → Y Remove test database and access to it? → Y Reload privilege tables now? → Y 보안 설정을 완료했으면 mariadb에 접속한다. sudo mariadb 원격에서 접속할 유저를 생성한다. CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER '유저 아이디'@'%' IDENTIFIED BY '비밀번호'; GRANT ALL PRIVILEGES ON mydb.* TO '유저 아이디'@'%'; FLUSH PRIVILEGES; 6. 외부 접속 허용 설정을 하기 위해 설정 파일에 접근한다. sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf bind-address = 127.0.0.1 줄을 찾아서 bind-address = 0.0.0.0로 변경한다. MariaDB를 재시작한다. sudo systemctl restart mariadb ``` Oracle Cloud의 Security Rules에서 TCP 3306 포트를 열어준다. 자세한 설명은 이전 글 에서 설명했다. HeidiSQL같이 SQL에 접속할 수 있는 유틸리티에서 IP, 유저정보, 포트를 기입하고 접속한다. DB에 접속하고 나서는 SQL을 Excute하거나 수정하는 등, 일반적인 DB를 사용하듯 사용하면 된다. 번외 유니티로 제작된 Embers를 제작하면서 DB 패키지를 MySql.Data.dll로 사용하고 있었는데 이게 어떤 문제가 있는건지 VM Instance에 올려서 접근하니까 계속 문자셋 오류가 발생했다. 문제에 대해 찾아보니 MySqlConnector를 사용하라는 정보가 있어 이 방식으로 변경했더니 오류가 해소됐었다. MySqlConnector 설치에 관해서는 아래 글을 참고했다. MySqlConnector 설치 방법 : https://itgongbu.tistory.com/32 혹시나 DB 접근에 문제가 발생했다면 MySqlConnector로 변경해보길 바란다. 이번에는 Oracle Cloud VM Instance에서 DB 서버를 돌리는 방법에 대해 알아보았다. 이 글에서 사용된 MariaDB가 아니어도 다른 DBMS도 비슷한 방식으로 구동할 수 있으니 참고하길 바란다.

2025.12.14.

Oracle Cloud - Hosting Dedicated Server

앞 선 글들을 통해 Oracle Cloud 환경 설정과 원격 접속을 위한 준비를 마쳤다. 이번에는 실제로 클라우드에 서버를 올려 외부에서의 접속을 테스트해보려고 한다. 여기서는 개발중인 Embers 게임의 Dedicated Server를 활용한다. 서버를 구동하기 위해서는 서버 파일을 VM Instance에 업로드해야 한다. 여기서는 파일 업로드를 위해 FileZilla를 사용하려고 한다. Link for download FileZilla FileZilla Client에서 VM Instance 접속 좌측 첫번째 아이콘(사이트 관리자)을 클릭하여 사이트 관리자에 접속한다. 새 사이트 를 만들고, 프로토콜을 SFTP 로, 호스트에 Public IP를 기입하고, 포트는 생략해도 된다. 로그온 유형은 키 파일 로, 사용자에 powershell로 ssh에 접근했던 이름을 기입하고, 키 파일에 그 전에 다운받았던 key 파일의 경로를 넣어준다. 접속에 성공했으면 다음과 같이 표시되고, 좌측이 로컬PC / 우측이 원격 VM Instance이다. Unity Mirror API 설정 이 글에서 테스트할 Embers 게임은 Unity에서 Mirror Network API를 사용했다. 때문에 이를 기준으로 설명한다. Mirror의 Network Mananger에서 Host IP를 0.0.0.0 으로 설정한다. Network Manager에서 사용하는 프로토콜 (예를 들면 KCP)에서 포트를 자신의 원하는 포트로 설정한다. 그리고 이 포트를 전 글에서 설명했던 Security Rules에 추가해준다. KCP를 사용한다면 KCP는 UDP 프로토콜 기반이기 때문에, Security Rule을 추가할 때 프로토콜을 UDP로 설정한다. 여기서는 KCP 8585 포트를 사용하겠다. Linux Server Upload VM Instance에 설치한 Ubuntu는 Linux 기반 OS이므로 유니티에서 Dedicated Server를 빌드할 때 Linux로 빌드해야한다. 유니티에서 Linux Dedicated Server를 빌드하기 위해서는 추가 모듈을 설치해야한다. 추가 모듈은 Unity Hub에서 설치해주면 된다. 모듈을 설치하고, 유니티 에디터에서 빌드할 때 프로파일을 Linux Server로 두고 빌드하면 된다. VM Instance에 server 라는 디렉토리를 만들고, 방금 만든 빌드파일을 업로드해줬다. powershell에서 Server 실행 powershell로 vm instance에 접속한다. (이에 대한 방법은 이전 글에 설명되어 있다.) 명령어 창에 tmux를 입력하고 tmux에 접속한다. tmux를 통해 로컬 PC에서 터미널의 접속이 끊겨도 세션을 유지할 수 있게 해준다. Unity Server 빌드 파일을 저장한 server 디렉토리에 접근해서, x86_64 파일을 실행한다. 만약 Permission denied가 표시된다면, 아래의 명령어로 실행 권한을 부여한다. chmod +x [FileName] ex) chmod +x Embers.x86_64 다시 x86_64 파일을 실행하면 Dedicated Server가 정상적으로 오픈되는 모습을 확인할 수 있다. tmux Terminal tmux는 멀티플 Terminal이다. 여러 세션을 관리할 수 있고 ssh가 끊겨도 작업을 유지할 수 있도록 해준다. 아래는 tmux의 활용에 관련한 일부 단축키이다. 서버 세션 관리를 위해 필요하므로 알아두면 좋다. - 접속된 세션 나가기 Ctrl + b → d 현재 실행 중인 세션 확인 tmux ls 현재 실행 중인 세션에 다시 접속 tmux attach -t [세션번호] 세션 번호는 tmux ls 명령어를 입력하면 출력된다. 여기까지 Oracle Cloud에 Unity Dedicated Server를 올려 호스팅하는 방법을 알아보았다. 이 글에서 예시로 사용된 Embers는 MMORPG이므로 Database를 사용한다. 이 때문에 사용하는 VM Instance에 DB Server도 돌리고 있으며, 이에 대한 자세한 설명은 다음 글에서 설명하도록 하겠다.

123