w99hyunw99hyun
메인프로필
프로젝트
전체 프로젝트 보기 →
FunnikulusUnity · XR / AI · 2026EmbersUnity · MMORPG · 2024 ㅡ 2025OrbitUnity · FPS · 2024리썰딜리버리 VRUnity · VR · 2024미로의 숲Unity · 교육 · 2024
블로그연락처
메인
프로필
프로젝트
블로그
연락처
메인프로필프로젝트블로그연락처

© 2026 w99hyun. All Rights Reserved.

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

블로그

기술의 흐름을 따라가며 작성한 블로그 포스트입니다.

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

2026.03.06.

글 보기

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++ 코딩테스트 주요 문법 정리

프로그래머스 PCCP Lv.3 이상을 목표로 코딩테스트 준비를 위해 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도 돌리고 있으며, 이에 대한 자세한 설명은 다음 글에서 설명하도록 하겠다.

2025.12.13.

글 보기

Oracle Cloud - Init (2) Romote Access

지난 포스트에 이어서 외부 접속을 위한 설정을 이어나가려고 한다. Oracle Cloud에서 VM Instance를 처음 설치하면 ssh 접근을 위한 22번 포트를 제외하고 모든 외부 접근을 차단하도록 설정되어 있다. 이를 해결하기 위해서 내부 파일의 수정이 필요하다. rules.v4 수정하기 지난 포스트에서 설명했던 것 처럼 로컬PC에서 Powershell로 VM Instance에 접속한다. ssh -i [ssh_key_place] [username]@[machine_ip] 접속되었으면 다음과 같은 명령어를 입력하고, 파일에 접근한다. sudo nano /etc/iptables/rules.v4 파일 중간에 아래와 같은 내용이 두 줄로 있을 것이다. 이 내용을 삭제한다. ``` -A INPUT -j REJECT --reject-with icmp-host-prohibited -A FORWARD -j REJECT --reject-with icmp-host-prohibited > 이 두 줄의 의미: INPUT: 🔒 22번 SSH 제외하고 모든 외부 접근을 전부 차단 FORWARD: 🔒 모든 포트 포워딩/NAT 트래픽을 전부 차단 키보드로 커서를 이동해서 삭제하면 되고, 내용을 삭제 한뒤, Ctrl + X → Y → Enter를 입력해서 저장하고 나온다. 파일 수정을 완료했으면, Oracle Cloud의 Instance 섹션으로 가서 해당 인스턴스의 우측 끝 점 세개를 눌러 Reboot를 진행한다. <img src="https://velog.velcdn.com/images/usfree/post/1b840682-ff3a-4642-afa8-f4f68e85ee9d/image.png" alt=""> <hr/> Security rules Setting <blockquote> 앞에서 VM Instance OS 내부에서 접근을 거부하도록 설정돼있는 것을 수정했다. 아래에서는 실질적으로 포워딩하고 싶은 포트를 설정하는 것이다. </blockquote> Virtual Cloud Networks(이하 VCN)에서 사용 중인 VCN의 Detail로 접근한다. 그 뒤, Security 탭으로 이동한다. <img src="https://velog.velcdn.com/images/usfree/post/f8b61814-0444-409b-b3d6-ad984b51ea36/image.png" alt=""> Default로 바인딩된 Security List가 존재한다. 해당 리스트를 누르고 Security rules 탭으로 이동한다. <img src="https://velog.velcdn.com/images/usfree/post/903ab017-7f67-4301-892a-35f47649ea08/image.png" alt=""> Ingress Rules에 있는 포트들을 통해 외부에서 VM Instance에 열려있는 서버로 접근할 수 있는 것이다. <blockquote> 가령 UDP 8585 포트를 열고 싶으면 아래와 같이 설정하면 된다. Add Ingress Rules 클릭 Source CIDR을 0.0.0.0/0 (모든 경로에서 들어오는 접속을 허용한다는 의미)으로 설정 , IP Protocol을 열고 싶은 프로토콜로 설정, Destination Port Range에 열고 싶은 포트를 기입한다. <img src="https://velog.velcdn.com/images/usfree/post/e5edac54-4337-43be-a96e-18d120c2a904/image.png" alt=""> </blockquote> 이 설정을 통해 VM Instance에서 UDP 8585를 Listen중이라면 외부에서 UDP 8585 포트로 접속할 수 있다. 만약 이 설정을 통해서도 외부 접근이 불가능하다면 VM Instance를 확인해야 한다. Compute의 해당 VM Instance의 Detail에 접근하여, Networking 탭의 Network security groups (이하 NSG)가 바인딩 되어있다면 여기서 동일하게 포트를 오픈해야한다. 불필요하다면 제거할 수도 있다. <img src="https://velog.velcdn.com/images/usfree/post/a1e18fe7-44bf-4c1a-aa17-26c774e72e31/image.png" alt=""> <blockquote> NSG (Network Security Group) ↓ Security List (Subnet) ↓ 인스턴스 내부 방화벽 이 순서대로 트래픽 우선순위가 정해지는데, NSG에서 막히면 위에서 설정한 Security List는 의미가 없기 때문이다. </blockquote> <hr/> 일련의 과정을 통해 외부에서 특정 포트로 접근하는 방법을 알아보았다. 앞선 Security Rules를 통해 원하는 프로토콜과 포트를 오픈할 수 있으니 서비스에 필요한 포트를 오픈해서 사용하면 되겠다.

2025.12.12.

글 보기

Oracle Cloud - Init (1) Environment Settings

Embers를 데디케이티드 서버로 동작하는 온라인 MMORPG로 개발하면서 로컬 PC에서 호스팅을 하는게 아닌, 클라우드 호스팅에 대해 관심이 생겼다. 실질적으로 라이브 서비스를 제공하는 것은 아니기 때문에 무료로 제공하는 클라우드를 찾아봤고, AWS 프리티어 EC2나 유니티에서 호스팅할 때 흔히 사용되는 Edgegap 같은 서비스들을 살펴봤지만 대부분 한정된 자원이나 시간동안만 무료로 사용할 수 있어 연속성이 떨어져 아쉬웠다. 오라클 클라우드는 상시 무료로 제공하는 VM Instance가 있어 이 서비스를 사용해보기로 했다. 아래에서부터 Oracle Cloud에 Unity Mirror API를 사용한 Linux Dedicated Server를 구동하기까지의 일련의 과정을 서술한다. 아래의 과정이 정답은 아닐 수 있고, 여러 시행착오를 겪어가며 작성한 것이므로 참고용으로 사용하면 되겠다. Oracle Cloud Sign Up 아이디를 생성하는건 표시된대로만 따라가면 되기 때문에 절차는 스킵한다. 다만 회원가입 후 아이디가 바로 활성화되지 않고, 등록할 때 적은 메일에 아래와 같은 메일이 도착한 후 부터 로그인할 수 있다는 것만 참고하면 된다. 참고로 회원가입시 선택한 Server Region은 추후 변경할 수 없으니 신중히 선택해야 한다. 현재 가용 가능한 대한민국 서버는 North 서버(춘천)만 확인된다. Create Virtual Cloud Network Oracle Cloud에 로그인했다면 가장 먼저 Virtual Cloud Networks(이하 VCN)를 설정해야 한다. Networking의 Virtual cloud networks에서 Create VCN 을 눌러 새로운 VCN을 생성한다. Name을 원하는대로 지정하고, IPv4 CIDR Blocks에는 10.0.0.0/24 를 기입한다. 게임 Dedicated Server를 호스팅하면서 굳이 DNS까지 필요하진 않기 때문에 Use DNS hostnames in this VCN 은 비활성화 했다. Internet Gateway 생성된 VCN의 Detail에 접근을 하고, Gateways 탭에 들어간다. 여기서 Internet Gateways의 Create Internet Gateway 를 클릭한다. 이름을 지정하고 생성한다. Advanced options의 내용은 수정하지 않는다. Create VM Instance 이 단계에서 VM Instance를 설정한다. VCN에서 더 설정해야하는 부분(Subnet 등)은 VM Instance를 생성하면서 설정할 것이다. Compute에서 Create instance 를 눌러 새로운 VM 인스턴스를 생성한다. 1. Basic Information Name을 원하는대로 지정하고, Image(OS)를 설정해야한다. Linux Server를 돌리기 위해 여기선 Ubuntu를 사용할 것이다. Ubuntu를 선택하고 Canonical Ubuntu 24.04 를 사용한다. Embers는 최신 버전의 유니티를 사용해서 구버전 Ubuntu를 사용시 서버 구동이 실패하는 경우가 있었다. 유니티의 버전이 낮다면 낮은 버전의 Ubuntu를 사용해도 문제 없을 것이다. 다만 Minimal 버전이 아닌 일반 버전을 선택하면 된다. 2. Security 여기서는 별다른 설정 없이 다음 단계로 넘어간다. 3. Networking VCN을 설정하면서 미비했던 설정을 여기서 해줄 것이다. Primary network의 Virtual cloud network를 아까 설정했던 VCN으로 설정하고, Subnet에서 Create new public subnet 을 선택하여 새로운 Subnet을 생성할 것이다. SSH Key를 다운로드 한다. Download private key 를 눌러 다운로드 받을 수 있는데, 추후 로컬PC에서 VM Instance로 접근시 반드시 필요하다. 4. Storage 스토리지는 개인의 필요에 맞게 조정하면 된다. Create를 통해 instance를 생성하면 Instances 목록에 새로 생성한 instance가 표시된다. 외부 접속을 위한 Settings Reserve Public IP Networking의 IP management 섹션을 보면 Reserved public IPs 가 있다. 여기서 Reserve public IP address 를 눌러 외부 접속을 위한 고정 Public IP를 할당받을 것이다. 식별 가능한 이름을 지정하고, Reserve Public IP address 를 클릭하면 간단하게 IP를 할당받을 수 있다. VNIC Setting 위에서 생성한 VM Instance의 Detail로 들어가서, Networking 탭을 누르고, Atttached VNICs에서 Primary VNIC 태그가 붙어있는 Name을 클릭한다. VNIC의 Detail에 접근했으면 IP administration 탭을 누르고, 우측 끝 점 세개를 눌러 Edit 창에 접근한다. Public IP type에서 Reserved public IP를 체크하고, Reserved IP Address 드롭다운 메뉴에서 위에서 설정한 public IP를 바인딩한다. Add Route Rules 생성된 instance를 눌러 Details에 접근하고, Networking 탭에서 Route table에 바인딩된 테이블을 클릭한다. 여기서는 Default Route Table for sample 이다. Route Rules 탭에서 Add Route Rules 를 클릭한다. Target Type을 Internet Gateway 로, Destination CIDR Block은 0.0.0.0/0 으로, Target Internet Gateway에 선술했던 Internet Gateway를 바인딩하여 생성한다. 여기까지하면 로컬PC에서 클라우드 VM Instance에 원격접속하기 위한 준비가 끝났다. PowerShell로 Oracle Cloud VM Instance 접속 테스트 윈도우에서 Powershell을 실행하여 아래의 명령어를 입력한다. [ssh_key_place]에 아까 다운 받은 ssh key의 경로를 입력하면 되고, [username]과 [machine_ip]는 VM instance의 Details에서 확인할 수 있다. ssh -i [ssh_key_place] [username]@[machine_ip] ex) ssh -i "C:\Users\Users\Downloads\ssh-key-2025-12-13.key" ubuntu@127.0.0.1 중간에 key와 연관된 질문에서 연결하겠냐고 묻는 질문에 yes를 입력하면 VM Instance에 접속이 완료된다. Oracle Cloud의 VCN 설정, VM Instance 생성까지 완료했고, 외부에서 클라우드로 접속까지 되는 것을 확인했다. 하지만 지금 상태에서 서버를 올려 실행시켜도 아직 외부에서 접속이 되지 않는다. 접속 포트가 다르기 때문인데, 처음 VM Instance를 설치하면 22번 포트를 제외하고 모든 포트가 막혀있다. 이 때문에 별도의 설정이 필요한데, 다음 포스트에서는 이 부분을 알아보겠다.

123